第 1 层 · 教学 · 1.7 训练闭环 · 第 2 讲
工具使用能力:训练与注入的双重来源
一个 agent 面对几十个工具时,凭什么知道该调哪一个?答案分两半:一半是训练时学到的通用能力(读懂说明书、按 schema 填表),另一半是每次请求动态注入的工具清单。把两半拆开,就能精确回答「模型用错 / 不用某个工具时该改什么」——也解释了为什么「看不见的工具不会被选中」。
概览
2
能力来源(训练 / 注入)
30–50
工具选择准确率下降阈值①
~55K
多 server 工具定义的 token 开销①
-95%
Tool Search 后的上下文节省③
| 项目 | 说明 |
|---|---|
| 本卡定位 | 「训练闭环」单元第 2 讲 · 微观机制:工具能力 = 训练学的通用能力 + 推理时注入的具体清单 |
| 前置知识 | 第 1 讲的五阶段链路;见过 tool_call 协议格式(ollama-tool-call-substitute §2 协议基线) |
| 读完会什么 | 能画出一次 Tool Use 完整往返;遇到工具行为问题时能三分归因(改描述 / 改训练 / 改可见性);知道轨迹该补记哪三件事 |
| 证据分级 | ① 官方文档 / 技术报告 · ② 论文 · ③ 逆向分析 / 工程博客 · ④ 新闻——同上一讲,数字连同级别一起引 |
第一篇 · 机制:tools 字段如何变成模型看到的东西
第 1 卡 · 2 官方证据 + 1 时序图
图 1 · 一次完整往返:提议(模型)→ 校验与执行(宿主)→ 观察(tool_result)→ 继续 → 终止。「Agent = function calling + while 循环 + 停止条件」,harness 是循环的持有者。
工具菜单是注入的:从 tools 字段到 special system prompt
候选 A「每个产品专门训练一个模型」——证伪:换一套完全不同的工具,同一个模型照样会用。候选 B「工具清单背在参数里」——证伪:请求删掉 tools 字段,能力即刻消失,参数没变行为变了。候选 C 成立:训练学「填表」,注入给「菜单」。官方两条证据①:
核心知识点
- tools 是顶层参数,模型不内置任何工具(官方原话①:All tools must be explicitly provided by you, in each API request);工具定义三件套 = name(函数名)+ description(说明书)+ input_schema(表单)
- 工具定义计入 input tokens:计费口径明确「包括 tools 参数」①——工具菜单和你说的话,在模型眼里是同一种东西:上下文里的 token
- 服务端构造 special system prompt:使用 tools 时 API 自动附加一段启用工具使用的特殊 system prompt,模板顺序为——工具使用指令 → 格式指令 → 工具定义(JSON Schema) → 用户 system prompt → 工具配置①;关键细节:工具定义注入在用户 system prompt 之前;这段固定开销约 286–406 tokens①
- tool_choice 的 prefill 行为:设为 any / tool 时,API 会预填 assistant 消息,模型不再先输出自然语言解释①
- description 是决定性因素(官方原话①:This is by far the most important factor in tool performance)→ 工具描述是可干预的实验变量:改一段描述就能改行为
sequenceDiagram
participant U as 用户
participant H as harness(宿主程序)
participant A as 模型 API
U->>H: 修一下这个 bug
H->>A: 请求 = messages + tools 菜单
Note over A: 服务端拼装上下文:
工具定义在用户 system prompt 之前 A-->>H: tool_use 块 {name:"Read", input:{...}} Note over H: 模型只是"填了一张表"
此刻现实世界什么都没发生 H->>H: 权限校验 → 真正执行 H->>A: messages + tool_result(文件内容) A-->>H: 下一个 tool_use 或合成回答 H-->>U: 交付
工具定义在用户 system prompt 之前 A-->>H: tool_use 块 {name:"Read", input:{...}} Note over H: 模型只是"填了一张表"
此刻现实世界什么都没发生 H->>H: 权限校验 → 真正执行 H->>A: messages + tool_result(文件内容) A-->>H: 下一个 tool_use 或合成回答 H-->>U: 交付
对本平台的直接含义:轨迹必须同时记录模型提议与环境实际执行两层——「模型以为调成功了但实际报错」是出错分析里缺一不可的一对(第 4 卡展开数据缺口)。
课堂实训
- 用 Anthropic SDK 定义 1 个工具并打印实际请求结构,指认工具定义在上下文里的位置
- 把 tool_choice 从 auto 改为 tool,观察输出是否还包含自然语言前言
doc: Anthropic Tool Use Overview / Define Tools
关联:harness 循环
第二篇 · 训练侧:训的是「会填表」,不是「记得这张表」
第 2 卡 · 1 边界表 + 1 辨析
什么能训、什么不能训:一张归因表
核心知识点
- 两阶段分工(本讲核心结论):训练 / 后训练学「读说明书 → 输出合法 tool_use」的通用能力与「何时该调、何时该答」;推理(每次请求)决定具体有哪些工具、叫什么、参数怎么填——没有任何 client-side 工具是被「永久学会」的,schema 一删,本次请求中该能力即消失
- 约束解码的真相:默认 strict: false 靠微调学会生成符合 schema 的 JSON 但不保证严格一致(可能出现字符串 "2" 代替整数 2、漏必填字段);开启 strict: true(OpenAI Structured Outputs)才把 JSON Schema 编译为语法约束 token 生成——Anthropic 未对 tool_use 做此承诺,宿主必须校验参数,不能赌模型永不出错
- 格式纪律可以被 RL 强化:Qwen3-Coder-Next 在 RL 里对非法 tool call 施加 turn 级 token 惩罚①(上一讲第 5 卡的三层奖励)——「工具调用的格式纪律是练出来的」
- 工具选择能力要专门训:ToolTrain 两阶段(拒绝采样 SFT → 基于规则的 RL,奖励用定位函数排名类指标)证明工具使用是独立可训练维度②
- ToolRM 的发现:在自然语言输出上训练的奖励模型判断工具调用很差,需要专门面向工具的奖励模型②——印证「工具调用是独立评估维度」
- server-side 工具是例外:联网搜索、代码执行等由厂商托管、模型训练过——注入生态里混着少量「真·内置」能力,表述时必须加限定
| 能力项 | 来源 | 归因后的动作 |
|---|---|---|
| 读 schema → 产出 tool_use | 训练(通用技能) | 换更强的模型 / 等厂商训练 |
| 参数合法性 / 格式纪律 | 训练(可 RL 强化) | 见三层奖励①;运行时仍须校验 |
| 具体有哪些工具 | 注入(每次请求) | 改 tools 列表即刻生效 |
| 何时该用哪个工具 | 注入的 description 决定 | 改描述——最便宜的行为干预点 |
| 工具的实际执行 | 宿主 | 改 harness 代码 |
| server-side 工具(搜索 / 代码执行) | 例外:训练内置 | 无注入可言,厂商托管 |
课堂实训
- 定义两个语义相近的工具(如 send-user / send-channel),观察描述模糊时的误选率
- 改一次 description 措辞,重跑同一任务,记录行为差异——体会「描述即软提示」
思考与讨论
- 默认模式不保证 schema 严格一致,运行时校验的责任落在谁身上?这对 harness 设计意味着什么?
- 「工具描述是实验变量」对 benchmark 的可复现性提出什么要求?(伏笔:第 4 卡的 schema 版本锚定)
paper: ToolTrain
paper: ToolRM / FC-RewardBench
doc: OpenAI Structured Outputs(strict 承诺口径)
第三篇 · 动态注入:Tool Search 与工具可见性
第 3 卡 · 2 代价 + 1 反直觉机制 + 1 图
图 2 · 工程博客案例(③):58 个 MCP 工具场景下,全量加载 ≈ 77K tokens vs Tool Search 后 ≈ 8.7K——省的是模型上下文;完整定义仍在请求里。官方文档口径为「降低 85%+」①。
defer_loading 与 Tool Search:控制的是「进不进上下文」
核心知识点
- 工具膨胀的代价:多 server 场景(GitHub / Slack / Sentry / Grafana / Splunk)工具定义可吃 ~55K tokens①;Anthropic 工程博客举过 58 个 MCP 工具 ≈ 55K、内部见过 134K 的案例③——模型还没干活先交「菜单费」
- 选择准确率在 30–50 个工具之后开始下降① → 「更多工具」不等于「更强」,反而更笨
- Tool Search 机制:被标 defer_loading: true 的工具不进入上下文;初始只加载 ToolSearch 工具本身(约 500 tokens③)+ 非延迟工具;模型需要时先搜索,命中项以 tool_reference 返回并展开为完整定义(默认最多 5 个)①
- 反直觉点(必须讲清):defer_loading 控制的是「是否进入 context」,不是「是否发送」——每个请求仍发送全部工具的完整定义,服务端需要它们来执行搜索与展开①;省的是模型上下文,不是网络流量
- 效果数据(口径各异,注意级别):官方文档口径「token 降低 85%+、选择准确率在数千工具规模保持稳定」①;工程博客案例「上下文约 77K → 约 8.7K(-95%),Opus 4 选择准确率 49% → 74%」③
- Claude Code 侧:内置 ToolSearch 工具按需装载 MCP 工具(③逆向分析佐证);配置旋钮 ENABLE_TOOL_SEARCH(unset / true / auto / auto:N / false)与 alwaysLoad 豁免③
思考与讨论
- 「模型没用某个工具」有哪些可能的解释?在轨迹数据上如何区分「不需要」与「看不见」?(直接引出第 4 卡)
doc: Anthropic Tool Search Tool(defer_loading / 30–50 / 85%)
blog: Anthropic Advanced Tool Use(-95% 案例,③)
第四篇 · 落到本项目:轨迹里该记录什么
第 4 卡 · 1 现状 + 3 缺口
trajectory_schema 现状与三处缺口
核心知识点
- 现有能力(experiment_modules/recording/trajectory_schema.py):12 类事件枚举、TOOL_USE / TOOL_RESULT 分离、parent_event_id 因果链、causality_id、tool_name / tool_input / tool_output / token_usage——「提议 / 执行分离」这条已经做对了
- 缺口一:工具可见性快照——不记录本轮请求实际装载了哪些工具、哪些被 defer,「为什么没用某工具」永远无法区分「不需要」与「看不见」
- 缺口二:提议 vs 执行的 outcome 判定——事件间有因果链接但没有状态标志,「模型以为成功」与「环境实际报错」在数据上不可区分
- 缺口三:工具 schema 版本锚定——工具描述是动态生成的软提示(第 1 卡),不锚定版本则跨期实验不可比:上个月的行为差异是模型的问题还是描述改了?
课堂实训
- 打开本仓一条真实轨迹(experiments/ 下任一 .traj),尝试回答「这一轮模型能看到哪些工具」——亲手体会数据缺口
收口:一张心智图 + 一句话
flowchart LR
subgraph M["模型侧"]
T["训练获得
读 schema 填表 + 格式纪律"] I["推理时注入
工具菜单(name/description/schema)"] end subgraph E["环境侧"] X["宿主执行
校验 → 跑函数"] V["可见性管理
defer / ToolSearch"] end T ---|填表| X I ---|可见| T V ---|控制| I
读 schema 填表 + 格式纪律"] I["推理时注入
工具菜单(name/description/schema)"] end subgraph E["环境侧"] X["宿主执行
校验 → 跑函数"] V["可见性管理
defer / ToolSearch"] end T ---|填表| X I ---|可见| T V ---|控制| I
一句话收束:菜单是注入的,填表能力是训练的,执行是宿主的,校验是必须自建的。前两讲回答了「能力从哪来」,最后一讲回答「分析到哪去」——过程监督如何把轨迹变成训练信号。