Agent Mentor Learn
从循环到图:Agent 系统的编排工程 · 第 2 / 6 节

第 2 课:串起来、分下去:链式与路由

学习目标:

  • 把一个塞了四件事的提示词拆成一条链,说清这么拆换来了什么、付出了什么
  • 在链的环与环之间安上程序化关卡,让不合格的中间结果停在该停的地方
  • 判断一个任务该链式、该路由、两者都用还是都不该动,并把路由写成一次输出收紧的廉价调用

前置要求:读完第 1 课,能手写以 stop_reason 驱动的 harness 循环(本系列第 7 门课),了解确定性验证器(本系列第 10 门课) | 上一课 第 1 课 << | 下一课 第 3 课 >>

一个提示词干四件事,总有一件会掉链子

你要给一个新功能写帮助文档:读产品需求 → 列一份提纲 → 按提纲写正文 → 检查全文有没有用错旧称。

第一版你多半是把这四步写进一个提示词,交给一个 harness 循环。第一次跑出来看着还行,问题出在第二次、第三次:这回提纲很好,正文却少写了一节;下回正文完整,通篇混着上一版就废掉的「用户组」;再下回它写到一半回头改提纲,交出来的提纲和正文对不上。

这几次失败长得不一样,根子是同一个:这一次调用里,模型要同时扛住理解需求、设计结构、生成文字、做一致性核对四件事,哪件挤掉哪件你控制不了,也看不见。更麻烦的是你没有插手的位置——等你拿到输出,四件事已经全做完了,「提纲不合格」这个事实被埋在最终稿里。

第 1 课讲过「谁持有计划」这根轴。这个一次性提示词把计划整个交给了模型。本课第一件事,就是把它拿回来。

链式:拿延迟换准确率

官方给这个模式的定义只有两句,每个词都在承重。

链式(prompt chaining)把一个任务分解成一串步骤,每次 LLM 调用处理上一次调用的输出;你可以在任何一个中间步骤加上程序化检查,确保整个过程还在轨道上1。什么时候用它:任务能被干净利落地分解成固定的子任务时。主要目的是拿延迟换更高的准确率——办法是让每次 LLM 调用都变成一个更容易的任务1

「让每次调用变成更容易的任务」这半句把病因和药方一起给了。一次调用扛四件事是难任务;一次调用只列提纲、什么都不写是容易任务。链式没有把工作变少,是把每一次要求模型完成的工作变简单了。

代价明码写在标价里:延迟。每多一个阶段就多一次完整往返。官方在同一篇文章开头就摊开过这笔账——agentic 系统常常是拿延迟和成本去换任务表现,你应该想清楚这个交换在什么时候划算1。「又慢又贵」不是链式的意外副作用。

text
需求 ──▶ 出提纲 ──▶ 按提纲写正文 ──▶ 查术语 ──▶ 交付

每一段的输入是上一段的输出。前一环歪了,后面只会照着歪下去。

每个阶段是一个完整的 harness 循环

链上的一个阶段不是「一次 API 调用」,而是一个完整的 harness 循环——就是你在本系列第 7 门课手写的那个 while 循环:发消息、看 stop_reason、是 tool_use 就执行工具再把结果发回去、不是就返回文本。

这个用法有出处。官方讲怎么给 Agent 做评测时给的跑法就是这个形状:直接调 LLM API、用简单的 agentic 循环(把 LLM 调用与工具调用交替包起来的 while 循环),一条评测任务配一个循环,每个评测 Agent 拿到一条任务提示词和你的工具2。那篇讲的是评测,这个构件本身是通用的:一条任务、一个循环、由代码驱动。链式就是把这样的构件串起来,用代码决定顺序。

后面几课都用同一个记号:

整条链就是一段你能一眼读完的顺序代码:

请注意这几行里没有的东西:没有「模型决定下一步做什么」的余地。顺序是写死的,中间结果 outlinedoc 是脚本里的普通变量。模型在每个阶段内部仍然自主(它想调几次工具就调几次),但阶段之间的控制权在代码手上。附带好处是每段提示词可以只针对一件事写严:出提纲那段要求只输出标题、一个字正文都不许写,写正文那段专心讲文风和禁用词——这两套要求塞进一个提示词里是会打架的。

这个形状在产品里也见得到。Claude Code 官方文档对多步骤工作流的建议是:让 Claude 按顺序使用子代理,每个子代理完成自己的任务、把结果交回来,Claude 再把相关的上下文传给下一个3。区别落在第 1 课那根轴上——产品形态里决定「传什么过去」的是 Claude 自己,你写脚本的时候那是你的代码。

关卡:把本系列第 10 门课的验证器搬到环与环之间

定义里最后半句,才是链式比「一个大提示词」真正多出来的东西:你可以在任何一个中间步骤加上程序化检查,确保过程还在轨道上1。原文给这种检查起的名字是 gate,而且是打了引号的:(see "gate” in the diagram below)——引号一边直一边弯,原文如此,不是这里打错。

「程序化」是重点:它是代码,不是又一次模型调用,就是几行 if

本系列第 10 门课教过确定性验证器:能用代码判定对错的事,就别花钱去问模型。那门课把验证器安在终态上——跑完了,检查产出合不合格。链式给同一种检查提供了一个新位置:环与环之间

七行,一次模型调用都没有,同样的输入判定永远一样。它挡住的正是「正文混进旧称」那种失败;数一数提纲有几个章节标题的那道关卡同样简单。

关卡失败时做什么是个设计决策:停下并报错(最适合你还在调这条链的时候,但失败信息里必须写清挂在哪一环,否则你只知道「不行」,不知道该改哪段提示词)、把失败原因塞回同一阶段的提示词重试(配次数上限)、或者记一笔用兜底值继续(只有这一环不承重时才行)。第 2 级练习用第一种。

顺带说本系列第 6 门课的账:派给别人干的活,提示词必须自包含——目标、输出格式、能用什么工具、边界在哪,四样都得写全。链上每个阶段的任务就是一份这样的派活提示词。这四要素有准确的一手出处,第 4 课讲编排者怎么派活时会拿出来逐条对。

路由:先分类,再分发

链式管的是「一件事分几步做」。另一类任务的形状完全不同:进来的不是一件事,是好几种事,各有各的做法。

官方的定义是:路由(routing)对输入做分类,然后把它导向一个专门化的后续任务。这个工作流带来关注点分离,也让你能写更专门化的提示词。没有它,为一类输入做的优化可能会伤害其他输入的表现1

最后那句是路由存在的理由。假设客服来信有退款、故障、账单三类,你用一个提示词处理全部。为了把退款处理好,你加了一条「先确认订单号和支付渠道」;这条规则对故障来信是纯干扰,模型会拿着它去问一个正在报页面白屏的用户要支付渠道。你再加一条「如果是故障就别问订单号」,提示词开始长出补丁的补丁。

什么时候用它:任务复杂、存在几个明显不同、分开处理更好的类别,并且分类这件事本身能被准确完成——用 LLM 做分类可以,用更传统的分类模型或算法也可以1。最后这个前提条件不是锦上添花:分错了错得很隐蔽——一封退款信被送进故障流程,会得到一份认认真真的故障排查回复。

代码上,路由比链式还简单:

三处值得注意。

分类调用要把输出收紧到一个词——本系列第 10 门课讲 LLM 裁判时用过同一招:把可选值列出来、明说不要解释,收紧之后解析这一步才有可能是确定性的。收不进表里就走兜底——那行 LABELS.includes(label) ? label : '其他' 不是防御性洁癖,模型偶尔会回「我觉得可能是故障,也可能是别的」,这时 HANDLERS[那串话]undefined,下一行就炸了;留一条兜底分支,分类的不确定性就被关在这一行里面。

分类器不一定要是模型——定义里明说了传统分类模型或算法也算1,来信带着固定的订单号格式、或者来自某个专门的表单入口,一条正则就够了,还快得多。

分发出去之后,每个 handler 可以是任何东西:一个 harness 循环、一条链、甚至一段完全不含模型的代码。

现行 API 词汇里的两个路由变体

上面那份路由定义来自 2024 年底的模式文章,页面自己挂着横幅说其中的工具生态描述已经过时。所以有必要核对:这个模式在现行的第一方词汇里还活着吗?活着,而且被单独点了名。Claude 平台的多代理编排文档里有两条就是路由:

  • 专门化(Specialization):把活路由给带有领域专属系统提示词和工具的代理,比如一个安全代理、一个文档代理,而不是把所有能力都塞进同一个代理里4。这就是 HANDLERS 那张表的官方说法。
  • 升级(Escalation):对其中一部分复杂子任务,去咨询一个能力更强的代理或模型4

第二条值得单拎出来:它按难度分发,而不是按主题分发。分类器不判断「这是退款还是故障」,而判断「这封信我这个便宜档位能不能处理」。这件事比主题分类更难判准,所以升级这一路有个更稳的写法:先按便宜档位跑,产出过不了关卡再往上升——把一个难判断的分类问题换成一个可判定的验证问题。

分寸:什么时候不该拆

链每加一环,就加一段延迟。 这不是实现没优化,这是官方给这个模式定的价:拿延迟换更高的准确率1。用户等的是每一段的和。如果用户是在界面上同步等结果,链再往长里加之前,先想想人还在不在。

类别只有一个时,路由是纯开销。 路由的收益来自关注点分离1。输入其实只有一种的话,你就付了一次分类调用的钱和延迟,什么也没换到,还多了一个分错的可能。

任务分不干净时,别硬拆。 「干净利落地分解成固定的子任务」这个条件是有牙齿的1。一份要来回看全局才能改好的稿子,拆成「先改结构、再改措辞」,第二环拿不到第一环没写下来的那些理由,只会照着字面改。这时候一个循环、一个上下文反而更好——这正是第 1 课那些反向条件的用处。

拿不准就先量。 官方这句话说了两遍:只在复杂度能被证明改善了结果的时候才考虑增加它1。本系列第 10 门课的评测跑道就是干这个的:先跑单提示词版本量一个分数,再跑拆开的版本量一遍,差多少、值不值那几秒延迟,这才是能拿去和同事吵的依据。

最后划一下边界。链式和路由都还是固定形状的编排:链有几环、路由有哪几类,写代码的时候就定死了。多个阶段同时跑再聚合,是第 3 课的并行化;连子任务有几个都要等看到输入才知道,是第 4 课的编排者-工人。

💻 练习

小结

  • 链式把任务分解成一串步骤、每次调用处理上一次的输出,让每一次调用都变成更容易的任务1;它明码标价:主要目的就是拿延迟换更高的准确率1
  • 链上的一个阶段是一个完整的 harness 循环,不是一次 API 调用——官方给评测推荐的跑法就是这个构件:一条任务、一个 while 循环、代码直接调 API2
  • 拆开之后多出来的位置才是重点:你可以在任何中间步骤加程序化检查,确保过程还在轨道上1。这就是本系列第 10 门课的确定性验证器换了个安装位置——从终态挪到环与环之间;产品形态里它长成子代理按序执行,每个完成后由上层把相关上下文传给下一个3
  • 路由对输入分类、分发到专门化的后续任务,换来关注点分离和更专门化的提示词;没有它,为一类输入优化可能会伤害其他输入的表现1。前提是类别清晰、且分类本身能被准确完成,LLM 或传统分类算法都行1
  • 分类调用要把输出收紧成一个标签,并留一条兜底分支接住收不进表里的回答。
  • 这个模式在现行第一方词汇里活着:按领域分发到带专属提示词与工具的代理叫专门化,就一部分复杂子任务去请更强的代理或模型叫升级4——后者就是按难度分发的路由。
  • 别硬套:类别只有一个时路由是纯开销,任务分不干净时硬拆会在阶段之间丢信息1。拿不准就先量,复杂度要过「能被证明改善了结果」这一关1

>> 第 3 课:并行化:分段与投票

Footnotes

  1. Building Effective AI Agents — Anthropic Engineering — https://www.anthropic.com/engineering/building-effective-agents 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

  2. Writing effective tools for agents — with agents — Anthropic Engineering — https://www.anthropic.com/engineering/writing-tools-for-agents 2

  3. Create custom subagents — Claude Code 官方文档 — https://code.claude.com/docs/en/sub-agents 2

  4. Multiagent orchestration — Claude API 文档(Managed Agents) — https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration 2 3

练习

01

下面四个任务,逐个判断该用链式、该用路由、两个都用,还是两个都不该用。除了结论,还要写清楚:判了链式,两环之间的程序化关卡查什么;判了路由,分类器放在哪、有几个类别、是模型还是代码。

Level 1:判断该链式、该路由,还是都不动(不写代码)
  1. 客服信箱每天进来几百封信,退款、故障、账单三类的处理方式完全不同,现在共用一个提示词,改一类的措辞就会影响另一类。
  2. 一份英文合同,要先审出一份结构化的风险意见(每条含条款位置、风险等级、说明),再把这份意见翻译成中文和日文两个版本,交给不同的团队。
  3. 一个混合工单队列:既有「密码重置」这种一步就能答完的,也有「数据迁移失败」这种要查日志、定位、给方案、写回复的多步任务。
  4. 用户在输入框里敲了一句话,你要把里面的错别字改掉,返回改好的那句话。
完成标准 · 本地勾选
02

写一个 chain.mjs,把开头那个写帮助文档的任务实现成三阶段链:出提纲 → 按提纲写正文 → 查术语一致性,中间两道关卡。要求:

Level 2:把三阶段链写出来并跑通(写代码)
  1. 用桩 client(本系列第 8 到 11 门课一直用的那套做法):一个固定响应队列,不联网、不花钱,同样的输入永远跑出同样的结果。
  2. runAgent(client, task, tools) 是真的 harness 循环——按 stop_reason 判断,tool_use 就执行工具再把结果发回去。第三阶段要真的走一次工具调用(读术语表)。
  3. 关卡 1:提纲必须恰好包含 3 个以 ## 开头的章节标题。关卡 2:正文里不得出现禁用旧术语(自己定一张两三个词的表)。两道都是纯代码,不再调模型。
  4. 关卡失败时打印挂在哪一环、为什么,然后以非零退出码结束;三个阶段全过则以 0 退出。
  5. 准备两组固定响应跑两次:一次全过,一次让第二道关卡拦下。把两次的真实输出和退出码都贴出来。
完成标准 · 本地勾选