Function Calling 不是魔法:一次工具调用链路的设计复盘

工具调用的关键,是链路可控。

我一开始接触 Function Calling 的时候,确实有一种“这下 Agent 终于能干活了”的感觉。

因为以前模型再会说,很多时候也只是停在“建议你去做什么”。但有了工具调用以后,系统一下子像被接上了手和脚。它不只是能回答,还能查数据、发请求、整理结果,甚至继续往下做下一步。

但真正做下来之后,我反而越来越不想把这件事讲得太神。

因为 Function Calling 不是魔法。它本质上只是把模型输出从自然语言约束成一个结构化动作。真正决定这条链路稳不稳的,还是你后面那整套工程设计。

我一开始最天真的想法

最早的时候,我会觉得这件事很直接:

  • 给模型一组工具。
  • 写清楚每个工具的描述。
  • 让它自己决定什么时候调。

看起来没什么问题,Demo 阶段也确实经常能跑通。

但只要一接真实接口,就会出现一堆很具体的问题。比如参数名模型理解错了,某个必填字段它没传,或者用户明明没给完整信息,它还是硬着头皮构造了一次调用。

这时候你就会发现,工具调用能不能发出去,只是最低门槛。更关键的是,它为什么这么调,调完之后系统怎么接住。

一次稳定的工具调用链路,至少要想清楚四件事

1. 哪些参数可以让模型填,哪些不行

这是我后面特别在意的一点。

如果一个字段本来就应该由系统上下文提供,比如用户 ID、租户信息、鉴权 token、当前环境,那就不应该把它暴露给模型让它自己猜。

模型适合处理的是从用户自然语言里提取意图和业务参数,不适合碰那些一旦填错就会出事故的系统字段。

所以我现在会把参数拆成两类:

  • 模型负责提取的参数。
  • 系统负责补全的参数。

这一步其实很重要,因为它直接决定了你是在让模型“理解需求”,还是在让模型“顺便兼职后端服务”。

2. 调用前要不要校验

以前我会想,既然都已经用了 schema,是不是就够了。

后面发现远远不够。

schema 只能保证结构像样,不保证业务上真的能执行。比如日期格式是对的,不代表日期范围合理;用户说“帮我查一下最近的订单”,模型可能会默认一个时间区间,但这个区间未必符合业务要求。

所以我现在更愿意把工具调用拆成两层检查:

  • 结构校验,防止字段类型和格式乱掉。
  • 业务校验,判断这次调用是不是合理。

如果校验不过,系统宁愿回退去追问,也不要假装自己已经理解了。

3. 工具返回结果要不要原样喂回去

这个坑我也踩过。

很多人会默认“工具查到了什么,就把什么直接扔给模型”。但真实场景里,工具返回往往又长又杂,还带很多无关字段。

如果你把这些结果原样塞回上下文,模型的阅读成本会很高,而且噪音会越来越大。一次调用还好,多轮下来就很容易把重点冲散。

所以我现在比较认同的做法是,工具返回给模型之前,先做一次系统层整理:

  • 只保留当前任务需要的字段。
  • 把结构转成更容易消费的格式。
  • 明确哪些是关键结论,哪些只是原始证据。

这样不是在替模型做推理,而是在减少它被无关信息带偏的概率。

4. 一次调用失败后怎么办

这个问题特别现实。

调用失败不一定是模型错了,也可能是接口超时、权限不足、下游异常,或者用户输入本身就不完整。

如果系统没有设计失败分支,最后就会出现一种很尴尬的情况:模型前面看起来挺聪明,最后一句“调用失败,请稍后再试”把体验全打碎了。

我现在更希望失败也分类型处理:

  • 缺信息就追问。
  • 权限不够就明确说限制。
  • 下游异常就做重试或降级。
  • 高风险操作就中止,不硬做。

说到底,工具调用链路不是只为成功 case 设计的,真正考验工程水平的,往往是失败的时候系统是不是还像个系统。

我现在怎么理解 Function Calling

我现在会把它理解成一种边界非常清楚的能力:

它负责让模型把“想做什么”表达成“结构化动作”,但它不负责替你完成任务建模、权限控制、参数兜底、错误恢复和可观测。

这些事如果你不自己补上,Function Calling 只会让模型更快地把错误执行出来。

如果面试官让我复盘这块

我会更想强调这一点:

“Function Calling 的价值不是让模型突然拥有了执行力,而是给系统提供了一个把自然语言转成可校验动作的接口。真正稳定的工具调用链路,还需要参数分层、前置校验、结果整理和失败兜底这些工程设计配合。否则它只是从会胡说,变成会胡调。”

我现在觉得,后面这半句其实比前面更重要。

主分类 AI 应用开发 / Agent 与 RAG

Agent、RAG、工具调用、上下文治理。

标签

阅读文章

相关记录