0%

大模型 Function Calling:底层原理与训练机制

很多人描述 Function Calling 的工作原理时会说:模型”读懂”了用户的意图,”判断”出需要调用工具,然后”决定”生成一段调用指令。这种说法很流行,但”读懂”、”判断”、”决定”这些词都有误导性,它们暗示模型拥有某种理解能力,仿佛它真的在思考。大模型的本质从来没变过:Next Token Prediction,也就是预测下一个词。Function Calling 没有引入任何神秘的推理机制,它只是让模型学会了在特定情况下切换输出格式,从自然语言切换成结构化的 JSON 文本。

核心原理

无论是否启用 Function Calling,模型的计算过程完全一样:输入经过 Transformer 计算,在整个词表上生成概率分布,采样出下一个 Token。启用 Function Calling 后唯一的区别是,模型的词表里多了几个特殊 Token,训练数据里多了工具调用相关的样本。模型本身并没有多出什么新的推理能力。

一个贴切的类比是训练鹦鹉说话。普通模式下,你教它说”你好”、”谢谢”、”再见”,它学会在合适场合说这些话。Function Calling 模式下,你额外教它一套暗号,比如听到”几点了”就输出 [CALL:get_time]。鹦鹉并不知道”几点了”是什么意思,也不知道 get_time 真的会获取时间,它只是学会了听到 X 就说 Y。模型的情况一模一样:它学会了在特定上下文下输出特定格式的文本,恰好这个文本是 JSON 格式的工具调用指令而已。

两阶段训练

模型通过两个训练阶段学会 Function Calling。第一个阶段是 SFT(监督微调),通过大量标注样本教会模型三件事:什么时候切换输出模式、怎么写调用指令、怎么处理工具返回结果。

学习内容 训练样本示例
什么时候切换输出模式 用户问”今天天气怎么样?” → 模型输出 [tool_call] 开始标签
怎么写调用指令 [tool_call]{"name": "get_weather", "args": {"city": "北京"}}[tool_call_end]
怎么处理工具返回结果 工具返回 {"temp": 25, "condition": "晴"} → 模型输出”今天北京天气晴朗,气温25度”

训练数据的结构大致如下:

1
2
3
4
5
6
7
8
{
"messages": [
{"role": "user", "content": "今天北京天气怎么样?"},
{"role": "assistant", "content": "[tool_call]{\"name\": \"get_weather\", \"args\": {\"city\": \"北京\"}}[/tool_call]"},
{"role": "tool", "content": "{\"temp\": 25, \"condition\": \"晴\"}"},
{"role": "assistant", "content": "今天北京天气晴朗,气温25度。"}
]
}

模型通过大量这样的样本学会了一种模式匹配:当用户问题涉及可调用的工具时,输出 [tool_call] 标签和 JSON 格式的调用指令。

SFT 阶段让模型学会了基本操作,但还有很多边界情况需要精调,比如该调工具时没调、不该调时调了、参数填错、调错了工具。这些问题通过第二个阶段 RL(强化学习)来解决:正确的调用行为获得奖励强化,错误的行为被抑制,参数生成和工具选择的准确性也在这个阶段被拉高。RL 阶段的核心价值是让模型学会分寸感,知道什么时候该调用,什么时候不该调用。

特殊 Token 的设计

Function Calling 的一个关键设计是往词表中加入了特殊 Token,比如 [tool_call] 表示工具调用开始,[/tool_call] 表示结束,[tool_result] 表示工具返回结果。这些不是普通文本,而是专门设计给外部系统识别的信号。

没有特殊 Token 有了特殊 Token
模型输出 get_weather(北京) 模型输出 [tool_call]{"name":"get_weather"}[/tool_call]
外部系统需要解析自然语言 外部系统只需检测 [tool_call] 标签
解析容易出错、有歧义 解析简单、确定性高

特殊 Token 的本质作用就是给外部系统一个确定性的信号。当模型输出 [tool_call] 时,外部系统就知道接下来的文本需要截获、解析、执行。不同厂商的标签形式不同,但核心逻辑完全一致。

厂商 开始标签 结束标签
OpenAI <function_call> </function_call>
Claude <tool_use> </tool_use>
部分国产模型 [TOOL_CALL] [/TOOL_CALL]

概率如何决定输出

模型在生成每个 Token 时,会在整个词表上计算概率分布。假设词表有 5 万个 Token,模型会计算每个 Token 在当前上下文下的概率值。以用户问”今天北京天气怎么样?”为例,如果系统提示中包含了 get_weather 工具的描述,[tool_call] 这个 Token 的概率可能被推到 0.45 左右,远高于其他候选项,于是模型输出它。

上下文对概率分布的影响非常强烈。同样的天气问题,工具列表包含 get_weather[tool_call] 概率飙升,工具列表为空时”今天”等自然语言 Token 概率更高,用户问”你好”时 [tool_call] 的概率则很低。工具描述的重要性就在这里:它被注入到上下文中,实质上是在调整概率分布,让模型在特定条件下更容易输出工具调用。工具描述中关于适用场景的关键词,与用户问题中的触发词匹配后,对应 [tool_call] Token 的概率就被推高了。

模型并没有主动决定调用工具,概率计算的结果恰好是 [tool_call],仅此而已。

外部执行机制

模型本身不执行函数,只生成文本,这是理解 Function Calling 最容易搞混的地方。完整的执行流程是这样的:用户提问后,模型输出包含 [tool_call] 标记的 JSON 文本;外部系统检测到这个标记,截获并解析 JSON,执行对应的函数调用;函数返回结果(比如 {"temp": 25, "condition": "晴"})被注入回上下文;模型看到工具结果后,生成最终的自然语言回答。

模型内部执行 外部执行
模型需要知道所有工具的实现细节 模型只需知道工具的描述和参数格式
新增工具需要重新训练模型 新增工具只需更新描述,无需重训练
安全风险高,模型可能执行危险操作 外部系统可以做权限控制和审计
无法处理实时数据 外部系统可以查询最新数据

外部执行的设计让职责边界非常清晰:模型只负责说,外部系统负责做。

四种失败模式

模型会犯错,常见的失败模式有四种。第一种是调用错工具,用户让查明天股票行情,模型却调用了 get_weather,原因通常是工具描述不够清晰或模型对工具功能的理解有偏差。第二种是参数填错,比如订机票时起点终点被搞反了,因为参数填充是基于上下文的语义理解,不是精确的逻辑推理。第三种是该调工具时没调,用户问几点了,工具列表里有 get_current_time,模型却自己编造了一个答案,这往往是因为工具描述不够突出,或者模型过度自信地认为自己知道答案。第四种是不该调工具时调了,用户想听关于时间的故事,模型看到”时间”就触发了工具调用。

失败模式 典型原因 缓解方法
调用错工具 工具描述不清晰 优化工具描述,增加示例
参数填错 语义理解偏差 参数校验,增加约束
该调没调 模型过度自信 提示词引导,RL 优化
不该调调了 过度积极触发 工具描述加适用场景说明

不同厂商的实现差异

Function Calling 的核心原理在各厂商间是相同的,差异主要体现在实现细节上。

特殊 Token 的设计方面,OpenAI 和 Claude 都采用 XML 风格标签,可读性好且易于解析,Claude 还支持多工具并行调用。部分国产模型则采用方括号风格,与系统提示词的格式更统一。

工具描述的注入方式也有区别。放在 System Prompt 中灵活易调试但占用上下文窗口;API 提供专门的 tools 字段结构清晰便于管理但需要额外解析;混合方式两者兼顾但可能出现冲突。工具描述的质量和位置直接影响模型对工具调用概率的判断,是工程优化中投入产出比最高的环节。

并行调用能力方面,OpenAI 支持一次返回多个工具调用结果,部分模型只支持串行,需要先调用一个工具拿到结果后再决定是否调用下一个。此外,tool_choice 参数提供了调用控制的粒度:auto 由模型自动决定,required 强制必须调用,none 禁止调用,指定具体函数名则强制调用特定工具。

理解 Function Calling 的底层原理,最大的实际价值在于调试思路的转变。当工具调用出错时,从概率角度分析比从理解角度分析更有效。工具描述是否足够影响概率分布,上下文中是否有干扰信息降低了 [tool_call] 的概率,这些问题的答案直接指向了优化方向。比笼统地说”模型理解有偏差”要实用得多。


参考资源

扫描二维码分享