Day 11 · 读书《AI Agents in Action》第3章:Tool Use
| 属性 | 值 |
|---|---|
| 日期 | 2026-08-27 |
| 周次 | 第3周:读书+对照分析 |
| 轨 | 轨1 · Agent 通用架构认知 |
| 时间 | 1 小时 |
| 状态 | ✅ 已完成 |
📋 Checklist
- 阅读《AI Agents in Action》第3章(Tool Use / 工具使用)
- 理解:Agent 如何定义 Tool、调用 Tool、处理 Tool 返回结果
- 对照 SmallRig 项目:AI 搜索的 Tool 是什么?缺少什么?
- 做读书笔记:3 个核心概念 + 1 个行动启示
✍️ 读书笔记
核心概念 1:Agent Loop(五步循环)
Tool Use 是 Agent 和 Chatbot 的分水岭。Chatbot 只能"说",Agent 能"做"。
五步循环:
用户输入
→ ① Plan:LLM 分析意图,决定需要什么工具
→ ② Call:LLM 输出结构化 tool_call({name, args})
→ ③ Execute:Harness 真正执行工具(调 API、查数据库等)
→ ④ Observe:结果喂回给 LLM
→ ⑤ Respond:LLM 判断——够了就输出回答,不够就回到 ① 继续调工具
职责边界:LLM 管 ①→②→⑤(决策),Harness 管 ③→④(执行)。
核心概念 2:Tool Schema 设计
Tool 用 JSON Schema 定义,三个关键字段:
| 字段 | 作用 | PM 视角 |
|---|---|---|
name |
LLM 内部标识 | 和开发对齐即可 |
description |
告诉 LLM「什么时候用这个工具」 | prompt engineering,PM 核心工作 |
parameters.description |
告诉 LLM「从用户话里提取什么」 | prompt engineering,PM 核心工作 |
enum |
限定可选值,防 LLM 瞎编 | 业务边界定义 |
required |
必填字段 | 业务规则 |
高质量 Tool 的 5 条标准:
- Name 见名知意(
search_product不是tool_01) - Description 说清"什么时候用"
- 参数 Description 说清"填什么"
- 用 enum 约束边界
- 考虑异常情况(为空、超时、报错时返回什么)
核心概念 3:ReAct 模式(Reasoning + Acting)
ReAct = 边想边做,且思考过程可见。
Thought / Action / Observation 含义:
| 标签 | 谁产生的 | 含义 |
|---|---|---|
| Thought: | 模型 | "我现在的判断是什么,下一步应该做什么" |
| Action: | 模型 | "我要调用这个工具,参数是这些" |
| Observation: | Harness | "工具执行完了,返回的结果是这个"(不是模型说的,是 Harness 塞回去的) |
ReAct 对话流:模型输出 Thought → 模型输出 Action → Harness 返回 Observation → 模型输出 Thought → ... 循环直到模型输出最终 Answer。
与普通 Tool Loop 的区别:
| 普通 Tool Loop | ReAct 模式 | |
|---|---|---|
| 思考过程 | 不可见(黑盒) | 可见(Thought 作为文本输出) |
| 典型输出 | 直接 tool_call: search_product(...) |
Thought: 我需要先搜索 → Action: search_product(...) |
| 调试性 | 不知道为什么调了这个 Tool | 能看到推理过程,好定位问题 |
| 用户体验 | 只能看到最终结果 | 可展示"正在为您搜索..." |
| 循环能力 | 有循环(执行→思考→再执行) | 有循环(且每步都显式写出) |
核心区别不是"有没有循环",而是"思考过程是否可见"。两者都支持多轮循环,但 ReAct 把每一步推理写出来。
思考步骤显式化:隐式 = LLM 心里想,不说出来。显式 = 边想边说,每一步都写下来。显式化同时提升推理质量和可调试性。
四种 Tool 类型
| 类型 | 干什么 | SmallRig 举例 |
|---|---|---|
| Retrieval(检索) | 查信息 | search_product、query_order |
| Computation(计算) | 算数据 | calculate_discount、convert_currency |
| Action(执行) | 做操作 | add_to_cart、create_order |
| Memory(记忆) | 存/读信息 | save_preference、get_history |
Action 类需要确认机制(Day 9 结论"确认优先"的理论支撑)。
行动启示:对照 SmallRig 项目
| 维度 | 书中模式 | AI 搜索现状 | 差距 |
|---|---|---|---|
| Tool 定义 | 多 Tool Schema | 只有搜索(隐式) | 需显式定义多个 Tool |
| Agent Loop | Plan→Call→Execute→Observe→Respond | 单次调用→返回 | 没有循环,没有多轮 |
| Tool 类型 | 检索+计算+执行+记忆 | 仅检索 | 缺 Action(下单/加购) |
| 多 Tool 编排 | 链式调用,ReAct | 无 | 需设计 Tool 衔接关系 |
| 错误处理 | Fallback 策略 | 无 | 搜索结果为空时无兜底 |
核心启示:改造第一步不是换模型,而是把隐式搜索变成显式 Tool 定义 + 五步循环。
PM 和开发的职责边界
| 角色 | 职责 |
|---|---|
| PM | 定义 Tool Schema(JSON),确保 description 清晰、enum 完整、异常场景覆盖 |
| 开发 | 实现 Harness 层:解析 tool_call → 路由 → 执行 → 异常处理 → 格式化 tool_result → 循环上限控制 |
| 接口 | 双方对齐 JSON Schema |
📘 PM 参考模块:如何阅读和设计 Tool Schema
以下内容可直接作为 PM 撰写 Tool 定义时的参考模板。
模块一:PM 阅读 Tool Schema 的心法
把 Schema 当成你写给 LLM 的"新员工操作手册":
{
"name": "search_product", // ← 工具的名字,LLM 内部标识用
"description": "搜索 SmallRig 商品,根据关键词返回商品名称和价格", // ← 🔑 PM 最该关注!告诉 LLM「什么时候用这个工具」
"parameters": {
"type": "object", // ← 固定写法,不用管
"properties": {
"keyword": { // ← 参数名
"type": "string", // ← 数据类型:字符串/数字/布尔
"description": "商品搜索关键词" // ← 🔑 PM 最该关注!告诉 LLM「从用户话里提取什么填进来」
},
"category": {
"type": "string",
"description": "商品分类",
"enum": ["cage", "plate", "light", "other"] // ← 🔑 白名单,限制 LLM 只能选这些
}
},
"required": ["keyword"] // ← 哪些参数必须填
}
}
PM 阅读心法表:
| 字段 | PM 怎么看 | 类比 |
|---|---|---|
name |
工具名,和开发对齐即可 | 函数名 |
description |
这是 prompt engineering——LLM 靠这段文字决定「现在该不该调这个工具」 | 告诉新人"什么情况下用这个工具" |
parameters.description |
也是 prompt engineering——LLM 靠这段文字从用户原话里提取参数值 | 告诉新人"这个字段填什么" |
enum |
业务边界——限定可选范围,防止 LLM 瞎编 | 下拉框选项 |
required |
业务规则——不填就报错 | 必填字段 |
一句话:PM 的核心工作是写好 description 和定义 enum/required,这些决定了 LLM 调工具调得准不准。
模块二:tool_result 错误返回的设计模板
这是 PM 设计 Tool 时必须覆盖的异常场景。 Harness 返回的 tool_result 直接引导 LLM 下一步行为。
正确返回格式:
// ✅ 正常返回
{
"success": true,
"data": [
{ "product_id": "220B", "product_name": "220B 兔笼", "price": 49.9 },
{ "product_id": "300B", "product_name": "300B 兔笼", "price": 79.9 }
]
}
错误返回格式(PM 需要设计每种异常场景):
// ❌ 差的错误返回(LLM 不知道怎么办)
{ "error": "查询失败" }
// ✅ 好的错误返回(LLM 知道下一步该做什么)
{
"success": false,
"error": "未找到商品",
"reason": "关键词 'xyz123' 未匹配到任何商品",
"suggestion": "请尝试用产品型号(如 220B)或品类名(如 兔笼)搜索,或告知用户当前没有找到相关商品"
}
PM 设计 Tool 时的异常场景清单:
| 异常类型 | 举例 | tool_result 应该包含 |
|---|---|---|
| 结果为空 | 搜索"xyz123"无结果 | error + reason + suggestion: "换关键词或告知用户" |
| 参数错误 | category 填了 "abc" | error + suggestion: "可选值为 cage/plate/light/other" |
| 服务超时 | 接口 30s 无响应 | error + suggestion: "告知用户稍后重试,不要再次调用此工具" |
| 权限不足 | 查询他人订单 | error + suggestion: "告知用户需要登录或无权查看" |
| 重试达上限 | 同一 Tool 已重试 2 次 | error + suggestion: "请不要再调用此工具,基于已有信息回答" |
设计原则:tool_result 里永远不只说"出错了",还要说"为什么出错"和"建议 LLM 下一步做什么"。
模块三:重试上限的三层设计
上限不直接告诉 LLM(LLM 不知道数字),而是通过 Harness 硬逻辑 + tool_result 终止信号间接传达。
| 上限类型 | 触发条件 | LLM 收到什么(终止信号) |
|---|---|---|
| 同 Tool 重试 2 次 | 第 3 次调用同一 Tool | tool_result: "请不要再调用此工具,告知用户稍后重试" |
| 总轮次 8 轮 | 第 9 次 Tool Call | Harness 拒绝执行,返回 "已达对话上限,请基于已有信息回答" |
| 超时 30s | 超过 30s | Harness 终止,返回 "处理超时,请告知用户稍后重试" |
LLM 不"知道"上限数字,但收到终止信号后会停止调用。
❓ 错题本:今日提问与答案
以下为今天学习过程中提出的全部 17 个问题及对应答案。
第一轮:六层讲课后确认(7 问)
Q1:作为 PM,怎么辨认 JSON Schema 里每个参数是什么意思?
把 Schema 当成你写给 LLM 的"新员工操作手册"。
| 字段 | PM 怎么看 | 类比 |
|---|---|---|
name |
工具名,和开发对齐即可 | 函数名 |
description |
prompt engineering——LLM 靠这段文字决定「现在该不该调这个工具」 | 告诉新人"什么情况下用这个工具" |
parameters.description |
prompt engineering——LLM 靠这段文字从用户原话里提取参数值 | 告诉新人"这个字段填什么" |
enum |
业务边界——限定可选范围,防止 LLM 瞎编 | 下拉框选项 |
required |
业务规则——不填就报错 | 必填字段 |
PM 的核心工作是写好 description 和定义 enum/required,这些决定了 LLM 调工具调得准不准。
Q2:ReAct 模式是指内部的 Loop 吗?LLM 自行决定调用哪些工具完成这次输出?
是的,完全正确。ReAct 就是 LLM 内部的决策循环:LLM 看到用户输入 → 自己判断需要调工具A → 拿到结果 → 自己判断还需要调工具B → 判断够了 → 输出答案。PM 不需要告诉 LLM「先调 A 再调 B」,只需要定义好工具包,LLM 自己决定调用顺序。
Q3:Plan → Call → Execute → Observe → Respond 展开说说
我理解成:计划用什么工具 → 通过 tool_call 调用 Harness 工具 → Harness 返回结果给 LLM → LLM 再决定是继续 loop 还是输出结果。
这个理解正确。具体:
- ① Plan:LLM 内部推理(不可见)
- ② Call:LLM 输出 tool_call(Harness 收到)
- ③ Execute:Harness 真正执行(LLM 不参与)
- ④ Observe:Harness 把结果喂回 LLM
- ⑤ Respond:LLM 判断够了就输出,不够就回到 ①
LLM 只管 ①②⑤(决策),Harness 管 ③④(执行)。
Q4:ReAct 是什么意思?就是 Loop 循环?
ReAct 不只是 Loop,它是「边想边做」的特定模式。ReAct = Reasoning + Acting。
与普通 Loop 的核心区别:ReAct 的思考过程可见(Thought 作为文本输出),普通 Loop 的思考过程不可见(黑盒推理)。
Q5:需要设计 Tool 链——PM 定义好了 Tool 包,LLM 自己决定调用,为什么还说是 Tool 链?
"Tool 链"不是指 PM 写死调用顺序,而是指 PM 在设计时需要考虑 Tool 之间的衔接关系:
- 数据格式兼容:Tool A 的返回结果,Tool B 能直接当参数吗?
- 语义衔接:用户的一句话会触发几个 Tool?它们配套吗?
- 避免死循环:LLM 会不会反复调同一个 Tool?
PM 不定义调用顺序,但定义"Tool 之间的关系图"。
Q6:显式 Tool 定义 + 五步循环——PM 和开发各自做什么?高质量 Tool 的要求是什么?
PM 职责:定义高质量 Tool Schema(JSON),写好 description + enum + required + 异常场景。
开发职责:实现 Harness 层(解析 tool_call → 路由 → 执行 → 异常处理 → 格式化 tool_result → 循环上限控制)。
高质量 Tool 的 5 条标准:
- Name 见名知意
- Description 说清"什么时候用"
- 参数 Description 说清"填什么"
- 用 enum 约束边界
- 考虑异常情况(为空、超时、报错时返回什么)
Q7:ReAct 模式和 Day 3 学的 Claude Tool Use 轮转,是不是同一个东西?"思考步骤显式化"是什么意思?
本质上是同一个东西(都是"调工具→拿结果→再决策"的循环),但 ReAct 更强调"思考步骤的显式化"。
显式化 = LLM 把思考过程写出来,而不是藏在黑盒里。隐式(Claude)= 心里想不说出来,显式(ReAct)= 边想边说,每一步都写下来。显式化的价值:可调试、可审计、用户可理解 Agent 为什么这么做。
第二轮:写学习记录后追问(5 问)
Q8:enum 如果实际类目有 50 个,需要全写吗?
不需要全写。三种策略:
| 类目数量 | 推荐策略 | 原因 |
|---|---|---|
| ≤10 | enum 全写 | 简单直接 |
| 10-50 | 只写一级类目 enum | 平衡精度和可维护性 |
| 50+ | 不写 enum,靠 description 软引导 + Harness 后校验 | 穷举不现实 |
enum 是"硬约束",类目多时改为 description "软引导" + Harness "后校验"。
Q9:ReAct 模式和普通 Tool Loop 由谁决定?模型还是 Harness?怎么判断模型有没有这个能力?
由模型训练时决定,Harness 不改模型输出格式。
判断方法:看 Model Card 是否写"supports ReAct"、看官方 System Prompt 示例里有没有 Thought:/Action:/Observation: 标签、或直接实测。
Claude 和 Hermes 原生都不支持 ReAct(它们用 JSON FC 格式),可通过 System Prompt 模拟。对 SmallRig 来说 ReAct 不是硬需求。
Q10:多个 Tool 之间的字段名如何保持一致?(search_product 返回 id,add_to_cart 需要 product_id)
PM 先定义全局字段字典,再写每个 Tool Schema,所有 Tool 对齐同一本字典。
先画表:商品ID→product_id, 商品名→product_name, 价格→price
然后所有 Tool 输入输出都对齐同一套字段名
Q11:空结果/异常时的重试上限怎么设计?
2-2-8-30 法则:
- 同 Tool 最多重试 2 次
- 总轮次上限 8 轮
- 超时 30 秒
三个上限都通过 Harness 硬逻辑 + tool_result 终止信号传达给 LLM,LLM 不"知道"数字但收到信号后停止。
Q12:工具报错时,引导 LLM 正确下一步的提示写在哪里?是 System Prompt 吗?
两个地方:System Prompt 给通用原则("不要重复调用相同参数的工具"),tool_result 给本次具体建议("订单号不存在,请用手机号查询")。
tool_result 是主力,因为它在当前对话上下文里,LLM 刚看到就据此决策。PM 重点设计每种异常场景的 tool_result 返回内容。
第三轮:打开文件后追问(3 问)
Q13:笔记中加一个 PM 参考模块——包含 Tool Schema 阅读心法和 tool_result 错误返回示意
已添加「📘 PM 参考模块」,包含三个子模块:
- 模块一:PM 阅读 Tool Schema 的心法(字段对照表)
- 模块二:tool_result 错误返回的设计模板(正常返回 + 错误返回 + 异常场景清单)
- 模块三:重试上限的三层设计(触发条件 + LLM 收到什么)
Q14:ReAct 模式和普通 Tool Loop 的区别再说一次。Thought / Action / Observation 分别是什么?
核心区别不是"有没有循环"(两者都支持多轮循环),而是思考过程是否可见。
| 标签 | 谁产生的 | 含义 |
|---|---|---|
| Thought: | 模型 | "我现在的判断是什么,下一步应该做什么" |
| Action: | 模型 | "我要调用这个工具,参数是这些" |
| Observation: | Harness | "工具执行完了,返回的结果是这个"(不是模型说的,是 Harness 塞回去的) |
ReAct 对话流:Thought → Action → Observation → Thought → ... → 最终 Answer。
Q15:重试上限放在哪里让 LLM 接收到?
上限不直接告诉 LLM,上限是 Harness 的硬逻辑。LLM 能"感知"到上限是通过 tool_result 的终止信号:
| 上限类型 | 触发条件 | LLM 收到什么(终止信号) |
|---|---|---|
| 同 Tool 重试 2 次 | 第 3 次调用同一 Tool | tool_result: "请不要再调用此工具,告知用户稍后重试" |
| 总轮次 8 轮 | 第 9 次 Tool Call | Harness 拒绝执行,返回 "已达对话上限,请基于已有信息回答" |
| 超时 30s | 超过 30s | Harness 终止,返回 "处理超时,请告知用户稍后重试" |
LLM 不"知道"上限数字,但收到终止信号后会停止调用。
第四轮:最新追问(2 问)
Q16:超时是直接砍断交互不通知 LLM 直接结束?
不是。三个上限都是通知 LLM 的,没有"直接砍断"。
| 上限类型 | LLM 是否收到通知 | LLM 还能做什么 |
|---|---|---|
| 同 Tool 重试 2 次 | ✅ 收到 tool_result | 基于已有信息回答用户,或换一个 Tool |
| 总轮次 8 轮 | ✅ 收到 tool_result | 基于已有信息回答用户 |
| 超时 30s | ✅ 收到 tool_result | 告知用户"处理超时,请稍后重试" |
超时是 Harness 在 30s 内主动中断工具执行,返回 {"error": "处理超时", "suggestion": "请告知用户稍后重试"},LLM 看到后输出友好回复给用户。不是系统级 HTTP 超时的"砍断"。
Q17:错题本没收集到所有问题,需要完整归档
本问题——已通过本次更新完整归档全部 17 个 Q&A。
📤 产出
- 读书笔记 → 归档到 产出归档/读书笔记.md
- PM 参考模块(Tool Schema 阅读心法 + 错误返回模板 + 重试上限设计)
- 错题本(17 个 Q&A,按四轮对话归档)