跳到主要内容

转圈的那个东西叫执行器 — 一次断点调试的复盘

这一章讲三件事: 那段脚本里到底是谁在转圈;一步里都发生了什么; 以及两类错误各自怎么被兜住、为什么只有一类留了开关。

它在全书链条里的位置: 第 01 章画了那一圈,第 03 章讲了模型面前那段文字, 第 04 章讲了工具怎么接。这一章讲的是把它们串起来的那段代码。 全书只有这一章真的打开了框架的源码,所以它也是最值钱的一章。

这是第三次用同一个输入。 第 01 章看整圈、第 03 章看提示词、 本章看执行器内部——同一个问题,三种转法。

1. 那段脚本里,哪几行是循环

这一节先制造一个疑问,整章都在回答它。

原书第 6 章那个鲜花定价的程序,从头到尾就一屏。 去掉那段十几行的模板文字,真正的代码只有十来行:设两个密钥、建一个模型、 装两件工具、拼一份模板、造一个 agent、造一个执行器、调一次1

问题来了:第 01 章说这一圈要转三轮,可这段代码里连一个 while 都没有。

答案是转圈的那个东西被一行代码带过去了:

agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
agent_executor.invoke({"input": "目前市场上玫瑰花的一般进货价格是多少?…"})

知道 create_react_agent 造出来的那个对象只负责问一次模型,转圈的是 AgentExecutor; 书里前面所有示例都只用一行把它带过,直到 6.5 才把它拆开——本章走的就是那一次拆解。

作者自己也知道这一段最要紧,所以他做了一件全书唯一的事: 打开框架源码、设断点、一行一行走下去,还把每一步的截图都贴了出来。 他的原话是:请原谅我在这里展示了大量源码截图,因为这个调试过程对理解它至关重要2

书给这个对象的定位是:它是 agent 的运行环境—— 先调模型、接收并观察结果、再执行模型选择的操作; 同时负责处理几种复杂情况,包括选了不存在的工具、工具出错、 以及产出无法解析成调用格式3这三种情况,本章第 5、8 两节各有交代。

2. 它就是一条链,循环体在 _call

这一节回答「它是个什么东西」。答案比你想的朴素。

作者按下 Step Into,进到的第一个文件里是一个叫 Chain 的类的 invoke 方法。 由此可以发现,那个执行器实际上就是一个链4—— 也就是第 02 章第 8 节说的那六块里的第四块。

学生当场恍然:用了半天这个框架,一直纳闷「链」到底在哪儿, 原来那个执行器就是从链继承下来的5

再往下走,进到 self._call 方法——这个方法蕴含了主要逻辑6。 方法名前面那个下划线是个约定,表示它是类内部用的、不是给外面调的7

agent_executor.invoke({...})


Chain.invoke ────────── 打日志:"> Entering new AgentExecutor chain..."


AgentExecutor._call ─── ★ 循环体在这里

│ ┌──────────────────────────────┐
└──▶│ _take_next_step (走一步) │◀─┐
└──────────────────────────────┘ │
│ │
产出是「结束」类型吗? │
否 ──────────────────────┘
是 ──▶ 收尾,返回最终答案

图说:整章要讲的东西全在这张图上。
`_call` 是那个「反复走一步、每次问一句还没完」的地方。

3. 一步 = 问一次模型 + 跑一次工具

这一节讲那个「走一步」里都发生了什么。

_call 里再往下,是 _take_next_step,它内部又调 _iter_next_step—— 观察、思考及行动这三件事就在这里面8

书把这一步的主要逻辑列成了六条,本节只抽出骨架9:

  1. 把已经走过的中间步骤准备好(这就是第 03 章那个记事本);
  2. 调用模型的 plan 方法,让它决定下一步;
  3. 如果解析它的产出时出了异常,按一个开关决定是抛出去还是兜住(第 5 节讲);
  4. 如果产出是「结束」类型,交出去,方法结束;
  5. 如果产出是一个或多个「动作」,逐个交出去;
  6. 对每一个动作,调 _perform_agent_action 真的去跑,并交出结果。

第 2 条和第 6 条就是第 01 章那张图里的奇数步和偶数步。 一步 = 问一次模型(第 2 条) + 跑一次工具(第 6 条),两件事在同一个方法里。

4. 主走查:跟着断点走完三轮

这一节是本章的主走查。每一步旁边的字串,全部来自书里那次调试记录。

输入(与第 01、03 章同一个): 「目前市场上玫瑰花的一般进货价格是多少?如果我在此基础上加价 5%,应该如何定价?」 两件工具: SearchCalculator。断点设在 agent_executor.invoke 那一行。

第 1 步 · 进链。 走过 on_chain_start 之后,命令行上打出 > Entering new AgentExecutor chain...,表示链已经正式启动10

第 2 步 · 看手上有哪些工具。 在调试台里打印一个叫 name_to_tool_map变量—— 程序里一个用来存放某个值的名字——里面正是之前定义的那两件11这张表决定了第 5 节那个分支走不走。

第 3 步 · 第一轮 plan。 进到 plan 方法,问题被交给模型; 返回的东西装在一个叫 chunk 的变量里12它不是一段文字,是一个对象:

AgentAction(
tool = 'Search',
tool_input = '目前市场上玫瑰花的进货价格',
log = '我需要先找到目前市场上玫瑰花的一般进货价格,然后在这个价格基础上
加价5%来计算最终的定价。\n\nAction: Search\nAction Input: …'
)

图说:三个格子。前两个是给代码用的,第三个 log 是模型写的原始文字 ——
第 03 章那三行就装在这里。**解析这一步已经做完了。**

第 4 步 · 第一轮跑工具。 走到 _perform_agent_action。 它先在第 2 步那张表里查 Search 在不在;在,就照 tool_input 把它跑掉, 产出一个观察结果13。拿回来的是十条新闻摘要,其中一条写着 今年基本上进货价都在 25~30 元

第 5 步 · 回到 _call,判断完没完。 这一步的产出是「动作」类型不是「结束」类型, 所以那句 isinstance 判断给出 False,任务未完成,继续循环14

这里有一句话必须记住: 书写得很直白——这个观察结果, 实际上就是下一次调用模型的输入15第 03 章那张「一段文字的三次形态」的图, 说的就是这件事在代码里发生的地方。

第 6 步 · 第二轮 plan。 再次进到 plan,这次模型面前多了那十条摘要。 它返回的对象变成16:

AgentAction(
tool = 'Calculator',
tool_input = '25 * 1.05',
log = '从观察结果中可以看到,玫瑰花的进货价格有很大的波动……
我们可以取一个中间值,例如25元作为计算基础。\n\nAction: Calculator…'
)

第 7 步 · 第二轮跑工具。 同一个 _perform_agent_action, 这次查到的是 Calculator,跑完命令行上输出观察结果 26.2517

第 8 步 · 第三轮 plan。 把前两轮的动作和观察一起交给模型再问一次。 这次它返回的不是「动作」,而是「结束」类型; 那句 isinstance(output, AgentFinish) 终于给出 True18, 命令行打出 I now know the final answer.循环退出,任务结束。

一趟走查的账:
plan 调了 3 次 ← 每次问一遍模型
_perform_agent_action 调了 2 次 ← 每次跑一件工具
isinstance 判断了 3 次:False → False → True

图说:三次判断里前两次都是 False。
**False 的意思不是「答错了」,是「它还想接着干」。**

5. 执行器碰到错不抛,把错变成下一份输入

这一节讲这一章最值钱的一个设计。它同时管两类错。

第一类:模型点了一个根本不存在的工具。

回看第 4 步那个查表动作。查不到会怎样? 书里写得很清楚: 如果请求的工具不在那张表里,它会走一个专门的**「无效工具」分支**, 返回一个包含错误信息的观察结果,例如请求的工具名称和可用工具的列表19

注意这句话的形状:它不报错,不停机,它产出一个观察结果。 而上一节刚说过,观察结果就是下一次调用模型的输入。 所以「你点的这个不存在,可用的是这些」这句话,是被当成一次观察喂回给模型的。

这正是第 03 章第 8 节那个现象的解释:那一趟里模型凭空点了「阅读和收集信息」这个工具, 两次都没成功,然后自己改了搜索词重来。它不是靠猜改的,是外面把「无效」告诉它了。

第二类:模型写的东西解析不出来。

第 3 节列的六条里第 3 条就是它:如果在输出解析时出现异常, 会根据一个叫 handle_parsing_errors 的设置来决定是抛出异常还是处理异常; 选择处理时,它会构建一个动作实例,然后产出一个步骤20—— 说白了,同样变成一次观察结果送回去,让它重写。

两类错并排放,差别只有一处:

这一类错怎么兜有没有开关
点了不存在的工具走专门的无效工具分支,回填「可用的是这些」没有。它总是兜。
产出解析不出来构建一个动作实例,回填一次观察有,就是 handle_parsing_errors

为什么偏偏是第二条留了口子? 书没有直说,这里给我们的判断:

判断(我们的,不是书里的): 这两类错的「谁的责任」不一样。 工具名不存在是模型挑错了——它挑错是常态,兜住让它重挑就完事; 而解析不出来往往是你自己的模板写坏了、或者换了个不听格式的模型。 后一种如果也默默兜住,你会看到程序一直转圈却永远转不出结果,却没有任何报错。 留一个开关,等于让你自己选:上线时兜住保命,开发时抛出来早点发现。 如果错,会错在: 如果框架后来给第一类也加了开关,这条推测就只适用于书里这个版本。

6. 退出判据只有一条

这一节把散在前面几节的判断收成一句。

回看第 5 步和第 8 步:两次都是同一句 isinstance 判断,判的是同一件事—— 这一步的产出是不是「结束」那个类型。

没有第二条出口。 书里那六条逻辑、那两个方法、那些截图,归结起来就这一条。 既不看答案对不对,也不看转了几圈,更不看花了多少钱。

书在小结里给了这一章的落点:整个流程是推理和行动的协同, 而执行器就是上述机制得以实现的引擎21

这条判据的好处和坏处一样明显。 好处是简单到不可能写错; 坏处是模型只要一直想干活,它就一直转——第 8、9 两节和第 09 章讲的都是这件事的后果。

7. 计算器工具内部,还是在调模型

这一节讲一个很容易被略过、但很能说明问题的细节。

第 7 步那个算 25 * 1.05 的工具,你大概以为它是一行 Python 乘法。不是。

书里跟进去看了:这个工具继承自框架的一个数学类,该类的内部实现其实也是调模型22。 作者把传给模型的那段文字也贴了出来,大意是: 把这道数学题翻译成一个能用数值库执行的表达式,再用运行这段代码的结果来回答问题23

所以真实的链条是:模型 → 计算器工具 → 又一次模型 → 数值库 → 结果。

书给了理由,一句话:这规避了大模型数学推理能力弱的局限24。 也就是说:不让模型算,只让它写算式;算这件事交给一个从不出错的库。

这条为什么值得记住? 因为它是一个可以到处套用的手法—— 凡是模型不擅长但能描述的事,都改成「让它写出来、交给别人执行」。 第 07 章那个「让它写代码画图」用的是同一招。

8. 工具真的抛异常怎么办:书里的空白

这一节讲书里一个明确的缺口。

第 5 节那两类错都被兜住了。但还有第三类:工具本身跑挂了。 搜索接口超时、数据库连不上、库存函数除以零——这些书里一个字都没讲。

有意思的是,书在介绍这个执行器时,开头那句话里明明列了「工具出错的情况」3, 可后面十页从头到尾没有回来处理它。这属于列了提纲、没写正文。

补充(不在书里,依据我们的前沿框架书架): 今天这一格已经有明确做法了,而且和第 5 节那两类错是同一个思路: 默认不让整趟运行失败,而是把错误信息当成工具产出喂回给模型。 依据: shelf=ai-frontier-reference/openai-agents-python#02-tools.md @2c5560339cd7f77b4dabcf7d85c5d150594fd74c 事实=工具抛异常时由 failure_error_function 处理(tool.py:2551), 默认生成一句给模型看的错误消息;显式传 None 才改为直接抛出、让整趟失败25

补充(不在书里,依据我们的前沿框架书架): 另一家把「超时」也归进了同一条路,并且给这条路加了刹车。 依据: shelf=ai-frontier-reference/pydantic-ai#02-tools-and-toolsets.md @606b0e581272eb9f06ca989d88be5828047eed9c 事实=工具超时不直接报错,而是转成一条「超时了,请重试」喂回模型 (toolsets/function.py:634);同时重试次数有上限,由一个默认最大重试数把关,超了仍然失败。26

「有上限」这三个字是书里最缺的一样东西。 把错误喂回去让模型重试, 听起来很聪明;但如果没有次数上限,一个总是超时的工具会让它无限重试下去。 下一节那个默认 15,就是同一个问题在另一层的答案。

9. 你今天照着敲,会撞上两件事

这一节是本章的落点:书里这段代码今天还能不能跑。

第一件:那个创建函数今天有两个来源,而且都劝你别用了。

先说一个词:命名空间——一组名字所在的那个「盒子」; 同一个名字放进不同的盒子,彼此互不冲突,但你导错盒子就会拿到另一个东西。

补充(不在书里,依据我们的前沿框架书架): create_react_agent 这个名字今天在两个地方各有一个,是两个不同的东西。 依据: shelf=ai-frontier-reference/langchain@src:libs/langchain/langchain_classic/agents/react/agent.py:33 @2019bf5ebe50324c548f67c2666a804343f9b772 事实=书里那个已经被搬进一个叫 langchain_classic 的旧命名空间, 它的说明文字里挂着一条警告,原话是它「older and not well-suited for production applications」 (更老、不适合用于生产环境),建议改用另一个函数 create_agent27

再说第二个词:弃用——官方宣布这个东西不再推荐使用、将来会删掉, 但为了不弄坏老代码,现在还留着,只是一用就给你一条提醒。

补充(不在书里,依据我们的前沿框架书架): 同名的另一个在图执行库里,也已经挂上了弃用提示。 依据: shelf=ai-frontier-reference/langgraph@src:libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py:275 @1e44bda48ff4982b8ccfeec9c14156ea9e8ae5a2 事实=该函数带着一条弃用提示,原话是「create_react_agent has been moved to langchain.agents」, 让你改成从另一个包导入 create_agent28

所以你今天搜到这个函数名,很可能撞到两个完全不同的东西。 判断办法只有一个:看它是从哪个包导进来的。

第二件:书里从没提过的那个上限,其实一直都在。

补充(不在书里,依据我们的前沿框架书架): 那个执行器有一个默认的轮数上限,而且默认值不是无限。 依据: shelf=ai-frontier-reference/langchain@src:libs/langchain/langchain_classic/agents/agent.py:1023 @2019bf5ebe50324c548f67c2666a804343f9b772 事实=字段 max_iterations 的默认值是 15,注释写着这是「结束执行循环之前最多走多少步」, 并且明说设成 None 可能导致无限循环29

给这个 15 配个参照物:本章那趟走查一共转了 3 轮—— 就是第 4 节那张账表里「plan 调了 3 次」的那个 3,离 15 还差 12 轮。 也就是说,书里的示例离这个上限都远得很——这大概就是它从没被提起的原因。

但第 09 章那趟需求不清的任务,光计划就列了 8 条、每一条还要另开一个执行器再转几轮; 第 11 章那一趟干脆把上限写死成 6。当任务变复杂,这个数就会开始咬人。

10. 可带走的

  1. 那段脚本里一个循环都没有。 转圈的是 AgentExecutor,它被一行代码带过;书直到 6.5 才把它拆开;
  2. 它就是一条链,循环体在 _call 里;这也是「链」这个词在这本书里唯一一次落到实处;
  3. 一步 = 问一次模型 + 跑一次工具,两件事在同一个方法(_iter_next_step)里;
  4. **模型返回的不是文字,是一个对象:**工具名、工具输入、以及它写的原始文字;解析在进这个对象之前就做完了;
  5. 观察结果就是下一次调用模型的输入——这是那一圈能转起来的全部机制,没有别的;
  6. **碰到错不抛,把错变成下一份输入:**点了不存在的工具→回填「可用的是这些」;产出解析不出来→回填一次观察;
  7. 两类错里只有第二类留了开关(handle_parsing_errors);第一类总是兜住;
  8. 退出判据只有一条:这一步的产出是不是「结束」类型。 不看对错、不看轮数、不看花费;
  9. 计算器内部还是在调模型——让它写算式、交给数值库跑,专门绕开它算术弱这一点;
  10. 工具自己抛异常怎么办,书里是空白; 今天的做法是把错误当工具产出喂回模型,并且给重试次数设上限;
  11. 今天照着敲会撞上两件事: 同名函数有两个来源且都劝你别用;以及一个书里从没提过的默认 15 的轮数上限。

11. 原文地图

主题原书章原文位置
那段脚本与那一行执行器6.4 通过create_react_agent创建鲜花定价Agenttext/45-ch06-04-6-4-create-react-agent-agent.txt:72(搜「用Agent和Tools初始化一个AgentExecutor」) · text/45-ch06-04-6-4-create-react-agent-agent.txt:78(搜「通过AgentExecutor执行任务」)
执行器是什么、它要处理哪几种情况6.5 深挖AgentExecutor的运行机制text/46-ch06-05-6-5-agentexecutor.txt:5(搜「Agent选择了不存在的工具的情况」)
作者为什么要贴源码截图6.6 小结text/47-ch06-06-6-6.txt:5(搜「请原谅我在这里展示了大量的LangChain源代码截屏图」)
它就是一条链、循环体在 _call6.5 深挖AgentExecutor的运行机制text/46-ch06-05-6-5-agentexecutor.txt:29(搜「agent_executor实际上是一个Chain」) · text/46-ch06-05-6-5-agentexecutor.txt:31(搜「AgentExecutor类就继承自Chain类」) · text/46-ch06-05-6-5-agentexecutor.txt:41(搜「_call方法。这个方法蕴含」)
下划线的约定6.5 深挖AgentExecutor的运行机制text/46-ch06-05-6-5-agentexecutor.txt:47(搜「程序设计风格上的约定」)
一步里的六条逻辑、解析异常的开关6.5 深挖AgentExecutor的运行机制text/46-ch06-05-6-5-agentexecutor.txt:65(搜「_take_next_step方法中继续调用」) · text/46-ch06-05-6-5-agentexecutor.txt:85(搜「准备中间步骤」) · text/46-ch06-05-6-5-agentexecutor.txt:89(搜「handle_parsing_errors」)
进链、工具表、第一轮 plan6.5 深挖AgentExecutor的运行机制text/46-ch06-05-6-5-agentexecutor.txt:35(搜「on_chain_start」) · text/46-ch06-05-6-5-agentexecutor.txt:59(搜「name_to_tool_map」) · text/46-ch06-05-6-5-agentexecutor.txt:103(搜「进入整个行为链条的第一步」) · text/46-ch06-05-6-5-agentexecutor.txt:117(搜「目前市场上玫瑰花的进货价格」)
跑工具、无效工具分支、返回步骤6.5 深挖AgentExecutor的运行机制text/46-ch06-05-6-5-agentexecutor.txt:156(搜「是否在name_to_tool_map映射中」) · text/46-ch06-05-6-5-agentexecutor.txt:166(搜「无效工具处理」) · text/46-ch06-05-6-5-agentexecutor.txt:168(搜「返回AgentStep」)
观察就是下一次的输入、两次判断6.5 深挖AgentExecutor的运行机制text/46-ch06-05-6-5-agentexecutor.txt:176(搜「实际上这个Observation就是下一个大模型调用的Input」) · text/46-ch06-05-6-5-agentexecutor.txt:182(搜「isinstance的值是False」) · text/46-ch06-05-6-5-agentexecutor.txt:268(搜「isinstance(output, AgentFinish)」)
第二轮 plan 与 26.256.5 深挖AgentExecutor的运行机制text/46-ch06-05-6-5-agentexecutor.txt:217(搜「25 * 1.05」) · text/46-ch06-05-6-5-agentexecutor.txt:243(搜「26.25」)
计算器内部还是在调模型6.5 深挖AgentExecutor的运行机制text/46-ch06-05-6-5-agentexecutor.txt:231(搜「该类的内部实现其实也是调用大模型」) · text/46-ch06-05-6-5-agentexecutor.txt:237(搜「numexpr」) · text/46-ch06-05-6-5-agentexecutor.txt:239(搜「规避了大模型数学推理能力弱的局限」)
这一章的落点6.6 小结text/47-ch06-06-6-6.txt:11(搜「AgentExecutor就是上述机制得以实现的引擎」)

Footnotes

  1. 出处:「6.4 通过create_react_agent创建鲜花定价Agent」第 72 段(text/45-ch06-04-6-4-create-react-agent-agent.txt:72,搜「用Agent和Tools初始化一个AgentExecutor」)与第 78 段(text/45-ch06-04-6-4-create-react-agent-agent.txt:78,搜「通过AgentExecutor执行任务」)。「十来行」是我们数出来的,不是书里的数:书里那段脚本分成六个代码块印在正文里,去掉那段十几行的模板字符串,可执行语句大约十四行。书里另有一个「用不到 50 行代码」的说法(text/21-ch02-05-2-5-agent-react.txt:87,搜「用不到50行代码」),那说的是原书第 2 章那个搜论文的例子,不是这一段。

  2. 出处:「6.6 小结」第 5 段(text/47-ch06-06-6-6.txt:5,搜「请原谅我在这里展示了大量的LangChain源代码截屏图」)。这句道歉本身是这本书最有价值的一处——它说明作者知道这一章和其余各章的写法不同,并且认为值得。

  3. 出处:「6.5 深挖AgentExecutor的运行机制」第 5 段(text/46-ch06-05-6-5-agentexecutor.txt:5,搜「Agent选择了不存在的工具的情况」)。原文一口气列了四种要处理的情况:选了不存在的工具、工具出错、产出无法解析成调用格式、以及在决策和工具调用期间记日志。第二种(工具出错)后文没有兑现,见第 8 节。 2

  4. 出处:「6.5 深挖AgentExecutor的运行机制」第 29 段(text/46-ch06-05-6-5-agentexecutor.txt:29,搜「agent_executor实际上是一个Chain」)。作者是通过 Step Into 进到那个基类文件里才发现这一点的。

  5. 出处:「6.5 深挖AgentExecutor的运行机制」第 31 段(text/46-ch06-05-6-5-agentexecutor.txt:31,搜「AgentExecutor类就继承自Chain类」)。学生这句话很值钱:它说明「链」这个词在整本书前五章里一直是个悬空的概念,直到这里才落地。

  6. 出处:「6.5 深挖AgentExecutor的运行机制」第 41 段(text/46-ch06-05-6-5-agentexecutor.txt:41,搜「_call方法。这个方法蕴含」)。同节第 51 段说明进到这个方法之后,「Agent 将在此不断循环计划、思考、调用工具、解决问题」(text/46-ch06-05-6-5-agentexecutor.txt:51,搜「不断循环计划」)。

  7. 出处:「6.5 深挖AgentExecutor的运行机制」第 47 段(text/46-ch06-05-6-5-agentexecutor.txt:47,搜「程序设计风格上的约定」)。原文说这表示「这是一个类内部使用的方法,而非类的公开接口的一部分」。

  8. 出处:「6.5 深挖AgentExecutor的运行机制」第 65 段(text/46-ch06-05-6-5-agentexecutor.txt:65,搜「_take_next_step方法中继续调用」)。这是作者回答学生提问时说的:细节都在这两个方法里。

  9. 出处:「6.5 深挖AgentExecutor的运行机制」第 85 段起(text/46-ch06-05-6-5-agentexecutor.txt:85,搜「准备中间步骤」)。书里那六条是原样列出来的,本节只是把每条译成大白话并标出它对应第 01 章那张图的哪一步。

  10. 出处:「6.5 深挖AgentExecutor的运行机制」第 35 段(text/46-ch06-05-6-5-agentexecutor.txt:35,搜「on_chain_start」)。这一行提示语在原书后面的运行输出里还会反复出现:光是原书 7.3 那一趟(本拆解第 09 章拆的就是它)就印了 13 次,另有 2 次是外层那条链的同款提示(text/51-ch07-03-7-3-plan-and-execute-agent.txt:125,搜「Entering new AgentExecutor chain」)。认得它能帮你在一大堆日志里找到一次调用的边界。

  11. 出处:「6.5 深挖AgentExecutor的运行机制」第 59 段(text/46-ch06-05-6-5-agentexecutor.txt:59,搜「name_to_tool_map」)。这个变量名直译就是「名字到工具的映射」——第 5 节那个「查不到」的分支查的就是它。

  12. 出处:「6.5 深挖AgentExecutor的运行机制」第 103 段(text/46-ch06-05-6-5-agentexecutor.txt:103,搜「进入整个行为链条的第一步」)与第 117 段(text/46-ch06-05-6-5-agentexecutor.txt:117,搜「目前市场上玫瑰花的进货价格」)。作者明说他不打算深挖真正调模型的那一段,「你可以将其视为一个负责调用大模型的黑盒」。

  13. 出处:「6.5 深挖AgentExecutor的运行机制」第 156 段(text/46-ch06-05-6-5-agentexecutor.txt:156,搜「是否在name_to_tool_map映射中」)。原文把这个方法的职责列成四块:记操作日志、查表并执行、无效工具处理、返回一个步骤对象。

  14. 出处:「6.5 深挖AgentExecutor的运行机制」第 182 段(text/46-ch06-05-6-5-agentexecutor.txt:182,搜「isinstance的值是False」)。原文的说法是:此时任务并未完成,执行器将继续循环。

  15. 出处:「6.5 深挖AgentExecutor的运行机制」第 176 段(text/46-ch06-05-6-5-agentexecutor.txt:176,搜「实际上这个Observation就是下一个大模型调用的Input」)。这一句是整章最要紧的一句,它把第 03 章那个「记事本一轮比一轮长」的现象和代码接上了。

  16. 出处:「6.5 深挖AgentExecutor的运行机制」第 217 段(text/46-ch06-05-6-5-agentexecutor.txt:217,搜「25 * 1.05」)。25 这个基数是模型自己从十条摘要里挑的中间值,书里那段 log 原文写着「我们可以取一个中间值,例如25元作为计算基础」。

  17. 出处:「6.5 深挖AgentExecutor的运行机制」第 243 段(text/46-ch06-05-6-5-agentexecutor.txt:243,搜「26.25」)。

  18. 出处:「6.5 深挖AgentExecutor的运行机制」第 268 段(text/46-ch06-05-6-5-agentexecutor.txt:268,搜「isinstance(output, AgentFinish)」)。同节第 266 段先说明 plan 方法这次返回的是「结束」类型的实例(text/46-ch06-05-6-5-agentexecutor.txt:266,搜「AgentFinish实例」)。

  19. 出处:「6.5 深挖AgentExecutor的运行机制」第 166 段(text/46-ch06-05-6-5-agentexecutor.txt:166,搜「无效工具处理」)与第 168 段(text/46-ch06-05-6-5-agentexecutor.txt:168,搜「返回AgentStep」)。原文明说无论是有效执行还是无效处理,最终都会生成一个步骤对象返回——也就是说这两条路在结构上是一样的。

  20. 出处:「6.5 深挖AgentExecutor的运行机制」第 89 段(text/46-ch06-05-6-5-agentexecutor.txt:89,搜「handle_parsing_errors」)。原文列的两条分支是:设成抛异常就抛出一个 ValueError;设成处理异常就构建一个动作实例、产出一个步骤。书没有说这个设置的默认值是什么。

  21. 出处:「6.6 小结」第 11 段(text/47-ch06-06-6-6.txt:11,搜「AgentExecutor就是上述机制得以实现的引擎」)。同节第 15 段还给了一条很实在的建议:你也可以参考它的实现方式,定制出专属于自己的那一套(text/47-ch06-06-6-6.txt:15,搜「定制出专属于自己的Agent思维框架」)。

  22. 出处:「6.5 深挖AgentExecutor的运行机制」第 231 段(text/46-ch06-05-6-5-agentexecutor.txt:231,搜「该类的内部实现其实也是调用大模型」)。

  23. 出处:「6.5 深挖AgentExecutor的运行机制」第 237 段(text/46-ch06-05-6-5-agentexecutor.txt:237,搜「numexpr」)。书里把那段英文提示原样贴了一行,大意是「把一道数学题翻译成能用 Python 的数值计算库执行的表达式,用运行这段代码的结果来回答问题」。

  24. 出处:「6.5 深挖AgentExecutor的运行机制」第 239 段(text/46-ch06-05-6-5-agentexecutor.txt:239,搜「规避了大模型数学推理能力弱的局限」)。同一条理由在原书第 4 章也出现过一次:那里给一个只会做加价计算的助手挂上代码解释器,作者给的第一个理由就是「早期的大模型被公认数学不好」(text/32-ch04-03-4-3-assistants-api.txt:95,搜「早期的大模型被公认数学不好」)。

  25. 补充(不在书里,依据我们的前沿框架书架):工具抛异常时默认不让整趟运行失败。依据: shelf=ai-frontier-reference/openai-agents-python#02-tools.md @2c5560339cd7f77b4dabcf7d85c5d150594fd74c 事实=由 failure_error_function 控制(tool.py:2551),默认调用一个内置函数生成给模型看的错误消息;显式传 None 才改为直接抛异常、让整趟失败。该拆解把这个取舍称作「对模型容错 vs 对调用者暴露」的一个关键旋钮。

  26. 补充(不在书里,依据我们的前沿框架书架):超时也走同一条「喂回模型」的路,并且有次数上限。依据: shelf=ai-frontier-reference/pydantic-ai#02-tools-and-toolsets.md @606b0e581272eb9f06ca989d88be5828047eed9c 事实=工具超时被转成一条「超时了,请重试」的消息喂回模型(toolsets/function.py:634),参数校验失败同理;而重试次数由一个默认最大重试数把关,超了仍然失败。

  27. 补充(不在书里,依据我们的前沿框架书架):书里用的那个创建函数已被搬进旧命名空间并挂了警告。依据: shelf=ai-frontier-reference/langchain@src:libs/langchain/langchain_classic/agents/react/agent.py:33 @2019bf5ebe50324c548f67c2666a804343f9b772 事实=该函数的说明文字里有一段 warning,原话是这个实现「older and not well-suited for production applications」,建议改用 create_agent;函数所在的包名已经是 langchain_classic

  28. 补充(不在书里,依据我们的前沿框架书架):同名函数在图执行库里也有一个,同样已弃用。依据: shelf=ai-frontier-reference/langgraph@src:libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py:275 @1e44bda48ff4982b8ccfeec9c14156ea9e8ae5a2 事实=该处挂着弃用提示,文案是「create_react_agent has been moved to langchain.agents」,要求改用 from langchain.agents import create_agent

  29. 补充(不在书里,依据我们的前沿框架书架):那个执行器有一个默认 15 的轮数上限。依据: shelf=ai-frontier-reference/langchain@src:libs/langchain/langchain_classic/agents/agent.py:1023 @2019bf5ebe50324c548f67c2666a804343f9b772 事实=字段声明是 max_iterations: int | None = 15,紧跟的注释说这是结束执行循环之前最多走的步数,并写着「设成 None 可能导致无限循环」。书里从头到尾没有提过这个字段——第 09 章第 7 节会说明这个空白的后果。