工具调用总在 SFT 才学?MidTool 把文档也变成了 Agent 的训练输入
一个内部工单 Agent 收到消息:“支付接口又有告警,先看一下,确认后关掉。”它知道工具列表里有 queryIncident、addComment 和 closeIncident,却没有拿到 incidentId。常见失败有三种:随便编一个 ID 调查询;跳过查询,直接调用关闭;或者把一段报错原样贴回去,完全不知道下一步该问什么。
这些问题看上去像函数调用没训够,于是团队常会继续往 SFT 数据里加几百条“用户问题 → tool call”的示例,或再用 RL 奖励成功完成的轨迹。MidTool 提出的角度更靠前:工具使用所依赖的很多知识,本来就散落在运行手册、API 文档、代码样例、PDF 操作说明和结构化 schema 中。如果模型在进入后训练前没有形成这些材料对应的工具边界、参数约束和失败分支,SFT 与 RL 需要在较窄的轨迹监督里同时补齐太多能力。
MidTool 的论文把这种思路称为面向通用工具使用的中训练数据构建:收集 web、PDF、代码和工具工件,再生成与上下文关联的样本和原生 Agent 轨迹。本文不训练模型,也不调用真实 MCP Server;我们用这位内部工单 Agent 贯穿全文,看看“先学会工具边界”在工程上究竟意味着什么,并给出一份可执行的训练样本验收器。
先别急着调用:工单 Agent 缺了什么
先把工单 Agent 的任务拆开。queryIncident 读取事件状态,需要 incidentId;addComment 写入排查说明,需要 incidentId 和 message;closeIncident 是有副作用的操作,除了 incidentId 还需要人工确认。模型只有在把这些条件同时放进上下文时,才有资格组织一次调用。
| 能力 | 工单 Agent 的动作 | 只靠“函数名匹配”会怎样 |
|---|---|---|
| 工具识别 | 把“看一下告警”对应到 queryIncident,而不是 closeIncident | 选择副作用更大的工具,或用自然语言替代调用 |
| schema 落地 | 从消息或追问中得到 incidentId,再填入必填参数 | 编造 ID,或漏掉必填字段 |
| 顺序编排 | 先查询,再补充评论,最后才讨论关闭 | 在状态未知时提前写入或关闭 |
| 缺信息恢复 | incidentId 不存在时明确澄清 | 用猜测填空,或停在一条无用报错上 |
| 权限边界 | closeIncident 前请求人类确认 | 把“确认后关掉”误解为已获得可执行授权 |
MCP 的工具规范提供了一个很具体的参照:工具有唯一名称、说明和输入 schema;客户端能列出工具并发起调用。规范同时提醒,敏感操作应让人可以拒绝,客户端应展示工具输入并保留审计。schema 的作用是把自然语言意图压到一个可校验的参数空间;人类确认则属于权限模型的一部分。
回到内部工单 Agent。消息中的“支付接口又有告警”可以帮助定位服务,却不等于 incidentId。此时应先追问:“请提供要查询的 incidentId,或允许我先按支付服务和时间范围搜索。”如果用户提供了 ID,Agent 才能调用 queryIncident;若结果显示仍在告警,它可以用 addComment 写入已知事实;只有在用户面对关闭操作重新确认后,closeIncident 才进入候选动作。这个案例里的 incidentId、内部工单 Agent 与 closeIncident 会在后面继续出现,因为工具使用不是一个单点选择,而是一串受约束的状态转移。
把文档、schema 与轨迹放进同一条训练链
MidTool 的关键不在“多做几条轨迹”。论文把输入拆成四类来源:web 页面、PDF、代码仓库和结构化工具工件,例如 API 与 MCP skills。它们提供的信息密度不同。文档和 PDF 往往解释业务词、限制、错误处理和操作顺序;代码展示接口如何被真正组装;工具 schema 明确参数、类型与可调用边界。把这些材料只当作 RAG 检索库,会遗漏一个问题:模型是否已经学会将它们压缩成稳定的工具使用表征。
论文因此描述了两条合成支路。第一条从 web、PDF 与代码出发,做上下文关联的轨迹增强:样本依据文档中的接口、字段、工作流和约束生成问答与轨迹,而非凭空写一个“天气工具”的玩具函数。第二条从真实 API 和 MCP skills 出发,构造原生 Agent 轨迹:工具定义被规范化后,轨迹要检查调用顺序、schema 落地、必填参数和工具结果的一致性。它还把信息缺失、澄清、工具切换和多轮恢复列为要覆盖的行为。

图中的左侧呈现的是对内部工单 Agent 有约束力的证据:运行手册说明支付告警的排查顺序,工具卡给出 queryIncident 的必填 incidentId,权限卡把 closeIncident 标为需要确认。中间的合成器把这些证据变成两类样本:有 ID 时的查询路径,以及没有 ID 时的澄清路径。右侧验证器拒绝编造参数、跳过查询和未确认关闭。这样,训练样本中每个工具调用都能追溯到一个 schema 或上下文片段,而非只因语言模型觉得“这一步看起来合理”。
这里有一个重要区别。合成轨迹可以教模型怎样表示一段可用的工具流程,却不能凭自身证明该流程在生产系统有权限执行。对于 closeIncident,数据里可以出现“请求确认”的动作,却不应把真实关闭权限、真实用户身份或私有事件内容放入训练语料。数据工程与运行时授权仍是两套控制面:前者培养对边界的识别,后者决定当前请求能否越过边界。
MidTool 报告的训练设置也符合这种分层思路:论文在 Qwen3-4B-Base 和 Qwen3-8B-Base 上先做 MidTool-Mix 中训练,再叠加 SFT 与 RL,并在 BFCL、tau2-Bench 和 MCP Universe 上比较下游表现。它的结论是这些设置下的实验观察,不应被改写成“任何模型只要吃文档就会变成可靠 Agent”。公开发布集合提供 MidTool-Mix、4B/8B 中训练模型、RL 模型与过滤器,方便研究者检查发布物;是否能在自己的工具域迁移,仍取决于数据许可、schema 质量、训练预算、后训练配方和运行时权限。
从数据配比看,这个方案还有一个容易被忽略的判断:工具轨迹不是越多越好。论文给出的 MidTool-Mix 由 web、PDF、代码和原生 Agent 轨迹共同组成,原生轨迹只是一部分。原因并不神秘。若训练数据几乎全是已经格式化的 tools/call 往返,模型会记住调用表面的序列,却可能不了解某个字段为什么必填、某段手册怎样限定操作、工具返回空结果时应该回到哪里寻找依据。把文档与代码保留在混合数据中,是在给轨迹提供可追溯的语义底座。
内部工单 Agent 的样本可以直观看出这点。假设运行手册写着“支付接口告警先查事件状态;未确认根因前不得关闭”,而 schema 写着 closeIncident 需要 humanConfirmed=true。一条只展示成功关闭的 SFT 样本会告诉模型某种语言形式对应某个调用;一条带上下文的样本还应让模型看见:用户的“确认后”是未来条件,不是当前布尔值;incidentId 缺失时先澄清;查询返回仍在告警时,评论可以记录事实,但关闭动作必须停住。前者是模仿,后者才开始形成对工具条件的归纳。
这也解释了为什么“从文档生成轨迹”不能简化为让一个强模型读完文档后随意编故事。MidTool 描述的上下文分支会先做来源过滤与质量判断,再从文档抽取工具可供性、schema/API 结构、工作流与领域术语等证据;结构化工具分支则对工具定义、参数和可行的轨迹类型做进一步处理。对于自己的数据管道,至少应把三个字段持久化:样本引用的证据片段、从证据抽出的参数约束、以及最终轨迹如何满足或违背这些约束。没有这些字段,后续很难定位一次 hallucination 是模型问题、样本问题,还是工具文档已经过期。
还有一个经常被忽略的边界:工具描述会变。MCP server 新增工具、参数改名、默认值调整,都会让旧轨迹迅速过时。因此训练集不应只保存自然语言提示和最终调用,还应记录所依据的工具版本、schema 摘要与采集时间。运行时再以当前 tools/list 返回的定义为准,并对未知工具、缺失参数和输出不符合预期的情况做校验。中训练提供的是对“如何尊重 schema”的倾向,不能把旧语料里的 schema 当作当前系统的事实源。
对多工具任务而言,恢复路径同样应该进入语料,而不是只记录成功结局。内部工单 Agent 查询不到 incidentId 时,第一恢复动作可以是澄清;查询返回多个候选事件时,可以请求用户选择或用受限的时间窗缩小范围;调用 addComment 失败时,应保留可重试的理由,而不是假装评论已经写入。每个恢复动作都应明确它是在补充信息、切换工具、终止操作还是请求人类介入。这样的标签既能提高数据质量,也能让离线评测更容易区分“模型不会调用”和“模型知道何时不该调用”。
把工具训练样本变成可验收的工程输入
真正落地时,最怕把“多样化”写成一句形容词。内部工单 Agent 的样本必须能被检查:它引用了哪张工具卡?incidentId 是用户给的、检索到的还是模型猜的?调用失败后有没有给出可执行的恢复分支?高风险 closeIncident 是否停在确认节点?
下面是一个教学用验证器。它读取两条内部工单 Agent 轨迹,检查工具存在性、必填参数、查询前置、缺少 incidentId 时是否澄清,以及关闭操作是否有人工确认。它没有访问 MCP,也不代表任何产品的安全认证;价值在于把训练数据的最低边界写成可重复运行的规则。
// lab/validate-trajectories.js
import cases from './trajectories.json' with { type: 'json' };
const required = {
queryIncident: ['incidentId'],
addComment: ['incidentId', 'message'],
closeIncident: ['incidentId', 'humanConfirmed']
};
const results = cases.map((item) => {
const calls = item.steps.filter((step) => step.kind === 'call');
const errors = [];
for (const call of calls) {
if (!required[call.tool]) errors.push(`unknown:${call.tool}`);
for (const key of required[call.tool] ?? []) {
if (!call.args[key]) errors.push(`missing:${call.tool}.${key}`);
}
}
if (!item.hasIncidentId && !item.steps.some((step) => step.kind === 'clarify')) {
errors.push('missing:clarification');
}
if (calls.some((call) => call.tool === 'closeIncident') && !calls.some((call) => call.tool === 'queryIncident')) {
errors.push('order:close-before-query');
}
return { id: item.id, ok: errors.length === 0, errors };
});
console.log(JSON.stringify(results, null, 2));
process.exitCode = results.some((result) => !result.ok) ? 1 : 0;
配合这段脚本,团队可以把训练集验收写成八项清单,而不是等模型在生产环境里暴露问题:
- 每个调用样本都能链接到工具定义、运行手册或代码片段,不接受无来源的“看起来合理”。
- 每个工具的 name、必填参数、类型约束和副作用级别可机器读取。
incidentId等关键参数标明来源:用户输入、已验证工具结果或明确的澄清回答。- 有参数时测试正常调用;没有参数时测试澄清,不用占位符冒充真实值。
- 每个可写工具至少有一条拒绝或确认分支;
closeIncident这类操作不把模型自述当授权。 - 多工具轨迹检查先后依赖,例如查询成功前不关闭内部工单。
- 工具错误、空结果和 schema 变化各有恢复样本,并记录哪个分支只是教学模拟。
- 训练样本与运行时日志分仓保存;生产事件、用户标识和令牌不进入语料。
这份清单并不要求所有工具都生成冗长轨迹。对于无副作用、参数简单的查询工具,一个清晰的 schema-grounded 单调用样本可能已经足够;对涉及写入、权限或跨系统状态的流程,则需要把澄清、确认、拒绝和恢复做成一等样本。重点是样本复杂度由工具风险和依赖关系决定,而不是由“Agent 味”决定。
哪些结论不能从 MidTool 推出
第一,论文中的下游提升是在指定基础模型、数据混合、SFT/RL 配方与三个评测集条件下得到的。它支持“通用工具使用值得作为中训练目标”这个研究方向,不等于你的模型、你的私有 API 或你的 MCP Server 一定获得相同增益。
第二,工具 schema 完整不表示工具本身可信。MCP 规范明确要求服务端校验输入、实施访问控制和限流;客户端也应把工具注解当作不可信信息,敏感操作要征求用户确认并记录使用情况。训练数据可以让内部工单 Agent 更会读 schema,却无法替代运行时认证、最小权限、审计与人工审批。
第三,中训练语料必须处理许可、隐私和污染问题。论文描述了对 web/PDF/code 的过滤、去重和基准排除,但团队自建语料仍要逐个确认来源许可、是否包含客户工单、是否泄露测试集,以及合成轨迹中是否混入了令牌或专有配置。公开集合可以帮助复查研究资产,不是把任何文档直接拿去训练的授权书。
最后,本地验证器只运行了两条虚构的工单轨迹:一条查询并评论,另一条因缺少 incidentId 而澄清。它没有连接 queryIncident、没有发送评论,也没有关闭任何真实工单。把它视为数据门禁的起点,而不是生产 Agent 的验收终点。
先训练可解释的工具边界,再谈更长的轨迹
内部工单 Agent 的第一步不该是抢着调用 closeIncident,而是能回答三个问题:我能用哪个工具?这个调用缺什么参数?现在是否获得了执行权限?MidTool 的启发是,把这些问题所需的文档、代码、schema 和失败路径提前纳入中训练,让后续 SFT 与 RL 不必从零拼接工具世界。
对工程团队而言,最有价值的后续动作是抽一组真实但脱敏的工具定义,按本文清单补齐澄清、确认和恢复样本,再用类似验证器拦住无来源参数与越序调用。数据链路能留下证据,运行时权限仍由独立控制面把关;两者一起工作,工具调用才更可能从一段漂亮的 demo 变成可维护的能力。
如果团队暂时没有中训练预算,也可以先按同一思路改造 SFT 集:把每条工具调用补上引用的 schema 版本、参数来源和失败后的下一步;再把写入和删除类动作拆成“请求确认”与“已经执行”两类标签。这样做不会等价于论文中的大规模中训练,却能先消除最常见的训练数据歧义,并为未来的数据混合、离线回放和运行时审计留下共同的坐标系。等到工具定义变化、失败样本积累或权限事故出现时,团队也能准确知道要补的是哪一层数据,而不是盲目堆更多成功轨迹,并让每次修订留下可复查的原因、范围和影响记录,便于持续改进与长期复用。
先把样本证据补完整。每个可调用动作都应有来源、边界和失败时可走的下一步,并留下清晰、可复核的追溯记录。
资料来源
- MidTool 论文
- MidTool Release
- Model Context Protocol 工具规范
更多推荐


所有评论(0)