☰
多Agent系统协作崩溃:从局部正确到整体稳定的排查与测试方法
2026/10/14 12:32:00 网站建设 项目流程

不管是自己做过的多 Agent 系统,还是帮别人排查过的,我听到最多的一句话就是:“每个 Agent 我都单独测过了,都能跑,为什么一合起来就崩?”

这句话几乎成了多 Agent 协作领域的“经典开场白”。而且说这话的人往往不是在推卸责任,他是真冤——每个 Agent 的单元测试确实过了,接口调通了,甚至单独压测都没问题。但系统一旦跑起来,要么任务卡死,要么状态错乱,要么消息疯狂重试,最后整个流程彻底瘫掉。

这篇我就把这类问题掰开揉碎讲清楚:为什么“每个 Agent 都测过”在协作系统里根本不成立,真正的坑藏在哪些位置,以及我实际用来排查和预防这些崩溃的一套方法。如果你正在搭多 Agent 系统,或者已经被线上协作问题折磨过,这篇文章应该能帮你省下不少排查时间。

1. “每个 Agent 都测过”是个典型的局部正确陷阱

先别急着往代码层面查。这个问题的根子,在于我们对“测过”这件事的理解出了偏差。

1.1 单点正常,组合未必正常

一个多 Agent 系统,本质上是一堆各自有输入输出的独立单元,通过消息互相配合,最终完成一件单个 Agent 干不了的事。这个过程很像一条手工流水线:每个工位的人都觉得自己手上的活儿干得没问题,但最终装配出来的东西可能就是缺个零件、拧反了螺丝、或者用了不匹配的型号。

Agent 的情况比流水线更复杂。流水线至少传送带顺序固定、节拍固定,而 Agent 之间的交互往往是异步的、并发的、有条件的。A 和 B 单独跑都没事,但 A 的某个输入恰好是 B 的某种输出格式;A 处理超时的时候 B 正好在等它;A 重试了三次,B 那边已经生成了重复数据。这些交互层面的问题,你在单独测任何一个 Agent 的时候根本触发不了。

所以“每个 Agent 都测过了”这句话,翻译过来就是:我验证了每个零件的静态属性,但我没有验证零件之间的咬合关系。而在协作系统里,恰恰是咬合关系决定了一切。

1.2 “测过了”到底是测到了哪一层

为了把问题看得更清楚,我一般都建议团队把“测过”分三层:

测试层级验证对象典型问题覆盖情况
单元测试单个 Agent 内部逻辑、状态转换覆盖不了协作问题
接口测试两个 Agent 之间的单次请求响应能发现报文格式问题,但覆盖不了时序、重复、并发
协作流测试多个 Agent 按真实流程共同执行能覆盖大部分协作问题,但最容易被忽视

绝大多数人说的“测过了”,指的是前两层。你测了 A 能处理输入 X,测了 B 能响应 A 的请求,但你没测 A 处理到一半时 B 发来了新消息、没测 A 的响应和数据落库之间的故障窗口、没测 B 在 A 故障时会不会自己把自己绕进死循环。这些恰恰是“系统崩了”的高发原因。

我第一次强烈意识到这个问题,是做一个模拟多 Agent 协作的 Demo。任务分配 Agent、执行 Agent、汇总 Agent 三个角色,分别测了个遍,全绿。结果联调一跑,整个流程十分钟就跑完的事,跑了四十分钟还没结束。查了半天,发现是汇总 Agent 把上游消息当成了必达消息,用了同步阻塞的等待方式,而执行 Agent 因为资源竞争频繁超时,超时之后又触发上游重试,重试的消息又把汇总 Agent 的队列塞满了。每个 Agent 单独拉出来都是正常逻辑,但放一起就是互相拖垮。

2. 多 Agent 系统真正会崩的位置,基本就这几个

排查多了就会发现,多 Agent 系统的崩溃位置是有规律可循的。别到处瞎查,优先看这四个地方。

2.1 共享状态和资源的所有权说不清

这是最常见、也最隐蔽的坑。多个 Agent 如果共享同一份状态数据,比如同一个缓存、同一个数据库记录、同一个内存对象,就必须明确一个问题:这份数据的“写权限”到底归谁。

我见过一个真实场景:两个 Agent 都会去更新某个任务的进度字段。Agent A 负责更新“执行状态”,Agent B 负责更新“质量检查状态”。单独看都没问题,但 A 更新时是按“当前记录”整行读出来再改的,B 也是这么干的。结果就是 A 写完了,B 基于旧的快照覆盖写回去,A 的更新直接丢了。这不是并发课里那种教科书级死锁问题,就是最简单的“丢失更新”,但因为它发生在两个服务之间,排查起来特别费劲。

更麻烦的是隐式共享。比如两个 Agent 都调用了同一个第三方接口,接口另一端的状态是共享的。Agent A 重置了某个配置,Agent B 还在按旧配置干活,两边都觉得自己是在正常执行。

这类问题有个常见的早期信号:系统偶尔不对,但单独查任何一个 Agent 的数据都找不出毛病。如果你发现“数据看起来都对,但整体结果就是怪”,优先怀疑共享状态的所有权是不是没有收紧。

2.2 消息契约只对了一半

多 Agent 协作是靠消息串联的,消息契约的问题是崩的第二大来源。这里的“契约”不只是字段格式对不对,还包括三个容易忽略的维度。

第一个是语义契约。两个 Agent 对于同一个字段的理解可能不一样。比如一个字段叫“status”,A 的想法是“业务生命周期状态”,B 的理解是“处理进度标识”。A 传了一个“completed”,B 按“已经结束,不再处理”来响应,但实际上 A 的意思只是“当前这一步完成了,后面还有步骤”。这种歧义在各自开发时很难暴露,联调时一旦出现,表现出的问题五花八门。

第二个是时序契约。A 发消息给 B,是不是一定要等 B 有响应才继续?如果 B 暂时不可用,消息是排队、丢弃、还是重试?A 发出去的消息有没有顺序要求?这些时序规则如果只写在文档里,代码里任何一方没遵守,协作就直接乱掉。

第三个是幂等契约。分布式环境里消息重复是常态,不是异常。B 收到重复消息时能不能正确去重?A 重试的时候会不会产生重复副作用?我见过最典型的崩溃现场:一个通知 Agent 收到重试消息后,把同一条通知发了五遍,用户端直接炸了。

判断消息契约是不是有问题的办法很简单:把所有 Agent 之间传递的真实消息样本收集起来,看看字段覆盖、取值空间、时序关系,再对照每个 Agent 的处理逻辑是否覆盖了这些情况。不需要多复杂,大部分契约问题在这些样本面前都会现形。

2.3 编排层是最大的单点伪命题

很多人把多 Agent 系统的希望寄托在编排层,觉得只要有一个靠谱的调度者在中间控制,就不会乱。但编排层往往自己就是最脆弱的环节。

编排层的工作本质上是做决策:下一步让谁干活、等待多久、失败了怎么办。这三个问题每一个都容易出严重事故。

等待多久这个事,最考验设计。超时设置得太短,Agent 明明在正常处理(只是稍微慢了点),编排层就判定失败并触发重试,然后重复任务把系统压垮;超时设置得太长,故障 Agent 会让整个流程长时间挂起,用户看到的就是“卡死了”。

编排层的另一个隐蔽问题是它本身持有的全局状态。如果编排层靠内存保存“当前流程执行到哪一步”,一旦编排服务重启,整个流程就断掉。想让系统稳定,编排层的状态必须落到可靠存储里,但这又引入了一致性和性能问题。很多系统的崩溃,表面上是某个 Agent 出了故障,实际是编排层在故障恢复过程中的状态错乱把局部问题放大成了整体不可用。

2.4 级联故障比故障本身更可怕

多 Agent 系统里最经典的崩溃模式,不是什么高深的理论问题,就是级联故障。A 依赖 B,B 处理慢了,A 等得不耐烦开始重试,重试让 B 的负载更高,B 更慢,A 更频繁地重试,最后 B 被压垮,A 也超时崩掉,C 继续等 A,把整个链路都拖死。

这个模式我见过太多次了。它最难受的地方在于:一开始只是一个 Agent 的轻微性能抖动,结果却演化成整个系统的瘫痪。单独测的时候你根本不可能触发这种场景,因为所有 Agent 都处在资源充足、响应快速的情况下。

应对级联故障,核心不是让每个 Agent 都变得更健壮,而是要在协作层面加入“熔断”和“降级”的机制。B 已经不行了,A 能不能不重试,直接走降级逻辑?C 能不能感知到 B 的故障,不要傻等?这些机制需要主动设计,不可能等系统崩了以后再指望 Agent 自己“商量”出对策。

3. 怎么把多 Agent 系统真正测到“敢上线”

既然“每个测过”不够,那要测到什么程度才算够?我的答案是:不仅要测 Agent,更要测 Agent 之间的“协作文档”。下面这套思路我实践过多次,虽然不能保证零故障,但基本能避免“上线就崩”的尴尬。

3.1 先把每个 Agent 的行为边界固定下来

Agent 之间出现问题,很多时候不是因为某一个 Agent 写错了,而是它的行为边界没定义清楚。我建议在写代码之前,先给每个 Agent 补上三个标准文档:

  • 接口说明:输入是什么、输出是什么、会不会有副作用;什么情况下返回成功、什么情况下返回失败、失败之后怎么重试。
  • 状态机:这个 Agent 有哪些状态,事件是什么,状态之间怎么迁移;尤其是“异常状态”和“恢复路径”必须写清楚。
  • 资源假设:这个 Agent 默认哪些资源是可用的,哪些是不可用的;如果假设被打破了,它应该怎么表现。

边界文档的作用是让每个 Agent 的开发者对“这个 Agent 的承诺”达成一致。我之前参与的一个模拟系统里,任务分配 Agent 和任务执行 Agent 各有一个“状态机”文档,两边对“failed”状态的理解一直不同,联调的时候来回改了好几轮。后来把状态机画出来贴在文档里,一对比就发现了问题。不需要什么高端工具,一张表、几行字就能避免大量的互相指责和返工。

3.2 用“场景编排测试”代替单测堆叠

单 Agent 的测试照做,但真正的协作验证必须放在完整场景里做。你可以把核心业务场景列出来,比如“用户发起任务、多 Agent 协作完成、返回结果”,为每个场景构造输入,让整个 Agent 群真实跑一遍。

这里有个关键点:不要只测“成功路径”。至少要覆盖下面这几种场景:

  • 某个 Agent 不可用(直接停掉它)。
  • 某个 Agent 响应很慢(人为加延迟)。
  • 消息重复送达(手动重发消息)。
  • 编排层重启(过程中任务正在执行)。
  • 多个 Agent 同时修改同一份数据。

如果你把每个核心场景都跑完这五种故障注入,大概率能发现真正会让系统崩掉的问题。而且这个过程不要手工点点点,最好把它脚本化,沉淀成自动化测试。每次改动任何 Agent 的逻辑,都要跑一遍整个协作场景,不然你就等于又回到了“每个 Agent 都测过”的状态。

3.3 给 Agent 加上“故障演练日”

比自动化测试更进一步的做法,是定期做故障演练。不是只在测试环境练,而是挑一个低峰期,在预发布甚至生产环境主动搞事。当然这要先在团队内部达成共识,演练期间不追求核心指标,专心观察系统表现。

我个人的经验是:一次好的故障演练,能让你发现至少三个单测完全覆盖不到的协作问题。印象很深的一次,我们在演练中人为让一个 Agent 响应变慢,本来预期只是增加一点处理延迟,结果一个小时后发现所有 Agent 的线程池都快满了,数据库连接数被打满,整个系统接近不可用。回头一查,是另一个 Agent 在等待过程中做了同步阻塞调用,而且没有设置超时上限,导致上游一慢、下游跟着全部挂起。

这种问题,不通过真实场景的故障演练,你根本意识不到它的存在。等线上真的发生了,你一边应付业务方的投诉,一边手忙脚乱定位,那种压力完全是另一个数量级。

4. 排查实录:一起典型的多 Agent 协作崩溃

理论讲多了容易飘,我给你看一个实际排查案例。这是我帮一个模拟团队处理过的真实问题(细节我已经处理掉,不影响理解)。

4.1 崩溃现象

这套系统跑着三个 Agent:调度 Agent、处理 Agent、汇总 Agent。调度 Agent 把一批数据分发下去,处理 Agent 并行处理,汇总 Agent 收集结果生成报表。某个下午,系统开始出现大面积卡顿,任务队列越积越长,没过多久整个流程不再推进。

4.2 排查过程

第一步,看日志。最直观的发现是处理 Agent 的日志里有大量超时和重试记录,重试次数在持续攀升。第二步,看资源。处理 Agent 的线程池居高不下,数据库连接池被打满。第三步,回看时间线,发现真正的引爆点是一个慢查询:某一条数据的处理触发了数据库层面的锁等待,导致处理 Agent 的响应时间从平均 200ms 涨到了 5 秒。

如果事情到此为止,那只是个普通的数据库问题。真正致命的是后续反应:调度 Agent 看到处理 Agent 没有及时确认,就开始重试,把同一条数据又发了过来;处理 Agent 的锁没释放,新请求继续排队;汇总 Agent 因为收不到完整结果,也在等,但它用的是阻塞调用,占着线程不释放。三个 Agent 的“正常应对”叠在一起,形成了完美的雪崩。

4.3 根本原因

事后复盘,有三个设计层面的问题叠在一起。第一,处理 Agent 的重试没有退避策略,而且重试上限设得过高,相当于故障期间把流量放大了好几倍。第二,汇总 Agent 对上游的等待没有超时机制,等于默认上游无限可靠。第三,调度 Agent 的重试和数据幂等没有做好,重复消息直接进队列处理,而不是被去重丢弃。

这三个问题,任何一个单独存在都不至于让整个系统崩掉,但合在一起就呈现出了“全线瘫痪”的状态。

4.4 修复方案

修的时候不是只改一个点,而是三层一起调整。调度 Agent 增加指数退避加抖动,每次重试等待时间翻倍,限制总重试次数,同时做好消息去重。处理 Agent 为所有外部依赖设置超时上限,超过时限直接返回失败而不是无限等待,同时对重复任务做幂等处理。汇总 Agent 改成异步响应的交互方式,不再占用线程干等,而且增加了守护机制——超过一定时间没有结果,主动向上游查询状态而不是一味等待。

改完之后,再模拟同样的数据库慢查询场景,系统仍然会出现部分处理失败,但不会再出现级联瘫痪。重试的次数被控制住了,其他 Agent 的正常运转没被打断。

5. 多 Agent 协作常见问题速查表

把日常排查的经验整理成一个速查表,遇到问题可以先对照看看。都是我实际碰到过的情况,不是从教科书上搬来的。

现象可能原因优先排查方向
系统偶尔结果不对,但单项数据都对共享状态丢失更新或覆盖写检查多个 Agent 对同一数据的写路径
任务卡住不推进,没有报错某个 Agent 在同步等待,没设超时找到对应的调用链,看是否有无限等待
上游一慢,整个链路全挂超时设置不当或没有熔断机制检查重试策略和下游保护机制
消息反复处理,产生重复数据缺少幂等控制检查消息去重和各 Agent 的幂等逻辑
编排服务一重启,流程全丢调度状态保存在内存中将编排状态持久化到可靠存储
多个 Agent 更新同一任务状态相互覆盖写权限未收敛或没有锁机制明确单一写入方或引入版本控制
压测时流量一大就崩级联故障和资源耗尽注入延迟,观察各 Agent 的排队行为
重试风暴导致故障放大重试无退避、上限过高增加指数退避、限制总重试次数

这个表不能解决所有问题,但每次排查前过一遍,至少能帮你快速排除掉一大半的“经典错误”。

6. 一些我踩过坑之后才想明白的事

老实说,我早期做多 Agent 系统的时候也犯过“每个 Agent 都测过就说能上”的毛病。当时还不理解为什么集成测试这么重要,总觉得自己把每个组件测稳了,整体自然就稳了。连续踩了两次协作故障的坑之后,我才意识到:组件正确和系统正确之间,隔着一整条交互逻辑的鸿沟。

现在我做这类系统,有个习惯:从一开始就把“协作场景测试”当成一级公民。写任何新 Agent 的代码,第一件事不是写单元测试,而是先把它会跟哪些 Agent 交互、交互的失败模式是什么列出来。所有交互都要配超时、重试上限、幂等策略,所有共享状态都要有明确的所有者。这些事情看起来费时间,但比起线上故障后的通宵排查,这点时间简直太划算了。

最后再分享一个小技巧:排查协作问题时,不要一上来就看代码,先把所有 Agent 之间的消息流时间线拉出来。哪条消息在什么时间发出、多久被消费、有没有重试、重试间隔是多长,这些信息一旦可视化,大部分协作崩溃的原因都会一目了然。这比看任何源码都管用。

希望这次的分享能帮到正在跟多 Agent 协作死磕的朋友。系统崩溃不可怕,可怕的是找不到一个靠谱的排查起点——希望这篇能成为你的那个起点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询