Function Calling(函数调用,工程里也常叫工具调用)让大模型不只生成自然语言,还能按约定格式调用搜索、浏览器、数据库、Python 代码执行器等外部工具。对于 Deep Research Agent 来说,工具调用能力直接决定系统能不能完成复杂研究任务。
通用模型已经具备基础工具调用能力,比如 GPT-4o、DeepSeek V3、Qwen3 都能根据工具描述生成调用参数。但“能跑通 Demo”和“能稳定跑生产任务”不是一回事。一个金融研究类 Agent 可能需要先搜索企业公告,再打开网页读取财报,再用 Python 计算债务期限结构,最后综合多份资料给出风险判断。这里任何一步工具选错、参数写错、顺序错了,都会让后续推理偏离事实。
一个典型现象是:基座模型不做专项微调时,工具调用准确率可能停在 65%~75%。以 Qwen3-30B-A3B 为例,在某个 Deep Research Agent 任务集上,直接使用原生工具调用能力得到 71% 准确率;经过 FC-SFT(Function Calling SFT,面向工具调用的监督微调)后,在同一测试集和判分口径下达到 94%。
这个提升不是靠“把学习率调一调”碰出来的,核心在数据工程:构造什么问题、让 Teacher 模型生成什么轨迹、过滤掉哪些样本、评估集怎么设计、如何防止模型只会调用工具而忘记普通问答。
工具调用到底错在哪里
工具调用不是一个单点能力,而是一条链路。模型需要判断是否需要工具、选择哪个工具、生成合法参数、根据工具返回继续推理,并在合适的时候停止调用。
sequenceDiagram
participant U as 用户
participant M as Agent 模型
participant T as 工具执行器
participant D as 外部数据源
U->>M: 提出复杂研究问题
M->>M: 判断是否需要工具
M->>T: 生成工具调用 name + arguments
T->>D: 查询 / 访问 / 计算
D-->>T: 返回结果
T-->>M: Observation
M->>M: 基于返回结果继续推理
M-->>U: 生成最终答案
常见错误可以拆成四类:
| 错误类型 | 例子 | 后果 |
|---|---|---|
| 工具选择错误 | 应该用网页访问工具,却只调用搜索工具 | 得到的是摘要片段,缺少原始证据 |
| 参数格式错误 | company_name 写成 company,或时间字段格式不符合约定 | 工具执行失败,Agent 被迫猜答案 |
| 调用顺序错误 | 还没拿到财报数据,就先让 Python 计算同比增长 | 计算基于空数据或错误数据 |
| 过度调用工具 | “2 + 2 等于多少”也调用 Python | 说明模型被训练成“有问题就调工具” |
FC-SFT 要解决的不是让模型“知道有工具”,而是让模型学会在领域任务里稳定走完整链路。
FC-SFT 冷启动的整体链路
SFT(Supervised Fine-Tuning,监督微调)需要有标注样本。对于工具调用任务,样本不是简单的“问题—答案”,而是“问题—推理—工具调用—工具返回—继续推理—最终答案”的完整轨迹。
冷启动阶段没有大量线上成功案例可用,所以需要从少量高质量种子问题出发,用 Teacher 模型生成轨迹,再经过质量门槛筛选,形成可训练数据。
flowchart LR
A[种子问题<br/>约 200 条] --> B[问题扩充<br/>约 1200 条]
B --> C[Teacher 模型生成轨迹]
C --> D[格式验证]
D --> E[逻辑验证]
E --> F[人工抽检]
F --> G[高质量轨迹<br/>约 840 条]
G --> H[混入通用指令数据]
H --> I[FC-SFT 训练]
I --> J[三层测试集评估]
这条链路里,每一步都在控制一个风险:
| 环节 | 控制目标 | 如果做不好 |
|---|---|---|
| 种子问题 | 覆盖真实任务分布 | 模型只会处理人工想象出来的问题 |
| 问题扩充 | 增加表达和参数变化 | 模型对措辞和实体变化不稳 |
| Teacher 轨迹 | 提供可模仿的工具调用路径 | 模型只学到最终答案,学不到过程 |
| 质量过滤 | 去掉格式错、逻辑错、绕远路样本 | 模型把坏习惯也学进去 |
| 数据配比 | 防止工具类型偏置和能力遗忘 | 模型过度调用某类工具,普通问答退化 |
| 评估闭环 | 确认提升真实存在 | 指标看起来变好,实际不可用 |
种子数据:少量高质量问题比盲目堆量重要
工具调用 SFT 的起点是种子问题。一个可操作的规模是 200 条左右,来源通常分两类。
真实日志
如果系统已经有早期用户或内部测试用户,线上日志是最有价值的种子来源。日志里的问题更接近真实分布,包含很多人工设计样本不容易覆盖的表达方式。
例如金融研究场景里,用户不会只问“查某公司的营收”,更可能提出这种组合型任务:
分析某大型企业未来三年的债务到期分布和再融资风险。
这个问题隐含了多步操作:查债务结构、定位到期时间、获取融资渠道信息、对比现金流和行业环境,最后才能形成风险判断。
专家编写
真实日志有一个问题:高频问题多,低频但关键的复杂场景少。专家编写可以补齐这些缺口,比如:
- 多跳推理任务;
- 需要实时信息的任务;
- 需要多个来源交叉验证的任务;
- 工具返回异常时的处理任务;
- 不应该调用工具的简单问题。
在一个比较稳妥的冷启动方案里,专家编写样本可以占 30%~40%,用来补齐日志天然覆盖不到的区域。
从 200 条扩到 1200 条:扩充的是分布,不是简单复制
200 条问题不足以支撑训练,需要扩充到约 1200 条。扩充不能只是同义句堆叠,而是要让模型看到表达、参数和任务组合的变化。
| 扩充策略 | 做法 | 例子 | 训练价值 |
|---|---|---|---|
| 问题改写 | 保持任务不变,改变表达方式 | “查 XX 企业营收” → “能帮我看一下 XX 企业 2024 年营业收入吗” | 提升对自然语言表达差异的鲁棒性 |
| 参数变化 | 替换时间、公司、行业、数值等变量 | “2024 年” → “2023 年”,“A 公司” → “B 公司” | 覆盖更多实体和时间组合 |
| 组合扩展 | 把单一任务组合成多步任务 | “查 A 公司营收” + “对比 B 公司” | 训练多工具协同和调用顺序 |
组合扩展尤其重要,因为 Deep Research Agent 的难点通常不在单次工具调用,而在多步依赖关系。搜索得到的信息可能决定下一步访问哪个网页,网页内容又决定是否需要 Python 计算。
Teacher 模型生成轨迹:让学生模型学过程,而不只学答案
扩充后的问题还不能直接用于 FC-SFT,需要 Teacher 模型生成完整轨迹。Teacher 可以选择工具调用能力较强、成本可控的大模型,例如 DeepSeek V3。
轨迹格式要稳定,不能让模型自由发挥。一个常见格式如下:
<think>
分析用户任务,判断需要哪些信息,决定调用哪个工具。
</think>
<tool_call>
{
"name": "search",
"arguments": {
"query": "某企业 2024 年 年报 营业收入",
"max_results": 5
}
}
</tool_call>
<observation>
工具返回的搜索结果或页面内容。
</observation>
<think>
根据工具返回结果判断是否需要继续访问网页、计算或交叉验证。
</think>
<tool_call>
{
"name": "python",
"arguments": {
"code": "revenue_2024 = 123.4\nrevenue_2023 = 110.2\ngrowth = (revenue_2024 - revenue_2023) / revenue_2023\nprint(growth)"
}
}
</tool_call>
<observation>
0.1197
</observation>
<final_answer>
综合工具结果形成最终回答。
</final_answer>
这个格式有三个关键设计。
<think> 让推理过程显式出现。训练目标不是死记“某类问题调用某个工具”,而是让模型学会判断:为什么要调用工具、当前信息是否足够、下一步该做什么。
<tool_call> 使用 JSON(JavaScript Object Notation,一种结构化数据格式)。结构化格式方便自动校验,工具名、参数名、字段类型都可以被程序检查。
<observation> 和 <think> 交替出现。这样模型学到的是“看结果再决定下一步”,而不是一次性生成一串不看反馈的调用。
质量验证:Teacher 生成的数据不能直接全收
Teacher 模型生成的轨迹并不天然正确。冷启动中,1200 条问题可能只留下约 840 条高质量轨迹,过滤比例达到 30% 并不罕见。
质量门槛可以分成三层。
| 验证层级 | 检查项 | 处理方式 |
|---|---|---|
| 格式验证 | JSON 能否解析、工具名是否存在、必填参数是否齐全、字段类型是否正确 | 自动过滤 |
| 逻辑验证 | 轨迹步数是否异常、是否重复调用同一工具、最终答案是否有工具结果支撑 | 自动过滤或打标复查 |
| 人工抽检 | 推理路径是否合理、是否绕远路、是否使用了不可靠证据 | 抽样检查,反推数据构造规则 |
格式验证只能保证“能执行”,不能保证“做得对”。例如 Teacher 可能连续搜索同一个关键词三次,格式完全合法,但这种轨迹会让学生模型学到低效行为。
轨迹步数也需要约束。对于 Deep Research 任务,少于 2 步可能说明没有真正使用工具,多于 15 步可能说明模型陷入循环或检索策略失控。阈值不需要固定照搬,但必须结合任务复杂度设定上限和下限。
数据分布:模型会学会样本里的偏见
高质量不只指单条样本正确,还包括整体分布合理。如果 840 条轨迹里 70% 都是搜索工具,只有 10% 涉及 Python 计算,模型就会偏向搜索;即使面对明确需要计算的问题,也可能先去搜索。
工具调用数据至少要检查这些分布:
| 分布维度 | 需要观察什么 |
|---|---|
| 工具类型分布 | search、browser、python、database 等工具比例是否符合线上任务 |
| 调用步数分布 | 单步、两三步、多步复杂任务比例是否合理 |
| 参数类型分布 | 时间、实体、行业、数值、布尔开关等字段是否覆盖充分 |
| 是否调用工具 | 需要工具与不需要工具的问题是否都有 |
| 成功与异常路径 | 工具返回空结果、错误结果、冲突结果时是否有样本 |
分布问题最好在种子构造阶段解决,而不是训练后靠调参补救。训练数据缺少某类行为时,模型很难凭空学出来。
防遗忘:不能让模型变成“工具调用机器”
专项 SFT 有一个常见副作用:模型在目标任务上变强,但通用能力下降。工具调用微调尤其容易出现这种问题,因为所有训练样本都在暗示模型“遇到问题就调用工具”。
解决办法是在训练集中混入一定比例的通用指令数据。例如在一个实验配置中,混入 30% 通用指令样本可以控制普通问答退化。这个比例不是理论公式推出来的,而是实验调出来的工程参数。
pie title FC-SFT 训练数据配比示例
"工具调用轨迹数据" : 70
"通用指令数据" : 30
通用指令数据的作用不是提升工具调用准确率,而是保住模型原有能力。没有这部分数据,模型可能对“2 + 2 等于多少”这类问题也调用 Python,表面上工具调用很积极,实际是过拟合。
评估:71% 到 94% 必须有固定口径
工具调用准确率不是一句话就能说清的指标。要让“71% 到 94%”可信,至少要固定四件事:
- 同一个基座模型;
- 同一套工具定义;
- 同一个测试集或严格隔离的同分布测试集;
- 同一套判分规则。
一个简单口径可以这样定义:
工具调用准确率 = 工具调用正确的样本数 / 参与评估的样本总数
但多步任务里还要说清“正确”的含义。常见判分方式有两种:
| 判分方式 | 规则 | 特点 |
|---|---|---|
| Step-level | 每一次工具调用单独判分 | 能定位具体错误类型 |
| Trajectory-level | 整条轨迹的工具选择、参数、顺序都正确才算对 | 更严格,更接近真实任务成功率 |
生产系统更关心 Trajectory-level,因为用户不会因为其中某一步对了就得到正确答案。训练调试阶段则需要 Step-level,方便定位是工具选错、参数错,还是顺序错。
三层测试集:既测基础,也测泛化和过拟合
评估集不能和训练集重叠。更关键的是,测试场景要覆盖训练中少见甚至没见过的分布。
| 测试层级 | 规模示例 | 目标 |
|---|---|---|
| 标准测试集 | 100 条 | 从种子数据中预留,不参与训练,用来验证基础能力 |
| 分布外测试 | 50 条 | 覆盖 4 个以上工具协同、工具返回异常等低频复杂场景 |
| 对抗测试 | 30 条 | 构造“不该调用工具”的问题,检查是否过度工具化 |
除了总体准确率,还要拆解指标:
| 指标 | 说明 |
|---|---|
| 工具选择准确率 | 是否选到了正确工具 |
| 参数格式准确率 | 工具选对后,参数字段和值是否符合 schema |
| 调用顺序准确率 | 多工具任务中,依赖顺序是否合理 |
| 最终答案支撑率 | 答案是否由工具返回结果支撑,而不是凭空生成 |
| 遗忘率 | 普通问答、知识理解等非工具任务是否退化 |
在一个完整迭代中,冷启动阶段可以先得到约 840 条高质量轨迹;后续继续通过日志、失败样本回流、人工修正扩到 5000 条级别。以 FC-SFT 5000 样本配置为例,工具调用准确率从 71% 提升到 94%,同时 MMLU(Massive Multitask Language Understanding,大规模多任务语言理解评测)下降 0.3%,处在误差范围内,说明专项微调没有明显破坏通用能力。
Claude Code 的 ExtractionCoordinator:选择性积累比无差别堆积更重要
Claude Code 里的 ExtractionCoordinator 可以作为一个类比。Deep Research Agent 的 FC-SFT 是通过筛选训练轨迹,让模型逐渐学会稳定调用工具;ExtractionCoordinator 则是在对话过程中提取可持久化记忆,让系统逐渐了解一个项目。
两者解决的问题结构相似:系统一开始缺少领域知识,需要通过使用过程积累信息,但不能把所有信息都塞进去。
class ExtractionCoordinator:
MIN_NEW_MESSAGES = 4
async def maybe_extract(self, session):
new_messages = session.messages_since_last_extraction
if len(new_messages) < self.MIN_NEW_MESSAGES:
return
if self.is_running:
self.dirty = True
return
await self.run_extraction(session)
async def run_extraction(self, session):
self.is_running = True
try:
memories = await self.model.extract_memories(
session.recent_messages
)
if memories:
await self.memory_writer.update(memories)
finally:
self.is_running = False
if self.dirty:
self.dirty = False
await self.run_extraction(session)
MIN_NEW_MESSAGES = 4 是一个门槛:不是每条消息都触发记忆提取,而是积累到一定数量后再异步处理。这样可以减少无意义提取,也避免对主流程造成阻塞。
这个机制和 FC-SFT 的质量过滤有相同的工程原则:
| 系统 | 积累对象 | 门槛 | 目标 |
|---|---|---|---|
| Deep Research Agent FC-SFT | 工具调用轨迹 | 格式验证、逻辑验证、人工抽检、分布控制 | 让模型稳定学会工具调用 |
| Claude Code ExtractionCoordinator | 项目记忆 | 至少 4 条新消息、模型判断是否值得写入 | 让系统逐渐理解项目上下文 |
有门槛的选择性积累,比无差别堆数据更可靠。训练数据如此,长期记忆也是如此。
把 FC-SFT 工程链路讲清楚的口径
如果要解释一个“工具调用准确率从 71% 到 94%”的指标,最好按问题、流程、评估三个层次讲。
问题口径
通用模型原生支持工具调用,但在复杂多工具协同任务中,准确率通常只能达到刚刚可用的水平。FC-SFT 的目标是在不更换基座模型的前提下,用领域内高质量轨迹数据提升工具选择、参数生成和调用顺序的稳定性。
流程口径
数据工程可以拆成六步:
flowchart TD
A[收集种子问题<br/>日志 + 专家编写] --> B[扩充问题<br/>改写 + 参数变化 + 组合扩展]
B --> C[Teacher 生成 Thought-Action-Observation 轨迹]
C --> D[自动过滤格式错误和逻辑错误]
D --> E[人工抽检推理质量]
E --> F[混入通用指令数据防遗忘]
F --> G[执行 FC-SFT]
关键数字可以这样描述:
| 环节 | 示例规模 |
|---|---|
| 种子问题 | 约 200 条 |
| 扩充后问题 | 约 1200 条 |
| 过滤后高质量轨迹 | 约 840 条 |
| 后续迭代训练样本 | 约 5000 条 |
| 通用指令混合比例 | 约 30% |
评估口径
评估不能只报一个总准确率,要说明测试集隔离、测试层级和拆解指标。一个可信评估至少包括:
- 标准测试集:验证基础工具调用能力;
- 分布外测试:验证复杂任务泛化;
- 对抗测试:验证是否过度调用工具;
- 参数格式、调用顺序、最终答案支撑率;
- 普通问答退化情况,例如 MMLU 变化。
这样讲出来的 94% 才不是孤立数字,而是一个完整实验闭环的结果。
容易踩的坑
只扩数量,不控质量
5000 条粗糙轨迹不一定比 840 条高质量轨迹更好。如果大量样本存在重复搜索、无依据回答、参数错误,模型会把这些模式也学进去。
只测工具调用,不测最终答案
工具调用正确不等于最终答案正确。Agent 可能调用了正确工具,却错误解读了工具返回结果。评估时要检查最终答案是否能被 Observation 支撑。
测试集和训练集泄漏
如果测试问题由训练问题简单改写而来,指标会虚高。标准测试集需要预留,分布外测试和对抗测试要单独构造。
忽略“不调用工具”的样本
工具调用微调不能只训练调用工具。否则模型会把“回答问题”误学成“必须调用工具”。简单算术、常识问答、开放闲聊都需要作为负例或通用指令混入。
不记录失败样本
FC-SFT 不是一次性工程。线上失败样本最能暴露真实缺口:工具描述不清、参数 schema 设计不合理、某类任务缺少训练样本、模型容易在某一步循环。失败样本经过清洗和修正后,可以进入下一轮训练数据。
关键结论
Deep Research Agent 的工具调用能力要从“偶尔会做”变成“稳定会做”,核心不在调参,而在数据闭环。
一条可复现的 FC-SFT 冷启动链路包括:用真实日志和专家样本构造种子问题,通过改写、参数变化和组合扩展扩大覆盖面;让 Teacher 模型生成 Thought-Action-Observation 轨迹;用格式验证、逻辑验证和人工抽检过滤低质量样本;控制工具分布,并混入通用指令数据防止遗忘;用标准测试、分布外测试和对抗测试验证真实收益。
“71% 到 94%”这类指标只有放在固定模型、固定工具、固定测试集、固定判分规则下才有意义。数字本身不难写,难的是把背后的数据构造、训练配比、质量门槛和评估闭环讲清楚。