多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

AI代理代为交互:多人多AI协同架构设计与落地实践

AI代理代为交互:多人多AI协同架构设计与落地实践 “AI代理代为交互”这句话看起来像是“让AI代替我发言”实际上是一种系统架构选择。我最早接触这个方向时团队里正好要做一套多人多AI协同系统几个业务角色各带一个AI助手又要让多个不同模型在后台协同完成任务。最初大家想的都是“每个AI自己聊天互相”结果一跑起来就乱成一锅粥。后来把“AI代理”变成正式的一层抽象节点由它统一代理人的意图、与其他代理交互所有链路才稳定下来。这篇文章就围绕这个架构思路展开适合正在做多智能体系统、智能助手平台或者想要把本地模型和云端模型混入一个调度架构的研发同学参考。1. 为什么“代理代为交互”是多人多AI协同的必然选择1.1 从M×N直连灾难到中间代理多人多AI协同最朴素的做法是谁要用哪个AI就直接去调用。假设5个人、3个AI模型人机之间最多有15条直连链路再算上AI与AI之间的协作每个人都要维护“谁在哪个任务里、用哪个模型、状态是什么”很快会变成一场噩梦。我见过最典型的失败案例是一个角色让AI-A生成文案AI-A为了获取素材又去问AI-BAI-B没人听清是哪个任务的请求直接把另一个团队的内容搬了过来结果所有人都在看错误的内容。这个问题的根子在于连接关系没有治理。引入AI代理之后连接关系变成“人-代理-代理-模型”。每个代理代表一个用户或一个角色对外只暴露任务接口内部再去协调模型和别的代理。人不需要直接面对多个AIAI也不需要理解复杂的人事关系。这个中间层带来的第一个价值是“拓扑清晰”。无论后端接了多少个模型、多少种协议前端用户面对的都只是一个稳定入口。1.2 核心价值异步解耦、上下文隔离和责任追溯很多人以为代理层只是转发消息其实它最大的价值是改变了交互的时序模型。一组多人协同天然是异步的。用户发出需求后代理可以先接收、校验、拆解再继续往下走其他代理响应慢也不影响整体流程。反之如果采用同步直连一个模型卡住整个调用链就可能同步阻塞。代理层让我可以把“需求提交”和“结果返回”解耦开时间线上不再是点对点的即时响应而是任务队列、状态机、回调通知的组合。上下文隔离也靠这一层才能做到干净。同一个用户可能在多个任务里同时使用AI不同任务之间如果共享同一个上下文轻则串味儿重则把私有信息带到另一个任务里去。代理层会用唯一的任务编号隔离上下文空间每个代理只看到它当前所在任务的上下文避免“多脑共写一份记忆”导致相互踩踏。责任追溯更是多人协同场景中的硬需求。一旦某个环节出错我得能回答“是谁的代理在什么时间、基于哪条消息调用了哪个模型”。代理层天然保存每一次发送和接收的记录配合链路追踪ID可以做到每个决策都有迹可循。演示阶段可能无所谓生产环境里没有追溯能力系统就是不可控的。1.3 适用场景与边界这套架构最适合的场景是“角色分工明确、任务可以拆解、结果需要多人确认”的团队协同。比如产品经理的代理和开发代理共同分析一个需求财务代理和业务代理核对一份报表项目经理代理做资源排期。每个代理代表一种视角模型可以在后台自由组合人类只需要在关键节点做判断。但它不适合所有场景。实时音视频控制、低延迟嵌入式控制、需要每50毫秒反馈一次的操作型系统中间加一层代理会让延迟不可接受。另外如果组织本身完全不能接受AI自主行为那代理层就只能做成纯路由分发器不能有任何决策逻辑。我在实际项目里会预先画一条边界线代理可以自主执行的限定在“信息搜集、格式处理、草稿生成、常规问答”这四类所有涉及资源承诺、对外承诺、最终结论的必须回到人工确认。2. 分层架构与核心模块拆解2.1 五层参考架构我落地时把系统分成五层接入层、代理层、编排层、模型层、数据层。每一层都只和相邻层交互不越层调用这样即使某个模型供应商很“花哨”也只影响模型层不会波及上层业务。层级核心组件主要职责接入层Web/IM/API网关接收用户消息、鉴权、限流代理层用户代理、角色代理、任务代理代表用户发言维护会话状态编排层调度中心、状态机、仲裁器拆解任务、分发消息、处理冲突模型层本地模型、云端模型、专用模型提供推理与生成能力数据层会话库、知识库、审计日志持久化上下文、记录操作链路接入层就像公司前台只负责确认“你是谁能不能进来”。代理层是各位同事每个代理代表一个角色。编排层是项目经理负责把活拆开、分派、汇总。模型层是内部的专家库可能有些专家是本地的有些专家在云端代理不知道专家在哪里只向编排层提交需要。数据层是记忆和档案室所有过程都有记录。2.2 消息总线与状态机为什么不是简单HTTP调用最先犯过的错是让代理之间直接用HTTP同步调用。A代理调用B代理接口B等C返回C又等A释放资源几个代理就形成了循环等待最后整条链路全部超时。后来我把通信方式改成了基于消息总线的异步投递代理从总线取消息处理完把结果再投回总线所有代理都不再持有远程会话。同步调用其实适合“一个有固定请求-响应模式的小任务”但多人多AI协同里任务状态复杂一个代理可能从“等待输入”跳到“分析中”再到“等待人工确认”。我用状态机来管理这种生命周期状态包括idle空闲、planning规划、executing执行、waiting等待外部响应、needs_review等待人工确认、done完成、failed失败。消息总线加状态机之后每个代理都变成一个“事件驱动”的组件收到消息时根据当前状态决定下一步动作超时时触发超时回调。这样做的好处是故障可以被隔离。某一个代理进入failed状态时总线仍然可以运行其他代理不受影响。而同步调用一旦有一个环节exception整个链路都要回滚这在多人多AI场景里代价太高。2.3 关键模块的接口设计接入网关接口接收用户消息转换成统一Envelope消息格式校验权限写入审计日志。用户代理长期保存用户画像、偏好、默认模型策略用户不在线时也可以“代为参会”。调度中心维护任务拓扑解析依赖关系记录“哪个人在哪个任务中拥有哪些权限”。仲裁器当多个AI代理给出不同结论时按策略选出最终结论所有仲裁结果必须生成原因说明。审计服务记录每个Envelope的流转路径、模型名、token用量、耗时支持溯源查询。接口设计上我坚持“所有组件只依赖消息结构不依赖具体实现”。这样后续替换模型、替换消息队列都不会大面积改动。现在很多项目喜欢把所有能力做成微服务然后服务间直接Feign调用这在多智能体场景里反而会把代理间的协作变成“强耦合一团麻”。多人多AI协同的核心是代理之间的“协议”不是微服务之间的“接口”。3. 代理通信协议与上下文隔离设计3.1 消息信封结构所有代理都讲同一种语言想让多个AI代理稳定交流首先要统一消息格式。我参考了多智能体通信和消息队列里常见的“信封”模式把一条消息拆成Header和Payload两层。from dataclasses import dataclass from typing import Any, Dict import time import uuid dataclass class Envelope: msg_id: str # 全局唯一消息ID sender: str # 发送方代理名 receiver: str # 接收方代理名或通配符 task_id: str # 任务上下文ID turn_id: int # 在任务内的轮次 msg_type: str # request / response / cancel / heartbeat payload: Dict[str, Any] # 业务数据 expire_time: float # 过期时间戳 staticmethod def new(sender: str, receiver: str, task_id: str, msg_type: str, payload: Dict[str, Any], ttl_seconds: int 300) - Envelope: return Envelope( msg_iduuid.uuid4().hex, sendersender, receiverreceiver, task_idtask_id, turn_id1, msg_typemsg_type, payloadpayload, expire_timetime.time() ttl_seconds, ) def is_expired(self) - bool: return time.time() self.expire_time每条消息都带全局唯一的msg_id和任务内的turn_id。msg_id用来去重和追踪turn_id用来让上下文树里的内容有序排列。receiver支持定向到某个代理也支持*作为广播通配符但是实际使用里我几乎不开放广播因为噪音会直接把系统搞崩。3.2 路由规则与多播控制路由是消息总线里的关键逻辑。我的路由规则按优先级顺序依次匹配定向路由消息里明确写明了target_agent例如receiver: product_agent系统直接投递。能力路由代理启动时向调度中心注册自己的技能标签例如capability: [code_review, arch_design]。任务消息会带着所需能力名调度中心根据标签匹配代理。多播路由一条消息发给同一角色或同一任务组内的多个代理但必须由消息发起方指定参与者列表不允许全局自动扩散。最需要注意的是“避免回声”。如果两个代理都拥有同一个能力且权限相同任务就会被重复处理。我在能力路由里加了一个lease机制每条任务消息在同一时刻只分配给一个可用的能力代理如果目标代理没空闲消息就进入待处理队列而不是再找另一个重复执行。3.3 上下文树与快照回滚多人多AI协同的上下文不能只当一个长字符串来管理。我用“上下文树”来组织树根是整个项目往下是任务节点再往下是子任务节点。每个节点存着与这个任务相关的关键信息、中间结论和未解决问题。当AI代理需要插入一个新结论时不是直接修改共享上下文而是先生成一个新分支并且保存父节点的快照。这样如果某个代理引入的结论明显错误我可以回滚到之前任意一个快照而不是让错误结论污染后面所有代理。有人会问快照会不会占用大量存储在实际实施中快照不必存完整副本可以存版本号加增量变更。设计原则是“没有共享内存只有消息和版本”。代理之间不能直接修改同一个全局变量任何变更都要通过消息传给持有上下文树的节点由它来决定是否需要产生新版本。这套设计避免了我在早期遇到的“多代理同时写一个dict最后谁都不知道谁覆盖了谁”的尴尬问题。4. 从立项到落地完整流程与最小实现4.1 一次多人多AI协同任务的生命周期以产品、设计、研发三个角色来举例。产品代理收到产品经理录入的“新用户引导页需求”它的生命周期是这样接入层校验身份创建task_id写入审计日志。产品代理将需求拆成“用户目标”“业务规则”“成功指标”三个部分先自己做一轮意图补充。调度中心根据技能标签把“业务规则”分发给研发代理把“页面布局建议”分发给设计代理同时给自己保留一份需求底稿。研发代理调用代码能力模型生成接口说明设计代理调用视觉理解模型输出页面结构。这两个代理是并行执行的互不等待。结果回到产品代理后产品代理做文本合并生成一份完整需求评审稿。调度中心发起人工确认把评审稿发给产品经理确认确认后标记为done否则回到第2步重新修订。整个过程中人类只在第6步介入但第6步是必须的。主流程里每个结果都会保留原始代理名和消息ID方便出了问题直接回溯。4.2 仲裁与共识AI之间不能互相当裁判当多个AI代理给出不同答案时怎么选这是系统设计里最容易想简单的点。最初的方案是“投票多数胜出”结果在代码评审类任务里两个AI意见一致但方向错误另一个AI意见正确却被否决。后来我增加了策略选择机制主要有三种策略适用场景关键点简单多数信息搜集、事实判断类任务需要提供事实来源加权投票不同角色可信度不同时权重由组织架构和任务类型决定人工复核高风险、涉及资源承诺的任务人类拥有最终否决权不管用哪种策略仲裁器都必须输出一条“为什么选择这个结果”的原因说明。比如“研发代理权重0.4设计代理权重0.3产品代理权重0.3研发结论在接口边界上更完整因此采用该结论”。这个说明会存入审计日志避免AI说了算却说不清。4.3 权限最小化与审计每个代理的实际权限取决于它代表的用户的角色。产品代理只能读取产品需求库研发代理只能读取代码库和接口文档不能反过来去读财务报表。我给每个代理做了“最小权限”的令牌令牌里包含可读的集合、可调用的模型类别、可生成的令牌最大过期时间。审计日志的字段包括时间戳、trace_id、actor、action、resource、result、token_usage。生产环境我会把审计日志接入日志检索系统检索维度以task_id为主。这样在排查问题时只要知道一个任务ID就能拉出整条决策链。4.4 可落地的代码骨架这里给一个非常精简的Python示例去掉业务细节只演示代理层面的核心结构。class MessageBus: def __init__(self): self.queues {} def register(self, agent_name: str): self.queues.setdefault(agent_name, []) def send(self, envelope: Envelope): if envelope.receiver *: for queue in self.queues.values(): queue.append(envelope) elif envelope.receiver in self.queues: self.queues[envelope.receiver].append(envelope) def poll(self, agent_name: str, timeout: float 0.1): queue self.queues.get(agent_name, []) if not queue: return None return queue.pop(0) class BaseAgent: def __init__(self, name: str, bus: MessageBus, own_capabilities: list): self.name name self.bus bus self.bus.register(name) self.own_capabilities own_capabilities self.context {} def handle(self, envelope: Envelope): raise NotImplementedError def run_loop(self): while True: envelope self.bus.poll(self.name) if envelope and not envelope.is_expired(): self.handle(envelope)MessageBus只做最简单的投递BaseAgent定义了消息处理入口。真实系统里总线要换成Redis Stream或RabbitMQ代理状态要用分布式锁和状态机管理但骨架核心是一样的每个代理用自己的名字订阅队列拿到的是一条完整Envelope处理完后再send出去。5. 踩坑实录多人多AI协同中最常见的5类问题与排查方法5.1 AI代理“聊偏了”上下文污染怎么定位最常见的问题是代理在长任务里突然开始答非所问。根因通常有两个一是多个子任务共用了同一个上下文对象导致某个子任务临时插入的说明影响了另一个子任务二是上下文太长模型窗口满了之后系统做了粗暴截断把关键信息截掉了。我的排查套路是先看task_id对应的完整消息链路分析上下文树里每个分支的写入者。如果发现某个分支来自非当前子任务的代理就是上下文隔离没做到。解决办法是每个子任务只给代理一个context_view切片代理无法看到任务外的节点同时上下文整理采用“摘要 原文引用”的混合模式重要结论保留原文细枝末节压缩成摘要。这个经验是从一次迭代评审任务里学来的。当时研发代理引用了产品代理早期的草稿但那句草稿后来已经被修正系统却还把它当最新依据。5.2 代理死循环与共振A让B查CC又让A查多个AI代理互相发送请求时很容易出现循环调用。A代理想让B代理确认一个术语B代理找不到答案又转给C代理C代理认为应该由A代理来定义这个术语于是消息又回到A。这种循环如果不打断会一直消耗token和算力。我使用的第一道防线是消息跳数限制每条Envelope里带一个max_hops每经过一个代理就减一减到0直接丢弃并且记录一条warning。第二道防线是任务级看门狗调度中心会给每个子任务设一个最大完成时间超时就触发on_timeout把任务标记为failed再回溯到上一版本。第三道防线是去重当检测到同一条消息被同一个代理重复处理超过两次就暂停这个代理的入站队列由人工介入。从定位角度讲死循环很好查因为trace_id完全相同的消息会反复出现。在日志检索里按trace_id分组看出现次数最多的sender/receiver对就能快速锁定循环路径。5.3 多人权限冲突两个人都想改最终结论多人多AI协同必定会有“谁说了算”的问题。两个人分别让各自的代理修改同一份报告随后代理同时提交修改结果直接互相覆盖。这个问题比模型问题更敏感。我的处理方式是在上下文树里引入“锁分支”。每个文档节点可以有多个草稿分支每个代理在自己的分支里修改最后进入仲裁阶段。仲裁时不是简单选一个分支而是把所有分支做diff把冲突区域提取出来发给相关人类用户确认。代理层可以提“建议优先采用谁的版本”但权限冲突的最终裁决权保持在人这边。这个规则写死进了架构里不允许AI代理自动覆盖他人分支的版本。5.4 本地模型与云端模型混合调度时的资源瓶颈做多人多AI协同很少只用一种模型。合理搭配是让本地模型承担敏感数据理解、意图识别、格式处理云端模型承担复杂生成、长文本总结、跨域知识问答。但在混合调度初期我吃过资源池没做隔离的亏几个本地模型进程被高并发任务打满云端模型调用又因为没限流导致账单迅速上涨。现在我会在模型层做两层规划。第一层是资源池每个模型配置最大并发数、队列长度和连续失败阈值。第二层是降级策略本地模型队列满时把任务标记为“低敏感小任务”优先排队而不是直接切到云端云端模型限流时把复杂任务转成离线批次等非高峰时段再处理。代理层不关心模型在哪里只关心“当前任务能不能按时完成”。同时我会给每个代理记录token用量和耗时出现超支时自动告警。这套机制可以避免某个任务独占所有模型资源导致其他协同任务整体停摆。5.5 可观测性没有链路追踪问题根本查不了多代理系统比单体应用难排查十倍核心原因就是消息在多个代理之间跳来跳去没有链路追踪时只能一个个代理翻日志半天找不到根因。我把每个任务从创建一开始就绑定一个trace_id同时每条Envelope还有msg_id和parent_msg_id用来构建父子关系。日志里强制打印这三个ID和当前代理名这样检索时按trace_id拉到全链路再按parent_msg_id还原调用树。实际运维里我还会定期扫两类异常指标。一是高重试消息某条消息被反复投递说明某个代理在处理时频繁失败。二是长尾任务大部分任务在60秒内结束某个任务卡了30分钟大概率是上下文树里出现了死循环或者某个代理等待人工确认太久。这两类异常我都会把对应trace_id单独拉出来复盘。多人多AI协同系统本质上是一个由人、代理、模型共同组成的分布式系统只有把可观测性做好才敢让AI多跑几步。我个人在实际操作中最大的体会是代理层越薄越可靠别想着让它替代所有业务判断。它只需要做好消息转换、隔离、路由和状态维护“何时让人类确认”这件事必须写死在架构里不能交给模型临场发挥。我刚做这个方向时被循环调用和上下文污染折腾了好几个通宵后来把所有“AI自主权”都画在一条明确的线以内系统才真正稳定下来。如果你也在设计类似系统建议先从消息信封和trace_id做起这比选哪个模型、用什么框架重要得多。
返回列表