跳到主要内容

让模型自己决定何时停 — Agent 循环与 ReAct

这一章讲三件事: 「行动」这个词在 agent 语境里到底拆出哪几层含义; 一个不用任何框架的手工循环怎么转——每一轮喂什么、吐什么、什么时候停; 以及用 LangGraph 实现同一件事时代码长什么样、还能拧哪两颗扩展旋钮。 读完你会拿到全书技术含量最高的一块:「让模型决定何时停」的完整机制。

1. 先看现象:它为什么不值得害怕

「Agent」这个词被市场炒得像新物种。作者选择先泄气再充气: 它的教科书定义古老得近乎平淡——「会行动的东西」, 这句「something that acts」出自 Russell 和 Norvig 的 AI 教科书1

但「行动」这个词经得起解剖,拆开是三层2:

  1. 行动要求有决定做什么的能力;
  2. 决定意味着存在不止一个可选项——没有选项的决定不叫决定;
  3. 要决定,就得拿到外部环境的信息(环境=agent 自身之外的任何东西)。

所以「agentic 的 LLM 应用」翻译成人话就是: 给它一组可选动作和一个现状描述,让它挑。没有任何玄学。

这跟上街买菜要带钱包一样需要两个零件,第一章全见过3: 工具调用(把可选项列进提示词并规定输出格式)+ 思维链 (让它把复杂问题拆成有序小步)。书中点明:新一代模型已经为这两件事专项微调过, 那些手工格式指令越来越不必写进提示词了4

2. 顶层全景

┌────────────────────────────────────────┐
▼ │
[模型节点] ──输出──▶ 有无新的工具调用? │
│ 是 → 执行工具,结果回填 ──┘
│ 否 → 输出最终答案,退出

图说:整张图只有一个岔路口:「还要不要调工具」。
谁来回答这个问题,就是人与模型的分权线——答案:模型。

上一个架构(路由器)是模型选一次路;这里是每一次绕圈都由模型重新投票。 作者把这个差异压缩成一个词组:LLM-driven loop,并给出全书最重要的一句机制定义: 「人人都写过循环……关键在于让 LLM 控制停止条件」5。这个架构有个正式名字 ReAct, 来自 Shunyu Yao 等人的论文6

3. 核心原理

3.1 走查①:不带框架的三轮手工循环

原书先用最裸的方式演示,问题仍然是那位美国总统:「第 30 任总统去世时几岁?」

第 0 步,把可选动作和规矩印在提示词上:

Tools:
search: this tool accepts a web search query and returns the top results.
calculator: this tool accepts math expressions and returns their result.

If you want to use tools to arrive at the answer, output the list of tools and
inputs in CSV format, with the header row: tool,input.

Think step by step; if you need to make multiple tool calls to arrive at the
answer, return only the first one.

温度设 0 保证服从格式,换行符被设为停止序列(stop sequence)——输出一到换行就掐断, 于是模型物理上无法一口气编造后面几步7。第 1 轮输出:

search,30th president of the United States

第 1 步,你的代码真的去执行搜索,拿到一段关于 Calvin Coolidge 生卒年的文字, 然后干一件决定性的事:把这轮的工具名和文本结果原样追加回提示词,再问一遍8。 第 2 轮输入还多了一样新东西——第三件工具:

output: this tool ends the interaction. Use it when you have the final answer.

这个 output 工具就是停车按钮:模型认为自己已有最终答案时会调用它, 你的代码看到它就终止循环9。带着搜索结果,模型这一轮报:

calculator,1933 - 1872

第 2 步,执行计算器得 61,回填,再来。第 3 轮模型按下停车钮:

output, 61

循环结束,61 就是最终答案的载体10。回头看这条路:三轮里模型每次只做一个小决定, 所有「力气活」都外包给十拿九稳的工具——这就是 Plan(规划下一步)与 Do(执行) 两动作的交替, 也是 chapter 标题里 Plan-Do Loop 的全部内容11

3.2 换成 LangGraph 写

同一个循环,框架化的三十行12:

  • 工具侧:@tool 装饰器把普通函数登记成工具(docstring 即说明书), 外加现成的 DuckDuckGoSearchRun;
  • 模型侧:model.bind_tools(tools) 把工具清单一次性挂上(替代手写 Tools 段落);
  • 图侧:模型节点 + ToolNode(预制的执行器)+ 条件边 tools_condition 收尾。

有两个组件值得单独说明,因为它们决定了这个架构的生产可用度。 ToolNode 负责执行最新一条 AI 消息里的全部工具请求,逐个把结果包成 ToolMessage 回填; 更关键的是它连异常也包成消息送回去——工具崩了不再炸掉整个流程, 而是变成一段「报错 text」交还给模型自行决断(重试?换路?)13tools_condition 则只看一件事:最新回复里有没有工具请求,有就路由去 tools 节点, 没有就 END——刚才手工版里的停车逻辑,现在是一条边14

走查②:流式输出的三块实拍

运行「How old was the 30th president of the United States when he died?」,三块输出15:

{ "model": tool_calls=[{ name: "duckduckgo_search",
args: { query: "…age at death" },
id: "call_ZWRbPmjvo0fYkwyo4HCYUsar" }] }
{ "tools": ToolMessage(content="Calvin Coolidge (born July 4, 1872 …—died January 5, 1933…)…",
tool_call_id="call_ZWRbPmjvo0fYkwyo4HCYUsar") }
{ "model": "Calvin Coolidge … died on January 5, 1933, at the age of 60." }

图说:id 像挂号单一样对上了「哪个请求对应哪份回答」;
第二轮模型认为资料已够,不再发起调用 → 条件边放行至终点。

3.3 两颗扩展旋钮

标准循环之后,书给了一立一破两颗旋钮,共同点是都在动「灵活性与可控性的兑换率」。

旋钮一:永远先调某个工具(first_model)。若你确知本应用第一步必须搜索, 就在 START 后塞一个不调模型的节点:直接构造搜索请求,省掉一次模型往返 (延迟立减一跳),同时杜绝模型偶尔「自作主张不搜」的误判16。 作者同时给了反面警告:如果不存在这种铁律,硬加只会把应用变差17

旋钮二:工具太多时先海选(select_tools)。经验值:超过约 10 个工具,模型的挑选准确率开始劣化18。 解法是把第一章的向量检索掉转头对准自家工具箱:每个工具的描述文本入库, 查询来了先取最相关的几个子集,只把这个小名单 bind 给模型19。 附带红利是 prompt 变短省钱;代价是多一跳检索延迟——作者的适用判据很克制: 观察到加工具后性能下滑了再上,不要预防性地堆架构20

判断(我们的,不是书里的): 本章实拍输出里埋着一个作者未点破的教学案例: 手工循环版算出 1933 - 1872 = 61,而基础版模型从同一段搜索结果里读出了正确的 60 岁 (Coolidge 生于 1872 年 7 月,去世时生日未到)。「外包给计算器」并不自动等于「正确」—— 先要把「出生满整年才算一岁」写进待办清单,否则精确工具只是放大了建模错误。 这为第 11 章「轨迹测试比终答测试更能定位问题」提供了绝佳注脚。 如果错,会错在: 若那轮搜索结果实际包含完整生卒日且模型仍算错, 问题就从「规划缺项」降格为「阅读理解失误」,结论不变但归因层级不同。

4. 作者的判断与证据

  • 三轮手工循环的全部提示词与输出都是书上原样给出的可复现实拍78910, 这是全章最硬的证据材料。
  • 「新模型已为工具调用微调」是作者的时效性判断,并配了一个书内编辑痕迹作旁证—— 正文残留一行没删干净的注释「add example prompt and output for tool-calling model」21, 说明该节在截稿前刚改写过。
  • 两个扩展的好处都是定性陈述(省一跳延迟 / 十个是个槛),无基准数据支撑。

5. 边界与局限

  • 失控防护集体缺席:本章的循环一旦遇到反复失败的调用或死循环,书内没有任何 轮数上限、错误熔断、超时的机制设计——这些要留到你部署时自查(第 10 章也只字未提)。
  • 60 vs 61 的事件说明轨迹级观测必要,而本章只给了「肉眼看 stream」一种手段; 系统化做法在第 11 章。
  • 工具异常虽然会被包回给模型,「报错信息会不会误导模型」没有讨论; 多个工具同时执行的场景同样未涉猎。
  • 补充(不在书里,依据我们的 frontier 书架):今天 LangChain 大版本已将本章手工搭的 循环封装为一键构建物,「循环到没有工具调用为止」成为内置文档级契约。 依据: shelf=ai-frontier-reference/langchain@src:libs/langchain_v1/langchain/agents/factory.py:971 事实=create_agent 文档字符串明写「The process repeats until no more tool_calls are present」。

6. 可带走的

  1. agent ≈ 会挑选项的接话机器;三层拆解(能决定/有选项/知环境)是识别「伪 agent」的试纸;
  2. ReAct = 模型控制停止条件的循环,其余全是工程包装;
  3. stop sequence 强制「一轮一个动作」,是防止越权代办的零成本护栏;
  4. output 停车工具模式:把「结束」本身注册成一个动作,终止信号与业务信号统一编码;
  5. 工具结果回填时保留工具名,让模型知道「哪份答复对应哪个请求」;
  6. ToolNode 连异常都回填成消息——失败也是一种可供决策的情报;
  7. first_model 救延迟、select_tools 保准头,两颗旋钮都要「对症才拧」;
  8. 别忘了那个 60 与 61:外部工具接管的是算术,接管不了任务分解的正确性;
  9. 本章的循环没有刹车片,上线前自己装(轮数上限 + 失败熔断 + 总超时)。

7. 原文地图

主题原书章原文位置
agent 定义(act 三层)Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:7(搜「something that acts」) · text/09-ch06-chapter-6-agent-architecture.txt:9(搜「capacity for deciding」)
agentic 应用的组合定义Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:15(搜「one or more possible courses of action」)
两零件与新模型免指令Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:21(搜「Chain-of-Thought」) · text/09-ch06-chapter-6-agent-architecture.txt:45(搜「fine-tuned to improve their performance」)
手工提示词全文(CSV/逐步/一次一步)Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:28(搜「top results」) · text/09-ch06-chapter-6-agent-architecture.txt:35(搜「return only the first one」)
温度 0 与停止序列Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:41(搜「stop sequence」)
第 1 轮输出 search,…Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:43(搜「30th president of the United States」)
结果回填方法解读Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:100(搜「what do you want to do next」)
output 停车工具Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:72(搜「ends the interaction」) · text/09-ch06-chapter-6-agent-architecture.txt:98(搜「signal to stop the loop」)
第 2 轮 calculator,1933 - 1872 与第 3 轮 output,61Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:94(搜「1933 - 1872」) · text/09-ch06-chapter-6-agent-architecture.txt:134(搜「output, 61」)
Plan-Do 循环定义Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:53(搜「control the stop condition」) · text/09-ch06-chapter-6-agent-architecture.txt:57(搜「Planning an action」)
ReAct 出处Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:140(搜「Shunyu Yao」)
@tool 与依赖安装Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:175(搜「simple calculator tool」) · text/09-ch06-chapter-6-agent-architecture.txt:150(搜「duckduckgo-search」)
bind_toolsChapter 6text/09-ch06-chapter-6-agent-architecture.txt:180(搜「bind_tools」)
ToolNode 职责与异常处理Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:253(搜「handles exceptions」)
tools_condition 与模型停机权Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:255(搜「Otherwise, it ends the graph」) · text/09-ch06-chapter-6-agent-architecture.txt:257(搜「in charge of deciding when to end」)
流式三块实拍(含 call id)Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:297(搜「call_ZWRbPmjvo0fYkwyo4HCYUsar」) · text/09-ch06-chapter-6-agent-architecture.txt:324(搜「age of 60」)
三步走查解读Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:331(搜「conditional edge to route us」)
first_model 动机与警告Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:343(搜「reduce overall latency」) · text/09-ch06-chapter-6-agent-architecture.txt:347(搜「make your application worse」)
first_model 实现(构造 ToolCall)Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:382(搜「first_model」) · text/09-ch06-chapter-6-agent-architecture.txt:465(搜「user's message verbatim」)
first_model 版实测输出Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:507(搜「9ed4328dcdea4904b1b54487e343a373」) · text/09-ch06-chapter-6-agent-architecture.txt:536(搜「Age at death」)
工具过多的阈值症状Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:549(搜「more than 10」)
select_tools 方案(RAG 选工具)Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:551(搜「subset of tools」) · text/09-ch06-chapter-6-agent-architecture.txt:582(搜「tools_retriever」)
select_tools 成本红利与时机判据Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:551(搜「cost of calling the LLM」)
与标准版的唯一差别(Note)Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:686(搜「only difference」)
select_tools 版实测Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:589(搜「selected_tools」)
小结Chapter 6text/09-ch06-chapter-6-agent-architecture.txt:770(搜「ability to decide between multiple options」)

Footnotes

  1. 出处:「Chapter 6. Agent Architecture」第 7 段(text/09-ch06-chapter-6-agent-architecture.txt:7,搜「something that acts」)。出处标注为 Russell & Norvig《Artificial Intelligence》(Pearson, 2020)。

  2. 出处:「Chapter 6. Agent Architecture」第 9–13 段(text/09-ch06-chapter-6-agent-architecture.txt:11,搜「a decision without options is no decision at all」)。

  3. 出处:「Chapter 6. Agent Architecture」第 17–23 段(text/09-ch06-chapter-6-agent-architecture.txt:19,搜「the actions it can decide to take」)。

  4. 出处:「Chapter 6. Agent Architecture」第 45 段(text/09-ch06-chapter-6-agent-architecture.txt:45,搜「removing the need for adding specific instructions」)。

  5. 出处:「Chapter 6. Agent Architecture」第 51–53 段(text/09-ch06-chapter-6-agent-architecture.txt:53,搜「have an LLM control the stop condition」)。

  6. 出处:「Chapter 6. Agent Architecture」第 140 段(text/09-ch06-chapter-6-agent-architecture.txt:140,搜「Shunyu Yao et al」)。

  7. 出处:「Chapter 6. Agent Architecture」第 27–47 段(text/09-ch06-chapter-6-agent-architecture.txt:41,搜「newline as the stop sequence」);第 43 段即第一轮输出。 2

  8. 出处:「Chapter 6. Agent Architecture」第 96–100 段(text/09-ch06-chapter-6-agent-architecture.txt:96,搜「we added two things」);回填内容示例在第 86–88 段。 2

  9. 出处:「Chapter 6. Agent Architecture」第 72 与 98 段(text/09-ch06-chapter-6-agent-architecture.txt:72,搜「Use it when you have the final answer」)。 2

  10. 出处:「Chapter 6. Agent Architecture」第 132–136 段(text/09-ch06-chapter-6-agent-architecture.txt:136,搜「picked the output tool」)。 2

  11. 出处:「Chapter 6. Agent Architecture」第 55–59 段(text/09-ch06-chapter-6-agent-architecture.txt:55,搜「What we'll run in this loop」)。「Plan-Do Loop」作为标题词见第 49 段标题行。

  12. 出处:「Chapter 6. Agent Architecture」第 160–196 段(Python 全代码)。「对话模型的 bind_tools 用法」在第 180 行。

  13. 出处:「Chapter 6. Agent Architecture」第 253 段(text/09-ch06-chapter-6-agent-architecture.txt:253,搜「using the error message to build a ToolMessage」)。

  14. 出处:「Chapter 6. Agent Architecture」第 255 段(text/09-ch06-chapter-6-agent-architecture.txt:255,搜「routes to the tools node if there are any tools」)。

  15. 出处:「Chapter 6. Agent Architecture」第 284–327 段(call id 在第 286 行起;终答在第 322–326 行,含原文实句「at the age of 60」);走查解读在第 329–335 段。

  16. 出处:「Chapter 6. Agent Architecture」第 341–345 段(text/09-ch06-chapter-6-agent-architecture.txt:343,搜「skip the first LLM call」)。

  17. 出处:「Chapter 6. Agent Architecture」第 347 段(text/09-ch06-chapter-6-agent-architecture.txt:347,搜「introducing such a constraint would actually make your application worse」)。

  18. 出处:「Chapter 6. Agent Architecture」第 549 段(text/09-ch06-chapter-6-agent-architecture.txt:549,搜「say, more than 10」)。

  19. 出处:「Chapter 6. Agent Architecture」第 551、582–601 段(text/09-ch06-chapter-6-agent-architecture.txt:582,搜「InMemoryVectorStore.from_documents」);bind 子集在第 595 行。

  20. 出处:「Chapter 6. Agent Architecture」第 551 段末(text/09-ch06-chapter-6-agent-architecture.txt:551,搜「when you see performance decreasing after adding more tools」)。

  21. 出处:「Chapter 6. Agent Architecture」第 47 行(text/09-ch06-chapter-6-agent-architecture.txt:47,搜「add example prompt and output」)。