Featured image of post 第三话|模型为什么能生成工具调用?

第三话|模型为什么能生成工具调用?

从训练样本、Toolformer、Gorilla 到 Native tools:工具调用能力究竟来自哪里。

系列 GYA(Get Your Agent) 第 3 / 12 篇
主题 AgentLLMFunction-CallingToolformer

上一话跑通了 Function Calling 协议,但留了个更根本的问题没收尾:

模型凭什么知道该从 tools 菜单里挑 get_weather、又凭什么把“北京”填进 city

不是提示词写得好。协议只是给了模型一个“填在哪里”的位置,真正让模型知道“填什么”的,是它的训练。这一话就追这件事:模型是怎么学会“提出一次工具调用”的。

先给结论,后面再拆:工具调用不是模型的内置能力,而是训练出来的“输出倾向”。训练改变的是它生成某些 token 序列的概率,不是给它装了一个真的会退款的函数。

一、模型学的,仍然是下一个 token

模型里没有一个叫“退款”的函数。它有的,只是“给定上文,预测下一个词”的能力。所谓“会调工具”,是它学会了:当上下文里出现“退款意图 + 工具定义”时,下一个该输出的,是 refund_orderorder_id 这几个 token。

训练样本把它要学的样子,直接摆出来:

上下文:订单 O-100 已支付,用户要求退款;可用工具有 refund_order(order_id)
目标:refund_order({"order_id":"O-100"})

反复见这样的样本,模型参数里就长出一套倾向:“退款 + 已支付”这类上下文,会抬高 refund_order 的概率;订单号 O-100,会抬高被填进 order_id 的概率。

注意,训练集还得有反例,否则模型会养成坏习惯:

  • 用户只问配送时间 → 直接回答,不调工具;
  • 订单状态不明 → 先追问,不调工具。

少了这些负样本,模型就会变成“看见什么都想调工具”。

关键洞察:模型只是提出了候选调用。它没有因此获得订单事实、权限,也没有执行权。 会“说”要干什么,和“真的干了什么”,是两码事。

二、这条“倾向”,是靠什么训练出来的?

问题来了:这些“上下文 → 正确调用”的样本,是怎么来的?人工一条条标注太贵,学界给了几条公开的路线。

Toolformer 解决的是“数据怎么规模化造出来”。它的做法很有意思——让模型自己当标注员:

  1. 在一段普通文本里,让模型猜哪些位置可能需要调 API,采样出一堆候选调用;
  2. 真的去执行这些调用,拿到结果;
  3. 关键一步:只保留那些“拿到结果后,模型预测后续文字更准了”的调用——具体说,就是比较“有结果”和“没结果”两种情况下,模型对后面 token 的预测损失,损失降得够多才留下;
  4. 用过滤后的样本去微调模型。

一句话:模型自己提出调用、自己执行、再自己判断“这次调用到底有没有用”,有用的才拿来当训练样本。 全程不需要人工标注“这里该不该调工具”。Toolformer

Gorilla 解决的是另一个问题:API 会变。哪怕模型学过 get_weather(city),文档也可能改成 get_weather(location, unit)。Gorilla 的做法是微调 + 文档检索结合:训练给模型调用能力,检索把“当前最新的接口契约”带进上下文,让它跟得上变化。Gorilla

CoT 常被顺带提起,但它其实是个对照,不是工具调用训练:它研究的是“在提示里给推理示例能不能改变输出”。它能改变当前这一轮的输出,但不改模型参数——和 Toolformer/Gorilla 那种真正动参数的训练,是两回事。CoT

三、一件必须分清的事:哪些证据能信,哪些不能

讲到这里,得划一条诚实的边界。关于“模型为什么能调工具”,能拿到的证据分三类,它们不能互相冒充

证据类型能说明什么不能说明什么
论文公开的方法(Toolformer/Gorilla)“存在这些训练工具调用的公开方法”不代表某个商业模型就用了它
厂商披露(OpenAI 公告)0613 模型经过微调、Structured Outputs 靠训练+约束解码只限定到那几家、那段时间,不能外推
黑盒 API 能观察到的输入什么、输出什么(比如 description 改了就选错工具)看不到训练语料、训练阶段、奖励方法

最关键的一条是第三类:你从 API 拿到的结果再稳定,也反推不出厂商到底是怎么训练的。 训练语料长什么样、分几个阶段、有没有用 RLHF——这些是黑盒,除非厂商自己说,否则只能标“未知”。

所以别犯那个错:看到 tool_calls 稳定输出,就说“这个模型用了 Toolformer”。稳定输出能证明“它训练得很好”,证明不了“它是怎么训练的”。

四、Prompt-only 和 Native tools,差的不只是“格式”

上一话讲了 Native tools 的协议形态,这里补一层它和训练/推理的关系。

Prompt-only:在 system prompt 里约定“需要退款时输出 JSON”。模型把 JSON 塞进普通 content 里,Runtime 还得从正文里把命令抠出来——这里混着自然语言、Markdown、可能还有好几个 JSON。

Native tools:在 API 的 tools 字段传 schema,模型用独立的 tool_calls 字段返回。Runtime 直接拿到工具名、参数和调用 id,不用猜哪里是命令。

这个差异不是“格式好看点”,而是责任归属变了:Prompt-only 把“保证输出长得像 JSON”的责任甩给了提示词和你的解析器;Native tools 把这层责任接了过去,一部分靠训练(让模型更会按 schema 输出),一部分靠推理阶段的约束解码(生成时卡住,只允许符合 schema 的 token 出来)。

OpenAI 官方也明确说过:Function Calling 的可靠性,一部分来自模型的微调,一部分来自 Structured Outputs 的 constrained decoding。Structured Outputs

这里有个真实的坑能说明这层差异。我们 Java 版最初只写了“需要时输出 JSON”,没说清 namearguments.order_id 的形状。结果 Native tools 模式仍能返回结构化调用,Prompt-only 模式却解析不出来。把提示词补成明确的 JSON 模板后,Java 的 8 个场景才全过。这不是“Java 不适合做 Agent”,而是Prompt-only 把输出协议的一部分责任,留在了你的提示词和解析器上——而 Native tools 帮你卸掉了一部分。

五、动手看一次:description 才是“选哪个”的开关

我们用一套虚构订单做实验,Python 和 Java 21 各跑一遍。Runtime 先注入已验证的订单状态,模型只做一次工具选择,程序只记录候选调用,绝不执行退款或取消

四种场景,正确行为是这样:

场景正确行为
已支付 O-100,要求退款refund_order(O-100)
未支付 O-200,要求取消cancel_order(O-200)
无订单号和状态追问,不调用
无退款/取消意图自然语言回答,不调用

基线下两种语言都过。然后做一个故障注入:只把 refund_ordercancel_order 的 description 对调,其余全不变——工具名、模型、用户请求、参数 schema 都一样。

结果很干净:已支付退款稳定变成 cancel_order(O-100),未支付取消稳定变成 refund_order(O-200),而订单号仍然是对的。恢复 description 后,回归全过。

这个实验说明两件事:

  1. 工具选择(选哪个)靠的是 description——模型是读描述来判断“这个工具是干嘛的”,描述写错了,它就选错。
  2. 参数提取(填什么)可以和工具选择分开看——订单号还是从用户消息和 Runtime 注入的上下文里来的,两个工具的参数 schema 没变,所以订单号没错。

但这能证明什么、不能证明什么,得说清楚:它证明了“description 影响路由”这个可观察行为证明不了模型用了哪种训练法。description 写对了,也不代表参数永远填对——Runtime 照样得校验。

六、所以,能信到什么程度

绕了一圈,回到开头那个问题:模型为什么能生成工具调用?

因为它被训练成了这样——训练样本让它形成了“意图 + 工具定义 → 结构化调用”的输出倾向。Toolformer、Gorilla 这些公开方法,展示了这条倾向可以怎么规模化地训练出来;厂商还叠加了微调和约束解码,让输出更稳。

但“会提请求”不等于“可靠”。训练让模型更会“说”要干什么,不会让它“变聪明”——它照样可能选错工具、编造业务参数、请求一个无权执行的动作。tool_calls 永远是候选,不是命令。 谁来判断“能不能执行、怎么执行”,是下一篇的事。


参考资料:

留言

欢迎分享你的想法。评论提交后会在审核通过后显示。