AI Agent 的 Tool Calling,远比你想象的要不靠谱

模型调用工具时的参数幻觉、类型错乱比想象中更常见,与其指望模型变完美,不如从系统层面建立防御、容错和自愈机制。

如果你最近在搞 AI Agent 开发,一定会对下面这个场景深有体会:

你精心设计了一套 Tool 定义,参数类型、描述、约束都写满了。Agent 在 80% 的情况下调用得完美无缺,剩下那 20% 的时间里,它要么传了个根本不存在的参数名,要么把字符串塞进了整数字段,要么干脆凭空编造一个 Tool 出来。

这不是 Bug,这是 Feature。准确说,这是 LLM 的固有属性——生成式模型的「幻觉」不会因为你给了它一个工具调用接口就消失,它只是换了一种表现形式。

一个让人血压升高的典型场景

我自己的项目踩过一个特别经典的坑。

系统里有一个 search_knowledge_base(query: string, top_k: int) 工具,用来从知识库里检索相关内容。看起来简单吧?三个轮次之后,模型开始发挥创造力了:

// 第一次调用:
search_knowledge_base(query="什么是RAG", top_k=5)

// 第三次调用,同一个对话里:
search_knowledge_base(query="RAG", topK=5)  // 参数名变了!

它把 top_k 写成了 topK。不是我定义的问题——我翻了一下当时的 Tool Schema,白纸黑字写着 top_k。但模型就是自我发挥了一把。

然后呢?API 报了参数验证错误。Agent 看到错误后……又尝试了一次,把参数改成了 topk。服了。

这种问题最恼人的地方在于:它不是每次都会出现。你测试 10 次可能 9 次都正常,可上线之后那个 10% 的异常率就足够把人折磨疯。

为什么模型在 Tool Calling 上这么「智障」?

先说一个反直觉的事实:对 LLM 来说,生成一段合法的 JSON 比生成一篇文章要难得多

不是说语法上的难——现在随便一个模型都能输出格式正确的 JSON。真正的问题在于:模型对 Tool 的理解是语义层面的,不是类型系统层面的

你是程序员,你看到 top_k: integer 就天然知道:这个名字是 top_k,类型是整数,不能传字符串。但模型看到的是 "top_k" 这个 token 序列,它是在「猜」这个参数的含义,然后用概率分布去决定用什么名字、填什么值。

更具体地说:

  1. 参数名不是标识符,是语义提示。模型会把 top_k 理解为「哦就是那个控制返回数量的」,然后在输出时可能记成自己更熟悉的变体(limitcounttopK)。
  2. 类型约束是隐式的。你告诉模型 temperature 是 number,但在某些模型内部,JSON Schema 的约束只是 prompt 里的一段话,模型看过了但不一定遵守。
  3. 工具选择是开放式的。传统的 API 调用是硬编码的——调什么接口,传什么参数,写死了。但 Agent 要在运行时决定「当前该用哪个工具」「传什么参数」,这事本身就充满了不确定性。

我学到的几个血的教训

踩过坑之后,我总结了几条应对策略,不是什么高深的架构,就是一些很土但有效的方法。

1. 别相信模型会传对参数名——在服务端做容错

这个是最容易想到但也最容易被忽略的。很多框架只做正向的 Schema 校验——不匹配就抛异常。但更好的做法是:做模糊匹配

比如用户定义了一个 search_documents 工具,参数叫 max_results。模型传了 limitmaxRestop_k 怎么办?你可以维护一个参数别名字典,或者在服务端做 Levenshtein 距离匹配。虽然听着不优雅,但实测能把 Tool Calling 成功率从 85% 拉到 95% 以上。

2. Tool 越少越好,参数越少越好

这是架构层面的取舍。

很多人的第一直觉是把所有能力都封装成 Tool,让 Agent「尽情发挥」。但 Tool 越多,模型选错的概率就越大。有研究表明,当 Tool 数量超过 10 个时,调用的准确率会急剧下降。

我的做法是:
- 能不做成 Tool 的,就别做成 Tool。能用 prompt 解决的,别用 Tool。
- 把相关功能合并成少数几个「大 Tool」,参数尽可能少。宁可在 Tool 内部做逻辑分支,也不要暴露一堆细粒度 Tool 让模型去选。
- 每个 Tool 的描述要写得像在跟小学生说话,不要用任何模糊表述。

3. 引入 Tool Call 的前置验证层

这个是我最近才加上的,效果立竿见影。

在 Agent 调用任何一个 Tool 之前,先过一个轻量级的验证层。这个验证层做的事情很简单:
- 检查参数名是否匹配
- 检查参数类型是否合法
- 检查必填参数是否缺失
- 如果发现小偏差,自动做修正(比如参数名模糊匹配)

关键是这个验证层的逻辑要快、要稳,不能依赖另一个 LLM 调用去验证当前的调用——那样会陷入无限递归。

4. 给 Agent 一个自我纠正的反馈回路

这个稍微高级一点。当 Tool Call 失败时,不要只返回「调用失败」这种只有程序员才能消化的信息。

要返回可执行的错误信息

// 不好的返回:
{"error": "Parameter validation failed: unknown field 'topK'"}

// 好的返回:
{"error": "参数 topK 不存在,该工具可用参数为: [query, top_k]。top_k 的类型是 integer。请重新调用。", "suggestion": {"function": "search_knowledge_base", "arguments": {"query": "你的问题", "top_k": 5}}}

没错,我甚至会让验证层生成一个建议的调用参数。模型看到这个往往就会直接采纳,而不是自己再瞎猜一轮。

最后说两句

Tool Calling 是 AI Agent 的「手」,它决定了 Agent 能不能真正跟现实世界交互。但很多人都把注意力放在了「手够不够长」(Tool 数量多不多)上,很少关注「手稳不稳」。

现实是,当前 LLM 在 Tool Calling 上的可靠性还远谈不上完美。作为一个开发者,最务实的做法不是期待模型一夜之间变完美,而是在系统层面做好防御、容错和自愈。

这不是什么高大上的架构设计理念,就是在跟模型打交道的过程中慢慢磨出来的经验。

你要是也在搞 Agent 开发,被 Tool Calling 坑过,评论区聊聊你遇到的怪事——我怀疑每个人的踩坑经历都能写一本《工具调用迷惑行为大赏》。

评论

此博客中的热门博文

我写了半年 prompt,最后发现最好的技巧就三个