上周排查一个 Agent 的问题,现象很怪:模型明明「调用」了工具,但传回来的参数永远差一个字段,重试三次全是同一个错。查到最后发现,问题不在模型,在我给它的工具描述里------那个字段压根没写进 parameters。
这件事让我把「Function Calling」这条链路重新捋了一遍。这篇从工程角度讲清楚:模型到底怎么决定调用哪个工具、参数是怎么来的、以及为什么你写的 schema 决定了它的成功率。
一、先纠正一个常见误解
很多人以为「AI 会调工具」= 模型自己去执行了函数。不是的。
模型做的唯一一件事,是根据上下文输出一段结构化文本 ,表示「我要调用 X,参数是 Y」。真正执行 X(Y) 的,是你的代码。
所以一条完整的调用链是:
rust
用户问题 + 工具清单 -> 模型输出 tool_call 意图
-> 你的程序执行对应函数
-> 把执行结果回传给模型
-> 模型生成最终回答
模型是「决策层」,你的代码是「执行层」。分清楚这一点,后面所有排查都有方向:调用失败,先看执行层收到了什么,再看模型为什么这么给。
二、工具清单就是一份 JSON Schema
各家 API 的字段名略有差异,但结构是通的。一个工具长这样:
json
{
"name": "search_article",
"description": "在本平台已发布文章里按关键词检索,返回标题与链接",
"parameters": {
"type": "object",
"properties": {
"keyword": { "type": "string", "description": "检索关键词" },
"limit": { "type": "integer", "description": "返回条数,默认 5" }
},
"required": ["keyword"]
}
}
注意 description 出现在两个层级:工具级和参数级。这两层描述,就是模型填参数的唯一依据。
三、参数填错,八成是 schema 的锅
我踩过的坑,基本都能归到这几类:
required漏标 。模型不知道哪些必填,于是有时传有时不传,你的函数就收到undefined。- 参数级
description缺失 。模型只能靠参数名猜语义,limit猜成「页码」也不奇怪。 - 类型不写 。本该是整数,模型传了
"5",服务端校验直接 400。 - 工具级
description太泛。两个工具都写「查询数据」,模型就随机挑一个。
第 4 条尤其隐蔽:出错时你以为是模型笨,其实是你给了两个它分不清的选项。
四、失败信息要原样回传
工具执行出错时,别只回一个 "error"。把真实原因带上:
vbnet
Tool error: missing required property 'keyword'.
Fix: provide the 'keyword' string argument.
模型看到具体缺什么、怎么改,才会换参数重试;只给一个 error,它大概率原地重试同一个调用,然后触发你的重试上限。
顺带加两层保护:参数校验 (schema 之外再校一次类型和边界)和单轮调用次数上限(防止模型在失败里打转)。
五、MCP 解决的是另一个问题
上面这套是「模型怎么表达调用意图」。但工程上还有一层麻烦:同一个工具,想让它在不同客户端里都能用,每个客户端都得适配一遍。
MCP(Model Context Protocol)就是来标准化这一层的:工具方写一个 Server 声明能力,任何支持 MCP 的客户端都能自动发现并调用,换模型、换客户端,工具侧不用重写。
它和 Function Calling 不是二选一------MCP 管「工具从哪来、怎么被发现」,Function Calling 管「模型怎么把调用意图表达出来」。实际链路是:
rust
MCP Server 暴露工具
-> 客户端转成 Function Calling 所需的 schema
-> 模型输出调用意图
-> 客户端执行并回传
六、什么时候不用上 MCP
只有一个客户端、工具三五个、也不会换模型------直接用 Function Calling 就够了,多一层协议只是多一层维护。
值得上 MCP 的信号:工具要跨团队复用,或者你希望换模型时工具侧零改动。
小结(自查用)
- 工具级
description:说清「什么场景用、什么场景别用」 - 参数级
description+required+ 类型:一个都不能省 - 失败时把真实原因回传,别只给
error - 有参数校验,有单轮调用上限
- 工具清单别堆相似项,模型会选错
一句话:模型负责决策,你负责执行和兜底。 把这两件事分清,工具调用的问题基本都能定位到具体那一层。
作者:信可维 · AI 实战。记录能跑通的方案。