拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

多智能体协作的核心:中介者模式而非简单调用链

多智能体协作的核心:中介者模式而非简单调用链

最近复盘一个多智能体项目时,我确认了一个判断:很多人以为多智能体协作就是多开几个Agent让它们前后跑一遍,但真正的协作本质是中介者模式,而不是把Agent实例堆成一条调用链。

这个现象太普遍了——装上Agent框架,注册几个角色,前后接力跑一遍,就觉得是Multi-Agent了。可真到了生产环境,链路一长问题全冒出来:任务重复执行、上下文互相覆盖、A Agent的产物格式B Agent不认、某个环节失败只能全链条重来。我踩过这些坑之后,决定把中介者模式在多智能体协作里到底承担什么角色、怎么落地、怎么选型讲清楚。这篇文章适合正在做Agent应用、纠结要不要上多智能体架构的人参考。

1. 两个Agent“直连”,问题比你想的多

1.1 一个典型的多开现场:调用链又长又脆弱

先还原一下最典型的“多开式”实现。假设你想做一个自动产出技术调研报告的系统,流程是:研究Agent查资料 → 写作Agent写文章 → 发布Agent做排版推送。很多人第一版是这样写的:

research_report = researcher_agent.invoke( {"task": "调研向量数据库的选型", "format": "markdown"} ) article = writer_agent.invoke( {"task": "基于调研报告写出技术博客", "material": research_report} ) publisher_agent.invoke(article)

这段代码看起来干净利落,但它只是演示代码。真实项目里第一轮就能暴露问题:research_report返回的内容可能是JSON包着Markdown表,writer_agent拿到后整个当一个字符串塞进提示词里,最后生成的文章全是未消化的原始参考材料。

这只是格式层面的错位。更麻烦的是,这三行代码把决策流写死了。写作者觉得材料不够怎么办?找不到“回退到研究Agent补查”的路径。发布者发现文章不合规怎么办?只能报错,没有任何机制触发“写作者重写”的动作。这不是智能协作,这是三条for循环。

我见过不少项目的“多智能体架构”,说白了就是这种调用链的变体。有的加了个内存队列,有的让Agent之间能互相发消息,但整体依然是两个Agent点对点交换数据。框架越上越多,问题没有本质变化。

1.2 直接互调最容易暴露的三类故障

这里我根据自己的项目经验,把直连式协作的故障归成三类,每一类都在生产环境遇到不止一次。

第一类是上下文不一致。每个Agent都有自己的系统提示词和对话历史,点对点互传时只传递文本内容,不传递调用链的全局状态。结果就是两个Agent各自维护一份“世界模型”,天然会互相矛盾。举个例子,研究Agent在报告里明明写了“方案A在写入性能上比方案B高40%”,写作Agent因为提示词里有一句“选型时要考虑团队熟悉度”,直接把结论改写成“方案B更适合长期维护”。它不是有意跑偏,是它根本不知道研究Agent得出A结论的前提条件是什么。

第二类是失败无处收敛。点对点调用里,A Agent一出错,调用方只能整体重试或者整体放弃。多智能体系统里的错误往往是局部的,比如一个工具调用超时、某个Agent返回了空结果。理想的做法是只重跑出问题的子步骤,而不是把前面全部工作推倒重来。这个能力,只有独立的协调层能提供。调用链模式下,谁都做不到局部恢复。

第三类是可观测性极差。你想回答一个简单问题——“当前这个任务进行到哪一步了,下一步是谁,为什么做出这个路由决定”——在直连模式里没有答案。日志散落在各个Agent函数调用里,没有一个地方能看到完整的事件流。排障的时候只能靠一个Agent一个Agent地查对话记录,靠猜。

这三类故障的根因是同一个:缺少一个拥有全局视角的独立协调者。

1.3 把“通信权”收回到协调者手里

说到这里,中介者模式的核心意义就很清楚了。它做的事情,本质上是从Agent手里把“通信权”和“决策权”收回来,集中到一个独立的协调组件上。

我经常用一个项目的比喻来解释这件事。一个开发团队拿到一个需求,最差的组织方式是让所有成员互相私聊、各自决定做什么,然后期待最后能拼出正确产品。稍微有点管理经验的团队都会安排一个项目经理:项目经理收集需求、拆任务、分配给人、检查交付物、协调冲突。多智能体系统里的“项目经理”,就是中介者。

如果你把这个比喻套回技术系统,就会发现中介者不是简单的消息转发器。它至少要具备三件事的能力:知道全局状态,拥有为下一步做决定的决策逻辑,并且能对Agent的产出做质量仲裁。后面的章节我会逐个展开。

2. 中介者不是消息队列,它要承担三个关键角色

2.1 调度中枢:路由不是写死的,是算出来的

很多文章把中介者模式等同于把消息汇总到一个中心再分发出去,这其实低估了它。在多智能体协作里,中介者首先是一个调度中枢,但它和ESB总线、消息队列有本质区别:消息队列不关心消息内容,中介者必须理解任务上下文才能决定路由。

举例来说,一次用户请求进来,中介者先做任务分解,得到三个子任务。它需要判断:子任务A适合交给“代码生成Agent”,子任务B需要调用“搜索工具”,子任务C必须人工审批。这个路由决策不是靠写死的if-else能完成的,因为用户的请求是开放的,不可能把每种组合都穷举出来。

我目前在项目里用的路由策略是三层混合。第一层是能力声明匹配:每个Agent在启动时向中介者注册自己擅长什么、能调用什么工具,中介者维护一张能力表。第二层是规则路由:稳定不变的路径直接走配置,例如“调研类任务永远先走检索节点”。第三层是模型决策:边界情况由LLM判断把子任务分给谁。三层从快到慢,成本可控,准确率比单靠一层高很多。

2.2 全局上下文:已经发生的事,中介者比Agent更清楚

Agent本质上是有状态但状态短暂的。每个Agent在自己的那一轮调用里拥有完整的上下文窗口,但一轮结束后就什么都不记得了。中介者要做的,是在整个任务生命周期里维护全局上下文——节点完成了什么、中间产物是什么、谁基于哪个版本改过内容、当前卡在哪个环节。

用一个更生活化的类比:一群专家轮流修改一份方案文档。如果每个人拿到的是上一版的批注,而且都在自己理解的基础上改,最终文档一定会乱套。可靠的机制是有一个主编守着唯一一份完整版本文档,控制“谁在什么时间基于哪个版本修改”。中介者就是这个主编。

技术上,这个全局上下文应该设计成结构化的,而不是一个大字符串。我用一个TaskState对象统一保存当前状态,关键字段包括task_id、completed_steps、pending_steps、artifacts、message_history。每一步Agent调用都先去读全局状态,再把自己的产出写回状态。因为这个对象是中介者唯一维护的,Agent之间不需要直接通信,避免了各自维护一份“真相”的混乱。

2.3 仲裁人:多个Agent想干同一件事,谁来定

多智能体系统里,冲突是常态,不是异常。两个Agent同时向全局状态里写入相矛盾的结论;一个Agent请求重跑上游任务,另一个Agent还依赖着旧结果;两个Agent都认为某个子任务应该由自己来做。

中介者的第三个角色就是仲裁。我实际用到的仲裁机制有四种。排队:冲突任务按优先级排队,避免多个Agent同时操作同一份资源。否决:当子任务的产出与已确认的全局结论冲突时,直接拒绝并记录理由。合并:多个Agent产出的内容可以取交集或做版本对比,再传给下一个节点。拆解:一个Agent请求的重跑范围太大时,中介者只重跑与该请求相关的子步骤。

这个仲裁逻辑不需要做得很重,但必须有。没有仲裁的中介者只是一个“广播站”,Agent之间的冲突不会自动消失,只会从“明面上的互踩”变成“上下文里的暗雷”。顺便提一句,LLM驱动的Agent比传统分布式节点更不可控,因为输出有随机性,同样的请求两次调Agent可能给出不同结果。仲裁策略要设计得更宽容,给Agent留下重试和纠偏的空间,而不是一遇冲突就全链报错。

2.4 中介者、黑板、直连:三种协作形态的取舍

多智能体系统的协作架构不止中介者一种。业界经常提到的还有黑板模式(Blackboard Architecture),以及前面花了很多篇幅批评的直接互调。我在这里做一个横向对比:

维度直接互调黑板模式中介者模式
Agent间依赖高耦合,互相知道接口只依赖共享数据空间只依赖中介者,互相不可见
决策能力无全局决策弱,靠各Agent自主读写强,路由与仲裁集中
故障恢复无,链路断了只能重来中等,部分状态可恢复强,局部重试易实现
可观测性差,日志分散中,看黑板历史好,中介者有完整事件流
适用规模2-3个Agent的极简场景并行计算型任务多角色、有流程控制要求的任务

黑板模式有一个明显特点:Agent之间通过共享空间隐式协作,每个Agent在“适当的时候”去读黑板上感兴趣的内容。这个模式非常适合处理并行计算密集的任务,比如一个黑板上有十个问题,十个Agent各取所需同时干活。但它的弱点在于没有集中决策,谁先谁后全凭各Agent自主判断,流程顺序很难控制。

中介者模式在流程控制和故障恢复上明显更强,适合业务上有明确阶段、顺序、质量要求的场景。实际应用中两者不是互斥的。我在自己项目里的做法是:中介者控制流程节奏和路由决策,把共享产出写入一个持久化的存储节点,这个节点本质上就是一块“黑板”。控制面用中介者,数据面用黑板,两者并行不悖。

3. 落地案例:让研究Agent和写作Agent产出协作成果

3.1 需求与两个Agent的职责定义

概念讲得再透,不如跑一个具体例子。这里我以实际做过的“自动生成技术调研报告”功能为例,把这个流程完整拆开。

需求是:输入“请帮我调研一下PostgreSQL和MySQL在写入性能上的差异,并输出一篇适合技术团队阅读的对比分析报告”。报告要有数据支撑、有适用场景建议、格式工整,可以被后续发布流程直接使用。

我注册了两个Agent:

  • ResearchAgent:负责检索资料,把多来源的事实整理成结构化摘要,字段包括topic、evidence、source、conclusion。它不写文章。
  • WriterAgent:接收结构化事实和写作任务书,把它润色成完整文章。它不做事实核查,不自行补充数据。

注意,这里我把职责边界划得很清楚:ResearchAgent只能产出结构化素材,WriterAgent只负责文体加工。这个边界由中介者的能力注册表约束,Agent之间互相不知道对方的存在。

3.2 多开式写法:链路短,但处处是隐患

如果按照“多开”的思路,代码会是这样:

research_output = researcher_agent.invoke( "调研 PostgreSQL 和 MySQL 在写入性能上的差异,给出数据对比" ) article = writer_agent.invoke( f"根据以下素材写一篇技术报告:\n{research_output}" )

这段代码最大的问题在于:ResearchAgent返回的可能是几百行原始注释、引用片段和未分类的链接,WriterAgent的上下文窗口会被塞满,最后生成的文章里原始素材占了一半。哪怕ResearchAgent按要求输出了evidence和conclusion,WriterAgent也可能因为prompt里的一句“可以根据你的理解补充”就自己编造了一组不存在的数据。

更致命的是没有反馈回路。如果WriterAgent发现素材不足,它只能硬写。它不能说“我需要补充关于高并发写入场景的数据”,因为代码里根本不存在让写作Agent往回传递需求的通信链路。这其实就是1.2里说的“失败无处收敛”的变体——问题不会因为Agent数量变多就消失,只会被链路放大。

3.3 中介者式写法:把决策流收回到一个可审计的地方

换成中介者模式后,核心是一个Coordinator类。它不生成任何内容,只做调度、评估、路由、汇总。

class Coordinator: def __init__(self, agents): self.agents = agents # {"researcher": ..., "writer": ...} self.task_state = {} # 全局上下文 def run(self, task): task_id = uuid.uuid4().hex self.task_state[task_id] = { "task": task, "steps": ["research", "write"], "artifacts": {} } # 第一步:路由给研究Agent,并声明产出格式 raw = self.agents["researcher"].invoke( TaskRequest(task=task, output_format="structured") ) # 第二步:质量评估——格式不对就重试,最多三次 for attempt in range(3): is_valid, reason = self._validate_research(raw) if is_valid: break raw = self.agents["researcher"].invoke( TaskRequest(task=task, feedback=reason, output_format="structured") ) self.task_state[task_id]["artifacts"]["research"] = raw # 第三步:把结构化素材路由给WriterAgent draft = self.agents["writer"].invoke( TaskRequest(task=task, material=raw["structured"], style="tech_report") ) # 第四步:汇总结果,只有通过校验才放行 self.task_state[task_id]["artifacts"]["article"] = draft return self._publish(draft)

这段代码的骨架体现了中介者的三个职责。调度中枢:任务被拆成research、write两步,按顺序执行;全局上下文:所有中间产物都写进task_state;仲裁人:_validate_research对研究Agent的产出做校验,不通过就带着反馈重新请求。

注意_validate_research是一个独立的评估节点。它可以是一个简单的schema校验——检查evidence字段是否存在、conclusion是否为空;也可以是一个独立的轻量模型,对素材的相关性打分。我强烈建议至少先上规则校验,成本低、稳定,效果立竿见影。

3.4 实测对比:同样任务,两种架构的差别

我在同一组任务上分别用两种架构跑了20轮,每轮都是同一个Prompt。大致结果如下:

指标直接互调中介者式
首轮产出可用率(无需人工改写)40%左右75%左右
因格式问题导致的失败重跑每轮约1-2次偶尔1次
失败后恢复方式整体重新生成局部反馈重试
连续5轮运行的稳定性越跑越偏上下文受控,结果收敛

这里需要说明,这不是严谨的基准测试,样本量也小,但行为差异非常典型。直接互调的40%可用率主要败在格式和上下文污染上;中介者式的75%里,有一半是第一次评估就通过了,另一半是经过一次反馈重试后通过的。

这套东西上线后还有一个隐形收益——排障。以前用户反馈“报告写得不对”,我需要同时翻ResearchAgent和WriterAgent的日志去猜问题出在哪。现在中介者的task_state里有完整的事件流,我可以直接看到“ResearchAgent的原始产出是什么、评估节点为什么让重试、最终是哪个环节产出不达标”。这个收益在维护阶段比前期开发阶段更值钱。

4. 框架选型:判断它有没有“中介者内芯”,就看这四点

4.1 为什么“编排层”比“多Agent并发工具”更接近中介者

现在市面上的多智能体框架很多,名字也乱,有的叫Harness,有的叫Orchestrator,有的叫Pipeline。很多人选型时被这些名词绕晕,不知道它们有什么区别。

我说句直接的:从中介者模式的角度看,真正重要的是这个框架有没有给你提供“独立协调层”的能力,而不是它管自己叫什么。Harness这类编排层,如果它允许你在Agent之外自定义调度逻辑、上下文存储、质量评估节点,那么它本质上就是在帮你实现中介者模式。反过来,如果框架只提供了“同时启动N个Agent”的并发能力,却不给你任何协调机制的房间,那它充其量是个多开的加速器,离真正的多智能体协作还差一层。

4.2 四个判断框架的硬指标

根据我自己的实践,选框架或者自研中间层时,用这四个问题去对照,能过滤掉大多数“名义多智能体、实为多开”的方案。

第一个指标:是否支持动态任务路由。框架允不允许你在运行期根据任务内容决定“下一步交给哪个Agent”,还是说只能在启动前用配置把流程写死?动态路由是调度中枢的基本能力,做不到的话,复杂任务只能退化为线性调用。

第二个指标:是否有独立的全局上下文管理。框架有没有提供一个跨Agent共享的状态存储,并且允许你在路由节点读写它?还是说上下文只能通过参数传递?独立的全局上下文是多智能体协作区别于“多开”的分水岭。

第三个指标:是否支持插入评估与仲裁节点。在流程中间,你能不能加一个“质量校验Agent”或者“规则检查函数”,不符合条件时触发重试或改走分支?如果框架只支持“调用Agent拿到结果就进下一步”,那它根本没有仲裁能力。

第四个指标:是否有状态持久化和恢复机制。运行中的任务如果因为网络或者模型服务故障中断,框架能不能从最近的检查点恢复?还是全链路重跑?这个指标平时不起眼,一旦上生产环境,直接决定你半夜要不要爬起来陪跑。

下面这个表可以做一个简化对照:

判断维度达标表现不达标表现
动态任务路由运行期可决策下一步配置写死,线性执行
全局上下文独立状态存储,跨Agent共享上下文只能靠参数硬传
评估与仲裁可插入校验节点,支持重试一步到底,无中间检查
状态恢复支持断点续跑中断就全链重来

4.3 已有系统的改造思路:不推翻重来也能落地

如果你手头已经有一套“多开式”Agent系统,不必急着推倒重来。我这里有一个按优先级排序的渐进式改造路线。

第一步,加一层薄薄的OrderCoordinator。所有Agent的入口统一从这层走,哪怕内部逻辑暂时不动。目标是先把可观测性和调用入口收口,让“谁调了谁、结果是什么”有日志可查。

第二步,统一消息结构。把自由格式的参数传递改成一个TaskRequest和一个TaskResult对象,至少包含task_id、sender、target、payload、timestamp。这一步做完之后,格式错位的问题会大幅缓解。

第三步,在关键节点插入评估动作。先做简单的规则校验,例如检查返回结果是否包含必需字段,不满足就触发一次带反馈的重试。这个改动通常一个晚上就能完成,但效果是立竿见影的。

第四步,把关键状态放持久化存储。把task_state从内存挪到Redis或者PostgreSQL,加上恢复逻辑。到了这一步,你的系统已经从“多开”进化成了标准的中介者模式,后面的扩展都是量变,不再是质变。

5. 中介者本身也会烂:我踩过的四个坑

5.1 坑一:中介者膨胀成“上帝对象”

早期我最容易犯的错误,是把所有判断逻辑都堆进Coordinator类。任务拆解放里面,prompt模板管理放里面,Agent能力描述放里面,甚至连业务规则也写进路由决策里。几个月下来,Coordinator.py到了两千多行,改一处逻辑要小心翼翼担心影响其他流程。

后来我意识到,中介者的职责边界应该是“协调”而不是“业务”。它需要知道哪一步交给哪个Agent,但不需要知道“为什么这一步的业务规则是这样”。修正的做法是把业务规则拆到独立的Policy模块里,比如路由策略、质量评估标准、重试策略各自成类,中介者只负责按策略执行。这样中介者保持轻量,策略可以独立演进。

5.2 坑二:Agent自由发送消息,中介者变翻译

有一段时间我没有统一消息协议,Agent之间通过自由文本传递内容。结果是中介者每收到一条消息,就得先做语义解析,猜它想表达什么,再翻译给下游Agent。这个解析过程消耗大量token,而且极易失败——Agent的表达方式稍有变化,解析器就挂了。

踩过几次坑之后,我把所有Agent的输入输出都改成了结构化格式。研究Agent只能输出JSON,发布Agent的回复必须是{"status": "published", "url": "..."}这种格式。模型确实偶尔不遵循格式,但重试一次基本就回来了,比起无休止地解析自然语言,成本和稳定性都强太多。最好在系统提示词里给一个输出示例,比在解析层做兜底更便宜。

5.3 坑三:状态外置没做,系统一挂就全乱

这个坑是上线第一个月被生产事故教育出来的。当时的中介者把状态全部放在内存里,某天模型服务因为限流导致整批任务失败,服务重启之后,所有进行中的任务全部丢失,只能从零开始跑。用户体验特别差。

教训是:中介者本身必须是“无状态可恢复”的。当前正在执行的任务详情、已经完成的步骤、各Agent的产出快照,都要放到独立的存储层。我自己现在用Redis保存task_state,每个任务一个key,里面存放完整的消息trace和产物引用。重启后根据task_id就能知道任务断在哪一步,选择性地恢复执行。这个设计不仅提升了可靠性,还让系统可以横向扩容——多个中介者实例可以处理不同任务,互相不打架。

5.4 坑四:光路由不评估,相当于没有协调

最后一个坑比较隐晦。有一段时间,中介者的调度做得很好,任务分发、路由决策都没问题,但整体质量还是上不去。后来排查发现,中介者把任务分配给Agent后就直接把结果透传下去了,中间没有任何验证和把关。这就像项目经理只管派活、不收作业,交付质量完全靠个人自觉。

解决的办法是在关键路径上设评估节点,验证Agent的输出是否真的满足下一步的输入要求。评估动作可以很轻,比如检查结果字段是否完整;也可以稍微重一点,用一个独立的Critic Agent审查内容与任务的相关性。关键是必须有“不过就重试”。从我的经验看,评估节点带来的质量提升往往比换更强的模型还明显,因为它把Agent的随机误差锁在了一个可控制的范围内,不再向下游扩散。

我现在设计多智能体系统时,已经把“评估节点、重试预算、状态持久化”当成默认配置了,不是可选项。中介者模式的核心价值,说到底就是让协作不再是碰运气,而是变成一条有校验、有反馈、可恢复的流程管线。

返回列表