1. 多 Agent 协作的“集成幻觉”:为什么单测全绿,一跑就崩
做过分布式系统或者微服务架构的朋友,对下面这个场景一定不陌生:每个服务单独跑测试用例都是绿的,接口文档写得清清楚楚,Mock 数据完美无缺。可一旦把这些服务部署到真实环境里串起来跑,各种超时、死锁、数据不一致就像约好了一样集体爆发。这两年随着大模型应用落地加速,多 Agent 协作系统正在重演这段历史,而且崩得更隐蔽、更让人摸不着头脑。
我所在的团队从去年开始把多个垂直领域的 Agent 拼成一个协作网络,用来处理跨模态的复杂任务。单个 Agent 的评测集准确率能到 92% 以上,工具调用成功率也稳定在 95% 左右。按理说这个底子不算差,可系统联调的第一周,端到端任务完成率只有 31%。更诡异的是,失败案例里几乎找不到“某个 Agent 本身答错了”的情况——错误全发生在 Agent 之间的交接、协商和状态同步环节。这就是典型的集成幻觉:你以为你在测系统,其实你只是在测一堆互不相干的零件。
这篇文章我想把踩过的坑、复盘出来的规律、以及后来真正把端到端成功率拉到 85% 以上的那套方法,完整地摊开讲一遍。不管你是刚接触多 Agent 编排的新手,还是已经在做复杂工作流的老手,只要你的系统里存在两个以上 Agent 互相传话、分工、汇总,这里面的问题你迟早会撞上。我会从架构设计、通信协议、状态管理、评测方法四个层面拆解,每个层面都给出可复现的配置和排查手段,尽量让你少走我走过的弯路。
2. 多 Agent 协作系统的核心架构与失效模式
2.1 从单体 Agent 到协作网络的范式转变
单体 Agent 的评测逻辑很直观:给一个输入,看输出对不对,中间的工具调用、推理链、最终答案都在一个上下文窗口里闭环。你可以把它想象成一个全能型员工,任务从头到尾他一个人包办,出了问题直接查他的工作记录就行。但多 Agent 协作不一样,它更像是一家公司:有负责拆解需求的“项目经理 Agent”,有专精检索的“资料员 Agent”,有做代码生成的“工程师 Agent”,还有负责审核的“质检 Agent”。每个角色在自己的职责范围内表现优异,可一旦跨部门协作,信息损耗、责任推诿、流程卡顿就全来了。
这种范式转变带来的第一个认知冲击是:系统的正确性不再等于各组件正确性的简单叠加。在传统软件工程里,如果模块 A 和模块 B 都通过了单元测试,且接口契约严格,那么 A 调用 B 的组合行为在很大程度上是可预测的。但 Agent 之间的交互是语义级的,不是语法级的。A 传给 B 的不是一个结构化的整数或字符串,而是一段自然语言描述的任务意图。B 对这段描述的理解,可能和 A 想表达的意图存在偏差,而这个偏差在单测阶段根本暴露不出来,因为单测里 B 收到的输入是人工精心构造的标准样例。
我举个实际遇到的例子。在一个文档处理流程里,拆解 Agent 把用户需求“把这份合同里的风险条款摘出来并给出修改建议”拆成了两个子任务:任务一“提取风险条款”,任务二“生成修改建议”。它把任务一派给检索 Agent,任务二派给生成 Agent,然后自己负责汇总。单测时,检索 Agent 收到的输入是“提取以下文本中的风险条款:[具体文本]”,表现完美。生成 Agent 收到的输入是“针对以下条款给出修改建议:[具体条款]”,也无可挑剔。可实际运行时,拆解 Agent 传给检索 Agent 的指令是“把合同里的风险条款摘出来”,没有附带具体文本,因为它默认检索 Agent 会自己去获取合同内容。而检索 Agent 的设定是“根据给定文本执行提取”,它没有主动获取文档的能力。结果就是检索 Agent 返回了一个空结果,生成 Agent 基于空结果编了一段看似合理但完全无关的建议,质检 Agent 没发现异常,最终输出了一份“看起来很像那么回事”的错误报告。
这个案例里,没有任何一个 Agent 违反了自身的单测契约,但系统整体失败了。失效的根源在于任务意图在传递过程中丢失了关键的前置条件,而单测环境人为地补全了这些条件,掩盖了问题。
2.2 四类高频失效模式与根因分析
把过去半年我们记录在案的 200 多个端到端失败案例做聚类分析,可以归纳出四类高频失效模式。理解这些模式,比盲目增加单测覆盖率有用得多。
第一类是上下文断裂。Agent A 在完成任务后,把结果传给 Agent B 时,只传了结论没传过程。B 拿到一个孤立的结论,无法判断这个结论的置信度、适用范围和前提假设,只能选择盲信或重新验证。盲信会导致错误传播,重新验证则会造成算力浪费和延迟增加。更麻烦的是,当 B 需要向 A 追问细节时,如果通信机制不支持反向询问,整个流程就会卡死或降级。
第二类是角色边界模糊。在多 Agent 系统里,每个 Agent 的职责描述通常是自然语言写的系统提示词。这些提示词之间存在大量语义重叠。比如“分析 Agent”和“总结 Agent”都可能认为自己应该对原始数据做一轮理解,结果就是同一份数据被反复处理,或者两个 Agent 都以为对方会处理某个边缘情况,最后谁都没处理。这种“三个和尚没水喝”的现象,在 Agent 数量超过三个之后出现频率急剧上升。
第三类是状态不一致。多 Agent 协作往往涉及共享状态,比如一个任务队列、一份中间结果缓存、一个全局进度标记。当多个 Agent 并发读写这些状态时,如果没有严格的同步机制,就会出现 A 认为任务已完成、B 认为任务还在进行、C 基于过期状态做出错误决策的情况。大模型 Agent 的响应时间波动很大,从几百毫秒到几十秒都有可能,这进一步放大了状态不一致的窗口期。
第四类是错误级联与静默失败。这是最危险的一类。Agent A 产生了一个轻微错误的输出,这个输出在 A 的评测标准下可能只是“轻微偏离”,但传到 B 之后,B 基于这个错误输出继续加工,错误被放大;传到 C 之后,C 又基于放大后的错误继续加工。更可怕的是,每个 Agent 都倾向于生成“看起来合理”的内容,所以最终输出可能语法通顺、逻辑自洽,但整体偏离原始需求十万八千里。质检 Agent 如果只检查格式和表面一致性,根本拦不住这种级联错误。
| 失效模式 | 典型表现 | 单测为何测不出 | 根因层级 |
|---|---|---|---|
| 上下文断裂 | 下游 Agent 反复追问或盲目执行 | 单测输入已包含完整上下文 | 通信协议设计 |
| 角色边界模糊 | 任务重复处理或遗漏处理 | 单测只验证单个角色职责 | 系统提示词工程 |
| 状态不一致 | 并发冲突、进度错乱、重复执行 | 单测无并发场景 | 状态管理架构 |
| 错误级联 | 最终输出看似合理但整体错误 | 单测无跨 Agent 传播链 | 评测体系缺失 |
2.3 为什么传统单测范式在多 Agent 场景下失效
传统软件测试的基石是“输入确定、输出确定、边界清晰”。单元测试之所以有效,是因为它把系统切分成足够小的单元,每个单元的输入输出空间是可控的,Mock 掉外部依赖后,测试环境是封闭且可重复的。但多 Agent 系统的三个特性直接瓦解了这个基石。
首先是输入空间的开放性。Agent 的输入是自然语言,自然语言的语义空间是无限维的。你在单测里构造的 100 个测试用例,可能只覆盖了实际运行中 1% 的语义变体。更关键的是,Agent 之间的通信内容不是预先定义的枚举值,而是动态生成的文本。A 今天用“提取”这个词,明天可能用“抽取”,后天可能用“找出来”,B 的提示词如果只针对“提取”做了优化,遇到其他表述就可能理解偏差。
其次是输出空间的模糊性。传统单测里,输出要么对要么错,最多加个容差范围。但 Agent 的输出是文本,什么叫“对”?语义正确但措辞不同算不算对?格式略有差异但信息完整算不算对?单测阶段我们通常用精确匹配或简单相似度来判定,这在实际协作场景下过于严苛或过于宽松,无法反映真实质量。
最后是交互路径的组合爆炸。假设系统有 5 个 Agent,每个 Agent 可能向其他 4 个发起请求,交互路径的数量是 5×4=20 条。如果考虑多轮交互,路径数量呈指数增长。你不可能为每条路径都写单测,更不可能覆盖多轮交互中的所有分支。这就导致单测覆盖率和系统可靠性之间出现了巨大的鸿沟。
注意:很多团队试图通过“提高单测覆盖率”来解决多 Agent 协作问题,方向就错了。单测覆盖的是组件行为,而协作问题出在组件之间的“缝隙”里。你需要的是集成测试和混沌工程,不是更多的单测。
3. 通信协议与状态管理:协作系统的隐形骨架
3.1 Agent 间通信的消息格式设计
多 Agent 协作的第一道防线是通信协议。如果消息格式设计得过于简陋,后面所有的状态管理和错误处理都会举步维艰。我们最初用的是最朴素的方案:Agent 之间直接传字符串。A 把结果拼成一段话发给 B,B 解析这段话。这个方案在 Demo 阶段跑得通,一旦 Agent 数量超过三个、任务轮次超过两轮,就彻底失控了。
后来我们改成结构化消息,每个消息包含六个必填字段:发送者标识、接收者标识、任务标识、意图类型、载荷内容、置信度。意图类型是一个枚举值,比如“请求数据”“返回结果”“请求澄清”“报告错误”“心跳确认”。载荷内容可以是文本、JSON 或二进制,根据意图类型决定。置信度是发送者对本次输出质量的自我评估,0 到 1 之间的浮点数。
这个改动带来的收益立竿见影。接收方 Agent 在解析消息时,首先看意图类型,如果是“请求澄清”,它就知道不需要执行任务,而是应该检查自己的上下文是否完整;如果是“报告错误”,它就知道需要触发错误处理流程而不是继续正常流程。置信度字段则让下游 Agent 可以决定是否需要交叉验证:置信度高于 0.9 的结果可以直接采用,低于 0.7 的结果必须经过至少一个独立 Agent 的复核。
消息格式的另一个关键设计是强制携带溯源信息。每条消息里都包含一个“祖先任务链”,记录这个任务是从哪个原始请求派生出来的、经过了哪些 Agent 的处理、每个处理步骤的输入输出摘要。这个设计在排查错误级联时价值巨大。当最终输出出现问题时,你可以沿着祖先任务链一路回溯,精确定位到是哪个环节引入了偏差。
{ "sender": "decomposer_agent", "receiver": "retriever_agent", "task_id": "task_20240521_0017", "intent": "request_data", "payload": { "query": "合同中的风险条款", "source_hint": "用户上传的合同文档,位于共享存储区 /docs/contract_2024.pdf", "required_fields": ["clause_text", "risk_level", "page_number"] }, "confidence": 0.95, "ancestry_chain": [ {"agent": "intake_agent", "action": "parse_user_request", "summary": "用户要求分析合同风险"}, {"agent": "decomposer_agent", "action": "split_task", "summary": "拆分为提取和生成两个子任务"} ], "timestamp": "2024-05-21T10:23:45Z" }3.2 共享状态与消息传递的取舍
多 Agent 系统里,Agent 之间交换信息有两种基本模式:共享内存和消息传递。共享内存模式下,所有 Agent 读写同一个状态存储,比如一个全局的键值数据库或一个共享的上下文对象。消息传递模式下,Agent 之间通过显式的消息发送来交换信息,没有全局共享状态。
我们两种模式都试过,最后选择了混合模式:任务级状态用共享存储,Agent 间通信走消息传递。具体来说,每个任务的元数据(任务 ID、创建时间、优先级、当前状态、负责 Agent 列表)放在共享存储里,所有 Agent 都可以查询。但具体的任务内容、中间结果、错误信息通过消息传递,不进入共享存储。
这么设计的原因是:共享存储适合存放“谁都能看且变化不频繁”的全局信息,比如任务进度、Agent 健康状态、资源配额。而任务内容往往体量较大、变化频繁、且只对特定 Agent 有意义,放在共享存储里会导致锁竞争和版本冲突。消息传递则天然支持异步和并发,每个 Agent 有自己的消息队列,处理不过来就排队,不会阻塞其他 Agent。
但消息传递也有代价。最大的问题是消息丢失和重复。在网络不稳定的环境下,A 发给 B 的消息可能丢失,B 可能永远等不到;或者 A 超时重发,B 收到两条一样的消息,重复执行。我们的解决方案是在消息层加两个机制:确认应答和幂等标识。B 收到消息后,先检查消息里的幂等标识是否已经处理过,如果处理过就直接返回缓存结果;如果没处理过,执行任务,完成后发送确认应答。A 如果在超时时间内没收到确认,就重发,但重发时携带相同的幂等标识。
3.3 状态同步的三种策略与选型建议
状态同步是多 Agent 协作里最容易被低估的环节。我们踩过的坑包括:两个 Agent 同时修改同一个任务状态导致覆盖、Agent 基于过期状态做出错误决策、任务完成后状态没有及时清理导致后续任务误读。
经过几轮迭代,我们总结了三种状态同步策略,分别适用于不同场景。
第一种是乐观锁加版本号。每个状态记录带一个版本号,Agent 读取状态时记下版本号,写入时检查版本号是否变化。如果变化了,说明有其他 Agent 修改过,当前写入失败,需要重新读取再修改。这种策略适合读多写少的场景,比如任务元数据查询。优点是实现简单、并发性能好;缺点是高冲突场景下重试次数多,延迟增加。
第二种是悲观锁加租约。Agent 在修改状态前先申请锁,拿到锁之后才能修改,修改完释放锁。为了防止 Agent 拿到锁后崩溃导致死锁,锁带一个租约时间,超时自动释放。这种策略适合写操作频繁且冲突概率高的场景,比如多个 Agent 竞争同一个任务队列。缺点是锁管理本身有开销,且租约时间设置需要权衡:太短会导致任务被重复执行,太长会降低并发度。
第三种是事件溯源加最终一致。不直接修改状态,而是把每个状态变更作为事件追加到事件日志里。所有 Agent 订阅事件日志,在本地维护自己的状态视图。当需要查询状态时,从本地视图读取。这种策略适合对实时一致性要求不高、但对可追溯性要求高的场景。优点是天然支持审计和回放,缺点是状态查询有延迟,且本地视图可能暂时不一致。
| 策略 | 适用场景 | 优点 | 缺点 | 我们的选择 |
|---|---|---|---|---|
| 乐观锁+版本号 | 读多写少,冲突低 | 实现简单,并发好 | 高冲突时重试多 | 任务元数据管理 |
| 悲观锁+租约 | 写频繁,冲突高 | 强一致,无重试 | 锁开销,租约难调 | 任务队列竞争 |
| 事件溯源+最终一致 | 可追溯性要求高 | 审计友好,可回放 | 查询有延迟 | 错误排查与复盘 |
实际选型时,不要试图用一种策略解决所有问题。我们的系统里三种策略并存,分别用在不同的状态类型上。关键是明确每种状态的读写特征和一致性要求,然后匹配最合适的策略。
4. 端到端评测体系:从“单测全绿”到“系统真稳”
4.1 构建跨 Agent 的集成测试用例
单测测的是“每个 Agent 在理想输入下能否正确输出”,集成测试测的是“多个 Agent 在真实交互下能否完成端到端任务”。这两者的测试用例设计思路完全不同。单测用例是人工构造的标准输入,集成测试用例应该来自真实用户请求的采样和变异。
我们的做法是:从生产环境日志里抽取 500 个真实用户请求,覆盖不同的任务类型、复杂度、输入长度和领域。然后对这 500 个请求做变异,生成 2000 个测试用例。变异方式包括:同义替换(把“提取”换成“抽取”)、语序调整、增加冗余信息、删除关键信息、注入噪声。这样做的目的是模拟真实用户输入的多样性和不规范性。
每个集成测试用例的执行过程被完整记录:每个 Agent 的输入输出、消息传递的时间戳、状态变更的版本号、最终输出与预期输出的差异。这些记录不仅用于判定测试通过与否,更重要的是用于失败归因。当一个用例失败时,我们可以沿着执行轨迹回溯,定位到是哪个 Agent 的哪个决策导致了偏差。
集成测试的判定标准也需要重新设计。单测里我们用精确匹配或 BLEU 分数,但端到端任务的成功与否往往不能只看最终文本。我们定义了三个层次的判定:任务完成度(是否完成了用户要求的所有子任务)、信息保真度(最终输出中的关键信息是否与原始输入一致)、过程合理性(Agent 之间的交互是否遵循了预设的协作协议)。三个层次都通过,才算用例通过。
4.2 混沌工程在多 Agent 系统中的应用
集成测试能覆盖已知的交互路径,但多 Agent 系统的失效往往来自未知的、偶发的、环境相关的因素。这时候就需要混沌工程。我们在测试环境里部署了一套故障注入框架,可以模拟以下场景:Agent 响应超时、消息丢失、消息重复、状态存储不可用、Agent 返回格式错误、Agent 返回语义模糊的结果。
故障注入的关键是可控和可观测。可控意味着你可以精确指定在哪个 Agent、哪个环节、注入什么类型的故障、持续多长时间。可观测意味着故障注入后,系统要有足够的日志和指标让你判断影响范围。我们最初做混沌测试时,注入了一个“检索 Agent 超时”的故障,结果整个系统卡死了十分钟,因为上游 Agent 没有设置超时重试,一直在等。这个测试暴露了超时机制缺失的问题,修复后系统的鲁棒性提升了一个档次。
另一个重要的混沌测试场景是部分失败。在真实环境里,很少出现所有 Agent 同时挂掉的情况,更常见的是某个 Agent 变慢、某个 Agent 返回质量下降、某个消息通道丢包率上升。我们模拟了“生成 Agent 的响应时间从平均 2 秒增加到 15 秒”的场景,观察系统是否会因为等待而积压任务,以及是否有降级策略。结果发现任务队列在 3 分钟内就满了,后续请求全部超时。这促使我们增加了队列长度监控和自动扩容机制。
4.3 可观测性建设:日志、指标与追踪
多 Agent 系统的可观测性比单体系统复杂一个数量级。单体系统里,一个请求对应一条日志链路,从头到尾一目了然。多 Agent 系统里,一个用户请求可能触发十几个 Agent 的几十次交互,每次交互又可能派生新的子任务。如果没有统一的追踪机制,排查问题就像在迷宫里找出口。
我们采用的核心方案是全链路追踪加结构化日志。每个用户请求生成一个全局追踪 ID,这个 ID 在所有 Agent 的所有日志里都携带。每条日志是结构化的 JSON,包含时间戳、Agent 标识、追踪 ID、任务 ID、日志级别、消息类型、关键字段摘要。这样你可以用追踪 ID 把所有相关日志串起来,还原完整的执行路径。
指标方面,我们重点关注四类:吞吐量(单位时间完成的任务数)、延迟(每个 Agent 的响应时间分布和端到端延迟分布)、错误率(按错误类型分类统计)、资源利用率(每个 Agent 的 CPU、内存、消息队列长度)。这些指标不仅用于监控告警,更重要的是用于容量规划和性能优化。比如我们发现某个 Agent 的 P99 延迟是 P50 的 20 倍,进一步分析发现是它的提示词太长导致推理时间波动大,优化提示词后延迟分布明显收窄。
追踪方面,我们实现了一个简单的调用链可视化工具。给定一个追踪 ID,工具会画出所有 Agent 的调用关系图,标注每个节点的耗时、输入输出摘要、状态变更。这个工具在排查错误级联时特别有用,你可以一眼看出错误是从哪个节点开始传播的。
实操心得:可观测性建设要趁早。我们最初觉得“先把功能跑通再补监控”,结果联调阶段花了大量时间在“猜”问题出在哪里。后来补上追踪和结构化日志后,排查效率至少提升了五倍。建议在系统设计阶段就把追踪 ID 和结构化日志作为强制规范。
5. 常见问题排查与避坑指南
5.1 典型故障场景速查表
下面这张表整理了我们在实际运维中遇到的高频故障场景、排查思路和解决方案。建议收藏,遇到类似问题时可以快速对照。
| 故障现象 | 可能原因 | 排查手段 | 解决方案 |
|---|---|---|---|
| 端到端任务卡死无响应 | 某 Agent 等待上游消息但上游已崩溃 | 查看消息队列积压情况和 Agent 心跳 | 增加超时机制和死信队列 |
| 最终输出与预期偏差大 | 错误级联,中间环节引入偏差 | 沿追踪 ID 回溯各 Agent 输入输出 | 增加中间结果校验和置信度阈值 |
| 同一任务被重复执行 | 消息重复或状态不一致 | 检查幂等标识和状态版本号 | 强制幂等处理,状态变更加锁 |
| 系统吞吐量突然下降 | 某 Agent 响应变慢导致队列积压 | 查看各 Agent 延迟分布和队列长度 | 增加超时降级和自动扩容 |
| Agent 之间互相等待 | 循环依赖或角色边界模糊 | 分析调用关系图,检查提示词重叠 | 明确角色边界,打破循环依赖 |
| 输出格式时好时坏 | 下游 Agent 对上游输出解析不稳定 | 检查消息格式和解析逻辑 | 强制结构化消息,增加格式校验 |
5.2 避坑经验与最佳实践
第一个坑是过度信任 Agent 的自我评估。我们最初让每个 Agent 在输出时附带一个置信度,然后下游 Agent 根据置信度决定是否采用。运行一段时间后发现,Agent 的置信度普遍偏高,很多错误输出的置信度也在 0.9 以上。原因是大模型倾向于对自己的输出过度自信。后来我们改成交叉验证:关键结果必须由至少两个独立 Agent 分别生成,然后比对一致性。一致性高才采用,不一致则触发人工审核或第三方仲裁。
第二个坑是提示词里的隐含假设。写 Agent 提示词时,我们经常不自觉地假设某些上下文是“显然”的。比如在检索 Agent 的提示词里写“根据用户提供的文档提取信息”,我们心里想的是“用户会在消息里附带文档”,但实际运行时,文档可能在上一条消息里,或者在共享存储里,或者需要主动向用户询问。这些隐含假设在单测时被人工构造的输入掩盖了,一到真实环境就暴露。解决办法是提示词显式化:把所有前置条件、输入来源、缺失时的处理方式都写清楚,不留任何“显然”的余地。
第三个坑是忽略 Agent 的“性格差异”。不同的大模型底座、不同的温度参数、不同的提示词风格,会导致 Agent 表现出不同的“性格”。有的 Agent 倾向于保守,遇到不确定就追问;有的 Agent 倾向于激进,遇到不确定就猜一个。当保守的 Agent 和激进的 Agent 协作时,保守的会不断追问,激进的会不断猜测,系统效率极低。我们的做法是统一协作风格:在系统提示词里明确规定,遇到不确定时先尝试从上下文推断,推断失败则向指定 Agent 发起澄清请求,澄清请求最多发起两次,两次无果则降级处理并记录日志。
第四个坑是测试环境与生产环境的差异。测试环境里 Agent 数量少、任务简单、并发低,很多问题不会暴露。一到生产环境,Agent 数量翻倍、任务复杂度上升、并发量激增,各种边界情况集中爆发。我们的教训是测试环境要尽量逼近生产:Agent 数量、任务类型分布、并发压力、故障注入都要模拟。如果资源有限,至少要在上线前做一轮全链路压测。
5.3 从失败中提炼的设计原则
经过大半年的迭代,我们提炼出几条多 Agent 协作系统的设计原则,写在这里供参考。
原则一:显式优于隐式。Agent 之间的所有交互,包括任务传递、状态查询、错误报告,都必须通过显式的消息或状态变更来完成。不要依赖“默认行为”或“隐含约定”。每一条消息都要有明确的意图类型和完整的上下文。
原则二:冗余优于单点。关键决策不要依赖单个 Agent 的判断。重要的中间结果至少经过两个独立 Agent 的验证。重要的状态变更要有日志和备份。重要的消息要有确认应答和重试机制。
原则三:降级优于崩溃。当某个 Agent 不可用或响应超时时,系统应该有降级策略,而不是整体卡死。降级策略可以是:使用缓存结果、跳过非关键步骤、切换到备用 Agent、返回部分结果并标注缺失。
原则四:可观测优于可猜测。系统的每个环节都要有日志、指标和追踪。不要等到出问题了才去加监控。可观测性不是锦上添花,而是多 Agent 系统的生存必需品。
原则五:简单优于复杂。在能满足需求的前提下,Agent 数量越少越好,交互路径越短越好,状态管理越简单越好。每增加一个 Agent,系统的复杂度和失效概率都会显著上升。能用两个 Agent 解决的问题,不要用五个。
6. 协作模式选型:集中式、去中心化与混合架构
6.1 三种协作拓扑的对比与适用场景
多 Agent 系统的协作拓扑决定了信息流动的方向和控制权的分布。我们实践过三种拓扑,各有优劣。
集中式拓扑有一个“协调者 Agent”负责所有任务的分发和汇总,其他 Agent 只与协调者通信,彼此之间不直接交互。这种拓扑的优点是控制流清晰、状态管理简单、排查问题容易。缺点是协调者容易成为瓶颈和单点故障,且协调者的提示词需要理解所有子任务的语义,负担很重。适合任务类型固定、Agent 数量少(3 到 5 个)、对可控性要求高的场景。
去中心化拓扑没有统一的协调者,Agent 之间根据预设的规则直接通信。比如检索 Agent 完成后直接把结果发给生成 Agent,生成 Agent 完成后直接发给质检 Agent。这种拓扑的优点是灵活、可扩展、没有单点瓶颈。缺点是全局状态难以维护、错误排查困难、容易出现循环依赖。适合任务流程相对线性、Agent 数量多、对吞吐量要求高的场景。
混合拓扑是我们最终采用的方案。顶层有一个轻量级的“调度 Agent”,只负责初始任务拆解和最终结果汇总,不参与中间过程。中间层的 Agent 按照预设的流水线直接通信,但关键节点会向调度 Agent 发送进度心跳。这种拓扑兼顾了可控性和灵活性,调度 Agent 的负担不重,中间层的效率也高。适合大多数中等复杂度的多 Agent 协作场景。
| 拓扑类型 | 控制流 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 集中式 | 协调者统一分发 | 清晰可控,易排查 | 瓶颈,单点故障 | 任务固定,Agent少 |
| 去中心化 | Agent间直接通信 | 灵活,可扩展 | 状态难维护,易循环 | 流程线性,Agent多 |
| 混合式 | 顶层调度+中层流水线 | 兼顾可控与灵活 | 设计复杂度中等 | 大多数中等复杂度场景 |
6.2 角色分配与提示词工程要点
角色分配是多 Agent 协作的“组织架构设计”。分得好,每个 Agent 各司其职、配合流畅;分得不好,要么职责重叠互相打架,要么职责真空无人负责。
我们的经验是:按能力边界分角色,不按任务步骤分角色。比如“检索 Agent”和“生成 Agent”是按能力边界分的,检索 Agent 擅长从大量文本中定位信息,生成 Agent 擅长基于给定信息组织语言。而“第一步 Agent”和“第二步 Agent”是按任务步骤分的,这种分法的问题是,当任务流程变化时,Agent 的角色定义就要跟着变,缺乏稳定性。
每个 Agent 的提示词应该包含五个部分:角色定义(你是谁,你擅长什么)、能力边界(你能做什么,不能做什么)、输入规范(你会收到什么格式的消息)、输出规范(你应该输出什么格式的消息)、异常处理(遇到不确定或错误时怎么办)。这五个部分缺一不可,尤其是能力边界和异常处理,最容易被忽略,也最容易引发协作问题。
提示词工程还有一个关键技巧是负向示例。告诉 Agent“不要做什么”往往比“要做什么”更有效。比如在检索 Agent 的提示词里写“如果消息中没有附带具体文本,不要自行猜测或编造,应该返回错误消息并说明缺少文本”。这种负向约束能有效防止 Agent 在信息不足时“自由发挥”。
6.3 动态角色切换与负载均衡
在任务量波动大的场景下,固定角色分配会导致某些 Agent 过载而另一些空闲。我们尝试过动态角色切换:当检索 Agent 的队列长度超过阈值时,调度 Agent 临时把部分检索任务分配给空闲的生成 Agent,并在消息里附带检索任务的提示词。
这个方案听起来很美,实际落地时遇到了两个问题。第一是能力不匹配:生成 Agent 的模型底座和提示词是针对生成任务优化的,临时切换到检索任务,效果明显下降。第二是状态混乱:Agent 在两种角色之间切换时,上下文容易混淆,导致输出格式错误。
后来我们改成同能力池内的负载均衡:把相同能力的 Agent 组成一个池,池内多个实例共享同一个任务队列,谁空闲谁取任务。这样既实现了负载均衡,又避免了能力不匹配的问题。池的大小根据历史负载动态调整,高峰期扩容,低峰期缩容。
注意:动态角色切换在理论上很吸引人,但实际落地时,能力匹配和状态隔离的代价往往超过收益。除非你的 Agent 能力高度通用,否则建议优先考虑同能力池的负载均衡。
7. 从能跑到好用:性能优化与成本控制
7.1 延迟优化:并行化与流水线
多 Agent 系统的端到端延迟是各 Agent 延迟的累加,再加上消息传递和状态同步的开销。如果串行执行,一个五步流程每步两秒,端到端就是十秒以上。用户等十秒才看到结果,体验很差。
优化的第一个手段是并行化。分析任务依赖图,找出可以并行执行的子任务。比如“提取风险条款”和“提取金额条款”两个子任务互不依赖,可以同时派发给两个检索 Agent,最后汇总。并行化能把端到端延迟从累加变成取最大值,效果显著。
第二个手段是流水线。对于有先后依赖的任务,不要让下游 Agent 等上游完全做完再开始。比如生成 Agent 可以在检索 Agent 返回第一批结果时就开始生成,边收边生成。这需要消息协议支持流式传输,且下游 Agent 能处理不完整的输入。实现复杂度较高,但对长任务的延迟优化效果很好。
第三个手段是缓存。很多任务的前置步骤是重复的,比如同一个文档的解析、同一个知识库的检索。把这些步骤的结果缓存起来,下次遇到相同或相似请求时直接复用,能省掉大量重复计算。缓存的粒度要把握好:太粗会导致命中率低,太细会导致管理开销大。
7.2 成本控制:Token消耗与调用次数
多 Agent 系统的成本主要来自大模型调用。每次 Agent 交互都意味着至少一次模型推理,Agent 数量多、交互轮次多,Token 消耗就上去了。我们统计过,一个中等复杂度的任务,五个 Agent 协作,平均消耗 15000 到 20000 个 Token。如果每天处理一万个任务,成本相当可观。
控制成本的第一招是减少不必要的交互。审查每个 Agent 的提示词和交互逻辑,看是否有冗余的确认、重复的查询、无意义的寒暄。我们曾经发现一个 Agent 在每次任务开始前都会发一条“我准备好了”的消息,接收方还会回一条“收到”,这两条消息对任务没有任何贡献,纯属浪费。删掉后 Token 消耗直接降了 8%。
第二招是压缩上下文。Agent 之间的消息里经常携带大量历史信息,其中很多是下游 Agent 不需要的。我们实现了一个上下文裁剪器,根据接收方的角色和能力,只保留它真正需要的字段。比如生成 Agent 不需要知道检索 Agent 用了什么检索算法,只需要知道检索结果。裁剪后消息体积平均减少 40%。
第三招是分级模型。不是所有 Agent 都需要用最强的大模型。简单的分类、提取、格式化任务,用轻量级模型就够了;复杂的推理、生成、审核任务,才用重量级模型。我们给每个 Agent 配置了模型等级,根据任务复杂度动态选择。整体成本下降了 35%,而质量没有明显下降。
7.3 质量与成本的平衡策略
质量和成本是一对矛盾。用更强的模型、更多的验证轮次、更详细的提示词,质量会提升,但成本也上升。关键是找到适合自己业务的平衡点。
我们的策略是按任务价值分级。高价值任务(比如直接影响收入或用户体验的)用最高配置:强模型、多轮验证、详细日志。低价值任务(比如内部报表生成、非关键信息提取)用经济配置:轻量模型、单轮执行、精简日志。分级标准由业务方定义,系统根据任务标签自动选择配置。
另一个策略是动态调整验证强度。对于置信度高的结果,跳过交叉验证;对于置信度低的结果,增加验证轮次。这样在保证整体质量的前提下,把验证资源集中在真正需要的地方。我们统计过,大约 70% 的结果置信度高于 0.9,可以跳过交叉验证,节省了大量成本。
最后是持续监控和调优。成本和质量的平衡点不是一成不变的,随着业务变化、模型更新、提示词优化,平衡点会移动。我们每月做一次成本质量复盘,分析哪些环节成本高但收益低,哪些环节质量差但成本低,然后针对性调整。
8. 写在最后:一些个人体会
多 Agent 协作系统的复杂度,本质上来自“不确定性”的叠加。单个 Agent 有不确定性(模型输出的随机性),Agent 之间的交互有不确定性(消息传递的时序和语义),系统整体还有不确定性(并发和状态变化)。单测之所以失效,是因为它试图用确定性的方法来验证一个本质上不确定的系统。
我的体会是,与其追求“消除不确定性”,不如学会“管理不确定性”。具体来说,就是承认错误一定会发生,然后把精力放在:让错误尽早暴露(可观测性)、让错误影响可控(降级和隔离)、让错误可以追溯(追踪和日志)、让系统从错误中恢复(重试和补偿)。这套思路和分布式系统的容错设计是一脉相承的,只是多 Agent 系统里,错误的形态从“服务超时”变成了“语义偏差”,排查手段从“看日志”变成了“看调用链和中间结果”。
还有一个很深的感受是:多 Agent 系统的设计,技术只占一半,另一半是“组织设计”。你怎么定义角色、怎么划分职责、怎么设计协作流程、怎么处理冲突,这些问题的答案,和一家公司怎么管理团队、怎么开会对齐、怎么处理跨部门协作,有着惊人的相似性。有时候我在调 Agent 提示词的时候,会恍惚觉得在写岗位说明书和协作规范。这可能就是多 Agent 系统的魅力所在:它不仅是技术问题,也是组织问题。
如果你正在做多 Agent 协作系统,我的建议是:先把两个 Agent 的协作跑通、跑稳、跑出可观测性,再扩展到三个、五个。每增加一个 Agent,都要重新审视通信协议、状态管理和评测体系。不要被“单测全绿”迷惑,端到端跑通才是硬道理。