人人都会AI编程

6.3 工具调用(Function Calling)的实现原理

更新时间:2026-07-09

在 1.1 节中,我们指出了大语言模型的一个根本局限:知识以静态权重的形式压缩在参数里,既无法自动更新,也不能与外部世界实时交互。而在 1.3 节描述的能力边界中,工具调用(Tool Use / Function Calling)正是打破这一局限的关键跃迁——它让 LLM 从“闭卷考试的复读机”变成了“可以查资料、算数据、发邮件、调 API 的数字代理”。

这一节我们把镜头推进到工程实现层面,讲清楚工具调用到底是如何在自回归生成的物理机制上“长”出来的。


一、本质:模型并不真的“执行”,而是“发出指令”

首先需要建立一个核心认知:LLM 本身不会调用任何外部工具。它没有网络权限、没有文件系统句柄、无法执行 SSH 命令。它的全部能力仍然局限在 4.1 节所述的因果语言建模(CLM)框架内——逐 Token 生成文本。

工具调用的本质,是让模型学会在合适的时机,生成一段符合约定格式的结构化文本(通常是 JSON,也可以是 XML、YAML 或伪代码)。这段文本被外部系统(客户端、Agent 框架、中间件)解析后,才真正去执行对应的函数,再把执行结果(Observation)回传给模型,由模型继续生成最终回答。

用一句话概括:模型扮演的是“翻译官”,把用户的自然语言意图,翻译成机器可执行的结构化指令。


二、训练层面:SFT 阶段植入“调用模式”

工具调用不是基座模型天然就会的能力,它主要通过 5.1 节的监督微调(SFT) 注入。数据构造方式直接决定了模型能否稳定输出合法的工具调用。

1. 训练数据的三元组结构

一条标准的 Function Calling SFT 样本通常包含以下角色序列:

  • System:注入可用工具的 JSON Schema 描述(函数名、参数类型、必填字段、参数含义说明)。
  • User:表达需要借助工具才能完成的意图(如“查一下北京明天天气,然后写一句穿搭建议”)。
  • Assistant
  • 若需要工具:生成一个结构化调用块(如 <function=query_weather>{"city": "北京", "date": "明天"}</function>),而非直接给答案。
  • 若无需工具:直接生成自然语言回答。

2. 关键训练技巧

  • Schema 忠实性:在 System Prompt 中不仅给工具名,还要给参数的类型约束(type: string, enum, required 等)。训练数据中的 Assistant 输出必须严格遵循这些约束,避免模型自由发挥。
  • 多轮调用链:训练数据需覆盖“调用→等待结果→再调用”的多轮对话格式。模型要学会在看到类似 [TOOL_RESULT] 的特殊 Token 后,把外部返回的原始数据(如 API 返回的 JSON)整合进下一轮生成。
  • 拒识与澄清:训练模型在参数不足时,不瞎猜,而是向用户追问(如“您想查询哪个城市?”),这是降低幻觉的重要策略。

三、推理层面:从生成到执行的闭环

在推理阶段,工具调用遵循一个标准的 Loop 范式

用户提问 
  → 模型生成(判断是否需要工具)
    → [无需工具] → 直接输出自然语言回答
    → [需要工具] → 输出结构化调用指令 
      → 客户端解析 JSON → 执行真实 API/函数
      → 将执行结果(Observation)封装后回传
        → 模型再次生成 → 最终回答或进入下一轮调用

这个循环正是 1.3 节提到的模型与外部世界交互的标准管道。

并行调用(Parallel Function Calling)

现代 LLM(如 GPT-4、Claude 3、Qwen2)支持一次输出多个独立工具调用。这并非架构层面的颠覆,而是训练数据与解码策略的扩展:在 SFT 数据中构造 Assistant 同时输出多个 JSON 对象的样本;推理时通过特殊分隔符(如多个 function_call 块)让客户端一次性批量执行,减少往返延迟。


四、解码约束:如何让模型“不瞎编参数”

如果完全让模型自由生成 JSON,它几乎一定会犯语法错误(漏引号、多逗号)或产生幻觉参数(编造不存在的枚举值)。工程上通常采用受限解码(Constrained Decoding)技术。

1. 语法约束生成

在自回归解码的每一步,利用外部语法(如 JSON Schema 或上下文无关文法 CFG)实时裁剪模型的词汇表概率分布,强制下一个 Token 必须满足语法合法性。例如:

  • 当前正处于一个 string 值域内 → 只允许生成合法字符串字符;
  • 某个参数为 enum: ["celsius", "fahrenheit"] → 下一个 Token 只能以这两个词之一开头;
  • 遇到 JSON 对象闭合位置 → 禁止继续生成同级非法字段。

开源生态中的 Outlines、Guidance、lm-format-enforcer 等库,都是在解码阶段拦截非法 Token,确保输出 100% 可解析。

2. 与 3.7 节架构的衔接

在 Decoder-only 架构中,这并不改变 Transformer 的自回归本质,而是在 Softmax 采样前加一层掩码(Mask):将不符合语法约束的 Token 的概率置为负无穷,只允许合法子集参与采样。这种“带镣铐的接龙”既保留了模型的语义灵活性,又保证了结构刚性。


五、工程落地的四大实用要点

1. 参数校验:永远不要信任模型输出

即使使用了受限解码,也必须在客户端对模型输出的参数做二次校验:

  • 类型检查(字符串/数值/布尔);
  • 范围检查(如 limit 参数不能超过 100);
  • 权限检查(模型输出的用户 ID 是否等于当前会话用户,防止越权)。

记住:模型没有身份意识和安全边界,它会非常自信地生成看似合理的越权参数。

2. 延迟与超时设计

每一次工具调用都意味着网络 I/O。如果模型 chaining 了 3 个串行 API,RTT(往返时间)可能累积到数秒甚至数十秒。工程上需要:

  • 设置 API 超时熔断;
  • 优先使用并行调用;
  • 对长链路采用流式输出(SSE),先给用户返回“我正在查询……”的占位文本,降低感知延迟。

3. 错误反馈与 Retry 机制

真实世界的 API 会报错(404、500、限流)。优秀的系统会把错误信息格式化后回传给模型:

[TOOL_RESULT]
Error: 404, City "北鲸" not found. Did you mean "北京"?

模型在下一轮生成中可能自我修正(Self-Correction)。训练时加入“错误恢复”样本,能显著提升鲁棒性。

4. 安全沙箱与最小权限

工具调用是 LLM 应用攻击面最大的环节之一。务必遵循:

  • 最小权限原则:给模型暴露的工具越少越好,参数越受限越好;
  • 沙箱执行:代码执行类工具必须在 Docker 或无服务器隔离环境中运行;
  • 人工确认:涉及资金转移、数据删除、消息发送等高危操作,必须引入人类在环(Human-in-the-Loop)。

六、工具调用与相关概念的边界

| 概念 | 核心动作 | 与工具调用的关系 |
|------|----------|------------------|
| RAG | 检索文档,将文本片段填入 Prompt | 工具调用的一种特例(检索工具),但 RAG 侧重“读”,工具调用侧重“做” |
| Code Interpreter | 生成 Python 代码并执行 | 工具调用可内置一个 python 工具;区别在于 Code Interpreter 侧重计算与数据分析,通用工具调用侧重外部 API |
| Agent | 自主规划、记忆、工具使用的循环 | 工具调用是 Agent 的原子能力;Agent 框架(如 LangChain、AutoGen)负责编排多个工具调用、维护状态机 |


七、小结

工具调用的实现原理,本质上是在 Decoder-only 自回归框架内,通过 SFT 数据教会模型输出结构化指令,并在推理阶段借助受限解码与客户端执行引擎形成闭环

关键 takeaway:

  • 模型只负责“写指令”,不负责“执行”,执行环境必须由开发者严格管控;
  • 数据质量决定稳定性:训练数据中 Schema 的准确性、多轮调用链的完整性,直接影响生产环境的可用率;
  • 解码约束是工程刚需:自由生成 JSON 的幻觉率极高,必须配合 Outlines 等语法约束工具;
  • 安全大于智能:工具调用打开了模型与物理世界的通道,权限设计和沙箱执行比模型能力更重要。

在 6.4 节中,我们将转向 LLM 最棘手的负面特性——幻觉(Hallucination),深入剖析它为何在文本生成、工具调用甚至推理链条中都无法根除,以及当前业界有效的缓解思路。