
提到AI代理不少人第一反应是“调API”。拿Key、拼Prompt、等返回然后就没有然后了。但如果你真把一个AI代理当作系统里的一个活体组件——它能自己做出决策、要代表用户去跟别的代理协作、要为最终结果负责事情就完全不是“调个接口”那么简单。我最近在做“基于AI代理代为交互的多人多AI协同系统架构”的预研和原型验证前后跑了两个多月期间换过三版方案踩了不少坑。这个方向里最核心的一个认知转变是AI代理的本质不是模型而是“交互面”。它替用户去感知任务状态、去跟其他代理协商、去调用工具再把结果以用户能理解的形态交回来。当系统里同时存在多个用户、多个代理、多个模型、多个异步任务时真正决定成败的是承载这套交互关系的系统架构而不是某个模型有多聪明。这篇内容我把整个架构的设计思路、核心机制、原型代码和踩坑实录完整整理出来适合正在做AI Agent平台、多人协作工具、智能体编排系统的朋友参考也适合想从“单代理Demo”往“多代理协同系统”迈进的团队当作路线图。1. 从“调API”到“代理交互面”——设计要解决的根本问题1.1 人员、代理和任务之间是“多对多”关系传统单体业务系统里用户和功能之间是“人找功能”菜单就在那里你点就行。但引入AI代理之后关系变成了“人委托代理代理完成任务”而且不是一个人一个代理是一个人可能同时挂着三四个代理一个代理又同时服务多个项目里的多个人。我预研阶段梳理出的第一个关键问题是关系矩阵瞬间爆炸。假设系统里有10个用户、8个代理、每天产生200个任务那么架构上需要管理的就不是200个“请求-响应”而是10×8×200维度的状态关系。用户可能中途换代理接手任务代理可能把一个任务拆成三个子任务分发给其他代理两个代理还可能争抢同一个工具的使用权。这种复杂性靠“每个用户直接调模型API”是撑不起来的必须有一个中间层来统一收口。1.2 不能简单“并发调接口”的三个理由很多人一开始会想多代理协同不就是开几个线程并发调模型吗我在原型阶段试过这个思路三个理由推翻了它。**第一长任务的模型不是一次调用就能结束的。**实际场景里代理要检索资料、写代码、跑测试、修改再验证整个生命周期可能持续几分钟甚至更久。HTTP请求的超时、断连、重试机制完全不适配这种长时交互。代理需要像一个常驻进程一样被系统跟踪、被用户追问、被其他代理等待。**第二代理之间需要共享上下文而不是各自隔离。**比如一个代理负责调研技术方案另一个代理负责写代码第一个代理需要知道第二个代理选择了什么框架否则推荐方案就是空谈。如果每个代理都各自持有一份独立的Prompt上下文信息就断层了。**第三权限边界必须被系统强制而不是靠提示词约束。**人与人之间的协作有职级、有视图、有审批代替代人交互之后这些边界不能丢。一个实习生级别的代理不该看到财务数据这是系统架构上要保证的不是靠一句“请忽略敏感信息”来兜底。1.3 架构选型的核心取舍控制面与数据面分离多次推倒重来之后我最终确定了整个架构的基调用一句话概括控制面与数据面分离消息驱动代替请求驱动。控制面管“谁说了算”——代理的注册、任务的编排、权限的判定、协商的仲裁。数据面管“事情本身”——文档内容、代码仓库、数据库记录、模型生成的中间结果。两者不混在一起好处很明显控制面可以做到极致的轻量和高可用哪怕数据处理集群抖动任务调度依然在线数据面可以独立扩缩容某个代理占资源再多也不会挤占调度逻辑。对比几个主流思路我的结论是架构风格优点缺点适用场景中心化编排中央调度器统管所有代理流程可控、容易调试、权限收敛调度器可能成为瓶颈团队规模小、任务流相对固定去中心化协商代理之间直接通信扩展性好、代理自治性强调试困难、难以全局约束代理数量大、协作关系动态变化混合式控制面中心化执行面去中心化兼顾可控性与扩展性架构复杂度高多人多代理协同的现实场景我最终选了混合式任务编排和权限判定集中在一个轻量核心里但代理在拿到任务后如何执行、内部调用什么工具完全自治不受干预。2. 系统架构分层与消息总线设计2.1 五层逻辑架构整个系统我按五层切分每一层的职责边界很清晰层级职责关键组件接入层面向Web端、客户端、API的入口网关、会话管理、SSE推送编排层任务分发、状态机流转、协商仲裁编排引擎、任务队列、仲裁服务代理层代理运行时负责模型调用与工具执行代理运行时、模型适配器、工具沙箱协作层代理间消息交换、共享上下文、事件溯源消息总线、上下文仓库、事件日志数据层文档、知识库、用户数据、审计记录PostgreSQL、对象存储、向量库接入层和人打交道代理层和模型打交道中间三层的存在价值就是让这两端的复杂度互相隔离。用户可以随时更换代理背后的模型代理也可以同时服务不同接入渠道的用户彼此不影响。2.2 代理注册与能力描述每个AI代理在接入系统前必须先完成“自描述”。我设计的代理注册信息里核心是一个能力描述文件用JSON Schema表达类似这样{ agent_id: code-reviewer-01, model: { provider: local, model_name: qwen2.5-coder:14b, max_tokens: 8192 }, skills: [ { name: code_review, description: 对Pull Request中的代码变更进行审查输出问题清单, input_schema: { type: object, properties: { repo_url: {type: string}, pr_id: {type: string} } } } ], permissions: [read_repo, comment_pr], visibility: project_scoped }这里有个细节我要着重提醒**能力描述不是为了给开发者看的而是给编排层的“代理路由”组件看的。**路由组件会把一个任务的意图描述和所有已注册代理的skills描述做语义匹配选出最合适的候选代理。如果能力描述写得含糊、字段层级混乱路由精度就会直线下降。我最初给代理写的skills描述是“擅长写代码”这种话结果路由几乎成了随机分发后来把所有能力都改成“动词对象输出物”的结构化表述路由准确率才上来。2.3 以“任务事件”为最小颗粒的消息总线系统内部的所有协作都通过消息总线传递消息的粒度不是“API调用”而是“任务事件”。一个任务事件包含足够完整的语义信息让任何订阅方不需要回溯历史就知道发生了什么。{ event_id: evt_8f2a9c, event_type: task_submitted, task_id: task_1024, source: user:zhang, target: agent:code-reviewer-01, payload: { repo_url: https://git.internal/project/api, pr_id: 187 }, context_ref: ctx_56, timestamp: 2025-06-18T14:32:10Z }我选Redis Stream作为消息总线载体理由是它天然支持消费组、持久化、多消费者负载均衡比在应用层自己封装消息队列省事太多。事件日志同时全量写入PostgreSQL用于之后的问题回溯和审计这个“事件溯源”习惯在后来的排障中帮了大忙。很多诡异的问题不靠事件日志回放根本定位不到。3. 多AI协同的核心机制详解3.1 三种协作模式路由分发、流程编排、协商式协同多人多AI协同不是一种模式而是三种模式混用。**路由分发是基座。**当任务目标明确、适合单一代理完成时编排层直接把任务路由给最匹配的代理。比如“给这份合同提取关键条款”路由组件匹配到文档抽取专用代理事件发出代理处理完回传结果任务结束。这个过程系统负荷最轻也最容易排查问题。**流程编排负责线性流水线。**典型场景是“调研-方案-实现-验收”链路调研代理先产出技术选型报告方案代理基于报告写设计方案代码代理再按方案生成代码验收代理最后跑测试。每个环节的产出作为下一个环节的输入编排层通过状态机控制流转。**协商式协同是最难、也是最有价值的部分。当多个代理对一个问题的判断不一致或者一个复杂任务需要多个代理同时贡献时就不能简单排队了。我实现了一个轻量协商协议发起方广播提案候选代理返回赞同/反对/附条件赞同仲裁服务聚合意见后在超时时间内给出结论。“协商”不是让代理们自由聊天而是结构化地交换“结论依据”。**否则两个模型对话起来上下文很快会退化成互相客套的废话。3.2 任务上下文管理与状态机流转每个任务从提交到结束会经历一个明确的状态机PENDING - ASSIGNED - RUNNING - WAITING_INPUT - RUNNING - COMPLETED | | ---- FAILED -------- ---- TIMEOUT --------状态机由编排层统一维护代理不直接修改任务状态只上报事件。这样设计的理由是在任何时间点系统都能给出“任务现在到哪一步了”的确定性回答。用户问“我的任务为什么还没好”不用去翻代理日志直接查状态即可。上下文管理我用了一个“上下文袋”的机制。任务创建时建立context_ref所有与该任务相关的事件、中间产出、代理回复、用户补充说明都按时间顺序追加进这个袋子里。代理每次被唤醒时编排层把上下文袋中与当前子任务相关的内容提取出来拼进Prompt而不是把全量历史都倒给模型。这个“按需提取”的设计解决了一个核心矛盾任务历史越来越长但模型上下文窗口是有上限的。3.3 多人协同的权限与会话隔离多人场景下权限控制的原则是**代理的权限不能超过委托它的用户的权限。**用户张三是项目的管理员他创建的代理可以读写项目仓库用户李四只是访客他用同一套系统调起来的代理就只能读不能写。我在实现里把权限控制放在编排层的“代理执行令牌”里。代理每次执行敏感操作写文件、发消息、调外部服务都要携带这个令牌由网关统一鉴权。代理本身不做任何权限判断——模型天然不可信你不能在自己写的代码里信任另一个模型“会乖乖遵守规则”。会话隔离上我按“用户维度的私密会话”和“项目维度的共享会话”做了区分。私密会话里的上下文只属于该用户共享会话里的内容对项目内成员可见但写入动作仍然遵循用户权限。这个区分一度被我忽略直到有一次一个用户的私密任务被另一个用户在项目视图中看到了才意识到隔离必须从架构上做。3.4 本地模型与远程模型的统一接入做这个架构研究时我一直在真实项目里混用本地模型和云端模型这是当前落地时绕不开的诉求。敏感数据不出内网、又要享受大模型能力的场景实在太普遍了。我实现的方案是“模型适配器网关”位于代理运行时和具体模型服务之间。代理不关心背后是本地模型还是远程模型只面向统一的接口发请求代理 - ModelGateway - local_adaptor (Ollama/vLLM HTTP) - remote_adaptor (云模型SDK)适配器负责处理两者之间的所有差异上下文窗口大小、服务是否长连接、超时阈值、Token计费、错误重试。本地模型我用Ollama跑过7B到14B的模型用vLLM跑过更大的模型两者的响应模式完全不同——Ollama加载慢但单次请求简单vLLM吞吐高但需要预热。这些差异全部封装在适配器内部后上面的代理层完全无感。有一个数据我实测下来很有参考价值本地14B模型在单张消费级显卡上处理代码审查类任务单次响应延迟在3-8秒之间对交互式场景略慢但对异步协作场景完全可接受。这个结论直接影响了我后来对系统是“同步请求”还是“异步任务”的架构取舍——我最终全面倒向了异步任务模型。4. 实操从零搭一个最小可用的多AI协同原型4.1 技术选型与目录结构原型环境我用了Python 3.11核心组件选型如下组件选择理由Web框架FastAPI异步原生、类型提示友好、WebSocket支持成熟消息总线Redis Stream轻量、支持消费组、自带持久化编排状态存储PostgreSQL事务可靠、便于审计查询代理运行时轻量Python进程避免过早引入重型框架模型服务Ollama 云模型SDK覆盖本地/远程两类场景目录结构保持极简multi-agent-collab/ ├── gateway/ # 接入层Web端API、SSE推送 ├── orchestrator/ # 编排层状态机、任务分发、仲裁 ├── agents/ # 代理层内置两个示例代理 ├── mcp_bridge/ # 模型适配器网关 ├── shared/ # 事件定义、能力描述Schema、鉴权工具 └── docker-compose.yml4.2 代理注册的最小实现代理启动后向编排层发送注册请求。实现里我用一个小函数封装注册逻辑async def register_agent(agent_info: dict): 向编排层注册代理返回代理令牌 async with httpx.AsyncClient() as client: resp await client.post( http://orchestrator:8001/agents/register, jsonagent_info ) resp.raise_for_status() data resp.json() return data[agent_token], data[agent_id]注册成功后代理进入“可用”状态编排层会定时发心跳探活。需要强调**代理的注册和注销都要走同样的流程不能直接杀进程。**有一次我图省事直接kill了一个代理进程结果编排层的状态表里一直残留着这个代理的“在线”记录任务被分发过去后全都超时。后来我加了“心跳超时自动置离线”的机制才解决。4.3 编排器核心任务分发与状态流转编排器的核心逻辑其实不复杂就是事件驱动。我贴一个简化版的任务分发实现async def dispatch_task(task: Task): # 1. 状态改为 ASSIGNED await task_store.transition(task.task_id, ASSIGNED) # 2. 语义匹配候选代理 candidates await route_matcher.match(task.intent, top_n3) # 3. 按顺序尝试分发失败则自动换下一个 for agent in candidates: try: await bus.publish( task_assigned, payload{task_id: task.task_id, agent_id: agent.agent_id} ) # 记录分发日志便于追踪 await task_store.record_assignment(task.task_id, agent.agent_id) return except PublishError: continue # 4. 全失败则进入 FAILED并通知提交者 await task_store.transition(task.task_id, FAILED, reasonno_available_agent)这里有个很有价值的实战细节**分发时要“先发布事件、后写状态”而回执时反过来。**事件发布成功不代表代理真的收到了所以编排层必须等待代理的状态回执事件才能确认任务真正被接管。如果先写状态再发事件代理接收失败的场景下状态就会和数据不一致。4.4 一次真实的多人双代理协作演示原型里我建了两个代理writer-agent负责写方案文档reviewer-agent负责审查并给出修改意见。演示场景是用户A提交“为内部工具开发写一份技术方案”用户B实时围观并随时补充要求。流程如下用户A通过Web端提交任务网关创建task_001状态PENDING编排器匹配到writer-agent分发任务状态ASSIGNEDwriter-agent调用模型生成方案初稿完成后发布task_completed事件编排器收到事件后自动触发reviewer-agent的审查任务reviewer-agent返回“方案缺少回滚策略”的审查意见状态变为WAITING_INPUT用户A看到意见后补充一句“加上灰度发布和回滚步骤”编排器把补充内容追加到上下文袋重新唤醒writer-agent修改修改完成后reviewer-agent复审通过任务COMPLETED整个过程中没有任何一个环节是“用户直接等模型返回”。用户B在围观时看到的是任务状态的流式变化想介入随时可以发事件。这种体验和“同步调API”的人机交互有本质区别。4.5 Docker Compose一键起开发环境我用Docker Compose串联所有组件services: redis: image: redis:7-alpine ports: [6379:6379] postgres: image: postgres:16-alpine environment: POSTGRES_DB: agent_collab POSTGRES_USER: agent POSTGRES_PASSWORD: agent_pass ports: [5432:5432] orchestrator: build: ./orchestrator depends_on: [redis, postgres] ports: [8001:8001] gateway: build: ./gateway depends_on: [orchestrator] ports: [8000:8000] agent-writer: build: ./agents/writer depends_on: [orchestrator] agent-reviewer: build: ./agents/reviewer depends_on: [orchestrator]启动后依次验证Redis和PostgreSQL健康检查通过、编排器注册接口可访问、两个代理完成注册、通过网关提交测试任务、观察任务状态流转日志。这套流程走通一个最小可用的多人多AI协同系统就立起来了。5. 常见问题与排查技巧实录5.1 代理“假死”进程活着但任务不响应原型跑起来后遇到的第一个头疼问题就是“假死”。代理进程没有崩溃CPU占用正常但就是不处理新到的任务。排查过程先看编排器日志发现任务事件已经发布到Redis Stream消费组也确实拉到了消息但代理内部处理循环卡住了。进一步追查是代理在处理一个长文本任务时同步调用了模型接口线程被阻塞后续的新任务全部排队等待。解决把所有模型调用改成异步任务处理循环里绝不阻塞。同时给每个任务处理加上超时上限超时后强制终止并上报失败事件。我还在代理层加了一个“当前处理数”指标编排器分发任务时会参考该指标代理忙不过来就自动跳过。5.2 上下文无限膨胀与模型失控多轮协同时“上下文袋”越来越大直接全量塞进Prompt的结果是Token消耗暴增、模型开始胡言乱语、把最早的信息错误地当成了当前指令。解决思路是“分级记忆”。短期记忆保留最近5轮完整交互中期记忆做摘要让模型在每轮结束时生成一段结构化摘要存入上下文袋长期记忆只保留关键决策和结论细节内容存向量库按需检索。实测效果同样是30轮交互全量上下文方案Token消耗约4.6万分级记忆方案降到1.2万而且模型回答的准确率明显提升。注意这里有个经验——**摘要一定让代理自己在结束对话时生成然后由编排层校验摘要结构不要在做下一次请求时临时压缩历史。**临时压缩会丢失对话中的隐性信息。5.3 协商死锁与重复执行两个代理互相等待对方确认协商流程卡死是我在实现协商协议时踩的另一个坑。场景是writer-agent把方案发给reviewer-agent审查reviewer提出“需要补充性能测试数据”于是writer等reviewer给更多信息reviewer等writer修改后再审两边都认为自己“已经回应过了”。解决协商里必须加“仲裁超时”。每次协商发起时仲裁服务启动一个定时器比如60秒超时后自动做决策要么强制通过当前提案要么选择置信度更高的代理的意见要么直接升级给人工用户。同时任务执行要加幂等控制每个任务事件带全局唯一的event_id代理执行前先去重防止同一个子任务被重复执行两遍。5.4 权限边界泄漏的“魔幻现场”有一次共享会话里一个只读权限的代理居然成功给项目仓库打了Tag。检查后发现问题出在模型适配器网关上代理的Prompt里获取到了“拥有者身份”的上下文模型“自以为”自己有写权限于是调用了写接口而网关没有校验代理令牌对应的用户权限直接放行了。这是我整个预研中最深刻的教训之一**所有权限判断都必须放在系统边界层绝不能信任提示词里封装的“角色”信息。**修复方案是给每次外部调用都附加“执行令牌”网关针对每个外部操作重新鉴权同时记录审计日志。从那以后我再也没遇到过代理越权的事。5.5 踩坑速查表症状根因解决代理在线但不处理任务同步模型调用阻塞处理循环改异步、加超时、上报处理数指标长对话后模型效果暴跌上下文全量注入、关键信息被稀释分级记忆、按需提取上下文协商双方永久等待缺少仲裁超时机制仲裁定时器、强制定向决策只读代理做了写操作权限判断被交给模型网关统一鉴权、代理令牌机制任务状态与实际不符事件发布顺序与状态写入不一致先事件后状态回执确认严格分离消息总线积压但无报错消费组没有确认消息设置合理的ACK策略监控消费延迟6. 还能往哪个方向扩展原型验证完成之后我还有几个明确的扩展方向已经在陆续测试。第一个是事件回放与复盘工具。既然所有交互都以事件形式持久化了理论上完全可以做“回放任意时间段、任意项目、任意代理的所有交互”。这对多人团队复盘AI代理的决策过程很有价值——出了问题不再是黑盒可以精确到某个时间点某个代理看到了什么上下文、做出了什么决策。第二个是多代理记忆的分层持久化。目前原型里上下文袋是跟任务绑定的任务结束就归档。但长期看代理自己应该有跨任务的“项目记忆”。比如reviewer-agent改进后应该能记住用户偏好的审查风格而不是每次任务都从零学习。这部分我计划用向量库来实现。第三个是人机混合编排。现在的编排器只能调用AI代理但实际工作中“需要真人审批”的环节一直存在。我想把人工审批节点也做成一种“特殊代理”接入同样的消息协议。这样一来整个流程的编排语言就统一了——对上层来说AI代理和人工审批节点都是一等公民都有状态、有超时、有回执。最后再分享一点体会两个多月的预研走下来我最大的感想是**做多人多AI协同系统难点根本不在模型能力而在于把“交互”本身做成可靠的基础设施。**模型会迭代、会换供应商、会升级参数这些都不重要重要的是你的架构能不能让新代理像插USB设备一样即插即用能不能让两个代理在完全不共享提示词的前提下高效协作能不能在某个代理发疯时把影响范围控制在最小。这套架构目前虽然还是原型但设计思路已经迁移到了我的实际项目里——团队里最近新接入的代码审查代理、文档生成代理都是按这个模式挂载的。有朋友听说我在做这个方向问我“要不要上K8s”“要不要用服务网格”我的回答是先把消息协议、代理注册、状态流转这几个基础机制做扎实比什么花哨的中间件都管用。架构从来不是越大越好而是每一层都能回答“为什么需要它”。