跳到主要内容

让模型自己决定

这一章讲三件事: 前面那些招数为什么装好了还不够、 把「挑哪一招」交给模型自己决定具体是怎么做的、 以及什么时候不该让它自己决定。

它在全书链条上的位置:第 02 章那条走查的整条线被包进了一个循环。 前面十二章讲的是「怎么把这条线的每一步做好」, 这一章讲的是「谁来决定这一次该走哪几步」。

1. 这一章讲什么

三句话:

  1. 第 10 到 12 章给了八九种改进检索的招数,而它们有一个共同的毛病: 每一种都要人事先挑好——可真实用户的问题千变万化,事先挑不了;
  2. 解法的内核朴素得出奇:模型只负责决定调哪个函数、传什么参数,程序负责执行; 而模型判断该调哪个,依据只有一样:你给那个函数写的说明文字;
  3. 一次调用只走一步,所以必须放进循环—— 而这个循环,就是「agent」这个词底下真正装着的东西。

2. 顶层全景:一个循环,四个格子

┌──────────────────────────────────────────────────┐
│ │
│ ① 观察:现在的对话里有什么(用户的问题 + 到目前为止的结果)
│ ↓ │
│ ② 规划:模型决定下一步该干什么 │
│ ↓ │
│ ③ 挑工具执行:模型说"调 add_numbers(5.0, 3.0)" │
│ 程序真的去执行它,拿到 8.0 │
│ ↓ │
│ ④ 把结果按固定格式追加回对话 │
│ │ │
└────────┘ 再问一次,直到模型不再要求调工具

图说:整张图只有一个新东西 —— 第 ③ 步里"模型说"和"程序执行"是分开的两件事。
这一章其余全部内容,都是这一句的展开或者它的外围设施。

3. 先看现象:前面那些招数,谁来挑?

这一节不讲任何新做法,只把一个问题摆出来。

回想第 10 到 12 章给了什么:

按字面搜(BM25)· 先按标注缩范围 · 在几个库之间先选源
编一段假答案再搜 · 多问几遍 · 拆成子问题
按结构补上下文 · 按位置补上下文 · 重排

九种招数,每一种书都给了"什么时候用、什么时候别用"。

问题是:这些判断是谁在什么时候做的?

答案是:你,在写代码的时候,一次性做完。

你写下的代码大概长这样:

def answer(question):
q2 = 多问几遍(question) ← 你决定了永远多问几遍
hits = 先缩范围后检索(q2) ← 你决定了永远先缩范围
hits = 重排(hits) ← 你决定了永远重排
return 生成答案(hits)

于是:
用户问 "重置密码的步骤是什么?" → 也走了一遍多问几遍 + 重排(白花钱)
用户问 "87 乘 99 等于几?" → 也去搜了一遍文档(搜不到,还编了一个答案)
用户问 "微软和谷歌谁赚得多?" → 没拆子问题(因为你没写这一步)

这就是这一章要修的东西。 第 10 章其实已经走到门口了: 那一节让模型在运行时判断「该问哪个库」——但它选完就停手了,选完之后的每一步仍然是写死的。 把「选完接着干、干完再选」补上,就是这一章。

4. 承重词一:工具调用 —— 模型只做决定,程序负责执行

这一章第一个承重词。一句话先给结论:工具调用就是「你把几个 Python 函数的说明交给模型, 模型回话时不直接给答案,而是说『请帮我调 add_numbers,参数是 5.0 和 3.0』, 然后由你的程序真的去执行它」。

先看它是来治什么病的

书给的理由极其具体,而且很扎心1:

模型做算术不可靠,所以工具调用把计算委托给 Python 函数去精确执行。

(原文这里用的词是 LLM。它是「大语言模型」的英文缩写——大语言模型就是那种读过海量文字、 靠「接着往下写」来产出内容的模型,也正是本组文档一路简称的「模型」。 这本书里只有这一种模型在做判断这件事,所以下面继续只说「模型」。)

这句话值得多想一层: 模型是照着「下一个字大概是什么」写出来的, 它写「8.0」不是因为它算了,是因为「5.0 加 3.0 等于」后面通常跟着 8.0数一大就露馅。 而 Python 的加法从来不出错。

所以分工是:模型擅长「判断该做什么」,程序擅长「精确地做」——各干各的。

那个分工写成代码是什么样

书用一句话概括2:模型只决定,代码去执行。

你手上有一个普通的 Python 函数:

def get_weather(latitude, longitude):
'''
这个函数调用 open-meteo 接口,取得给定经纬度的天气数据。

Args:
latitude (float): 要查天气的地点的纬度
longitude (float): 要查天气的地点的经度
Returns:
dict: 包含该地点天气数据的一个字典
'''
...

↑ 函数正文里那段三引号包起来的说明,叫 docstring(说明文字)。
平时它是写给人看的注释。在这里,它是**写给模型看的**。

用户问:"纽约明天天气怎么样?"
→ 模型读到这段说明,判断"这个工具能回答这个问题"
→ 它填好参数(纽约的经纬度),回话说"请调 get_weather(40.71, -74.01)"
→ 你的程序执行它,拿到温度
→ 把温度交回给模型,让它组织成人话

图说:注意模型自始至终没碰过网络、没执行过任何代码。
它做的全部事情是"读说明 → 决定调谁 → 填参数"。
(纽约那对经纬度是我们为演示填的,书只给了这个问句。)

模型的判断依据只有一样,所以那段说明是承重的

书把这件事说成一句很重的话1:

那段说明文字,是模型和工具之间的合同。

为什么是「合同」而不是「注释」: 模型看不见函数体、看不见你的业务逻辑、 也不会去试。它只能读那段字。 写得含糊,它就调错工具或者填错参数; 而这类错误不会报错,它会安安静静地给你一个用错数据算出来的答案。

交给模型的那份工具清单里,每个工具装三样东西3:

装什么干什么用
函数名模型回话时报这个名字,你的程序照名字找到函数
一段详细的描述模型判断「该不该用它」的唯一依据
输入参数,以及每个参数是什么类型模型照这个填值

书给了一个省事的做法3:描述那一栏直接复用函数的说明文字 (Python 里读 函数名.__doc__ 就能拿到)。一份东西,人和模型共用。

工具怎么设计,书给了四条

这四条都是反面判据,而且都很实在4:

  1. 每个工具只承担一件事。 一个「查天气顺便算日期」的工具,模型永远搞不清什么时候该用;
  2. 别为已经有现成方案的事自己造工具(网页搜索、执行代码这类);
  3. 每个工具都要测试、调试,并且随着外部接口的变化而更新;
  4. 调外部服务的工具必须自己处理超时、限流和失败—— 因为它挂掉的时候,模型只会看到一段乱七八糟的返回值,然后照着它继续编。

5. 一次调用只走一步,所以必须放进循环

这一节是这一章的机制核心,而书讲它的方式非常好:先让读者撞一次墙。

书先给了一段不带循环的代码,跑完之后说5:

你会看到它只执行了一次工具调用,也就是括号里的第一步加法。 这一次工具调用的结果还没有回答用户的问题——但它是正确的第一步。

「还没回答问题,但那是正确的第一步」——这一句是整章的枢纽。

用户问:(5.0 + 3.0) * 2.0 等于几?

第一次调用模型,它回话说: "请调 add_numbers(5.0, 3.0)"
程序执行,得到: 8.0

然后呢?
模型不知道你已经算出 8.0 了 —— 它上一句话说完就结束了。
你必须**把 8.0 告诉它**,再问一次。

图说:这就是"必须放进循环"的全部理由。
不是因为循环更优雅,是因为一次调用在结构上就只能走一步。

循环怎么写

书给的循环只有四件事,而且顺序不能变6:

finish_reason = "tool_calls" ← 先赋这个值,好让循环进得去

while finish_reason == "tool_calls":
① 带着当前的整段对话 + 工具清单,调一次模型
② 把模型这次的回话原样追加进对话
③ 如果它要求调工具:
执行那些工具
**把每个结果按固定格式追加进对话**
④ 如果它不再要求调工具:循环自然停下,它这次的回话就是最终答案

那个"固定格式"长这样:
{"role": "tool", "content": "8.0", "tool_call_id": "call_abc123"}
↑ ↑ ↑
身份是"工具" 结果(必须是字符串) 对应哪一次请求

那个 tool_call_id 容易被忽略,但它是必需的: 模型一次可能要求调三个工具,结果回来的时候得对得上号。

「对话」在这里就是一个列表,每一轮往后加一条。 书说循环跑完之后那个列表里有三条: 用户的问题、第一次工具调用、第二次工具调用7

6. 主走查:(5.0 + 3.0) * 2.0 从头走到尾

这是本章的主走查,前面每个机制都在它上面占一步。 用的是书那个真实跑例5

(这个查询串、「只执行了一次工具调用」「那是正确的第一步」、 {"role": "tool", ...} 那个格式、循环跑完对话里有三条,都是书里的; call_abc123 这个编号和下面的中间输出是我们为演示写的具体值。 另外:书演示循环时把查询换成了 (6.7 + 3.3) * 2.0,我们为了走查连贯, 一路用同一个查询。)

第 0 步:准备工具清单

两个普通的 Python 函数:
add_numbers(a, b) 说明:把两个数相加,返回它们的和
multiply_numbers(a, b) 说明:把两个数相乘,返回它们的积

交给模型的清单里,每个装三样:名字 / 上面那句说明 / 两个 float 参数

第 1 步:第一次问模型

对话现在是:
[ {"role": "user", "content": "What is the result of (5.0 + 3.0) * 2.0?"} ]

模型回话:
finish_reason = "tool_calls"
tool_calls = [ { id: "call_abc123",
name: "add_numbers",
arguments: '{"a": 5.0, "b": 3.0}' } ]

↑ 注意它只要求了**一次**调用,而且是括号里那一步。
它没有一口气把两步都规划出来。

第 2 步:程序执行

照名字找到 add_numbers,把参数解开,执行:
add_numbers(5.0, 3.0) → 8.0

← 这是这一章最要紧的一步:这个 8.0 是 Python 算的,不是模型写的。

第 3 步:把结果追加回对话

对话现在是三条:
① {"role": "user", "content": "What is the result of (5.0 + 3.0) * 2.0?"}
② {"role": "assistant", "tool_calls": [ add_numbers(5.0, 3.0) ]}
③ {"role": "tool", "content": "8.0", "tool_call_id": "call_abc123"}

第 4 步:第二次问模型(循环的第二圈)

带着这三条再问一次。

模型回话:
finish_reason = "tool_calls"
tool_calls = [ { id: "call_def456",
name: "multiply_numbers",
arguments: '{"a": 8.0, "b": 2.0}' } ]

↑ 它读到了第 ③ 条里的 8.0,于是知道下一步该乘 2.0。
**8.0 这个数是它从对话里读来的,不是它自己算的。**

程序执行:multiply_numbers(8.0, 2.0) → 16.0
追加进对话。

第 5 步:第三次问模型(循环的第三圈)

模型回话:
finish_reason = "stop" ← 不再是 "tool_calls"
content = "(5.0 + 3.0) * 2.0 的结果是 16.0。"

while 的条件不成立,循环退出。这句话就是最终答案。

第 6 步:把这一圈圈数清楚

模型调用次数 Python 执行次数 模型自己算过数吗
这道题 3 2 没有

图说:注意最后一列。整道题里模型一次乘法一次加法都没做,
它只做了三次判断:"先加""再乘""可以收工了"。
而三次模型调用就是三次来回 —— 这正是下一节要处理的代价。

7. 承重词二:agent —— 以及那个循环的名字

这一章第二个承重词。一句话先给结论:agent 不是一种模型、也不是一种产品, 它是「让模型当决策者」的那一类系统;而它的内部,就是上一节那个 while 循环。

书给的定义

书的第一句话就是定义,值得原样记住8:

「agent 是这样一类系统:模型在其中充当决策者。 模型观察当前情况,规划一串动作,并挑选工具去执行这些动作。 就像一个人在火车取消时会改行程,agent 在新信息出现时会调整自己的策略。」

「火车取消」那个比方到这里就用完了,下面一律用「观察 / 规划 / 执行 / 修正」这些说法。

它由三个部件组成

书给的三件9:

部件干什么
模型推理与规划——也就是「下一步干什么」
工具执行动作
记忆(短期与长期)让过去的结果影响将来的决定

在第 6 节那个走查里,「记忆」就是那个不断变长的对话列表。 这是最朴素的一种短期记忆:把发生过的事原样留在上下文里。

那个循环的名字

书说规划模式有好几种,但最常用的叫 ReAct10:

agent 在「想下一步做什么」和「调工具去做」之间交替,拿结果去修正计划; 循环持续到达成目标,或者撞上预设的上限(比如最多调几次工具)。

这个名字是「推理(Reasoning)+ 行动(Acting)」拼出来的。 你会在几乎每一家 agent 框架的文档里撞见它。

「撞上预设上限」那半句要留意: 它是这个循环唯一的刹车。 没有它,一个判断失误的模型可以无限地调下去——每一圈都在花钱。

判断(我们的,不是书里的):这个循环最贵的地方不是工具执行,是「圈数」, 而书从头到尾没有把这笔账算给读者看。 第 6 节那道两步算术题用掉了三次模型调用——比不用工具多两次。 而每一圈都要把到目前为止的整段对话重新发一遍(第 01 章那条:模型不记事), 所以第三圈发出去的内容比第一圈长得多。 也就是说,成本随圈数增长得比线性更快,而书只说了「每次工具调用多一个来回」。 如果错,会错在: 如果你的对话本来就短、工具返回的结果也短, 那多出来的那点长度可以忽略,近似线性成立。 判据是:把最后一次调用发出去的那段对话打印出来,量一下它比第一次长几倍。

三段演化:这一章为什么排在全书最后几章

书给了一条很清楚的时间线11:

早期 孤立的单个功能(摘要、翻译)—— 一次调用,一个结果

中期 多步工作流 —— 步骤是人排好的,模型只填内容

现在 完全的 agent 系统 —— 模型自己决定用哪些工具

图说:第 03 章到第 12 章讲的全部内容,都落在第二格里 ——
那些流水线的每一步都是我们写死的。这一章是往第三格迈的那一步。

8. 五种固定形状的工作流

书说这五种模式出自 Anthropic,并且明说这一节是当参考表用的12

先说清楚这一节和上一节的关系: 上一节讲的是「让模型自己决定」, 这一节讲的是「不让它完全自己决定,而是把形状先固定下来」。 两者不是递进,是一个选择题——分界线在这一节末尾。

前三种:形状简单,靠一次判断就能定

模式它怎么转书给的例子
提示链拆成顺序的几步,每一步的模型调用处理上一步的输出。因为后一步依赖前一步,只能串行从 PDF 抽出文字 → 翻译 → 改写成简单英语
选路第一次模型调用当路由器,分析请求、挑最合适的工具或子流程收到一封邮件:有附件就触发分析、预处理、保存;简单邮件就直接用日历或向量库里的信息作答
并行多个调用同时跑各自那一份,最后有一个汇总的步骤把结果合起来一份长的扫描版合同逐页抽取到期日和解约条款

「选路」这个词要和第 10 章那个「选源」分清楚(这是我们做的区分,书没有点破): 它们在书里是同一个英文词(routing),但选的东西不同—— 第 10 章选的是去哪个数据源查,这里选的是走哪一条处理流程

书给选路配了一个很有用的规模感12: 大约 5 到 10 种情形,就能覆盖处理邮件所需的大部分动作。 这个数说明了这类固定形状的适用面:它不是要穷举世界,它是要覆盖你那点业务。

后两种:形状里套着别的模型

模式它怎么转书给的例子
协调者带工人一个协调者手下有几个各自专精的工人,协调者决定叫谁;最后有一个综合的步骤出一份答案三个工人:把请求翻成 SQL 去查库的 / 查 wiki 数据的(向量库 + 按意思搜)/ 用专门提示直接调模型的
起草者带评审一个模型出初稿,另一个模型评审;在起草与评估之间来回,直到评审满意,或者来回的圈数到了预设上限(书里管这种一圈圈来回叫迭代)写技术博客:一个产出段落草稿,评审给反馈,直到通过

注意「协调者带工人」和上一节那个 agent 循环的关系: 协调者干的正是循环里的第 ② 步(规划),只是它能挑的「工具」是别的模型。 (这一层第 14 章会讲得更细:那里会把 agent 本身包装成一个工具。)

分界线:什么时候用固定形状,什么时候放手

书把这一句写得很干脆13:

可靠性比灵活性更重要时,选工作流模式; 任务需要随机应变地做决定时,才选自主 agent。

它还给了三条「别过度设计」的反面判据,每一条都有具体的触发条件13:

模式什么时候别用
并行它带来编排的额外开销(编排就是「谁先跑、谁后跑、结果怎么合起来」这些调度活儿),只有子任务本身耗时较长时才划算
选路它要求类别清楚,分类含糊时别用(和第 10 章那条分类器的判据一模一样)
协调者带工人它因为多次模型调用而拉高延迟

9. 并行:收益是可以算的

这是这一章另起的一处走查,因为它落不到第 6 节那条主走查上—— 那道算术题的两步天生有先后,并行不了。

先把三个会混着用的词分清

这一节和下一节会反复出现三个词,先各给一句(它们是同一件事的三个侧面):

并行指的是同时开跑几件互不依赖的活,而不是一件干完再干下一件。 三页发票各自独立,谁也不等谁,所以它们可以并行。

由此还有一个数:并发数,就是同时开跑的件数——三页同时抽,并发数就是 3。 这个数不是越大越好,它有一条硬上限,本节末尾讲。

异步则是让并行在代码里成立的那套写法:一件活开始等外部回话的时候, 程序不干等,先切去跑别的。它是手段,前两个是效果。

书那组真实的实测

这是全书仅有的两组实测之一14:

任务:一份扫描版发票,三页,逐页交给一个多模态模型抽取里面的字段

各页耗时(实测):
第 1 页 19 秒
第 2 页 34 秒
第 3 页 38 秒
─────────────────
串行相加 91 秒 书的说法是"至少 90 秒"

并行跑(实测):
整个脚本 49 秒

对照:
串行 ≥ 90 秒
并行 49 秒 ← 省了将近一半

为什么并行之后不是 38 秒,而是 49 秒: 因为脚本里还有别的事要做 (读文件、把图编码、最后汇总)。书给的机制描述是「大致等于最长的那一段」, 而实测 49 秒比最长那段 38 秒多出 11 秒——这 11 秒就是那些串行的边角料。 (这个差额的解释是我们做的,书没有拆解这 49 秒。)

它凭什么能省

书给的机制只有一句15:

这套做法在等待外部响应的时候把当前任务挂起、切去跑别的已就绪的任务。 串行执行的耗时是各段之和,而这样跑,总时间大致等于最长的那一段。

关键在于「等待」两个字。 一次模型调用里,你的机器绝大部分时间在等对面回话, CPU 闲着。并行不是让机器算得更快,是让它别干等。

它的硬约束

书给的约束很具体16:接口的每分钟 token 上限。 同时跑太多,会撞上这个上限并报错。

书还给了怎么估17:模型厂商通常会给出「某个分辨率的图片大约折算多少 token」, 拿它乘以并发数,再对着每分钟上限算一下,就知道最多能同时跑几张。

在这一章的走查里,并行发生在哪一步

回到第 2 节那个循环:并行改的是第 ③ 步。

模型一次要求调三个工具(比如同时查三个数据源):

串行: 查 A(2 秒)→ 查 B(3 秒)→ 查 C(1 秒) 共 6 秒
并行: 三个同时发出 共 3 秒(最慢那个)

(这些秒数是为演示编的。)

图说:注意并行只在"模型一次要求了多个工具"的时候有用。
第 6 节那道算术题一次只要求一个工具,并行一秒都省不下来。

10. 异步的真实代价是调试

这一节很短,但它是这一章唯一一条「别急着上」的忠告。

书说得很坦白18:

代价是代码复杂度。你要理解协程、事件循环和 await 的语义。 调试更难,因为错误可能发生在同时跑的多个任务里,很难追出是哪个任务出的问题。 错误处理还必须考虑「部分失败」。

三个词各解释一句(这三个名字你在任何一份 Python 异步文档里都会撞见):

名字它是什么
协程一种「可以中途暂停、待会儿再接着跑」的函数
事件循环那个负责「谁暂停了就切去跑别人」的调度器
await写在代码里的那个标记,意思是「这里要等,你先去忙别的」

「部分失败」是这三条里最容易被低估的一条:

三页同时抽取:
第 1 页 成功
第 2 页 接口超时,失败
第 3 页 成功

串行的时候你会在第 2 页当场崩掉,一眼就看见。
并行的时候你拿到的是"两成一败"的混合结果 ——
如果代码没有专门处理它,你会拿着两页的数据当成三页在用。

书给的跳过条件19:每一步都依赖上一步结果的串行流程别用它; 调用次数很少的简单脚本也别用——多出来的复杂度换不回什么。

11. 作者的判断与证据

说法它是什么
「模型做算术不可靠」是事实,而且是这整套做法存在的理由
「模型只决定、代码执行」是流程事实,写得很准
「说明文字是模型和工具之间的合同」作者的说法,是个比方,但它准确地描述了一个事实:模型看不到别的东西
工具设计四条作者的工程经验,没有来源。 但四条都能从机制推出来
「一次只执行了一步,但那是正确的第一步」是跑出来的真实结果,而且是全章最好的一次教学设计
那个 while 循环是可运行的代码,不是示意
agent 的定义是作者的定义;和业界通行说法一致
ReAct 那个循环与论文出处书给了论文名和第一作者,这是全书为数不多这么做的地方。但书写的年份(2023)是会议年份,预印本更早
三段演化是事实描述,和公开的时间线一致
五种工作流模式书明说出自 Anthropic,并给了自己的例子
「5 到 10 种情形覆盖大部分邮件动作」作者的经验数,没有来源
「可靠性比灵活性重要时选固定形状」作者的判断,没有实验。 但它是这一章唯一一条清晰的分界线
19 / 34 / 38 / 49 秒是书里的真实实测,全书仅有的两组之一。但没有交代机器配置、模型、页面尺寸
「串行至少 90 秒」是三段相加的推算,不是实际跑出来的
「总时间大致等于最长的那一段」是机制描述,而实测 49 秒比最长那段多 11 秒——书没有解释这个差额
异步的三条代价是事实描述,而且「部分失败」那条是最容易被忽略的

12. 边界与局限

  • 书没有讲工具选错了怎么办。 模型挑错工具、或者参数填错, 这类失败不报错,只会给出一个用错数据算出来的答案——全书没有一处讨论怎么发现它;
  • 书没有讲循环的上限该设多少。 它提了「撞上预设上限」这个刹车, 但没有给任何取值建议,也没讲撞上上限之后该怎么办;
  • 书没有讲工具太多会怎样。 三个工具时模型挑得准,三十个呢? 全书没有讨论工具数量对判断质量的影响;
  • 那三页发票的实测缺条件。 什么机器、什么模型、页面多大、网络如何, 一个字没有,所以那三个秒数只能当量级看;
  • 书没有把 agent 和前面九种检索招数真正接起来。 这一章的例子是算术和天气, 而全书前十二章讲的检索招数,一次都没有被包装成工具在这一章里出现—— 第 3 节那个「谁来挑」的问题,书提出了它,但没有在代码上回答它;
  • 五种工作流模式没有一个可运行的实现。 书自己说「具体实现见 8.5 和 8.8」, 也就是五种里只有两种有代码;
  • 记忆那一格几乎没展开。 三个部件里「记忆」被列了出来, 但除了「把对话往后加」之外,长期记忆怎么做,全书没有讲。

13. 可带走的

  1. 前面九种改进检索的招数有一个共同毛病:每一种都要人事先挑好、写死在代码里。 而真实用户的问题千变万化,事先挑不了;
  2. 工具调用的内核只有一句:模型只负责决定调哪个函数、传什么参数,程序负责执行。 模型自始至终没碰过网络、没执行过代码;
  3. 理由很具体:模型做算术不可靠——它写「8.0」不是因为算了, 是因为那个位置通常跟着 8.0;
  4. 模型判断该调哪个工具,依据只有一样:你给那个函数写的说明文字。 它看不见函数体、也不会去试。写得含糊,它会安静地用错数据算出一个答案;
  5. 交给模型的工具清单里,每个工具装三样:名字、一段详细描述、参数及其类型。 描述可以直接复用函数的说明文字,一份东西人和模型共用;
  6. 工具设计四条:一个工具只干一件事 / 别为已有现成方案的事造工具 / 要测试并随外部接口更新 / 调外部服务的必须自己处理超时、限流和失败;
  7. 一次模型调用只走一步。 「还没回答问题,但那是正确的第一步」—— 所以必须放进循环;
  8. 循环的四件事:调模型 → 把回话追加进对话 → 执行工具并把结果按 {"role": "tool", "content": ..., "tool_call_id": ...} 追加进对话 → 再来一圈, 直到它不再要求调工具;
  9. agent 不是一种模型,是「让模型当决策者」的那类系统。 三个部件:模型负责推理与规划、工具负责执行、记忆让过去影响将来;
  10. 那个循环的名字叫 ReAct(推理 + 行动),它唯一的刹车是「最多调几次工具」这个上限;
  11. 五种固定形状的工作流: 提示链(串行)、选路、并行、协调者带工人、起草者带评审。 分界线是一句话:可靠性比灵活性重要就用固定形状,需要随机应变才放手;
  12. 并行的收益书量过:三页发票 19 / 34 / 38 秒,串行至少 90 秒,并行 49 秒。 它省的是「干等」的时间,不是让机器算得更快;
  13. 并行的硬约束是接口的每分钟 token 上限,估法是「每张图折算多少 token × 并发数」;
  14. 异步真正的代价是调试:错误可能发生在同时跑的多个任务里, 而且你必须专门处理「部分失败」——否则会拿着两页的数据当三页用。

14. 原文地图

主题原书章原文位置
agent 的定义Chapter 8. Agentic RAG(章导言)text/10-ch08-chapter-8-agentic-rag.txt:4(搜「an LLM acts as a decision-maker」)
三段演化Chapter 8. Agentic RAG(章导言)text/10-ch08-chapter-8-agentic-rag.txt:8(搜「isolated features such as summarization」)
三个部件Chapter 8. Agentic RAG(章导言)text/10-ch08-chapter-8-agentic-rag.txt:16(搜「short- and long-term memory」)
ReAct 与那篇论文Chapter 8. Agentic RAG(章导言)text/10-ch08-chapter-8-agentic-rag.txt:20(搜「Synergizing Reasoning and Acting」)
工具就是带说明的函数第 8 章 · 配方 8.1(自定义工具)text/11-fm-solution.txt:3(搜「detailed docstring that describes its purpose」) · text/11-fm-solution.txt:32(搜「What is the weather like in New York tomorrow」)
模型读说明来挑工具第 8 章 · 配方 8.1text/12-fm-discussion.txt:5(搜「The LLM reads these docstrings」)
工具设计四条第 8 章 · 配方 8.1text/12-fm-discussion.txt:7(搜「Keep each tool focused on a single responsibility」) · text/12-fm-discussion.txt:9(搜「timeouts, rate limits, and failures」)
五种模式出自 Anthropic第 8 章 · 配方 8.2(工作流模式)text/14-fm-solution.txt:3(搜「five common workflow patterns defined by Anthropic」)
提示链 / 选路 / 并行第 8 章 · 配方 8.2text/14-fm-solution.txt:9(搜「each LLM call processes the previous step's output」) · text/14-fm-solution.txt:19(搜「roughly 5 to 10 scenarios」) · text/14-fm-solution.txt:29(搜「10 to 20 seconds per page」)
协调者带工人 / 起草者带评审第 8 章 · 配方 8.2text/14-fm-solution.txt:43(搜「orchestrator with access to three worker agents」) · text/14-fm-solution.txt:57(搜「one LLM creates an initial draft」)
分界线与别过度设计第 8 章 · 配方 8.2text/15-fm-discussion.txt:11(搜「reliability matters more than flexibility」) · text/15-fm-discussion.txt:13(搜「Avoid overengineering」)
模型只决定、代码执行第 8 章 · 配方 8.4(函数调用)text/20-fm-solution.txt:5(搜「the LLM only decides; the code executes」)
工具清单三件第 8 章 · 配方 8.4text/20-fm-solution.txt:44(搜「The function name」) · text/20-fm-solution.txt:50(搜「reuse the function's docstring」)
只走一步、那是正确的第一步第 8 章 · 配方 8.4text/20-fm-solution.txt:114(搜「a correct first step」)
那个 while 循环与 tool 格式第 8 章 · 配方 8.4text/20-fm-solution.txt:120(搜「appends the tool result to the messages」) · text/20-fm-solution.txt:138(搜「tool_call_id: invocation.id」) · text/20-fm-solution.txt:146(搜「while finish_reason」)
循环跑完对话里有三条第 8 章 · 配方 8.4text/20-fm-solution.txt:168(搜「It contains three entries」)
算术不可靠、说明文字是合同第 8 章 · 配方 8.4text/21-fm-discussion.txt:3(搜「unreliable at arithmetic」)
每次工具调用多一个来回第 8 章 · 配方 8.4text/21-fm-discussion.txt:9(搜「each tool call adds another model round trip」)
三页发票的实测第 8 章 · 配方 8.5(异步提速)text/23-fm-solution.txt:133(搜「page 1 took 19 seconds」)
怎么估并发上限第 8 章 · 配方 8.5text/23-fm-solution.txt:87(搜「estimate token usage for images」)
机制与代价第 8 章 · 配方 8.5text/24-fm-discussion.txt:3(搜「suspends tasks during I/O waits」) · text/24-fm-discussion.txt:9(搜「Error handling must account for partial failures」)
什么时候别用异步第 8 章 · 配方 8.5text/24-fm-discussion.txt:7(搜「Avoid asyncio for sequential workflows」)

Footnotes

  1. 出处:第 8 章 · 配方 8.4 的讨论段第 3 段(text/21-fm-discussion.txt:3,搜「unreliable at arithmetic」)。原文两句都在这一段:模型做算术不可靠,所以把计算委托给 Python 函数;那段说明文字是模型与工具之间的合同,描述这个函数做什么、怎么调它。说明:原书第 8 章每个配方的解决段、讨论段、延伸阅读段在我们的清洗文本里各是一个独立文件,文件名看不出属于哪一节,所以这一章的每条出处都额外标了配方号。 2

  2. 出处:第 8 章 · 配方 8.4 的解决段第 5 段(text/20-fm-solution.txt:5,搜「the LLM only decides; the code executes」)。同一段说这套做法里模型当规划者、Python 运行时逐步执行那些调用。

  3. 出处:同一解决段第 44 段起(text/20-fm-solution.txt:44,搜「The function name」)列出工具清单的三件;复用说明文字那条见第 50 段(text/20-fm-solution.txt:50,搜「reuse the function's docstring」)。补充(不在书里,来自通用知识):Python 里函数正文开头那段三引号包起来的文字就是它的 docstring,可以用 函数名.__doc__ 读出来。 2

  4. 出处:第 8 章 · 配方 8.1 的讨论段第 7 段(text/12-fm-discussion.txt:7,搜「Keep each tool focused on a single responsibility」)与第 9 段(text/12-fm-discussion.txt:9,搜「timeouts, rate limits, and failures」)。工具是什么见同段第 3 段(text/12-fm-discussion.txt:3,搜「tools are functions, API calls, database queries」);模型读说明文字来挑工具见第 5 段(text/12-fm-discussion.txt:5,搜「The LLM reads these docstrings」)。那个天气函数与「纽约明天天气怎么样」的例子见配方 8.1 的解决段(text/11-fm-solution.txt:3,搜「detailed docstring that describes its purpose」与 text/11-fm-solution.txt:32,搜「What is the weather like in New York tomorrow」)。

  5. 出处:第 8 章 · 配方 8.4 的解决段第 114 段(text/20-fm-solution.txt:114,搜「a correct first step」)。那句查询串见第 90 段(text/20-fm-solution.txt:90,搜「user_query = "What is the result of (5.0」)。注意:书演示循环时把查询换成了 (6.7 + 3.3) * 2.0(见第 142 段,text/20-fm-solution.txt:142,搜「6.7」),我们的走查为了连贯一路用同一个查询。 2

  6. 出处:同一解决段第 120 段(text/20-fm-solution.txt:120,搜「appends the tool result to the messages」)是对整个循环的文字描述;那个固定格式见第 138 段(text/20-fm-solution.txt:138,搜「tool_call_id: invocation.id」);while 那一行见第 146 段(text/20-fm-solution.txt:146,搜「while finish_reason」)。

  7. 出处:同一解决段第 168 段(text/20-fm-solution.txt:168,搜「It contains three entries」)。原文说的三条是:用户的问题、第一次工具调用、第二次工具调用。同一节还有第二个跑例,混用了两类工具——「我过几天要去伦敦,告诉我伦敦的天气,并算一下我从今天起几天后回家」,见第 175 段(text/20-fm-solution.txt:175,搜「I'm planning a trip to London」)。

  8. 出处:「Chapter 8. Agentic RAG」第 4 段(text/10-ch08-chapter-8-agentic-rag.txt:4,搜「an LLM acts as a decision-maker」)。

  9. 出处:同章第 16 段(text/10-ch08-chapter-8-agentic-rag.txt:16,搜「short- and long-term memory」)。同章第 14 段(text/10-ch08-chapter-8-agentic-rag.txt:14,搜「runs in a continuous loop」)还写了一句:agent 的内核是一个持续的循环——做一个动作、观察结果、决定下一步。

  10. 出处:同章第 20 段(text/10-ch08-chapter-8-agentic-rag.txt:20,搜「Synergizing Reasoning and Acting」)。书给的是论文名《ReAct: Synergizing Reasoning and Acting in Language Models》、第一作者 Shunyu Yao,并标为 2023 年。补充(不在书里):2023 是它在会议上发表的年份,预印本更早——2022 年 10 月 6 日提交。 论文摘要声称它靠与一个简单的维基百科接口交互,克服了纯思维链里常见的编造与错误传播问题。来源:https://arxiv.org/abs/2210.03629(查阅于 2026-08-25)。

  11. 出处:同章第 8 段(text/10-ch08-chapter-8-agentic-rag.txt:8,搜「isolated features such as summarization」)。

  12. 出处:第 8 章 · 配方 8.2 的解决段第 3 段(text/14-fm-solution.txt:3,搜「five common workflow patterns defined by Anthropic」);提示链见第 9 段(text/14-fm-solution.txt:9,搜「each LLM call processes the previous step's output」);选路与那个「5 到 10 种情形」见第 19 段(text/14-fm-solution.txt:19,搜「roughly 5 to 10 scenarios」);并行与逐页抽取见第 29 段(text/14-fm-solution.txt:29,搜「10 to 20 seconds per page」);协调者带工人见第 43 段(text/14-fm-solution.txt:43,搜「orchestrator with access to three worker agents」);起草者带评审见第 57 段(text/14-fm-solution.txt:57,搜「one LLM creates an initial draft」)。补充(不在书里):书说的这五种出自 Anthropic 工程博客《Building Effective Agents》(2024 年 12 月 19 日),那篇文章同时给出一条建议:先用最简单的提示、配上完整的评测,只有在简单方案不够时才加多步系统。来源:https://www.anthropic.com/engineering/building-effective-agents(查阅于 2026-08-25)。 2

  13. 出处:第 8 章 · 配方 8.2 的讨论段第 11 段(text/15-fm-discussion.txt:11,搜「reliability matters more than flexibility」)与第 13 段(text/15-fm-discussion.txt:13,搜「Avoid overengineering」)。 2

  14. 出处:第 8 章 · 配方 8.5 的解决段第 133 段(text/23-fm-solution.txt:133,搜「page 1 took 19 seconds」)。原文写第 1 页 19 秒、第 2 页 34 秒、第 3 页 38 秒,整个脚本 49 秒,不用异步的话串行至少要 90 秒。书没有交代机器、模型和页面尺寸。

  15. 出处:第 8 章 · 配方 8.5 的讨论段第 3 段(text/24-fm-discussion.txt:3,搜「suspends tasks during I/O waits」)。

  16. 出处:第 8 章 · 配方 8.2 的解决段第 31 段(text/14-fm-solution.txt:31,搜「token-per-minute limit」)。原文说同时跑太多任务可能撞上 token 上限并导致报错。

  17. 出处:第 8 章 · 配方 8.5 的解决段第 87 段(text/23-fm-solution.txt:87,搜「estimate token usage for images」)。原文的说法是:厂商通常会说明某分辨率的图片大致折算多少 token,拿它加上每张图的处理时间,就能估出并发多少不会超限。

  18. 出处:第 8 章 · 配方 8.5 的讨论段第 9 段(text/24-fm-discussion.txt:9,搜「Error handling must account for partial failures」)。补充(不在书里,来自通用知识):协程、事件循环、await 是 Python asyncio 这套机制里的三个基本概念,分别对应「可以中途暂停的函数」「负责调度谁跑谁停的那个循环」「标记这里要等」。

  19. 出处:第 8 章 · 配方 8.5 的讨论段第 7 段(text/24-fm-discussion.txt:7,搜「Avoid asyncio for sequential workflows」)。