跳到主要内容

大模型应用开发:动手做 AI Agent — 本课题摘录

读了哪几节: 2.4(调用工具)、2.5(ReAct 框架)、5.4(工具调用的接口实现)、6.5(执行器的运行机制)。 全书十章,其余各节本轮没读。

为什么读这本:前面读的三十多份材料全是"某个项目怎么做的",这本是唯一一份把"一个最朴素的循环长什么样"从提示词到断点逐行摊开的。它讲的东西比任何一家都浅,但正因为浅,它把别家默认省略的那几步写出来了。

它对本课题回答了什么

决定一:驱动整个循环的提示词,原样只有这么多字

这是本课题最有用的一条实物:

尽你所能回答以下问题。你可以访问以下工具:
{工具}
请使用以下格式回答
问题:你必须回答的输入问题
思考:你每次都应该思考接下来怎么做
行动:要采取的行动,应该是[{工具名称}]中的一个
行动输入:行动的输入
观察:行动的结果
……(这个思考—行动—行动输入—观察过程可以重复 N 次)
思考:我现在知道最终答案了
最终答案:原始输入问题的最终答案
开始!
问题:{输入}
思考:{Agent 记事本}

(依据:书 · 大模型应用开发:动手做AI Agent §2.5 —— 书里原样贴出 LangChain Hub 上那条 ReAct 提示模板,四个变量是 tools / tool_names / input / agent_scratchpad,格式行是「思考/行动/行动输入/观察」加一句「这个过程可以重复 N 次」,收尾行是「思考:我现在知道最终答案了」加「最终答案:」)

四个变量的分工值得单独看:

  • 工具清单工具名清单是两个变量——一个给模型看能干什么,一个约束它只能从这几个名字里选;
  • 输入是这次的问题;
  • 记事本是历史,而且它接在最后一行"思考:"的后面。

最后一点是这条提示最巧的地方:历史不是一个独立段落,而是接在"思考:"这个词后面。 模型看到的是一句没写完的话,于是它只能接着写。

这条对我们的最小原型有直接用处: 如果第一版不用原生工具调用, 这十几行就是全部的协议。 前面 tongyi-deepresearch 用四种标签、agenticseek 用代码围栏, 这一份用四个词的固定格式,是三者里最省的。

书里点明了这条提示的作用:没有这个提示,模型将不知道如何去做,可能自行其是。

决定四:执行器的循环体,以及唯一的退出判据

书用断点调试逐行走了一遍:

执行器.invoke() ← 它本身就是一个「链」
└ _call() ← 循环在这里
└ _take_next_step() ← 走一步
└ _iter_next_step() ← 单步的枢纽:准备中间步骤 → 调模型计划 → 分派动作
├ 产出是「结束」类型 → 产出它,方法结束
└ 产出是「动作」类型 → 逐一执行,每个执行完产出一个「步」
判断:这一步的产出是不是「结束」类型?
是 → 跳出;否 → 继续循环

(依据:书 · 大模型应用开发:动手做AI Agent §6.5 —— AgentExecutor 继承自 Chain,循环体在 _call 方法里,单步在 _iter_next_step 里;退出判据只有一条——isinstance(output, AgentFinish),为真才跳出)

注意这个结构和 agentscope 的"决策函数返回三选一"几乎一样,只是少了一种:

  • agentscope:推理 / 行动 / 收工,三选一;
  • 这一家:动作 / 结束,二选一(推理被合进"算出下一个动作"这一步)。

二选一是最小可用的形态。 我们的原型可以从二选一起步。

书里明确写了执行器要处理的四种复杂情况:模型选了不存在的工具、工具出错、模型产出无法解析成调用格式、以及全程日志记录。

这四条正好是本课题第二和第三个决定的难点清单,而且是一本入门书就点出来的—— 说明它们不是高级问题,是第一天就会撞上的问题。

决定三最值得抄的一条:工具不存在时,把这件事当成一次观察回填

如果模型点的工具不在工具表里,不报错也不停,而是走一个专门的"无效工具"分支,返回一段包含错误信息的观察结果——里面有它点的工具名和可用工具的清单。 (依据:书 · 大模型应用开发:动手做AI Agent §6.5 —— _perform_agent_action 先查 name_to_tool_map,找不到就用一个特殊的「无效工具」处理,返回一段包含「你点的工具名」与「可用工具列表」的 observation,并同样生成一个 AgentStep 返回)

这条要抄。它的形状是:错误不是异常,是一次观察。

前面 agenticseek 说"拒绝话术本身就是修改指令",这一条是同一原则用在"工具选错"上: 不要抛异常打断循环,把"你点错了,可选的是这些"写回去,让模型自己改。

而且这条正好接上 deepagents 那条"不支持的工具不能只报错,还得从清单里消失"——两条配合才完整。

书里紧接着给了这条为什么必要的证据:它贴了同一个问题跑两次的真实轨迹,第二次模型凭空点了一个不存在的工具"阅读和收集信息",发现无效后改了搜索词重来。 (依据:书 · 大模型应用开发:动手做AI Agent §2.5 —— 书贴出同一问题跑两次的真实输出,第二次模型点了一个不存在的工具「阅读和收集信息」,判定无效后自己改用搜索工具重试;书把这当成 agent 的正常工作方式展示而非 bug)

这是一份很实在的证据:模型会点不存在的工具,而且这不是罕见情况。 本课题第二个决定里,"认出模型要调工具"之后还有一步——"确认这个工具真的存在"。

决定三:一次响应带多个调用时,按 id 配对回填

书里的例子问了三个城市的库存,模型一次返回三个同名调用,各带一个 id;这一轮的结束原因字段直接写着"要调工具"。

回填时每条结果都要用工具结果角色,并带上对应的 id。 (依据:书 · 大模型应用开发:动手做AI Agent §5.4 —— 一次响应可带多个 tool_calls(三次同名调用各有独立 id),finish_reason 为 tool_calls;回填时每条结果作为一条 role=tool 的消息、带 tool_call_id 与函数名,再整体第二次发给模型)

这就是"调用与结果必须配对"那条不变量最原始的样子。 前面七家从各个角度强调它,这本书直接给出了它长什么样:id 是配对的钥匙。

书里的循环是顺序的 for 循环——三次调用一个接一个跑。

前面 kimi-code / haystack / dexter 讨论的"哪些能并发"在这里完全没出现。 对最小原型,顺序就够;但要知道那是一个被跳过的决定,不是不存在的决定。

结构化输出模式的四条注意 —— 一份很实用的清单

注意说的是
上下文里必须出现"JSON"这个词否则接口直接报错
可能生成无限空白直到烧完预算上一条的防呆就是为了拦这个
达到输出上限时结构可能不完整解析前要先查结束原因
保证合法,但不保证符合你要的结构合法 ≠ 对

(依据:书 · 大模型应用开发:动手做AI Agent §5.4 —— JSON 模式的注意事项包括:上下文里没出现「JSON」字样时接口会报错、否则模型可能生成无限空白直到 token 上限、达到 max_tokens 或超限时返回的 JSON 可能不完整所以解析前要先查 finish_reason、以及保证合法但不保证匹配特定架构)

第三条正好印证了 qwen-code 那条护栏(输出被截断时拒绝执行编辑类工具): 一本入门书和一个成熟产品在同一个坑上给出了同一个警告。

还有一条小巧思:书里的计算器工具内部其实是让模型写一段表达式再交给数值库跑,而不是让模型自己算——这规避了模型算术能力弱的局限。

"工具的实现可以再套一次模型"这件事值得记:工具不必是纯函数。

它没回答什么

  • 并发——三次调用是顺序跑的,书里没讲能不能并行。
  • 停止条件的护栏——没有轮数上限、没有连错熔断、没有打转检测,只有"模型说完成了"这一条出口。
  • 上下文怎么压——中间步骤一路累加进记事本,没讲长任务怎么控长度。
  • 工具真的抛异常时怎么办——只讲了"工具不存在"这一种。
  • 从零手写循环——全书都在用现成的执行器,没有一处自己写外层循环。

坑与代价

  • 书里的代码已经过期。 它用的那套包在这两年重构过多次,照抄会报错。 有价值的是那段提示词和那条调试路径,不是那几行导入语句。
  • "模型说完成了就是完成了"是它唯一的退出判据。 结合 db-gpt 的"要有外部裁判"和 kun 的"模型经常在没干完的时候就停下来",这条判据在真实长任务里明显不够。

    判断(无锚): 这本书的循环适合当我们最小原型的第一版骨架,但"什么时候停"这一问必须从别处补,不能只用它这一条。 如果错,会错在: 如果我们的第一版任务都很短(三五轮内完成),那这一条判据完全够用,提前加护栏是过度设计。

  • 它是入门书,遇到有分歧的地方一律选最简单的做法,而且不说明还有别的选择。 读它要配着别的材料看。