多人多AI协同架构设计:从代理网格到事件总线与状态同步 这年头聊AI协同的不少但真正把多个AI代理Agent替多个用户跑事落地成一套可维护系统的并不多见。我最近把内部的一套原型重构成了多人多AI协同架构核心思路是让每个AI代理Agent通过统一的交互层代表用户和其他AI发起对话、完成任务而不是让用户在各AI之间手工搬运上下文。这篇文章把系统架构的关键设计、通信协议、状态同步、异构模型接入和踩坑过程整理出来适合正在做多智能体系统、AI工作流编排或者被一堆AI各管一摊、没法协作折磨过的人参考。先打个预防针这篇里说的代理都是Agent智能体不是网络转发那种代理服务。我们讨论的是AI怎么替人跑事、AI之间怎么互相配合不涉及任何网络层面的转发概念。1. 多AI协同难在哪先讲清楚代为交互解决的真实问题1.1 用户在不同AI之间手工搬运上下文是我重构的直接原因我最早做这个项目起因很朴素团队里每个人手头少则两三个、多则五六个AI工具写方案、查资料、生成图表、审代码各用各的。AI互相之间根本不认识协作方式停留在人肉中转——从对话A复制结论粘贴到对话B继续问再把结果转发给同事。遇到两三个人的小组任务搬运成本直接指数上升经常是A问了一遍、B又问了一遍、C把A和B的结论再喂一遍。后来我意识到真正缺的不是更多更强的AI而是让AI代理们彼此能说上话的系统。所谓代为交互就是把人带着上下文跑腿这件事抽象掉用户只负责定义目标、约束和验收条件剩下的事由各个AI代理通过统一的交互协议去接力完成。这个转变不只是省掉复制粘贴而是把协同的主体从人换成了代理。1.2 交互主体变了从人围着AI转变成代理围着任务转传统使用方式里交互主体是人-单个AI。人或主动发起请求或被动接收结果。而代为交互模式下交互主体有三个层次第一个层次是用户-代理用户向某个代理下达意图并持续对话这个代理成为用户侧的会话代言人负责理解用户、拆解任务、维护这一段对话的目标。第二个层次是代理-代理用户侧代理发现任务需要另一个专业能力比如画架构图、跑数据、审代码它会把子任务以格式化消息发出去其他代理按自身能力应答。这里的关键是代理之间交互的不再是聊天记录而是带明确语义的任务、参数和约束。第三个层次是代理-工具/系统代理调用检索、计算、存储、消息通知等基础能力把模型输出和真实世界连接起来。这很像项目会。如果一个需求非得靠参会人全部口头同步才能推进一旦有人缺席会议就卡死但如果有个秘书把待办、结论、负责人写进工单会后每个人照着工单走推进会顺畅很多。代为交互的架构本质就是给AI协同配上这个工单系统。1.3 我给自己定的三个设计目标动手前我列了三条硬性设计目标后面所有取舍都围绕这三条透明可观测任何一条消息都必须能追溯是哪个用户、经由哪个代理、为什么这样决策。没有可观测性协同系统排错会是一场灾难。横向可扩展新接入一个AI模型或新代理不应该改动其他代理的逻辑只做注册和配置。有边界的数据隔离代理代表用户交互时必须有清晰的数据边界。多用户场景下A用户会话里的数据不能被B用户随便读到。这三条目标直接决定了架构的形态。你会发现后面每个组件几乎都能对应到这三点中的某一条。2. 总体架构设计控制面与数据面分离的代理网格2.1 分层接入、协同、代理、模型、基础设施我把整个系统划分成五层。这个分层不是拍脑袋定的而是按照谁在跟谁说话来切接入层统一的会话网关对外提供WebSocket/HTTP接口处理用户连接、身份认证、会话创建。协同层包含协同编排器、代理注册中心、路由调度、仲裁组件是整个系统的核心控制面。代理层一组常驻的AI代理进程。每个代理有自己的角色描述、任务队列、上下文窗口和工具权限。模型层统一模型网关屏蔽后端不同模型的差异不管是本地私有模型还是云端大模型都走同一套接口。基础设施层消息总线、状态存储、对象存储、监控链路。2.2 为什么叫它代理网格而不是直连网格不是营销词汇。早期我做的是N对N直连——每个代理持有其他代理的地址遇到任务直接调用。跑了一个月后问题全暴露了地址散落、权限无法统一、排错困难、新增代理要改所有人的配置。后来我把交互关系收敛成代理网格核心变化是代理之间不直接知道对方地址只通过协同层路由来找到谁有能力处理这个消息。每个代理只在注册中心暴露能力声明而不是暴露内部实现。所有跨代理消息都走统一的事件总线控制面和数据面彻底分开。改完之后新增一个代理时只要完成注册和配置其他代理无需感知这正是横向可扩展目标落地的关键。2.3 控制面与数据面分离相当于给系统装了交通调度和车道控制面与数据面分离这个设计很多人听过但未必理解为什么对AI协同特别重要。我直接用对比表说明维度控制面数据面职责路由决策、会话状态、权限仲裁、任务调度消息内容、模型输出、文件、临时结果存储尽量无状态状态放Redis/数据库按需持久化到对象存储或消息队列扩展方式横向加实例即可关注吞吐和延迟可独立扩容故障影响控制面故障影响新路由和新任务数据面故障影响消息传输但不影响控制面逻辑这样设计有个直观代价控制面会多一跳初期看起来绕路。但换来两个非常实在的好处。第一控制面逻辑可以做到幂等。协同层是无状态的请求打到任意实例结果都一样横向扩缩容不需要迁移会话状态。第二消息风暴不会直接把控制面打挂。即使某个代理在疯狂产出消息数据面队列把消息兜住控制面按规则限流和调度而不是被拖进同一个进程一起死。这个思路和分布式交换机非常像——控制协议和数据帧分离交换机才能在转发层全速跑路由策略又可以随时下发调整。AI协同系统的控制面承担的就是交换机里控制角色那部分工作。2.4 组件清单与选型下面是我在实际项目里用的组件清单。选型不是唯一解我给每项都标了理由仅供参考组件选择为什么这样选会话接入WebSocket 短轮询兜底双向消息推送需要长连接短轮询只做降级消息总线NATS JetStream或 Redis Streams支持持久化、消费者组、消息回放轻量够用状态存储Redis PostgreSQLRedis放会话短状态PG放审计和最终一致数据代理运行时独立的容器进程隔离资源、独立扩缩容、便于单独更新模型可观测OpenTelemetry Prometheus统一trace和metrics跨代理链路肉眼可查3. 代理之间的通信协议事件总线加能力路由3.1 消息类型先分清楚协议才不会写成四不像我在设计通信协议时先把协同场景里出现的消息按性质分成了四类指令型消息代理A要求代理B执行某个动作例如生成架构图执行这段SQL。这类消息必须确认且可重试。状态型消息某个代理广播自己的工作进展例如代码审查完成结果在附件里。可以接受到达但不强一致。查询型消息请求事实判断或上下文信息例如用户最新确认的设计约束是什么。需要快速响应。治理型消息心跳、注册、能力变更通知、仲裁请求。短小且高优先级。这四类的可靠性和超时特征完全不同。我最初把指令和状态混在同一个主题里推进结果踩了不少坑。后来强制按消息分类规划主题整个链路清爽了很多。3.2 统一消息信封的设计所有消息都用一个统一信封包装字段如下JSON示例{ event_id: evt_01J0XYZ..., event_type: task.request, session_id: sess_9f3a..., version: 43, from: { type: agent, id: agent.user.alice, abilities: [planner] }, to: { type: skill, id: skill.arch_diagram }, payload: { task_id: task_8831, input: 根据上面的方案生成部署架构图, constraints: {format: png, style: clean} }, trace_id: tr_8d2f..., reply_topic: session.sess_9f3a.reply }这个信封里有几个字段是我后来才补上去的但对系统稳定至关重要version会话状态版本号用于乐观锁冲突检查。trace_id贯穿所有代理调用链路。没有它排错只能靠猜。reply_topic异步响应地址避免代理之间必须互知对方地址。to可以是代理ID也可以是能力ID。我强烈建议优先用能力ID由协同层做路由。3.3 为什么不用纯REST而选带确认的事件总线很多人问代理之间为什么不用HTTP REST调用我的回答是REST是请求-响应的强同步模型适合控制类操作但AI代理之间的任务绝大多数是长耗时、多参与方、需要持续跟进的过程。举个例子代理A请代理B生成一份数据分析报告B可能要先调模型、再查数据、再生成图表整个过程几十秒甚至几分钟。如果A用HTTP同步等B那A的所有后续工作都会被阻塞如果用异步回调那其实已经是在实现轮询和回调队列了——不如一开始就用事件总线。事件总线的另一个好处是支持多接收方和订阅。一个代理完成子任务后发布消息关心这件事的多个代理比如用户侧代理、监督代理、审计模块都能收到而不是像REST那样强制一对一。带确认的语义是消费方处理完成后要给总线一个ack处理失败时总线按重试策略重新投递。我们用的是NATS JetStream的consumer group模式读消息需手动ack超时自动重投。但要注意重投会导致重复任务所以下游处理必须做成幂等的这个我在踩坑章节详细讲。3.4 能力注册与路由决策每个代理启动后会向注册中心上报自己的能力声明agent: id: agent.arch.designer display_name: 架构设计助手 abilities: - arch_diagram - tech_selection model_affinity: local_70B max_concurrent_tasks: 4 trust_level: internal协同层的路由调度器维护一张技能-代理路由表。当消息的to填的是skill.xxx时调度器找到当前可用的、可信等级匹配的代理实例然后把消息转给它。如果多个代理具备同一技能调度器按负载、模型偏好、延迟三个因子打分选路。这个机制的收益很大代理升级、替换、缩容都不会影响消息发送方消息只认能力不认具体实例。4. 多人会话与多代理协同状态同步到底怎么做4.1 先把三种状态分清楚多人多AI场景下状态这个词太容易混淆了。我心里必须先分清三种会话状态整个协同会话的公共上下文比如目标、约束、已确认的决策、参与者列表。它是所有代理共享的。任务状态某个子任务在哪个代理手里、进展如何、依赖哪些上游任务。它是局部的但需要上报。代理记忆每个代理自己的上下文窗口、偏好和缓存。它属于代理私有但受控时可以导出共享。三种状态混用很容易出问题。我见过一种典型事故代理A在私有记忆里维护了一份用户需求清单代理B也在自己的记忆里维护一份两人对需求是否变更产生了分歧后续输出全偏了。所以我的原则是凡是会被多个代理用于决策的公共事实一律进共享事实表不允许存在代理私有的副本里。4.2 共享事实表加版本化乐观锁共享事实表的实现并不复杂关键是写入规则每个事实记录带版本号。代理要修改某条事实时必须带上自己读到的版本号发起修改。版本落后时拒绝写入并返回冲突信息。追加型操作比如添加一条备注不做冲突拒绝直接追加。用Redis存JSON配合Lua脚本做原子乐观锁local cur redis.call(GET, KEYS[1]) if cur ARGV[1] then redis.call(SET, KEYS[1], ARGV[2]) return 1 else return 0 endARGV[1]是发起方携带的base_versionARGV[2]是新的状态JSON。冲突时调用方收到0就进入冲突仲裁流程。4.3 冲突消解先追加、再仲裁、最后人工我把冲突处理设计成三级追加型更新直接合并不需要仲裁。例如新的聊天记录、新的附件索引。覆盖型更新进入仲裁。仲裁规则依赖场景——可以配置最新写入优先、优先级高的代理优先或者需要多数代理同意。高风险更新例如删除已确认的决策、修改用户身份标识直接挂起通知用户侧代理由人类确认后放行。这个策略一开始就实现了但初版很粗糙只用了最新写入优先结果两个代理交替覆盖同一个字段陷入你写我覆盖、我写你覆盖的循环。后来给仲裁增加了同一字段变更冷却窗口一个字段被某代理更新后两秒内其他代理的覆盖请求会被拒绝并排队等待。效果立竿见影。4.4 上下文压缩别让协同变成上下文大杂烩多代理共享一个会话时token消耗增长是线性的但上下文混乱度增长接近指数。我试过直接把所有代理消息都塞进同一个上下文窗口结果模型很快被大量历史淹死回答质量越来越差。解决方案是三段式上下文管理滑动窗口每个代理只保留最近N条与自己相关的消息。定期摘要每K条公共消息后由一个摘要代理生成会话摘要追加到共享事实表的summary字段。关键事实库从消息中抽取实体和已确认决策单独入库。代理做决策时优先读取事实库而不是翻整个聊天记录。这个机制上线后同样任务量下模型输出质量明显回升token成本也压低了将近一半。5. 异构模型接入本地模型与云端大模型的统一网关5.1 代理层不应该直接绑死单一模型大部分多AI协同方案的默认前提是接入同一个大模型但实际项目里往往是一个AI助手代理加本地模型的组合核心业务相关的对话和数据处理必须放在本地防止数据出域通用创意、长文本润色、快速检索这些任务可以交给云端大模型。两种模型能力不一样、协议不一样、成本不一样如果让每个代理自己对接那么每换一个模型所有代理全要改。我在架构里加了一个模型网关让所有代理只面对一组统一接口ChatComplete(request)通用对话补全参数标准化为输入、系统提示、工具定义。Embedding(text)向量化供检索和记忆模块使用。ToolCallExecution(spec)部分模型无法原生调用工具时由网关做工具调度和结果回填。5.2 本地模型的资源隔离IOMMU与显存分配本地模型是异构接入里最折腾的部分。我曾在同一台GPU服务器上同时跑一个7B模型和一个70B模型结果两个模型都在抢显存70B频繁OOM7B响应也变慢。后来我做了两件事。第一通过容器化隔离进程。每个模型实例分配独立的显存配额超配直接拒绝而不是运行时才OOM。第二用IOMMU对DMA访问做隔离。这类硬件能力在虚拟化环境里很关键每个设备或设备实例绑定独立的IOMMU domain让模型进程无法越权访问其他设备或内存。对我实际部署来说最重要的价值是能把模型A打爆CUDA上下文导致模型B一起挂这类故障隔离开。研究Linux内核IOMMU软件架构时我发现把IOMMU当成地址映射加权限闸门来理解比当成特殊硬件更容易上手。它承担的就是隔离和故障域收敛的角色跟我们系统控制面与数据面分离是同一个思想。5.3 路由策略什么时候走本地什么时候走云端模型网关的核心职责之一就是路由。我的路由配置大概长这样routes: - name: private_data_analysis match: data_class: [private, internal] task_type: [analysis, sql] target: local_70B priority: 100 - name: code_generation match: task_type: [coding] target: cloud_code_model priority: 90 - name: creative_writing match: task_type: [drafting, polish] target: cloud_llm priority: 80 - name: fallback match: * target: local_7B基本逻辑是涉及私有数据的任务强制走本地需要特定领域能力的任务优先指定模型最后是兜底路由。每条路由都配了熔断开关当目标模型的错误率超过阈值网关会把流量降级到次优目标并通知协同层标记该模型不健康。5.4 统一网关带来的工程收益网关上线后代理层从识别十种模型API缩减到只对接一种内部API。新增模型时只需要按协议做一个适配器再到路由表加规则。我们后面新接一个模型单点耗时从一两天降到两小时左右主要时间都花在测试输出质量上而不是写协议转换。6. 落地坑位实录三个真实事故与排查链路6.1 事故一代理之间无限循环对话模型资源被跑空现象某天晚上消息量突然暴涨两小时后多个模型实例开始返回超时。查看监控发现负载主要来自事件总线消息几乎都在task.request和task.review两个topic之间反复横跳。排查链路先按trace_id捞了一条完整消息链路发现代理A发了一条请B评审方案B评审后又发请A修改A修改完又发请B评审无限循环。检查业务规则评审变更确实没有定义终止条件。A和B的指令都合法但组合到一起形成了环。根因定位任务消息缺少循环深度的上下文代理无法判断自己收到的任务是不是自己曾经发起过的任务的派生任务。解决方案是三管齐下任务消息增加task_chain字段记录祖先任务ID和深度。当深度超过阈值默认5层时路由调度器自动拒绝转投。增加循环检测器统计同一(from, to)组合在单位时间内的消息数超过阈值则触发告警并暂停后续路由。这也是我强调可观测性的重要原因。如果没有trace_id贯穿链路这种循环问题在业务日志里极难定位。6.2 事故二共享事实表覆盖循环代理各自为政现象多代理协同做一份技术方案时方案里的部署架构字段在两个代理之间来回变最终结果和两个代理各自记忆里的版本都对不上。排查链路从共享事实表的更新日志里发现代理A和B在20秒内交替提交了7次覆盖更新。检查各自上下文A看到的是自己上一轮的输出B看到的是自己上一轮的输出而二者都认为自己拥有最终决定权。根因定位覆盖型更新没有仲裁机制简单的最新写入优先会在高并发下放大竞争。解决方案对覆盖型更新引入仲裁规则默认负责人代理优先。给同一字段增加冷却时间窗。变更日志里增加conflict_resolved_by字段任何仲裁结果都可追溯。6.3 事故三消息重投导致任务重复执行现象某代理在处理生成报表任务时任务执行成功但ack确认失败消息被总线重投同一个报表生成了两份且其中一份使用的参数是旧版本。排查链路查看消费者日志发现第一次处理其实已经成功写库但在ack前进程发生了GC停顿导致ack超时。总线按策略重投第二次处理读取的是旧版本快照结果覆盖了新版本。针对这类问题我把所有下游处理都改成了幂等任务模式每个任务都带有task_id source_version。任务执行前先查任务状态表如果已经是completed状态则直接返回已有结果。写操作必须在事务里同时更新任务状态和数据避免结果写了但状态没更新的窗口。6.4 实测性能轮廓最后给一组压测参考数据。你们环境不同重点看趋势场景并发用户AI代理数消息吞吐条/秒P95协同链路时延控制面CPU占比2人2AI多轮协作22382.4s12%5人5AI方案评审552105.8s23%10人8AI大规模协同1086809.7s31%前两个场景中时延大头在模型推理到了10人8AI场景控制面的调度、路由和状态同步开始占据明显比例。如果继续往上扩我的计划是先把控制面实例扩容到3副本再把会话状态按session分片。7. 后续演进记忆层、代理团队与审计能力7.1 从会话级协同走向长期记忆协同当前架构的记忆还停留在会话级别。会话一结束代理对合作方的理解就归零。下一步我打算把代理记忆抽象成独立记忆层按实体、用户、项目、偏好存储代理之间可以有条件地共享部分记忆索引。这里的关键是有条件不应该让一个代理的私有历史直接暴露给所有代理而是提供可检索但受权限约束的接口。7.2 代理团队自组织能力注册之后再加团队契约目前代理之间的协作仍由协同编排器统一调度。下一步准备引入团队契约一组代理在承接大型任务前先协商各自角色、上下文共享范围和验收标准形成契约后写在共享事实表里。这相当于从临时拼凑走向项目制组织适合更复杂的任务。7.3 审计与治理多人多AI协同一旦进入生产审计能力不是可选项。我们正在完善四个维度的审计谁授权的任务、哪个代理执行了什么动作、调用外部模型时输入了哪些数据、冲突仲裁的决策记录。这里纯粹从工程治理角度讲只有把这些事件全部落库后续优化和事故复盘才有依据。最后再说一点个人体会这类系统的核心复杂度不在单个AI能力上而在控制面。控制面设计得清爽、可观测、可仲裁多AI协同就能跑得稳控制面偷懒图省事后面一定会在某个凌晨的故障里加倍还回来。如果你也在做类似架构建议先把状态同步和消息重放的功课做扎实再回头调模型效果。