多人多AI协同,听起来像是把几个大模型接在一起就完事了,但真正把“替人干活”这件事落进系统架构,坑比我预想的多得多。这个项目最初源于一个很实际的痛点:团队里有产品、研发、测试、运营四种角色,日常要同时使用代码生成模型、文档模型、数据分析模型和内部知识库助手,每人开几个窗口来回切换,上下文全靠人肉搬运,消息复制粘贴到变形。后来我们决定不搞“给AI加界面”这种浅层集成,而是让每个成员拥有一个AI代理,由代理替人去协调、调度、分发和汇总其他AI,最终沉淀出一套“多人对多AI”的代理化协同架构。这篇就把这套架构从设计决策到落地细节完整拆开讲,包括为什么代理化优于直连、多智能体协同的上下文与路由怎么设计、人怎么插队介入、以及我踩过的几个比较经典的坑。
1. 为什么需要“代理代为交互”而不是直连多个模型
1.1 入口式集成的核心价值
第一版方案很直接:给每个客户端配一个模型选择器,想用哪个模型就调哪个API。结果跑了几周就发现,这不是协同,是转接头。
问题出在“交互成本”被忽略了。四个角色、五个模型、三套API规范,组合起来就是几十种调用路径。研发问代码生成模型要解释,产品非得把同一段话再贴给文档模型重写一遍,测试又要让数据分析模型把结果重新描述一次。每个模型收到的都是没有上下文的孤立提问,每个用户都要手工对齐各方输出。这种模式下,模型不是助手,是十几个需要分别伺候的“外包专员”。
换成AI代理代为交互之后,架构逻辑就变了:所有用户请求先进代理层,由代理统一理解意图、拆解任务、确定路由,再代表用户去分发和回收。用户面对的是一个能听懂“帮我看看这段接口性能,顺便按测试规范出几条用例”的入口,而不是五个互不相识的黑盒子。代理在中间做的事,本质上是用一套统一协议把“人的意图”翻译成“模型的任务”,再把多个模型的输出汇编成“人的答案”。
1.2 多AI协同需要“代理”作为隔离层
另一个容易被忽略的原因是:模型之间不能直接互相信任。模型A的输出作为模型B的输入时,如果没有任何质量闸门,错误会沿着链路被放大。比如代码模型生成了一段不存在的函数名,文档模型拿到了这个错误信息,会一本正经地写出一段使用说明,最后人拿到一份描述不存在功能的文档。这种“垃圾进、垃圾出”在链式调用里非常常见。
代理层在这里起到了“语义校验”和“格式归一”的作用。每当一个模型的输出要被传给下一个模型之前,代理先做三件事:检查输出是否满足预定义结构;抽取关键实体和结论;把内容重新包装成目标模型能理解的prompt格式。这个过程很像企业内部的项目经理:各团队交付的东西先由项目经理核对、合并、再转述,而不是让后端直接把半成品丢给前端。
热词里提到的openclaw+ros那套思路也是同一个逻辑:ROS本身解决机器人组件间的通信协议,openclaw让AI代理拥有操作外壳,两者组合的关键不在单个工具,而在“路由与转换”这一层。AI代理协同系统的架构核心,不是模型本身,而是连接、翻译、仲裁这些“代理化”基础设施。
1.3 多人多AI场景下的特殊挑战
单人多AI已经够复杂,多人多AI则多了一层社会性挑战:人的优先级不同、模型的能力边界不同、会话的归属也不同。产品经理让文档模型生成的方案,研发希望在同一份文档里直接看到代码模型的技术评审意见,两条请求都涉及同一个文档,但写权限和决策权重完全不一样。
如果每个人直接操作各自的AI,冲突没人仲裁。A让AI把文档标题改成“v2方案”,B让AI把同一处改成“final版”,两个模型各自执行,最后文档被改乱。代理层接管后,这类冲突被建模为“同一资源上的多个写意图”,由代理层的调度器根据角色权限、时间优先级、依赖关系做合并或排队。
这套机制本质上就是一个轻量级的事务管理器,只不过它管的不再是数据库行锁,而是文档段落、代码文件、甚至一段对话的上下文空间。
2. 系统整体架构与关键设计决策
2.1 四层架构总览
在这个项目里,我最终把系统分成四层:交互层、代理调度层、模型底座层、数据与审计层。这是经过三轮重构后定下来的形态,每一层都有明确边界,改模型不碰界面,改调度不碰模型。
| 层级 | 核心职责 | 关键组件 |
|---|---|---|
| 交互层 | 对接用户与呈现结果 | Web客户端、IM机器人接口、事件订阅 |
| 代理调度层 | 意图解析、任务路由、编排、仲裁 | 意图引擎、路由表、状态机引擎、消息总线 |
| 模型底座层 | 接入异构模型与本地能力 | 模型网关、函数调用注册中心、向量检索 |
| 数据与审计层 | 上下文存储、会话追溯、质量评估 | 时序会话库、向量数据库、日志采集、血缘追踪 |
给每层加上一句解释。交互层解决“人以何种方式提出需求”;代理调度层是核心大脑,解决“任务怎么拆、交给谁、怎么合”;模型底座层解决“不同的AI能力如何被统一调用”;数据与审计层解决“整个协同过程凭什么可信、可查、可复盘”。
顺手提一句,分布式交换机系统架构的思路对代理调度层很有借鉴价值。交换机不关心数据包内容,只关心MAC地址和端口映射;代理调度层也不应该理解所有业务细节,只需要维护“能力注册表”和“路由策略”。把模型看成端口,把任务看成帧,调度器的工作就是快速、准确地把帧转发到对应端口。
2.2 代理的组成结构与生命周期
单个AI代理不是一个大而全的模型,而是一个由多种组件拼接而成的“虚拟身份”,包含意图识别器、记忆容器、工具执行器、策略配置和会话状态机。意图识别器决定了任务属于“代码生成”“文档撰写”还是“数据分析”;记忆容器保存了与特定用户相关的偏好和历史结论;工具执行器允许代理调用外部API、数据库和文件系统;策略配置则写明了这个代理在冲突时的优先级、超时时间、以及哪些操作需要人工审批。
生命周期上,我采用“长驻代理+临时会话”的组合。每个用户有一个常驻的代理实体,负责维护长期偏好、权限身份和历史风格;每次具体任务则生成一个临时会话子代理,负责执行当前请求并收集结果。这样做的理由是:长期状态与短时任务解耦,避免上下文无限膨胀,出问题时只需要销毁会话级代理,不用影响整个用户代理。
2.3 中心化编排还是去中心化自治
这是整个架构里争议最大的决策:到底由中心调度器统管所有任务,还是让各代理之间自由协商?我最后选了中心化编排为主、局部自主为辅的混合模式。
纯中心化的优点在于可控性强,所有任务都经过统一网关,路由、审计、限流都好做;缺点也很明显,调度器成为单点,一旦状态机卡死,所有代理都停摆。纯去中心化自治,比如让多个代理通过消息协商完成任务,好处是扩展性强,但排错极度困难,而且容易出现两个代理互相等待的死锁。
我采用的混合设计是:网关中枢负责全局状态与跨代理路由;已经下发给单一代理的任务,在代理内部以自主执行为主,允许代理自行决定调用哪个工具、以什么顺序执行子步骤。这种“中央定方向、地方管执行”的模型,既保住了审计和路由的全局可控性,又给单个代理保留了灵活操作的空间,不会因为每一步都要回中枢而性能暴跌。
3. 多AI协同过程中的核心机制
3.1 能力注册与动态路由
要让多个AI协同工作,第一步是让调度器知道“谁有什么能力”。我在模型底座层维护了一张动态能力注册表,每个模型或工具启动时,向注册中心上报自己的能力标签、输入输出格式、平均延迟和最大并发数。代码模型的上报可能是code_generation; max_tokens=8192; latency_p99=3s,数据分析模型则是sql_execution, chart_generation; max_rows=5000。
路由策略不是简单的“关键字匹配”,而是三维度打分:能力匹配度、上下文契合度、负载健康度。举个例子,用户发起“分析一下上周线上事故分布”,调度器先通过embedding把意图映射到“数据分析”标签,然后在注册表里找到两个候选模型,再检查当前会话的上下文是否包含相关表结构信息,最后看两个模型当前的队列长度与错误率。三个分数加权求和,得分高的获得任务。
这套动态路由在热词里其实有一个很经典的底层参照——“linux系统iommu软件架构分析”。IOMMU做的事情就是为设备建立DMA地址映射,决定内存访问走哪条路径,同时做权限隔离;代理调度器做的事几乎可以一一对应:建立任务到模型的路由映射,隔离不同会话的数据空间,以及对敏感模型做访问控制。
3.2 工作流编排:串行、并行、条件分支
单一意图被拆解成多个子任务之后,还需要一个状态机来编排它们之间的关系。我的项目里用了一套非常轻量的DSL来描述协同流程,没有引入重型工作流引擎,因为代理协同的流程大多数是动态生成的,预定义流程图满足不了灵活性。
编排器的核心数据结构是DAG(有向无环图),节点代表子任务,边代表依赖关系。任务执行时按照“入度为零优先”的原则逐个执行,只有前置节点完成,后续节点才能启动。比如“生成接口性能报告”这个任务会被拆成三个节点:调用数据分析模型跑SQL、调用代码模型解释性能瓶颈、调用文档模型整合成报告。前两个节点可以并行执行,第三个节点必须等前两者完成,这就是一个典型的分叉-汇聚模式。
条件分支则依靠意图引擎的判定结果。比如分析模型输出的结论如果是“性能瓶颈集中在数据库”,后续节点自动触发数据库索引优化建议任务;如果是“网络IO瓶颈”,则触发另一条链路。这个机制的实现难度不在于DAG算法本身,而在于每个分支条件都要有明确的、可被程序判定的触发信号,不能依赖模糊语义。
3.3 上下文管理与会话隔离
多AI协同的另一个核心问题是上下文共享边界。每个模型有各自的上下文窗口,但如果各模型的上下文完全隔离,协同就成了“接力赛跑,每棒重新起跑”;如果完全共享,又会互相污染,A模型看到的B模型的内部思考过程未必是有用的信息。
我采用的方案叫做“分层上下文”:
- 全局会话层:保存目标、约束、角色身份,对本次协同的所有模型可见,比如“评估订单系统的接口稳定性”就是全局目标。
- 任务执行层:保存当前子任务的输入输出与中间结果,只对当前执行节点可见。
- 私有暂存层:保存模型独立思考过程中的候选信息,仅对所属模型可见,除最终结论外不外泄。
每一层上下文都用一个向量索引ID关联,切换任务时只加载对应层,既控制了token消耗,又避免了干扰。
这里踩过一个很深的坑:多个模型同时读写同一个全局会话变量,会出现类似数据库“脏读”的现象。场景是代码模型先写入“接口QPS为800”,随后数据分析模型在另一个分支读到这个还没被校验的值,直接把它当成既定事实用于汇总报告。后来所有跨模型变量写入都强制经过“发布订阅”通道,只有被主调度器标记为“已确认”的变量才会被其他模型感知,这个问题才彻底解决。
3.4 人类介入的“控制反转”设计
多人多AI协同不代表人在圈外等着,恰恰相反,人在关键节点必须能插入。我把人工介入设计成三种模式:旁观模式、审批模式、接管模式。
旁观模式是人只观察代理的执行日志和中间结果,不打断,适用于低风险任务,比如生成日报草稿。审批模式是代理执行到某个指定节点(比如修改线上配置、发送对外邮件)必须停下来等待人工确认,只有收到“批准”信号才能继续,这一步本质上是给系统上了权限闸门。接管模式是人主动终止当前代理流程,改为直接操作某个底层工具,比如数据分析模型生成的SQL有问题,用户可以直接把该节点切换到SQL编辑器执行,然后把结果回传给后续节点。
控制反转的核心经验是:必须在流程启动前声明“介入点”,不能等任务执行到一半再动态判断哪里需要人。原因是代理一旦进入自动流程,它的执行节奏很快,人工响应跟不上。我在DSL里为每个流程模板提前标注了human_in_the_loop节点,运行时遇到该节点自动挂起,等人工信号或超时信号二选一,这样既保证了流程不卡死,也给人留了足够决策时间。
4. 实操落地:一套可运行的简化架构
4.1 技术选型与部署思路
理论框架说了一圈,最终要能跑起来才算数。我的简化版架构没有引入云原生全家桶,而是围绕一个核心原则:只用能明确控制数据流的中件件。
消息总线选择了Redis Streams而不是Kafka,原因是这个场景下的消息量远没到Kafka的必要规模,而Redis Streams天然支持消费者组、消息确认和短延迟,处理代理间的任务分发足够。状态存储用了PostgreSQL加JSONB字段,没有单独引入图数据库,因为协同流程的DAG节点数量通常在几十以内,关系型数据库配合递归查询完全够用。模型网关是自己写的轻量HTTP转发层,负责统一鉴权、超时控制和结果解析,避免依赖各家SDK的差异逻辑。
部署形态上,我把这套架构内部亲切地叫“边缘软化”模型:中心调度器跑在一台普通服务器上,模型底座则既有云端API也有本地模型。热词里提到的“ubuntu查看系统架构”和“u盘安装麒麟系统arm架构”这类基础操作在这里也很有意义——本地模型在异构ARM/x86环境上的部署差异,会直接影响模型网关的超时和算力分配策略,所以我在网关里给每个模型实例打上了cpu架构标签,用于调度时的负载预估。
核心组件清单如下:
| 组件 | 技术选择 | 说明 |
|---|---|---|
| 消息总线 | Redis Streams | 任务分发、事件广播、延迟队列 |
| 状态存储 | PostgreSQL | 会话状态、DAG定义、执行记录 |
| 向量检索 | 本地向量数据库 | 分层上下文检索与相似意图匹配 |
| 模型网关 | 自研HTTP代理 | 统一协议、鉴权、超时熔断 |
| 人工介入服务 | WebSocket消息服务 | 实时推送审批请求、接收用户指令 |
4.2 核心数据结构设计
代理间的交互和人与人之间的沟通一样,需要固定格式才能减少歧义。我定义了统一的任务消息协议,包含五个核心字段:task_id用于全局唯一定位,intent用来标记任务类型,input_payload是结构化的输入数据,constraints携带权限和超时信息,trace_chain记录血缘链路。
这个trace_chain字段非常关键。它本质上是一个不短的数组,每经过一个代理节点就追加一条记录,包含节点ID、输入摘要、输出摘要和执行时间。等到最终结果汇总给人时,用户可以倒推“这个结论是哪个模型基于哪条输入得到的”,而不是面对一个黑盒合成结果只能听天由命。我在调试多AI协同链路时,超过一半的时间都在看trace_chain,它能直接暴露“路由绕了一圈”和“上下文在哪个节点漂移了”。
DAG流程定义的伪代码结构长这样:
{ "flow_id": "perf_report_20241108", "nodes": [ {"id": "n1", "type": "model_call", "target": "data_analyzer", "input": {"sql": "query_perf_stats"}}, {"id": "n2", "type": "model_call", "target": "code_explainer", "depends_on": ["n1"], "input": {"code": "module_check_online"}}, {"id": "n3", "type": "merge", "depends_on": ["n1", "n2"], "strategy": "synthesis_to_report"} ], "human_nodes": ["n3"] }4.3 代理网关的关键执行逻辑
调度器最核心的执行循环大致如下(用Python伪代码表示核心逻辑,不是完整工程代码):
# 简化版代理调度主循环 def dispatch_task(task): # 1. 意图解析,确定任务类型 intent = intent_engine.match(task.input_payload, session_ctx) # 2. 路由决策,基于能力注册表打分 candidates = capability_registry.query(intent_labels) target = route_score(candidates, session_ctx, load_metrics) # 3. 检查人工介入点 if task.flow_definition.has_human_node(task.current_node): wait_for_human_approval(task.task_id) # 挂起等待 # 4. 调用模型网关,带超时熔断 result = model_gateway.invoke(target, task.input_payload, timeout=task.constraints.timeout) # 5. 更新上下文与血缘 session_ctx.update(task.session_id, result.summary) trace_chain.append(task.task_id, target, result) # 6. 推进DAG,找出下一步可执行节点 next_nodes = flow_engine.advance(task.flow_id, task.current_node) for node in next_nodes: dispatch_task(node)这个循环里因为早期缺少等人工确认机制,卡死过两次。后来加入了超时信号,逻辑修改为:遇到人工介入点后挂起,但超过max_wait_seconds如果没收到人工指令,默认采用"safe_reject"策略——即取消该节点的自动执行并回滚到上一个稳定状态,而不是擅自继续。宁可任务中止,也不要让错误结论继续向下游传播。
4.4 压测与性能调优经验
我把这套架构跑在8核16G的服务器上,同时接入4个外部模型API和1个本地模型,初始压测目标是支撑20个并发用户、每个用户有3个连续协同任务。最初结果惨不忍睹,调度器单节点吞吐只有每秒5个任务,瓶颈集中在PostgreSQL的会话状态读写上。
排查耗时一周,最终做了三项优化。第一,把会话热数据从PostgreSQL搬到Redis里,只把最终结果落库;第二,给模型网关加了响应式背压限流,当外部模型P99延迟大于3秒时,不再爆量发送,而是把新进任务放入延迟队列等待;第三,把意图识别模型换成了更小更快的学生模型,虽然准确率下降约4%,但单次识别时间从600ms降到90ms,在协同场景下,这4%的准确率损失完全可以通过后续人工介入节点兜底。
实测稳定后,峰值吞吐达到每秒22个任务,P95链路延迟压缩在6秒以内。对于这种“人机不断往返确认”的协同架构,这个指标已经满足日常团队协作的体感要求了。
5. 常见问题与排查技巧实录
5.1 上下文漂移:AI协同的经典怪病
最典型的现象是:第一个模型输出的结论是对的,但第二个模型引用时添油加醋,第三个模型最终输出离事实越来越远。我把这类问题统一诊断为“上下文漂移”。
排查时重点看两层内容。第一层是trace_chain里每条记录的输入摘要是否包含了原始事实;如果某一个节点的输入里已经没有“原始SQL结果”这个实体,说明上层传给它的上下文已经带了转述损失。第二层是检查分层上下文的引用关系,我一度允许子任务读取父任务的全部会话内容,结果子任务被无关信息干扰,导致输出偏离。
修复方案很朴素:每个节点只能看到依赖节点的输出,不能看到整棵DAG树的全局会话。需要参考全局目标时,必须显式在constraints里声明allow_global_context: true,一旦声明,系统会在写入前对全局信息做一次“事实抽取”,只传递关键实体,而不是把整个上下文原样复制。这个改动上线后,上下文漂移类问题减少了大概七成。
5.2 路由震荡与循环调用
多代理系统还有一个“鬼打墙”式问题:任务在几个模型之间反复横跳,始终不产生最终结果。有一次调试发现,代码模型输出了一段JSON,被调度器误判为“意图不匹配”,重新路由给意图解析模型,解析模型又把它还原成文本,又触发代码模型……形成了一个无限循环。
排查方法是在调度器里加了“跳数计数器”。每个任务从创建开始最多允许经过6个节点,超过6次路由仍然没有生成最终结论,系统自动把当前所有中间结果打包发给人工审核通道,并标记为abnormal_completion。这个硬上限看起来简单粗暴,但在工程上非常有效——它保证系统任何时候都不会因为逻辑漏洞而死循环。
更优雅的解法是用分布式交换机架构里的“MAC地址学习”思路做路由缓存:第一次任务经过某条链路后,调度器记录“同类意图→最佳路由路径”,后续相同意图直接走缓存路径,减少中间模型的反复仲裁。我现在在生产环境同时保留跳数上限和路由缓存,前者兜底,后者提效。
5.3 权限边界与审计追溯
多人多AI协同一旦开始涉足写操作,权限问题就变得尖锐。最初我天真地以为把审批节点加在关键位置就够了,结果发现“间接越权”更难防。比如数据分析模型没有权限删除文件,但它可以调用代码生成模型生成一段执行删除的Python脚本,再由人点击运行,实际完成了删除动作。这种跨Agent的“逻辑借权”是系统架构层面必须堵住的。
我的解决方案是在工具调用链路上增加“动作累积审计”。每个代理内部维护一份操作列表,当某个会话内累积的动作组合触碰敏感行为模式时,触发强制审批。例如“读取用户表”和“导出数据”单独看都是普通操作,但同一会话内5分钟内同时出现这两者,就被标记为“疑似数据导出”,必须人工确认。这一层规则基于“操作间的相关性”而非单点权限,到目前为止是比较管用的做法。
5.4 日志观测的粒度设计
多AI协同系统的排错难度,远超单机应用。因为一个任务的流转涉及多个模型、多次网络调用、原生的异步事件,传统按服务维度看日志的方式完全失效。我把日志按“任务ID+节点ID”作为复合索引,形成了以任务为主线索的追踪视图。
节点级日志只打印三件事:收到的输入摘要、做出的路由决策、产生的输出摘要。不需要打印完整prompt和完整response,否则日志量会非常恐怖。实践下来,单条任务日志控制在2KB以内就能满足绝大多数问题追溯需求,如果确实需要看完整prompt,单独存对象存储并配置采样率。这个“少打多存”的原则让日志系统稳定服务了整个项目周期,从来没因日志洪峰拖垮主流程。
另外,给每个任务生成一张结构化的“协同过程表格”非常实用。表格的行是节点,列是输入来源、路由目标、耗时、是否经过人工批准,最后再加上一个结论差异度评分。这张表格同时服务两个场景:研发排错时当作线索地图,用户验收时当作协作凭证。我在多轮迭代中能快速定位问题,一半功劳要归给这张“协同过程表格”的规范化沉淀。
这个架构做下来,我个人最深的体会是:多人多AI协同的瓶颈从来不在模型能力,而在于“组织这些能力”的系统机制。代理化交互解决了入口统一的问题,编排器解决了任务串联的问题,上下文分层解决了信息污染的问题,人工介入模式解决了信任边界的问题。每一步看起来都不复杂,但把它们耦合在一起,要考虑的状态组合会指数膨胀。
如果让我对准备动手做类似系统的朋友给一句最直接的建议:先把trace_chain和“跳数上限”这两个看似不起眼的机制做扎实,再考虑花哨的协同算法。它们不能让你赢在起跑线,但能保证你不在深坑里爬不出来。下一步,我打算把动态路由的策略参数化,让每个团队可以通过配置文件调整模型选择权重,而不是每次改路由策略都要动代码——这个方向看起来比继续堆模型更有实用价值。