主题
09b · 多 Agent 编排:拆 · 派 · 跑
这一站解决「单 Agent 什么时候不够用、不够用了怎么办」。上一站客服系统的 Supervisor 已经是多 Agent 的第一形态(路由 Agent + 三个专家工人)——本站把它泛化成可配置的编排器:LLM 把目标拆成子任务单、按依赖 DAG 并行派发、逐任务验收,失败有四分支退路,全程预算受控。配套代码
packages/engine/src/orchestrator/,一条命令体验:pnpm team "目标"。
前置:09 · 产品线组装(Supervisor 是本站的第一形态)· 05 · 工具层加固(幂等壳/审批壳在编排里继续复用) 深讲:参考库 · 编排器(代码走读 / 设计取舍 / 面试级追问)
一、为什么:单 Agent 的四个「超过」信号
单 Agent 够用时不要拆(02 站岔路 3)。出现以下信号且全部满足时,拆分才是可管理的工程:
| 信号 | 症状 |
|---|---|
| 工具表超载 | 单 Agent 塞 20+ 工具,选错率飙升 |
| 上下文需要隔离 | 不同子任务的 system prompt 互相打架 |
| 职责冲突 | 同一 prompt 同时要求「严谨查询」和「灵活创作」,行为不稳定 |
| 并行机会 | 子任务互不依赖,拆开能并行提速 |
何时不拆——能 Workflow 别 Agent,能单 Agent 别多 Agent。拆分是代价高昂的重构(跨 Agent 传状态、失败传播、调试复杂度全部翻倍)。
递进关系一句话:09 站的 Supervisor 是「按业务域」切的第一形态;编排器是「任意切法」的通用形态。
二、核心框架:拆 · 派 · 跑
编排的全生命周期就是三个字,每个字对应一组工程问题:
| 字 | 问题 | 编排器的回应 |
|---|---|---|
| 拆(Split) | 大任务怎么变小任务 | Planner 接口:LLM 动态拆单,或调用方给静态任务单;产物 = TaskSpec[](id / goal / dependsOn / acceptance) |
| 派(Dispatch) | 小任务分配给谁执行 | 依赖图分波:入度 0 的任务同波并行,完成后解锁下游;每任务最小权限工具表 |
| 跑(Run) | 协作不打架 | 显式状态机 + 逐任务验收 + 失败四分支 + 预算 + 事件流 |
三条生产级语义(玩具没有的分水岭)
- 验收闭环——「完成」必须有显式定义。每个子任务带
acceptance标准(contains关键词 /llm-judge对照 rubric /manual人工),产出先过 Critic:不达标打回重做,manual直接升级人工。全部子任务完成后还有编排级终审。 - 失败四分支——子任务失败不是只有「报错」一条路:
retry(退避重试,耗尽降级)/degrade(占位产出续链)/compensate(回滚已完成副作用)/escalate(升级人工)。失败终态落盘后,同 id 重入从断点续跑。 - 预算是控制信号——全局 token 池跨子任务共享扣减,波次之间判超支;超支不是监控告警,是「立即停下来」的命令。
三、跑起来(先体感)
powershell
cd agent-app
# 动态模式:LLM 把目标拆成子任务单(演示规划/执行/验收三分工)
pnpm team "查订单A-1024的物流状态,如果还没签收就建一个催发工单" --trace
# 静态模式:跳过规划按任务单执行(拆单结果可预期,适合演示与回归)
pnpm team --plan samples/team-plan.json --trace --id demo-run-1
# 断点续跑:同 --id 重入,已完成子任务自动跳过
pnpm team --plan samples/team-plan.json --id demo-run-1真实跑一次动态模式,轨迹长这样(节选):
text
🧩 编排目标:查订单A-1024的物流状态,如果还没签收就建一个催发工单
🧭 编排 team-x 状态:pending → planning ← 拆:LLM 生成 3 个子任务
🧭 编排 team-x 状态:planning → executing ← 派:入度 0 的任务并行开跑
✓ 子任务 1 完成(查订单,getOrderStatus 真调)
✗ 子任务 2 失败:未通过验收(产出与订单实际状态冲突)
⚠ 子任务 2 降级跳过(重试耗尽,占位产出续链) ← 跑:验收拒绝→打回→降级
✓ 子任务 3 完成(基于前置产出正确判断无需建单)
🧭 编排 team-x 状态:reviewing → done ← 终审通过
◆ 编排完成:3 个子任务,共消耗 1429 tokens注意子任务 2 的故事线:验收拒绝了它 → 打回重做 → 重做仍不达标 → 降级跳过 → 编排继续。这条链就是「质量闭环 + 失败退路」的实战形态——降级不阻塞主线,占位产出让后续子任务知道上游缺失。
四、设计取舍(三问三答)
| 追问 | 回答 |
|---|---|
| 拆出的子任务怎么验收? | 显式标准进 TaskSpec.acceptance,Critic 对照判定——不靠感觉。「被拒绝的调用不污染缓存」:验收壳在外、幂等壳在内的叠加顺序保证这点 |
| 失败了为什么还能续跑? | 失败终态(failed/escalated)落盘后,failed → executing 是合法迁移——同 id 重入自动跳过已完成子任务;动态规划的任务单也进快照,重入不重新拆 |
| 为什么编排器用 Effect 而其他模块没有? | 编排是多任务并发/取消/超时的主场——结构化并发的收益在这里兑现;其他模块是顺序流水线,async/await 已是完整体。且编排器留了 Promise 出口 runAsync(),不会 Effect 也能完整使用 |
五、动手任务(做完才算通关)
- 跑通静态模式:
pnpm team --plan samples/team-plan.json --trace,对照轨迹说出「拆/派/跑」各发生在哪一行 - 打开
samples/team-plan.json:给任务 2 换一种验收(llm-judge+ rubric),重跑观察打回行为 - 用
--id demo-run-1跑两次,第二次观察「已完成子任务自动跳过」(断点续跑) - 造一个循环依赖(任务 A 依赖 B、B 依赖 A)的任务单,确认编排器在派发前拒绝
自测题(先凭记忆答,再展开)
- 多 Agent 相对单 Agent 只多出哪三个新问题?编排器里 Planner / Executor / Critic 分别对应哪个?
- 条件性子任务(「若 X 则建单,否则说明原因」)的验收标准该怎么写?为什么
contains: ["TK-"]会出事? - 失败终态落盘解决什么问题?没有它,失败后同 id 重入会发生什么?
答案
- 分工(谁干什么)、共享状态(互相知道什么)、质量闭环(谁验收);Planner 对应分工、StateStore 快照对应共享状态、Critic 对照 Acceptance 对应质量闭环(Executor 是干活的工人)。
anyOf必须覆盖全部分支:["TK-", "无需", "不需要"]——只写["TK-"]时「无需建单」的正确产出会被验收误杀,打回重做也产不出工单号,最后降级。示例任务单的初版就踩了这个坑。- 失败后把
failed/escalated终态写进 StateStore——迁移表里failed → executing合法,重入即从断点续跑。没有终态落盘,快照停在executing,重入触发「executing → executing」非法迁移直接抛错(真实踩过的坑)。
延伸
- 参考库 · 编排器 —— 代码走读 / 拆分方法论 / 面试级取舍
packages/engine/src/orchestrator/README.md—— 模块地图与关键语义速查- 09 · 产品线组装 —— Supervisor 星型:编排器的第一形态前身
- 05 · 工具层加固 —— 幂等壳与审批壳(编排器的两个「壳」)
agent-app/BACKLOG.md② —— A2A 对等 / 编排 SSE 在线观测等后续批次