多 Agent 系统在设计阶段看起来很直觉:把一个复杂任务拆成几步,每步交给一个 Agent,串起来就行了。但实际落地时,最常见的失败不是某个 Agent 能力不够,而是整个链路的行为难以预测——前面一个 Agent 的一个细微偏差,到后面已经变成完全错误的结果,而且你很难知道问题出在哪一步。
这篇文章的核心论点是:Agent 编排最难的部分不是调度逻辑,而是上下文管理——每一步传什么信息、传多少、如何截断、如何防止错误累积。把这个问题想清楚,其他问题大多可以推导出来。
一、三种基本编排模式
Agent 编排有三种基本拓扑,其他复杂模式都是这三种的组合。
顺序链(Sequential Chain)
最简单的形式。A 完成 → 把输出传给 B → B 完成 → 传给 C。每一步都有完整上下文,理解前一步做了什么。
适合:任务步骤之间有严格依赖顺序,后一步需要前一步的完整结果。例如:搜索→分析→写报告。
问题在于:上下文随步骤数线性增长。到第五步时,Agent 要读的上下文已经包含前四步的全部输入和输出,很容易超过上下文窗口,而且越后面的步骤越容易被早期噪声干扰。
并行扇出(Parallel Fan-out)
把同一个任务分发给多个 Agent 并行处理,最后聚合结果。最常见的用法:同一个问题让多个 Agent 独立回答,取投票结果或最佳答案。
适合:子任务之间相互独立,速度比精度更重要,或者需要多样性观点。
问题在于:聚合逻辑本身就是一个复杂问题。三个 Agent 给出三个不同答案,聚合 Agent 要做什么?简单投票在文本任务里意义不大,语义相近的答案可能在字面上完全不同。聚合步骤往往比各个子 Agent 更难写好。
层级编排(Hierarchical)
由一个 Orchestrator 负责任务规划和分配,Sub-agent 负责执行具体步骤,结果汇报给 Orchestrator 再决定下一步。
适合:任务复杂、需要动态调整执行路径、子任务类型差异大。这是目前生产级 Agent 系统最常见的架构。
问题在于:Orchestrator 需要维护全局状态和执行计划,这对它的推理能力要求很高。一旦 Orchestrator 出错(比如任务分解错误、忘记某个子任务的结果),整个任务就会走偏,而且没有外部观察者能发现。
二、上下文管理:最被低估的工程问题
在单 Agent 系统里,上下文管理是"把历史对话塞进 prompt"。在 multi-agent 系统里,这个问题变成了:每个 Agent 在执行时,应该拥有哪些信息?
两种极端都是错的。
传太少:Agent B 不知道 Agent A 做了什么决策,会重复工作或做出矛盾的选择。比如 A 已经决定用方案一,但 B 不知道,又把方案二推了一遍,最终输出自相矛盾。
传太多:把前面所有步骤的完整输入输出都传给每个 Agent,会造成上下文膨胀、注意力稀释、推理质量下降,同时消耗大量 token。
正确的做法是结构化上下文截断:每个 Agent 拿到的不是前序步骤的原文,而是一个经过精心设计的摘要或结构化数据。这个摘要只包含当前步骤需要的信息,丢弃不相关的细节。
设计摘要格式时有一个反直觉的原则:让 Agent 的输出格式服务于下游 Agent 的输入需求,而不是让下游 Agent 自己解析上游的自然语言输出。也就是说,如果 Agent B 需要从 Agent A 的结果里提取"用户的核心诉求"这个字段,那么 A 的输出结构里就应该有这个明确字段,而不是让 B 去读 A 写的一大段自然语言再自行理解。
这个原则本质上是让 Agent 之间的接口契约化——和微服务之间用 API Schema 通信同理,Agent 之间也应该有明确的数据契约,而不是松散的自然语言传递。
三、错误传播与放大
Agent 链路和函数调用链路在错误处理上有一个根本性的区别:函数调用链路的错误通常是确定性的(抛异常、返回错误码),而 Agent 链路的错误是模糊的、累积的。
Agent A 给出了一个"方向正确但细节有误"的输出,不会触发任何异常。Agent B 拿到这个有瑕疵的输出,产生了一个"在错误前提下合理"的结果。到了 Agent D,最终输出可能已经偏离原始意图很远,但整个链路里没有任何步骤"报错"。
这个问题被称为误差放大(Error Amplification)。它有几个典型来源:
事实性偏差的传递。 Agent A 对一个技术细节理解有误(比如把 API 限流规则记错了),把这个错误作为事实传给了后续 Agent。后续 Agent 基于这个错误事实做出了合理但错误的决策。
格式解析的歧义。 Agent A 输出了一个列表,Agent B 把它理解成了有序优先级列表,但 A 的意图是无序列举。这种语义歧义在自然语言传递中很常见,但在单次对话里人类可以通过语境消歧,Agent 无法做到。
目标漂移。 在很长的 Agent 链路里,每个 Agent 的 prompt 里都有对"最终目标"的描述。但经过多步变换,最终目标的表述可能已经与原始任务偏离。最后执行的 Agent 服务的是它理解的目标,而不一定是用户真正想要的目标。
缓解误差放大的工程手段是在关键节点引入验证步骤:专门设计一个 Reviewer Agent,它的职责不是完成任务,而是检查前一个 Agent 的输出是否符合预期的结构和约束。一旦发现偏差,中断链路并请求修正,而不是让错误继续传播。
四、中断、检查点与人机协作
完全自动化的 Agent 链路在受控场景下工作良好,但在生产环境中,总有一些决策节点需要人类介入。设计 Agent 编排系统时,中断点的设计和检查点的持久化是两个经常被忽略但至关重要的工程问题。
中断点设计
中断点是链路执行到某个阶段时,主动暂停并等待人类确认再继续的节点。设计中断点的标准不是"这个操作风险高",而是这个决策是否可逆。
典型的不可逆操作:发送邮件、提交代码、修改数据库、调用外部付款接口——这类操作应该设置强制中断点,必须经过人工确认。可逆操作(搜索、读取数据、生成草稿)可以自动执行。
Claude Code 的设计是一个好例子:它在执行文件写入、运行 shell 命令这类操作前会请求用户确认,但在只读操作(列出文件、读取内容)上默认不打扰。这个区分不是基于操作难度,而是基于可逆性。
检查点持久化
长时间运行的 Agent 任务(几分钟到几小时)必须支持检查点持久化:定期把执行状态写入持久存储,以便在失败时从上次成功的节点恢复,而不是从头重跑。
检查点需要记录的不只是"执行到哪一步",还包括:
- 已完成步骤的输出(结构化存储,不是原始对话历史)
- 已消耗的外部资源(避免重试时重复调用)
- 当前的执行计划(Orchestrator 的任务分解结果)
- 尚未完成的步骤列表
一个常见的反模式是把整个对话历史作为检查点——这会导致恢复时上下文过长,而且对话历史里混杂了大量与恢复无关的推理过程。正确的检查点应该只存储下一步执行所需的最小状态。
五、工具调用的幂等性
Agent 系统里,工具调用(Tool Calling)是 Agent 和外部世界交互的主要方式。工具调用出错或超时时,系统会重试——这会引出一个经典分布式系统问题:幂等性。
幂等的工具调用,执行一次和执行多次结果相同(查询、读取)。非幂等的工具调用,重试会产生副作用(发邮件重试 → 发了两封邮件,扣款重试 → 扣了两次)。
在设计 Agent 可用的工具集时,需要对每个工具明确标注其幂等性。对于非幂等工具,系统层面需要保证:
- 去重机制:用唯一请求 ID 标记每次工具调用,工具服务端识别重复请求并返回第一次的结果,而不是重复执行
- 执行状态持久化:在调用非幂等工具之前,先把"准备调用"的状态写入持久化存储,调用成功后更新为"已完成"。重试时先检查状态,避免重复执行
这个问题在 LLM 时代之前已经被分布式系统工程师解决过,但在 Agent 系统里很容易被遗忘——因为 Agent 看起来像"对话",不像"分布式事务"。本质上它是同一个问题。
六、可观测性:你调试不了你看不见的东西
Agent 系统的可观测性比传统服务更难,因为 Agent 的"执行过程"是自然语言推理,不像函数调用有清晰的堆栈跟踪。
但这不代表没有办法。一个可调试的 Agent 系统,至少需要记录以下内容:
每次 LLM 调用的完整输入和输出。 包括完整的 prompt(system + messages)和模型输出。不要只记录摘要——摘要会丢失关键的细节,而你往往在事后才知道哪个细节是关键的。这是最重要的一条,绝大多数 Agent 调试问题都可以通过回放 LLM 调用序列来定位。
工具调用的输入、输出和延迟。 包括工具是否成功、失败原因、重试次数。这是 Agent 和外部世界的边界,也是最常出现问题的地方。
每个 Agent 节点的上下文大小。 记录每次 LLM 调用时的 token 数(输入和输出)。当某个步骤的性能异常或行为异常时,上下文大小是最快速的诊断线索。
执行图(Execution Graph)。 记录任务是如何被分解的、每个子任务被分配给哪个 Agent、执行顺序和依赖关系。这是理解"系统做了什么"的地图,没有它你只有一堆孤立的日志条目。
这些数据的用途不只是调试,还用于优化:哪些步骤 token 消耗最高?哪些工具调用失败率最高?哪些 Agent 节点产生的输出质量最不稳定?有了数据,优化才有方向。
七、几个关键的工程决策
把上面的内容落地,有几个决策点需要提前想清楚。
Agent 之间用结构化输出,不用纯自然语言。 让每个 Agent 以 JSON Schema 定义的格式输出结果,下游 Agent 通过字段名读取,而不是解析自然语言。这是最有效的减少歧义手段。开销是需要为每个 Agent 节点设计输出 Schema,但这个成本远低于后期调试自然语言歧义的成本。
Orchestrator 不做执行,只做规划和路由。 一个常见的设计错误是让 Orchestrator 既做任务分解又直接执行某些步骤。这会让 Orchestrator 的上下文同时包含规划信息和执行信息,两类信息会互相干扰。Orchestrator 只应该知道"任务图",不应该知道执行细节。
失败处理要明确,不要依赖模型自动处理。 当工具调用失败或子 Agent 返回错误时,系统应该有明确的重试策略、降级策略和中止策略,而不是把错误信息塞给模型让它"自己决定怎么办"。模型在面对错误时的行为是不确定的,有时会过度乐观地继续执行,有时会陷入循环,都不是你想要的。
设置最大步骤数和最大 token 数的硬限制。 Agent 链路可能陷入循环(Orchestrator 分配任务 → Sub-agent 完成 → Orchestrator 又分配同一个任务)或无限细化(不断把任务拆成更小的子任务而不实际执行)。硬限制是防止这类问题失控的最后一道防线。
八、总结
Multi-agent 系统的编排难题可以归结为几个核心问题:
- 上下文管理:每个 Agent 拿到的信息应该是精心截断的结构化摘要,而不是前序步骤的全量原文
- 接口契约化:Agent 之间用 Schema 定义的结构化输出通信,消除自然语言歧义
- 误差放大:在关键节点引入 Reviewer Agent,中断错误传播而不是让它累积
- 可逆性驱动的中断:不可逆操作设置强制人工确认点,可逆操作自动执行
- 幂等性设计:非幂等工具调用需要去重机制,防止重试产生副作用
- 全链路可观测:记录每次 LLM 调用的完整输入输出和工具调用详情,这是调试的唯一可靠手段
最后一句话:Agent 编排听起来像工作流编排,但它的失败模式更接近分布式系统——错误是模糊的、状态是可变的、边界是不清晰的。用设计分布式系统的思维来设计 Agent 编排,而不是用设计脚本的思维。