Chapter 05

自测与跨章辨析

前四章建立了从协议到设计的完整链条。这一章不教新东西——用三层梯度的题,把它从「读过」逼成「调得出来、讲得清楚」。

这一章你要做到

  • 概念层:不翻书,复述「模型只请求、harness 执行」的 5 个角色与本质。
  • 原理层:重建协议字段(tool_use / tool_result / stop_reason)与 agent loop 的机制。
  • 应用判别层:在具体场景里判断「该用哪一章的方案」——这是把知识迁移到真实工作的唯一检验。

所有答案集中在文末一个折叠块里。每道题先合上教程,把答案写在纸上或编辑器里,写完再展开对照——直接点开看答案,等于把这一章当成又读了一遍,「我读得很顺」的错觉正是这么来的。

·三层梯度

概念层 · 回忆 原理层 · 重建机制 判别层 难度 ↑
图 5.0三层梯度,自下而上越来越靠近真实工作(概念层 ← 01 章;原理层 ← 02–03 章;判别层 ← 01–04 章)。 注意:最上面朱红色的判别层才是检验你真懂了没的地方——它要求你在 01–04 的方案之间做选择,而不是复述任何单章。

A概念层 · 对应 01 章

  1. 一次工具调用往返的 5 个角色,按发生顺序是哪 5 个?模型「停机」发生在第几个? 提示:01 章 §1.3
  2. 「模型执行了 get_weather 工具」这句话错在哪里?正确的表述是什么? 提示:01 章 §1.2
  3. function calling 和 tool use 是两种不同的机制吗?一句话说清两者关系。 提示:01 章 §1.4
  4. 模型「看得到」和「看不到」的东西,分界线在哪里?举一个它看不到的例子。 提示:01 章 §1.1

B原理层 · 对应 02–03 章

  1. Anthropic 把 tool_result 放在哪个 role 的消息里发回去?OpenAI 用的是哪个 role? 提示:02 章 §2.3
  2. OpenAI 的 tool_calls[].function.arguments 是什么数据类型?拿到手要先做什么才能用? 提示:02 章 §2.3
  3. tool_choice 设成 any / required,能钉死模型一定调用你指定的那个工具吗?怎样才能钉死具体工具? 提示:02 章 §2.4
  4. agent loop 的终止由谁决定?写出 while 循环「继续下一轮」的判断条件。 提示:03 章 §3.1 / §3.3
  5. 约束解码(constrained decoding)保证了什么、保证不了什么? 提示:02 章 §2.7
  6. 一个跑了 20 步的 agent,为什么第 20 步特别贵?这个成本叫什么? 提示:03 章 §3.1

C应用判别层 · 跨 01–04 章

这一层的题没有「背过就会」的答案——每一道都要先判断它落在哪些章的概念上,再做选择。括号里标了它横跨的章节。先自己定位根因,再展开答案。

  1. 【03 × 04】生产环境里,一个 agent 偶尔会烧掉大量 token 才返回。日志显示它反复调用同一个搜索工具,且每次返回都很大。这是 03 章的「循环 / 终止 guard」问题,还是 04 章的「工具返回值设计」问题?你会先查什么来分辨这两者?
  2. 【02 × 03】调试时 API 报 400,提示 tool_use 与 tool_result 不匹配。根因更像出在 02 章的「协议配对规则」本身,还是 03 章的「循环在裁剪 / 压缩上下文时破坏了配对」?你怎么定位?
  3. 【01 × 04】模型总是不调用你提供的 calculator 工具,宁可自己硬算、还算错。结合 01 章「调不调由模型决策」和 04 章「description 是最高杠杆」,给出你的排查顺序。
  4. 【04 设计 × 04 前沿】要给一个 agent 接入 8 个内部系统、几十个工具。直接为每个端点写一个 tool 定义、全塞进 context,会出什么问题?在「合并成更少的高层工具」「用 Tool Search 按需加载」「用 MCP 标准化接入」之间,你怎么权衡取舍?
  5. 【03 × 04 前沿】一个数据处理流水线要串起 5 个工具,中间结果体积很大。每一步都吐 JSON、过一遍模型 context,太贵。该用哪种前沿方案?结合 03 章的 re-prefill 成本,解释它到底省在哪里。
亲手画一张图

合上教程,在纸上或 Excalidraw 里画出工具调用的核心循环——只画 5 个角色,加上那条「交还控制」的边。画完翻回 01 章的图 0 / 图 1.1 对照:你画的图里,模型停机的那一步标出来了吗?「执行」和「回填」落在分界线的哪一侧?

答案(先把三层都做完再展开)

概念层

  1. 顺序:① 工具定义 → ② 模型决策 → ③ tool_use 请求 → ④ harness 执行 → ⑤ tool_result 回填。模型停机发生在第 ③ 个(生成 tool_use 请求后,stop_reason 变成 tool_use,生成中止)。
  2. 错在「执行」二字。模型只生成了一个调用请求然后停机,真正执行 get_weather 的是 harness(你的代码)。正确表述:模型请求调用 get_weather,harness 执行后把结果回填。
  3. 是同一套机制。OpenAI 早期叫 function calling、现在把 function 当作 tool 的一种 type,Anthropic 叫 tool use——声明工具 → 模型请求 → 你执行 → 回填,流程完全一致。
  4. 分界线是「context 里的 token」:模型只能看见进入 context 的内容(系统提示、对话历史、工具定义、回填的 tool_result)。看不到的例子:你的数据库、实时天气 API、文件系统、工具的真正实现——这些它永远碰不到,只能请求 harness 代它去碰。

原理层

  1. Anthropic 放在 role: "user" 的消息里(不是专门的 tool 角色);OpenAI 用一条 role: "tool" 的消息。
  2. 是 JSON 字符串,不是对象。要先 JSON.parse(或等价反序列化)才能访问字段。对照之下 Anthropic 的 input 到手已经是对象。
  3. 不能。any / required 只强制「调用某个工具」,不指定是哪个。要钉死具体工具,得用指定形式:Anthropic {"type":"tool","name":"x"}、OpenAI {"type":"function","function":{"name":"x"}}。
  4. 由 harness 决定,不是模型。继续条件:resp.stop_reason == "tool_use"(模型还在请求工具就再转一轮;否则跳出循环)。模型自己不会可靠地判断「任务完成」。
  5. 只保证语法:产出的 JSON 一定符合 schema 的结构(字段、类型、枚举合法)。保证不了语义:参数形状对、值却错(valid but wrong)完全可能。
  6. 因为每一轮都要把不断变长的对话历史整个重新发一遍给模型(re-prefill);第 20 步要把前 19 步的全部内容再喂一次。这个随轮数累积的成本就叫 re-prefill(重新预填充)。

应用判别层

  1. 两者都查,但先分辨症状。「反复调用同一个工具」指向 03 章——缺少重复调用检测 / max_iterations guard,模型在原地打转;「每次返回都很大」指向 04 章——返回值没有分页 / 截断 / response_format,结果撑大 context、又反过来让模型更难收敛。先看日志里「同样的工具 + 同样的参数」是否重复出现(→ 03 的 guard 问题),再看单次 tool_result 的体积(→ 04 的返回值设计)。两者常常同时存在、互相放大。
  2. 根因几乎总在 03 层,不在 02。协议的配对规则(§2.2)是固定的、本来就对;真实事故里破坏配对的,是循环在裁剪 / 压缩上下文时丢了 tool_use 却留下孤立的 tool_result,或并行结果回填顺序错乱。定位方法:打印发给 API 的完整 messages,检查每个 tool_use_id 是否都有且仅有一个配对的 tool_result,以及它们的先后顺序。
  3. 排查顺序:先接受 01 章的事实——「调不调工具是模型的概率判断,不是 if-else」,所以这是个可以用 prompt / 定义来影响的问题;然后直接动 04 章的最高杠杆——把 calculator 的 description 写清楚「何时必须用它」(例如「任何算术运算都调用本工具,不要自行心算」),必要时配合 tool_choice 在该场景下强制调用。换模型是最后才考虑的。
  4. 全塞进 context 会让工具定义吃掉大量 token,且工具一多越容易重叠、模型越选不准(04 §4.5)。权衡:先做减法——能合并成更少的高层工具就合并(降低歧义 + 省 token);工具数量本身降不下来时,用 Tool Search Tool 让定义按需加载(2025-11,省约 85% 定义 token);如果这些系统还要被多个 agent 应用复用,则用 MCP 把它们标准化成 server(M×N → M+N),一次接入处处可用。三者不互斥,常组合使用。
  5. 用「模型写代码调工具」(Programmatic Tool Calling,2025-11)。它让模型写一段代码在沙箱里把 5 个工具串起来调用,中间结果留在沙箱、不进 context,只把最终结果带回模型。省的正是 03 章那笔 re-prefill 成本——「每步吐 JSON」要把每个中间结果都塞进 context、并在后续每一轮重新预填充;代码方式把这 5 步的中间数据挡在了模型 context 之外。