跳转至

6. 一个不够用:多 Agent 协作原理

你让 Agent 重构一个支付模块。

这个模块有近万行代码,涉及订单创建、支付网关对接、回调处理、退款逻辑、对账流程。代码是三年前写的,当时的架构决策已经不适合现在的业务规模。你希望 Agent 把它拆成几个独立的子模块,重新设计接口,迁移到新的错误处理模式,同时保证所有现有测试通过。

Agent 开始工作了。它先读取了所有源文件,分析了模块结构,制定了重构计划。然后它开始逐个函数重写,先改订单创建,再改支付网关对接,再改回调处理。每改完一个函数,它就运行测试,确认没有破坏现有功能。

到第 15 步的时候,事情开始变味了。

上下文已经堆了不少东西:旧代码片段、重构计划、改完的新代码、每次测试的输出、中间发现的边界情况。Agent 自动做了一次压缩,把前面几步的执行细节折叠成了一段摘要,这是现在主流 Coding Agent 的标准动作。但摘要里漏掉了一个关键决策:第 5 步对订单创建接口签名的修改。摘要把它当成已经完成的常规改动一笔带过,没有保留新签名长什么样。Agent 后面写退款逻辑时,按照印象中的旧签名调用了订单创建接口。测试失败了,它看到错误信息,试图修复,但修复方向是错的,它以为是退款逻辑本身的问题,没意识到是接口不匹配,因为那个改动在它看到的上下文里已经不存在了。

后面几步又跑了几轮压缩,早期那些边界情况、那条对账流程要先走灰度再切流量的硬约束,也陆续被折叠成更短的摘要,再被新的工具结果挤到上下文中段。Agent 不是窗口爆了,恰恰相反,它的窗口看起来还很宽裕,但宽裕的窗口里,重要的东西已经悄悄变形或被稀释。它开始重复之前做过的检查,偶尔生成和早期决策矛盾的代码,对测试失败的诊断越来越不准。你不得不中断任务,手动检查它到底做了什么。

这不是 Agent 的 bug,也不是模型不够强。这是单 Agent 架构的结构性限制。

6.1 单 Agent 的天花板

开头的重构场景不是个例。任何足够复杂的任务:涉及多个模块、多种角色、大量上下文信息,都会把单 Agent 推向它的结构性极限。这些极限不是因为 Agent 不够聪明,而是因为单 Agent 架构有几个结构性的瓶颈,这些瓶颈不会随着模型能力或者窗口大小的提升而消失。

第一个瓶颈和上下文有关,但已经不是窗口塞不下这么简单。早期的 Agent 确实经常硬撞上下文上限,但今天主流的 Coding Agent,Claude Code、Cursor、Codex 之类,都内置了上下文管理:自动摘要压缩、长文件按引用而非全文保留。窗口本身也已经从 4K、8K 涨到了 200K,上百万 Token。单纯装不下的硬墙,在日常编码里其实越来越少撞到。

但问题没有消失,只是换了形态。第一个真正的问题是压缩有损。自动压缩不是无代价的,它要在不知道下游会用到什么的前提下,决定哪些细节保留。开头那个例子就是典型:第 5 步对订单接口签名的修改,在压缩当下看起来是一次普通的代码改动,于是被收进了已经完成 N 处重构这种摘要里,新签名长什么样没了。等几步之后写退款逻辑要调用这个接口时,Agent 拿到的是一个被压扁过的版本,行为就和忘了几乎没区别。窗口越大、任务越长,压缩发生的次数越多,这种看起来都还在、实际上关键细节已经变形的情况就越常见。

第二个问题是注意力稀释。第二章讲过 lost in the middle:模型对上下文中信息的关注度并不均匀,开头和结尾天然容易被关注,中段容易被忽略。这个问题不是窗口大小能解决的,反而窗口越大越严重,你把 50 万 Token 喂进去,模型仍然只对最前和最后的几千 Token 真正在意,中间那一大段哪怕原文还在,也容易看到了但没注意到。重构计划里那条对账流程要先走灰度再切流量的硬约束、早期发现的某个边界情况,只要被新的工具结果不断往中间推,模型就越来越不会主动调用它做判断。这是大窗口和自动压缩都治不了的病。

这两点合起来造就了单 Agent 真正的瓶颈:所有信息都堆在同一条时间线、同一个注意力分布里,要么被压缩产生失真,要么被稀释到看不见,这时候窗口本身够不够大,是个次要问题。

同一个上下文里塞了所有东西,还会引出另外两个表面上独立、本质上同源的瓶颈:角色混乱和能力冲突。它们都源于同一个事实,所有信息互相挤在一起,谁也没法干净地隔开。

角色混乱最典型的例子是写代码和审代码。写代码的时候,Agent 处于创造模式,它倾向于认为自己的方案是合理的,倾向于推进而不是质疑。审代码的时候,Agent 需要切换到批判模式,怀疑每一行代码,寻找潜在的问题。让同一个 Agent 先写代码再审自己写的代码,效果通常不好:它会倾向于认为自己写的代码是正确的,因为写代码时的思考过程还留在上下文里,这些思考过程会影响审查时的判断。这就像让一个人批改自己的作文,你很难发现自己的错误,因为你的大脑会自动脑补你想表达的意思,而不是看到你实际写下的文字。这一条和窗口大小完全无关,再大的窗口,残留的"创作思路"还是会污染"批判判断"。

能力冲突是同一种病的另一种症状。第五章讲过,不同的 Skill 可能存在指令冲突。在单 Agent 架构下,所有 Skill 都加载在同一个上下文里,冲突无处可逃。

这三个问题讲的是质量:压缩丢信息、注意力稀释、角色互相干扰、指令互相打架,最终结果都是输出变差。还有一个问题讲的是速度:单 Agent 是串行执行的,它一次只能做一件事。在 ReAct 循环中,每一步都要等上一步完成才能开始。但很多任务天然包含可以并行的子任务:给一个模块的 10 个函数写单元测试,这 10 个函数之间没有依赖关系,理论上可以同时写,但单 Agent 只能一个一个来。如果每个函数的测试需要 2 分钟,10 个函数就是 20 分钟;如果能并行,可能 3 分钟就全部完成了。

这些问题不是孤立的,它们会相互放大。上下文越长、压缩越频繁,关键信息越容易被压掉或被稀释;角色越多,需要加载的 Skill 越多,能力冲突越可能发生;任务越复杂,需要的步骤越多,串行执行的时间越长,上下文累积也越快,又把前面那条放大回来。

下图直观地展示了这几个瓶颈以及它们如何相互放大:

单 Agent 的四个结构性瓶颈

这是一个恶性循环。任务复杂度超过某个阈值后,单 Agent 的表现不是线性下降,而是断崖式下降,它突然从能用变成几乎不可用。

怎么打破这个循环?

思路其实很直接:既然一个 Agent 装不下所有东西,那就用多个 Agent,每个 Agent 只装自己需要的东西。

6.2 SubAgent:主从分工的基本模式

多 Agent 协作最基本的模式是主从分工,一个主 Agent(Orchestrator)负责规划和协调,多个 SubAgent 负责执行具体的子任务。

回到开头的重构场景。如果用多 Agent 的方式来做,流程会变成这样:

主 Agent 接收到重构任务后,先分析模块结构,制定重构计划,然后把任务拆成几个子任务: - 子任务 1:重构订单创建模块 - 子任务 2:重构支付网关对接模块 - 子任务 3:重构回调处理模块 - 子任务 4:重构退款逻辑模块 - 子任务 5:更新所有测试

每个子任务交给一个独立的 SubAgent 处理。每个 SubAgent 有自己独立的上下文,它只接收与自己任务相关的信息:重构计划中和自己相关的部分、需要修改的源文件、相关的接口定义。它不需要知道其他 SubAgent 在做什么,不需要看到其他模块的代码,不需要关心全局的重构进度。

这个设计的核心优势是上下文隔离

每个 SubAgent 的上下文都是干净的,只包含与当前子任务相关的信息。没有其他任务的历史记录来干扰,没有无关的工具描述来占用空间,没有冲突的 Skill 指令来制造混乱。SubAgent 可以专注于自己的任务,就像一个只负责一个模块的开发者,它不需要理解整个系统,只需要理解自己负责的部分和与其他部分的接口。

上下文隔离还带来了一个附带的好处:角色清晰。每个 SubAgent 可以加载最适合自己任务的 Skill,负责写代码的 SubAgent 加载编码规范 Skill,负责写测试的 SubAgent 加载测试规范 Skill,负责做审查的 SubAgent 加载审查清单 Skill。不同的角色在不同的上下文中运行,不会相互干扰。

主 Agent 的职责也简单了。它不需要亲自执行每个子任务,它只需要做三件事:把一个复杂任务拆成多个可独立执行的子任务(这是最关键的能力,拆得好每个 SubAgent 都能高效完成自己的部分,拆得差 SubAgent 之间会频繁冲突);决定每个 SubAgent 需要哪些信息,精确地给每个 SubAgent 它需要的、且仅是它需要的信息,不是把所有信息都发给所有 SubAgent,否则就失去了上下文隔离的意义,这就像一个项目经理给每个开发者分配任务时,会附上相关的需求文档和接口定义,而不是把整个项目的所有文档都甩过去;收集所有 SubAgent 的执行结果,检查是否有冲突或遗漏,做最终的整合,如果两个 SubAgent 的修改有冲突(比如都修改了同一个接口的签名),主 Agent 需要发现这个冲突并协调解决。

这是软件解决复杂问题的一贯思路:分而治之。把一个大问题拆成多个小问题,每个小问题交给一个专注的执行者。主 Agent 负责拆分和协调,SubAgent 负责执行。它们之间通过定义好的接口通信,不直接访问彼此的内部状态。

但 Agent 之间的通信比传统的程序间通信要复杂得多,因为传递的不仅仅是数据和文本,还有其背后代表的意图和判断。下图展示了这个完整的协作流程:从主 Agent 的三个职责,到 SubAgent 的独立执行,再到结构化的结果报告:

主从分工:多 Agent 的基本协作模式

图中右侧的"结构化报告"是多 Agent 通信的关键设计。SubAgent 完成任务后,不是把所有执行细节都传回去,而是按照固定格式汇报关键信息。这个设计直接引出了下一个问题:报告里该包含多少信息?

6.3 Agent 间通信:信息压缩与信息完整性的博弈

多 Agent 系统中最核心的设计决策之一,是 Agent 之间怎么传递信息。

一个 SubAgent 完成了子任务,它需要把结果传回主 Agent。问题是:传什么?

这个问题没有现成答案:信息越完整,主 Agent 的上下文越快被撑爆;信息越压缩,关键判断越容易丢失。多 Agent 的通信设计,就是在这两端之间找一个能用的位置。

偏向完整那一端的做法,是把 SubAgent 的关键执行轨迹一并回传,读了哪些文件、改了哪些地方、跑了哪些测试、中间遇到的报错。这种做法在小规模任务里还行,因为主 Agent 自己也想看一看 SubAgent 是怎么走到这个结果的。但只要 SubAgent 数量多起来、单个任务步数长起来,效果会大打折扣。一个 SubAgent 执行十几步,相关的轨迹和工具结果就能堆出几万 Token;如果同时有几个 SubAgent 都按这种方式回传,主 Agent 一下子要消化几十万 Token 的过程信息。这些信息里大部分对它做下一步决策没用,它不需要知道 SubAgent 在某一步读了哪个文件,也不需要知道某次测试的完整 stdout,但这些东西混在一起进了它的窗口,前面讲过的所有问题,压缩失真、注意力稀释,立刻就在主 Agent 重演一遍。单 Agent 的上下文病,会沿着 Agent 之间的通信链原样传染过来。

偏向压缩那一端的做法,则是只回传一个简短的结论:任务完成、测试通过、修改了 3 个文件。Token 上是省了,但风险藏在结论之外。SubAgent 在执行过程中很可能发现了一些主 Agent 必须知道、但任务定义里没要求它汇报的事情:退款逻辑里依赖了一个即将废弃的 API、某个改动顺手暴露了另一个模块的隐患、为了让测试通过临时绕过了一个检查。如果回传只有一句完成,这些信息就永远停在 SubAgent 自己的上下文里,跟着任务一起被丢弃。更隐蔽的情况是任务看起来完成了:SubAgent 改了一个函数的签名,自己范围内的调用方都更新了,测试也跑过了;但范围之外还有调用方没改,这一情况 SubAgent 不一定能意识到,即使意识到了,“完成 / 测试通过”这种格式里也没有它的位置。等问题暴露出来,往往已经是另一个 SubAgent 或集成阶段的事了。

工程上真正在用的,是这两端中间的一种结构化结果报告。报告大致包含几块固定的内容:执行状态(成功 部分成功 失败)、变更摘要(动了哪些文件、做了什么级别的改动)、关键发现(过程中遇到的重要问题、风险、建议)、依赖声明(这次执行依赖了哪些假设、可能影响哪些其他模块)、遗留问题(哪些事情自己没处理、需要主 Agent 或其他 SubAgent 接着做)。它的好处不是信息更多或更少,而是把什么算关键这件事固化成了字段,SubAgent 不能只回一句完成,它必须回答这几个具体问题;主 Agent 也不用在一大段自由文本里去猜哪句话重要,它知道去看哪个字段。

但这个方案没有把矛盾消掉,它只是把矛盾推到了一个更隐蔽的地方,字段的格式定下来了,往字段里填什么仍然是 SubAgent 自己判断的。一个能力一般的 SubAgent,可能把真正重要的发现当成例行细节略过、却把无关紧要的小事郑重写进关键发现里。结构化报告不能替它做这个判断,它只能保证该有的位置都在,至于位置上填得对不对,仍然回到模型本身的能力上。

所以这个问题的本质并不是传多还是传少,而是谁来决定什么是关键信息? 在单 Agent 里这个判断是模型自己对自己做的,做错了至少还在同一个上下文里有机会回溯;到了多 Agent,这个判断要由 SubAgent 替主 Agent 做,而且做完之后,没被写进报告的那部分信息就再也回不来了。结构化报告虽然改善了这一状况,但它仍旧无法做到毫无损失。这也是为什么本章后面反复会回到同一个主题:多 Agent 不消除单 Agent 的问题,它只是把问题搬了个位置。

6.4 协议化的尝试:A2A 与"Agent 间互联网"

之前讨论的传什么、怎么压缩是 Agent 间通信的认知层问题。还有一个更基础的问题站在它后面:Agent 之间到底用什么语言通信?

到目前为止,本章描述的多 Agent 协作几乎都是同框架内的,主 Agent 和 SubAgent 由同一个系统创建、运行在同一个进程或同一个调度器下,它们之间的通信本质上就是函数调用、消息队列或者结构化字典传参。这是同质多 Agent。

但真实世界正在出现另一种场景:你的代码 Agent 是 A 公司做的,你的安全审计 Agent 是 B 公司做的,你的部署 Agent 是 C 公司做的,它们各自基于不同的模型、不同的框架、不同的工具栈,但你希望它们能围绕同一个任务协作。一个代码 Agent 写完了一段涉及支付的关键代码,需要把它交给一个独立的安全审计 Agent 复审;审计完成后再交给一个独立的部署 Agent 上线。三个 Agent 来自三个不同的供应商,它们怎么互相发现、怎么交接任务、怎么传递中间产物、怎么报告失败?

这个场景下,主从分工的函数调用模型不够用了,异质 Agent 之间没有共享的内存、没有共享的调度器、甚至没有共享的语言运行时。它们需要一套协议:一种 Agent 与 Agent 之间互相说话的标准方式。

这一现象在技术发展史上并非首次出现。互联网早期,每台机器上的服务都用自己的私有协议彼此对接,对接成本高到几乎不可能形成生态;HTTP 把一次请求 / 一次响应标准化下来之后,Web 才发展出来。微服务时代,服务之间一开始也是各种 RPC 框架各自为战,gRPC 把契约描述、流式传输、错误模型这些事拉齐之后,跨语言、跨团队的服务调用才真正变得日常。每一波分布式形态在走向规模化之前,都要先把对方说话的方式沉淀成一层公共协议。Agent 现在正站在同一个位置上:模型层、推理层、Agent 框架层各自演化得已经够快,但只要 Agent 还被关在自己的框架里说话,跨供应商的协作就成立不了。

Google 推出、后来交由 Linux Foundation 维护的 A2A(Agent2Agent)协议就是在这个位置上的尝试。它的设计取舍很清楚:不去碰 Agent 内部怎么思考,那是模型自己的事;也不去碰 Agent 怎么调工具,那一层已经有别的协议在做。A2A 把自己的边界画在 Agent 与 Agent 之间:两个互不认识的 Agent,怎么找到彼此、怎么把一件任务交出去、怎么知道对方做到哪一步、怎么把结果拿回来。

这套协议的核心,可以顺着一次跨供应商 Agent 协作的完整流程串起来看。

第一步是发现。两个 Agent 在协作开始之前并不认识,A2A 通过 Agent Card 来解决认识问题:每个 Agent 用一份机器可读的描述声明自己是谁、能做什么、在哪个端点上、需要什么认证。这相当于 Agent 版的服务名片。调用方拿到一张 Card 就能在不预先集成的情况下接入对方,就像浏览器拿到一个 URL 就能访问任意 Web 服务。Card 这层抽象看似简单,但它是整套协议成立的前提:没有标准化的自我描述,互相发现就只能靠手工配置,跨供应商的协作永远做不起来。

第二步是任务流转。A2A 把一次跨 Agent 的协作建模成一个明确的 Task 对象,而不是一次模糊的请求-响应。Task 有自己的生命周期状态机:已提交、执行中、需要补充输入、已完成、失败。这个状态机是协议的一部分,不是各家自己定义的。它解决的是异步、长任务这类场景下最让人头痛的"对方现在到底在干嘛"问题,一个安全审计 Agent 可能要跑十几分钟,调用方不需要轮询模糊的还在跑吗,而是订阅 Task 的状态变化,对方该到哪一步就主动推过来。这一层抽象让 Agent 之间的协作从同步阻塞调用,升级成了带状态的任务委托。

第三步是消息通道。Task 在执行过程中往往不是一锤子买卖,审计 Agent 可能需要回头问"这段代码的预期使用场景是什么",部署 Agent 可能需要在中途上传一份变更摘要给上游审批。A2A 的消息通道支持多模态内容(文本、文件、结构化数据)的双向传输,长任务可以通过推送主动汇报进度,中间产物可以以引用而不是全量内嵌的方式传递。这一层让协作真的成立,Agent 之间不是把整段上下文一次性扔过去就不管了,而是能在任务过程中持续对话。

发现 + 任务 + 消息通道,这三层抽象合起来,A2A 把Agent 怎么和别的 Agent 打交道标准化成了一套和具体框架、具体模型解耦的接口。你可以用任何语言、任何模型实现一个 A2A Agent,它只要遵守这套协议,理论上就能被任何别的 A2A Agent 调用。这在今天听起来或许还有点抽象,但回头看,HTTP 在 1990 年代初也是这个状态。

但要诚实地说:A2A 离成熟还远。 规范层面已经稳定下来,开始有早期落地,但生态远没有铺开,大多数团队目前还在先把单 Agent 跑稳这一关,根本没到需要跨供应商互操作的阶段。

所以这一节讲 A2A,不是因为它今天就能解决你团队的问题,而是因为它把一个结构性的方向画了出来:当多 Agent 协作从同框架内的函数调用发展到跨供应商的服务调用时,必然会需要一层协议。同质多 Agent 用不上 A2A,因为它们已经共享了运行时;但只要你开始把Agent看成可以独立部署、独立演化、来自不同来源的对象,就像我们今天看微服务那样,A2A 协议就会变为必须品。

6.5 并行与串行:子任务的依赖图

多 Agent 的一个重要优势是并行执行,多个 SubAgent 可以同时工作,大幅缩短总执行时间。但并行不是同时跑就行,它需要处理子任务之间的依赖关系。

最理想的是无依赖的子任务。给 10 个独立函数写单元测试,这 10 个函数之间没有调用关系,测试之间也没有共享状态,10 个 SubAgent 可以同时工作,每个负责一个函数的测试,执行总时间等于最慢的那个 SubAgent 的执行时间。最麻烦的是有依赖的子任务:先设计接口,再写实现,再写测试,这三个子任务有严格的顺序依赖,实现依赖接口定义,测试依赖实现代码,你不能让他们同时开始。最常见的是部分依赖的场景,一个重构任务中,有些子模块之间有依赖(模块 A 调用了模块 B 的接口),有些子模块之间没有依赖(模块 C 和模块 D 完全独立),你可以让独立的子模块并行重构,有依赖的按顺序重构。

子任务之间的依赖关系形成了一个有向无环图(DAG)。图中的每个节点是一个子任务,每条边表示一个依赖关系。没有入边的节点可以立即开始执行,有入边的节点需要等所有前置节点完成后才能开始。这和 CI/CD 流水线中的任务编排是同一个模型——有些 Job 可以并行,有些 Job 必须等前置 Job 完成。

但在 Agent 的世界里,构建这个依赖图本身就是一个挑战。

CI/CD 流水线的依赖关系是人工定义的,开发人员在配置文件里明确写出Job B 依赖 Job A。但在多 Agent 系统中,依赖关系需要主 Agent 自己判断。主 Agent 需要分析任务的结构,理解子任务之间的数据流和控制流,然后决定哪些可以并行、哪些必须串行。

这个判断是依然基于LLM的概率生成。主 Agent 可能遗漏了一个依赖关系,比如它没有意识到模块 A 的重构会影响模块 C 的接口,于是让两个 SubAgent 并行工作。结果两个 SubAgent 各自修改了同一个接口的不同方面,产生了冲突。

并行执行时的冲突是多 Agent 系统中最棘手的问题之一,它会以三种不同的形态出现。

最明显的是文件级冲突。这和 Git 的合并冲突是同一类问题,两个人同时改了同一个文件的同一段代码,合并的时候就会冲突。在 Agent 的世界里,这个问题更难处理,因为 Agent 不像人类开发者那样能协商解决冲突,它们各自在自己的上下文里工作,不知道对方在做什么。

更难处理的是语义级冲突,它比文件级冲突更隐蔽。两个 SubAgent 没有修改同一个文件,但它们的修改在语义上是冲突的。比如一个 SubAgent 把某个函数的返回值从 error 改成了 (result, error),另一个 SubAgent 在自己的代码里调用了这个函数,但还是按照旧的签名来处理返回值。两个 SubAgent 各自的代码都是正确的,但合在一起就不对了。还有一种是状态级冲突:两个 SubAgent 都修改了某个共享状态,一个配置文件、一个数据库 Schema、一个全局常量,各自的修改可能都是合理的,但合在一起可能产生不一致。

处理这些冲突的责任落在主 Agent 身上。它需要在汇总 SubAgent 结果的时候,检测冲突、协调解决。但检测语义级冲突和状态级冲突需要对代码有深入的理解,这本身就是一个高难度的任务。

实践中的一个务实策略是:宁可少并行,也不要处理复杂的冲突。 如果你不确定两个子任务之间有没有依赖,就让它们串行执行。串行的代价是时间,冲突的代价是正确性。在大多数场景下,正确性比速度更重要。

6.6 多 Agent 的拓扑结构

主从分工是最基本的多 Agent 模式,但不是唯一的。随着任务复杂度的增加,Agent 之间的组织方式也会变得更复杂。

最简单的是星型拓扑(Hub-and-Spoke),这就是我们前面讨论的主从模式,一个主 Agent 连接多个 SubAgent,所有通信都经过主 Agent,SubAgent 之间不直接通信。这是最容易理解的拓扑,适合子任务之间相对独立的场景。它的优势是控制集中——主 Agent 对全局有完整的视图,能做出全局最优的决策;劣势是主 Agent 成为瓶颈。所有信息都要经过它,如果 SubAgent 数量太多,主 Agent 的上下文会被各种结果报告撑满。

拓扑再上一层是层级拓扑(Hierarchical),主 Agent 下面有几个中层 Agent,每个中层 Agent 管理自己的一组 SubAgent。比如一个后端重构中层 Agent 管理几个负责不同模块的 SubAgent,一个前端重构中层 Agent 管理另一组 SubAgent。主 Agent 只和中层 Agent 通信,不直接和底层 SubAgent 交互。这缓解了主 Agent 的瓶颈问题,信息在每一层都被压缩和汇总,主 Agent 只需要处理中层 Agent 的报告,不需要处理所有底层 SubAgent 的细节。但层级越深,信息损失越大,底层 SubAgent 的关键发现可能在逐层汇总的过程中被丢失。

还有一种是流水线拓扑(Pipeline),Agent 按顺序排列,每个 Agent 的输出是下一个 Agent 的输入。比如:分析 Agent → 设计 Agent → 实现 Agent → 测试 Agent → 审查 Agent。每个 Agent 专注于一个阶段,把结果传给下一个阶段。它适合有明确阶段划分的任务。优势是每个 Agent 的职责非常清晰,上下文非常干净,它只需要关注自己阶段的输入和输出。劣势是灵活性差,如果审查 Agent 发现了设计阶段的问题,信息需要逆流回到设计 Agent,这在流水线拓扑中不太自然。 下图对比了这三种拓扑的结构差异和各自的适用场景:

多 Agent 的三种典型拓扑结构

除了这三种之外,还有一种理论上很有吸引力的模式,对等拓扑(Peer-to-Peer)。Agent 之间没有明确的主从关系,它们可以直接相互通信、协商、辩论。

对等拓扑在理论上很有吸引力,它模拟了人类团队中的协作方式。但在实践中,它的协调复杂度极高。没有一个中央协调者,Agent 之间的通信可能陷入死循环(A 等 B 的结果,B 等 A 的结果),或者产生不一致的决策(A 认为用方案 X,B 认为用方案 Y,没有人来裁决)。

在 AI 编程领域,星型拓扑是最常用的,因为它最简单、最可控。层级拓扑在研究类、长程检索类、覆盖完整软件工程流程的大型任务里相当常见。Anthropic 公开过的 Claude 内部 Research 系统、LangGraph 提供的 Hierarchical Agent Teams 模板、把多 Agent 组织成软件公司的 MetaGPT / ChatDev 这类项目,本质上都是层级结构。它在日常 AI 编程里出现得没那么频繁,但只要任务规模大到一个主 Agent 装不下所有 SubAgent 的报告,层级几乎是必然的下一步。流水线拓扑在代码审查、CI/CD 集成等有明确阶段的场景中有应用。对等拓扑还处于实验阶段,实际落地的案例很少。

6.7 多 Agent 的成本与可观测性代价

前两节把多 Agent 协调的方法和组织形态讲完了。但只要真正在生产里跑过几次多 Agent,你就会发现协调还有两个问题是前面没正面碰过的。一个是钱,一个是出了问题之后无法追溯。

多一个 Agent 就多一份上下文,多一份上下文就多一份 Token。主 Agent 自己要装任务描述、分解计划和每个 SubAgent 的结果报告,每个 SubAgent 又要装自己那份子任务描述、相关源文件和执行历史。同一份任务背景,主 Agent 看一遍,每个 SubAgent 也得各自看一遍属于自己的信息。原来在单 Agent 里只走一遍的上下文,到了多 Agent 这里要被复制好几份。再加上每一步推进都对应一次模型调用,主 Agent 在拆任务、收报告、做整合时各调用一次,SubAgent 在自己的循环里又各调用若干次,总调用次数也是好几倍,两笔账叠在一起,账单的曲线和单 Agent 完全不在一个量级。

这一现实结论十分朴素:不是所有任务都值得用多 Agent。 单 Agent 能十来步走完的任务,硬拆成几个 SubAgent 之后,总步数和总 Token 都涨了一截,完成时间未必就能压下来,SubAgent 之间的等待、报告的回传、主 Agent 的整合,都要时间。如果你做的事情本来就不复杂,多 Agent 多出来的只是开销,而不是收益。多 Agent 的合理位置一直是单 Agent 装不下的时候才上,不是默认配置。

单 Agent 出问题,调试方式我们都很熟:把执行日志从头翻到尾,沿着思考和工具调用一步步看下去,错在哪一步基本能定位到。它是一条线,时间是直的,因果关系也是直观的。

多 Agent 不是,一个任务跑完,背后是一棵树或者一张图:主 Agent 拆出了几个分支,每个分支里 SubAgent 又各自跑了一段,分支之间还有报告和指令往来。出问题的时候,你要同时看好几条线索,主 Agent 给了什么指令、SubAgent 怎么理解的、SubAgent 回报了什么、主 Agent 又据此推出了什么。问题往往不在某一个 Agent 内部,而在 Agent 与 Agent 之间的交接上:指令本身就模糊、报告里没把关键发现写出来、上一个 SubAgent 的产出被下一个误读。这些故障表面上看是某个 SubAgent 做错了,根因却散落在交互链路里,单看任何一个 Agent 的日志都看不出来。

要排查这种问题,需要的是一个全局视图:每个 Agent 的执行轨迹、Agent 之间的每一次通信、出错时能从结果一路回溯到最初那一次指令。这听起来或许理所当然,做起来一点都不简单,可观测性在多 Agent 里不是锦上添花,是基础设施。一些头部工具这一两年在执行轨迹可视化、Agent 间交互追踪上确实在往前推,但在更广泛的工具生态里,你大概率还是只能看到 Agent 的最终输出,看不到它怎么走到这里、和别的 Agent 之间到底说了什么。在这层基础设施真正铺开之前,对关键任务上多 Agent,是要承担出事之后查不清这份风险的。

多 Agent 系统就不能只准备协调一定成功这一条路径,必须给自己留好降级方案。比较务实的做法通常有:给每个 SubAgent 设执行时间上限,超时就把任务收回;允许部分结果被保留,不因为一个子任务失败就整盘作废;当协调成本明显高于收益时,自动切回单 Agent 串行执行;如果连自动降级都无法解决,再明确请求人工介入。它们背后的原则其实很简单:宁可慢一点得到正确结果,也不要快速得到错误结果。

6.8 什么时候该用多 Agent

多 Agent 不是更强的 Agent。它是一种用协调复杂度换取能力扩展的架构选择。和所有架构选择一样,它有适用场景,也有不适用场景。

一个简单的判断标准:如果你不确定该不该用多 Agent,那就不要用。 多 Agent 是一个需要的时候才用的工具,不是一个默认就该用的架构。先用单 Agent 试试,如果撞上了天花板:上下文不够、角色混乱、执行太慢,再考虑引入多 Agent。过早引入多 Agent,就像一个人的活非要分给三个人干,光是沟通协调的成本,就可能超过多人并行带来的收益。

也正因为如此,多 Agent 并不会让失败消失,它只是把失败从一个 Agent 做错了什么,变成了一组 Agent 怎么一起出错。当能力继续往上堆,失败模式也会随之变复杂,这正是下一章要正面展开的问题。