Chapter 09 · 组合
组合:把零件集成成一个真实系统
第 08 章把治理立成横切关切:护栏、权限、可观测。前八章给了零件;组合是把它们集成成一个真实系统的收口——选型与搭 Harness。这一章回答两个问题:面对一个业务需求,先选哪个模式;选定之后,怎么把工具、循环、记忆、护栏、观测装配成一个能托住模型自主性的运行时。
本章你将建立的 schema
- workflow(代码预先编排的固定路径)与 agent(LLM 在循环里自主决定流程)的根本二分,以及"能 workflow 别 agent"的复杂度纪律。
- 五大模式选型卡:提示链 / 路由 / 并行化 / 编排者-工作者 / 评估者-优化者——前四个本质是 workflow,不是 agent。
- 六步选型法:从业务问题到架构的一棵剪枝决策树,每一步默认选更简单的那一侧。
- Harness 集成:工具 + 循环 + 记忆 + 护栏 + 观测五件事,以及长程任务把状态外化到磁盘工件的机制。
09.1选型卡:在双轴上定位,而非背模式清单
模式选型的本质是把任务投影到两根轴上——可预测性↔灵活性、自主度等级——而不是记忆一张模式清单。
面对"该用哪个 agent 模式",最自然的反应是去背一张包含提示链、路由、并行化的清单,再凭名字像不像往上套。这个反应抓错了层次。模式不是平铺的选项,而是两根轴上的取值——第 01 章的双轴框架在这里复用:一根轴是可预测性对灵活性(= workflow↔agent),另一根是自主度等级(路由级到全自主)。把任务投影到这两根轴上,模式自己就浮出来了。
Anthropic 在《Building Effective Agents》里给了一个根本二分:workflow 是用代码预先编排 LLM 与工具的固定路径,agent 是让 LLM 自己在循环里动态决定步骤与工具调用。判据是可预测性——定义良好、要一致性的任务用 workflow,要灵活、要模型自主决策的用 agent。这一层之下的机制是成本:越往 agent 端走,每个决策点都多一次 LLM 调用,延迟与成本随决策点数量线性上升;更糟的是错误会在循环里复合(compounding errors),一步偏差喂给下一步,越长的循环越放大。这正是"能 workflow 别 agent"的物理原因——agentic 系统是用延迟和成本换开放任务的表现,对已定义良好的任务,这笔交换是纯亏。
| 模式 | 触发条件 | 性质与代价 |
|---|---|---|
| 提示链 chaining | 能拆成固定顺序的子任务 | workflow;最简,每步可单独校验,无动态分支 |
| 路由 routing | 先分类再分流到专用路径 | workflow;分类错则全程错,分类器要单独评测 |
| 并行化 parallelization | 子任务独立可并发 | workflow;sectioning 提速 / voting 提可信度,扇出成本叠加 |
| 编排者-工作者 | 运行时才知道要拆几步 | workflow;编排者动态拆分,跨文件改码 / 多源研究典型 |
| agent 循环 | 步骤数不可预测、无法硬编码路径 | 选中此处才是真 agent——用延迟与成本换开放任务表现,错误会复合 |
一个团队把"先用 LLM 给工单分类,再按类别路由到三条不同的处理链"称为他们的"路由 agent",并给它配了完整的 agent 循环、最大轮数和重试护栏。这个命名错在哪?带来什么不必要的代价?
展开答案(先停 10 秒再点)
路由本质是 workflow——控制流是代码写死的(分类 → 查表 → 分流),不是 LLM 在循环里动态决定的。把它叫 agent 会让它背上不属于它的开销:agent 循环每轮多一次 LLM 调用、要设最大轮数、要配重试与循环护栏,而一个确定性的分流根本不需要循环——它跑一次分类、查一次路由表就结束。Anthropic 列的五大模式里有四个(chaining / routing / parallelization / orchestrator-workers)都是 workflow 不是 agent;正确的做法是把它当 workflow 部署,省掉循环与护栏成本。
09.2六步选型法:一棵默认偏简侧的决策树
六步选型法把双轴展开成一串短路式判断,每一步用"最简方案能否满足"做剪枝,默认答案都偏向更简单的那一侧。
§09.1 给了坐标系,但坐标系不会自己告诉你"这个具体业务该落在哪一格"。需要的是一个把业务问题反推成架构的决策流程。OpenAI 在《A Practical Guide to Building Agents》里给的方法是一串判断,每一步只问一件事,且每一步的默认值都偏向更简单的那侧——只有出现具体失效信号时才升级复杂度。这是"从简单起步"复杂度纪律的可执行版本。
这棵树的机制是短路剪枝:从最便宜的方案开始,逐步问"它还不够吗",不够才进下一层。Step 1 决定该不该上 agent——OpenAI 给三条判据:(1) 需要细腻判断、处理例外、上下文敏感的复杂决策(如退款审批);(2) 规则集臃肿、改起来易错的难维护规则(如供应商安全审查);(3) 重度依赖自然语言、文档解读、对话的非结构化数据(如保险理赔)。三条都不满足时,"a deterministic solution may suffice"——确定性方案就够,止步,别上 agent。Step 2 任务能否拆成固定顺序子步骤——能则 prompt chaining,仍是 workflow。Step 3 步骤数是否运行时才知道——是则需要动态编排(orchestrator-workers 或 agent loop)。Step 4 是否需要跨会话记忆——单轮够则无状态,跨上下文窗口的长程任务才上 harness 的状态工件。Step 5 是否需要多 agent——默认否。Step 6 按已识别风险叠护栏、按失败阈值设人在回路。
| 痛点 / 失效信号 | 设计回应 | 剪枝纪律 |
|---|---|---|
| 凭直觉就上全自主多 agent | Step1 三判据先剪:都不满足→确定性方案 | 默认偏简侧,止步而非升级 |
| 步骤其实定序却套了 agent 循环 | Step2→提示链,留在 workflow | 能 workflow 别 agent |
| 长程任务靠超长上下文硬扛 | Step4→上 harness 磁盘状态工件 | 跨窗口才升级到有状态 |
| 按职责直觉过早拆多 agent | Step5 默认单 agent,先加工具顶满 | 选中——拆分由失效信号驱动,不由直觉 |
| 高风险动作无安全阀 | Step6 叠分层护栏 + 高风险升级人工 | 按已识别风险按需叠加 |
Step 5 的拆分信号(最反直觉的一步):默认单 agent,靠加工具把单 agent 顶满。真正的拆分触发信号是两个可观测的失效——prompt 里 if-then-else 分支爆炸,或模型反复选错工具——而不是"看起来该分开"的职责直觉。关于工具选错,OpenAI 给了一个反直觉判据:问题不在工具数量而在相似/重叠度——有的实现稳管 >15 个界限清晰的工具,有的 <10 个重叠工具就失效。所以先靠改名、明确参数、写详细描述提升工具清晰度,无效再拆 agent。要拆时分两种形态:Manager 模式(agents-as-tools,中心 agent 用 tool call 委派并综合结果)与 Decentralized 模式(peers 之间 handoff 单向转移执行权)。这两种可统一建模为图——这正复用了第 01 章执行拓扑那根轴:manager 的边是 tool call(可回中心综合),decentralized 的边是 handoff(单向、不回头)。
一个客服 agent 配了 9 个工具就频繁选错,工程师的第一反应是"工具太多了,拆成 3 个专用 agent"。按 OpenAI 的判据,为什么这个反应搞错了方向?正确的第一步是什么?
展开答案(先停 10 秒再点)
问题判据搞反了:选错工具的根因不是数量而是相似/重叠度。OpenAI 明确——稳定实现可管 >15 个界限清晰的工具,而 <10 个语义重叠的工具就会失效。9 个工具选错,几乎可以肯定是其中几个职责边界含混(如 refund 与 issue_credit description 雷同)。正确的第一步不是拆 agent(拆分会引入协调开销、上下文丢失、更难观测),而是先提升工具清晰度:改名、明确参数、写更精确的 description。只有清晰度改进无效、或 prompt 里 if-else 分支爆炸,才把它当成拆分信号。
09.3集成 Harness:装配托住模型的运行时
Harness 是把工具、运行循环、记忆、护栏、可观测五件事集成在一起、托住模型自主性的"外壳"。
§09.2 选定了"要 agent 循环",但一个裸的 while LLM 调工具 循环跑不起真实系统。模型的自主性需要一层东西去托住它——退出条件、状态、违规拦截、每步留痕。这层就是 harness。OpenAI 的单 agent 图把它具象成 Instructions + Tools + Guardrails + Hooks 环绕着 Agent,外加一个跑到退出条件的 run 循环。集成 harness,就是把前八章的零件(感知、记忆、行动、反思、协作、治理)装配进这五个横切面。
先看运行内核。agent 的 run 循环就是一个 while:LLM → 调用工具 → 读环境反馈 → 再决策,直到退出条件。OpenAI 列的常见退出条件有四个:final-output 工具被调用、模型返回无工具调用的回复、报错、或达到最大轮数(max turns)。这就是"agents are just LLMs using tools in a loop"的字面实现——它正是第 01 章那个原子循环加上一个 until。护栏这一面用纵深防御:单个护栏覆盖不全,多个专用护栏叠成多层——相关性分类器(挡跑题)、安全分类器(挡越狱/提示注入)、PII 过滤、moderation(有害内容)、工具风险分级(按只读/可逆/权限/财务影响打 low/med/high,高风险暂停或升级人工)、规则型(黑名单/长度/正则挡 SQLi)、输出校验。SDK 默认乐观执行:主 agent 先出 output,护栏并发跑,违规抛 tripwire——这平衡了安全与延迟,串行前置会徒增延迟。人在回路则有两类触发:超过失败阈值(多次无法理解意图就升级人工),以及高风险/不可逆动作(取消订单、大额退款、付款)在信心建立前都要人工监督。
横向集成纪律(LangChain 的啤酒比喻):harness 里哪些该自建、哪些该外包?Harrison Chase 的划分是——智能体基础设施(状态持久化、任务队列、后台运行、部署伸缩容错)"不会让你的啤酒更好喝",该外包;认知架构(应用专属的控制流、状态追踪、工具选择与 prompt 策略)"会让啤酒更好喝",必须自有。可靠性来自任务专属架构,通用认知架构(如把一切塞进 messages-in-a-thread)会被框架的预设形态卡死、限制复杂高价值 agent 能造的形态。所以任务特异性应落在工具与 prompt 上,harness 骨架保持相对固定。
# 选型 → harness 集成的一体化决策(浓缩 L39 + L40 + L41)
def pick_and_build(task):
# L40 Step1:该不该上 agent(OpenAI 三判据)
if not (task.complex_judgment or task.brittle_rules or task.unstructured):
return Deterministic() # 确定性方案就够,止步
# L39/L40 Step2-3:双轴定位 → 选 workflow 模式(能 workflow 别 agent)
if task.steps_fixed_at_design_time:
if task.needs_classify_then_route: return Routing()
if task.subtasks_independent: return Parallelization(mode="section|vote")
return PromptChaining()
if task.subtasks_unknown_until_runtime and task.has_clear_eval_criteria:
return EvaluatorOptimizer()
if task.subtasks_unknown_until_runtime:
return OrchestratorWorkers() # 仍是 workflow,动态拆子任务
# 走到这里才需要真正的 agent 循环
agent = SingleAgent(model=best_model, tools=[], instructions=routine)
# L40 Step5:单 agent 顶满,仅在失效信号下才拆多 agent
while agent.fails_complex_instructions or agent.picks_wrong_tool:
if not improve_tool_clarity(agent): # 先改名 / 参数 / 描述
agent = ManagerPattern() if task.needs_central_synthesis \
else DecentralizedHandoffs()
break
# L41:集成 harness = 工具 + 循环 + 记忆 + 护栏 + 观测
harness = Harness(
loop=run_until(exit=["final_output", "no_tool_call", "error", "max_turns"]),
memory=DiskState("claude-progress.txt", "feature-list.json", "git"),
guardrails=[relevance, safety, pii, moderation, tool_risk_rating, output_check],
observability=[commit_per_feature, e2e_self_verify],
human_in_loop=on(failure_threshold, high_risk_action),
)
return harness.wrap(agent)
# 纪律:每一步默认选更简的一侧;只有 eval 证明改善才升级复杂度
"从简单起步"不是一句口号。Anthropic 的原话是"add multi-step agentic systems only when simpler solutions fall short"、"consider adding complexity only when it demonstrably improves outcomes"——加复杂度前要有 eval 证明它确实改善了结果。方法是先用最强模型建 baseline,再逐任务下探小模型;为延迟/成本一刀切用小模型,会过早砍掉能力,且把失效点埋进难诊断的地方。
09.4长程 Harness:状态外化到磁盘工件
长程 agent 的可靠性主要不靠更大的上下文窗口,而靠把状态外化成磁盘工件——记忆是文件系统,不是 context。
§09.3 的 harness 默认任务能在一个 run 里跑完。但跨越多个上下文窗口的长程任务有一个硬难点:每个新 session 都无记忆。Anthropic 给的心智模型是"轮班的工程师,每班都失忆"——上一班做了什么,这一班从零开始。纯靠超长上下文加 compaction(第 03 章的有损压缩)跨会话不够:窗口有限、压缩有损,复杂项目无法在单窗口完成。出路是把状态外化到磁盘。
Anthropic 在《Effective Harnesses for Long-Running Agents》里给的机制是双 agent + 磁盘工件。Initializer agent 只跑一次:搭好环境、把 prompt 展开成 >200 条 feature-list.json、写好 init.sh。Coding agent 每个 session 被反复唤醒,按固定开机流程推进——读 git log 与 claude-progress.txt → 挑最高优先级未完成 feature → 跑 init.sh → 先做端到端自验 → 做一个 feature 的增量 → 跑测试 → 写进度笔记 → git commit。状态靠这些磁盘工件而非上下文压缩来桥接:记忆 = 进度笔记 + feature 清单 + git 历史(可回滚的检查点),观测 = 每步 commit message + E2E 验证结果。这呼应了第 03 章的结论——长任务真正的 source of truth 不是 context window,而是写在外部的进度文件。
passes 字段、必跑 init.sh、强制 commit)比模型能力更决定成败——因为每个 session 从零开始,桥接全靠这些磁盘约定。| 方案 | 优势 | 为什么没选 / 选中 |
|---|---|---|
| 超长上下文窗口全塞 | 实现零成本 | 窗口有限,复杂项目跨多窗口无法单窗完成 |
| 纯靠 compaction 跨会话 | 无需额外工件 | 压缩有损,新 session 失忆、重复劳动、丢进度 |
| 磁盘工件(progress + feature-list + git) | 无损、可回滚、可被新 session 重读 | 选中——代价是维护工件规约:只改 passes、必跑 init.sh、强制 commit |
一个长程 coding agent 把某个 feature 的 passes 字段标成 true,但下游集成时发现这个功能在真实页面上根本点不动。它是怎么"宣告假完成"的?Anthropic 的修复是什么?
展开答案(先停 10 秒再点)
这是一个被命名的、可复现的失效模式:Claude 会"未经测试就宣告完成"。根因是它常用 unit test 或 curl 判定 feature 完成——这两者验不出真实用户路径,于是产生假阳性。修复有三条:(1) 每个 session 开头先跑基础 E2E 验证再做新功能;(2) 用浏览器自动化(Puppeteer MCP)像真人那样验,而非 unit test / curl;(3) 只能改 feature 的 passes 字段,不可删改测试——否则会漏掉或留 bug。注意 E2E 也有视觉盲区,比如 Puppeteer 看不到原生 alert 弹窗,且更慢更贵。
Anthropic 的 initializer + coding agent 范式当前是为全栈 Web 开发优化的,对科研、金融建模等场景的适用性未验证。而且"单个通用 coding agent vs 多 agent 架构孰优",按官方说法仍不清楚。harness engineering 作为一门独立学科(工具/循环/记忆/MCP/权限/观测的集成),术语与边界都还在成形——把这套照搬到非 Web 任务前,要按 §09.3 的纪律先建 baseline、用 eval 验证它确实改善了结果。
§本章 self-check
先合上教程,把你能想到的答案写在纸上或编辑器里。写完再点开答案对照——直接点开等于把这一节当再读一遍。
- workflow 与 agent 的根本判据是什么?为什么"能 workflow 别 agent"在物理上成立?
- 五大模式里哪四个本质是 workflow?把它们当 agent 部署会多付什么成本?
- 六步选型法 Step 1 的三条判据是什么?三条都不满足时该怎么办?
- 判断是否拆多 agent,OpenAI 的工具过载判据为什么是"相似度"而非"数量"?拆之前先做什么?
- 长程 harness 为什么把状态外化到磁盘工件而非靠 compaction?工件规约的三条硬约束是什么?
答案(先做完再展开)
- 判据是可预测性:workflow 是代码预先编排的固定路径,agent 是 LLM 在循环里动态决定流程。物理原因是成本——越往 agent 端走,每个决策点多一次 LLM 调用(延迟×成本线性上升),且错误会在循环里复合。对已定义良好的任务,这笔交换是纯亏。
- chaining / routing / parallelization / orchestrator-workers 这四个是 workflow(加上 evaluator-optimizer 也是)。当 agent 部署会无谓背上 agent 循环(每轮多一次 LLM 调用、要设 max turns)与循环护栏的成本。
- 三判据:复杂决策(需细腻判断/例外/上下文敏感)、难维护规则(规则臃肿易错)、非结构化数据(重度依赖自然语言/文档/对话)。三条全不满足时 "a deterministic solution may suffice"——上确定性方案,连 workflow 都不必。
- 因为问题在工具的相似/重叠度而非个数:稳定实现可管 >15 个清晰工具,<10 个重叠工具就失效。拆之前先提升工具清晰度(改名、明确参数、写详细描述);只有清晰度无效或 prompt 的 if-else 分支爆炸,才作为拆分信号。
- 因为 compaction 有损、窗口有限,复杂项目无法单窗完成,新 session 失忆会重复劳动丢进度。磁盘工件提供无损、可回滚、可被新 session 重读的状态。三条硬约束:只改
passes字段、必跑init.sh、强制git commit。
给一个"自动给供应商发付款"的需求选型并搭最小 harness
需求:客户提交一张发票,系统读 PDF 解析金额、核对采购单、若匹配则向供应商账户发起付款。用 §09.2 的六步法走一遍——它满足哪几条 agent 判据?哪些步骤是 workflow、哪些必须进 agent 循环?然后给它配最小 harness:列出退出条件、至少三层护栏、以及人在回路应该卡在哪个动作上。说清你的每个选择对应六步法的哪一步。
提示(卡住再展开)
Step 1:它命中"非结构化数据"(解析 PDF/发票)和部分"复杂决策"(匹配例外的人工判断),所以该上 agent——但只在解析与核对的判断部分。Step 2-3:读 PDF→解析→核对→付款是固定顺序,主干其实是 prompt chaining(workflow),只有"解析含混发票"那一格需要模型自主。这正是组合的要点:不是整条流程都做成 agent。护栏至少三层:PII 过滤(发票含账户信息)、工具风险分级(付款是高风险/不可逆/财务影响→打 high)、输出校验(金额与采购单一致性)。人在回路必须卡在付款动作上——这是不可逆的高 stakes 动作,在信心建立前都要人工监督,正对应 §09.3 的两类 HITL 触发之一。代价:每加一层护栏与一道人工门,都增加延迟,要用乐观并发执行把护栏与主流程并行起来抵消。