ARTICLE DETAIL

资讯详情

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

多智能体协作系统架构实战:从单Agent到稳定上线的全复盘

多智能体协作系统架构实战:从单Agent到稳定上线的全复盘 最近翻出一个早前的项目复盘感触挺深。当时我们把一套客服工单系统从单Agent升级成多智能体协作系统过程远比想象中曲折。很多人以为多Agent就是把几个大模型接口串起来让它们各自干活——真这么简单的话市面上就不会有那么多跑不起来或者跑起来就失控的Demo了。我自己踩过的坑包括任务重复执行、Agent之间互相覆盖数据、上下文越传越臃肿、一个节点卡死导致整条链路阻塞。最终能稳定上线靠的不是更聪明的模型而是把架构当成一套组织设计来做。这篇就把整个案例的拆解思路、架构细节和踩坑过程完整写出来给正在往多Agent方向走的同学做参考。1. 当单Agent撑不住时才真正理解多智能体协作系统要解决什么1.1 一个Agent要干所有活的真实瓶颈最初我们的系统很朴素一个大模型Agent负责接收用户发来的客服工单调用各种工具去查订单、查库存、排班、写回复。一开始觉得够用但随着业务接入越来越多问题一个接一个冒出来。首先是上下文预算被吃光。一个跨部门问题进来Agent要先调用订单接口然后把结果塞进对话历史再调用库存接口再塞一遍中间还可能穿插用户的历史工单记录。几个工具调用下来窗口就满了模型开始犯糊涂把上一个工具的返回当作下一个工具的输入来理解。其次是工具调用互相干扰。让同一个Agent既做信息检索又做方案生成还做质量校验本质上是把三个岗位塞进一个脑袋里。检索阶段需要的是精准、快、范围广生成阶段需要的是创造性、结构感质检阶段需要的是批判性、严谨。这三者放在同一个上下文里模型很难在同一个环节切换正确的行为模式。最后是完全没有并行能力。一个用户的工单处理过程中其实有好几段可以并行查订单和查库存彼此独立完全可以同时进行。但单Agent串行执行白白浪费了等待时间。后来我们统计过早期单Agent处理一个中等复杂工单平均要22分钟其中一大半时间浪费在顺序调用上。1.2 多Agent协作在设计上要回答的四个问题后来我们意识到单Agent的瓶颈本质不是模型不够强而是职责边界太模糊。多智能体协作系统的核心就是把这团模糊拆成边界清晰的协作流程。做这个设计的时候我反复在想四件事谁来做决定。是有一个中心编排者统一调度还是让Agent们自治协商前者可控性强适合业务逻辑明确的场景后者灵活但出了问题很难追溯。信息怎么共享。是所有Agent共享同一个大上下文还是通过消息传递交换必要信息共享内存实现简单但很容易互相污染消息传递更干净但要考虑消息的丢失、重复、超时。任务怎么分配。是按照流水线方式把任务切成固定工序还是由编排者根据实时情况动态分发固定工序好调试动态分发更高效但复杂度直线上升。失败怎么办。单个Agent出错后是整条链路回滚还是跳到仲裁节点重新处理重试策略设计不好就会引发重复执行的事故后面会专门讲这个。说句实话这四个问题并不是纯技术问题更像是组织设计问题。你等于是在搭一支虚拟团队要让每个人知道自己的职责、知道找谁协作、知道出了问题该找谁。1.3 适不适合上多Agent先看三个信号多Agent不是万能药甚至可以说大部分内部工具用单Agent加一个良好的提示词模板就够了。我在项目启动前做过一轮评估当时判断是不是要做多Agent主要看三个信号任务能不能拆成互相独立的子任务。如果业务逻辑强耦合、每一步都依赖上一步的全部中间状态硬拆只会增加通信成本。子任务是否需要不同的工具或不同知识域。比如一个环节查数据库另一个环节调外部API还有一个环节要阅读长文档。每类操作背后的最优解并不相同。是否存在可验证的中间产物。也就是做完一个阶段后能不能用客观标准判断这个阶段是否合格。如果没有中间校验点多Agent协作就会变成黑箱接力错误会被一路放大。我们的客服系统三条都满足工单可以拆成信息采集、方案生成、质检、执行几个阶段各阶段涉及的工具差异很大每个阶段的产物都能被下游校验。所以我才确定这笔投入是值得的。2. 从“三个Agent跑通Demo”到真正可用的架构角色、编排层与共享上下文2.1 角色划分不只有LLM才叫Agent当时团队里不少人对“Agent”的理解是一个大模型配上工具调用能力再加上记忆就是一个Agent。这理解不能算错但容易让人忽略一个事实——很多节点根本不需要大模型。我们的实际系统里角色是这样划分的角色核心职责底层实现入口编排Agent接收工单维护任务状态机调度各节点规则引擎 状态机少量LLM用于意图识别资料检索Agent拉取订单、库存、客户历史记录工具调用 传统检索ES/数据库方案生成Agent根据检索结果生成处理方案LLM重点在结构化输出质检Agent校验方案是否完整、合规、无幻觉LLM 规则校验器双重检查执行Agent发送邮件、创建工单、更新状态普通代码服务确定性执行你可能会问资料检索Agent和执行Agent根本没有大模型凭什么也叫Agent我的看法是Agent的本质是“一个拥有明确职责边界、能够响应指令并产出结果的执行单元”。它可以是纯代码也可以是规则引擎也可以是LLM。多Agent系统的设计重点不在于每个节点都有多聪明而在于节点之间的协作效率。这种混合设计带来的好处非常明显系统里只有方案生成和质检两个环节真正消耗LLM调用其他环节全部走传统逻辑。成本可控出问题也好排查。2.2 编排层它不是路由器是“编舞者”很多人一开始会把编排层理解成一个消息路由器收到上游消息根据类型转给下游。这个理解差得有点远。真正的编排层更像是一个“编舞者”。它不负责具体跳舞的动作但负责确保每个舞者在正确的时间出现在正确的位置。具体到我们的系统里编排层维护着一张任务状态机每个工单从进入系统到处理完成都会经历这些状态received → info_collecting → solution_generating → quality_checking → executing → done每个状态对应一个或多个Agent。编排层根据状态机的推进决定下一步该调谁并且把当前任务的共享上下文传递给对应节点。它还要记录每一步的执行结果方便事后追溯。我当时给编排层定了一个原则编排层本身不产生业务判断只做流程控制。任何涉及内容质量的判断都交给质检Agent。这样设计的好处是编排层可以保持极低的出错率它就算再笨也不会把流程带偏。2.3 共享上下文不要一个池子装所有的水关于Agent之间的信息共享我们讨论过两种方案一个全局共享内存所有Agent都可以读写还是基于消息传递每次只传下游需要的那部分。全局共享内存实现起来最省事但带来两个新问题一是信息的可见性边界很难控制比如质检Agent本来不应该看到客户手机号但如果所有信息都在一个大池子里它随随便便就能读到二是并发访问很容易出现脏读和覆盖两个Agent同时改一个字段后写的覆盖先写的。所以最终我们采用了分层共享上下文按作用域区分会话级保存用户身份、本次交互的对话历史、用户画像所有Agent只读。任务级保存当前工单的中间产物包括检索结果、方案草稿、质检意见。这是Agent之间交换的核心区域由编排层决定谁在什么阶段可以写。全局级保存系统参数比如当前并发限制、预算上限、模型版本所有Agent只读。任务级上下文的写入权限被严格限制检索Agent只能写检索结果区方案Agent只能写方案草稿区质检Agent只能写质检意见区。谁也不能跨区写别人的数据这样就从机制上避免了互相覆盖的问题。2.4 第一版别做太复杂的自主规划很多团队一上来就想让Agent自己规划任务、自己拆解步骤号称“全自主”。我强烈建议第一版不要这么玩。全自主意味着不可控不可控意味着线上事故。我们第一版的编排是固定链路工单进来先检索再生成方案再质检再执行。所有流程顺序写死在编排层里。跑通之后我们才在几个关键节点加了决策点比如质检不通过时可以走“打回重写”分支而不是每次都从头开始。等这些稳定了才开始讨论要不要让编排层根据任务复杂度动态选择链路。控制不住的创新就不是创新是事故。3. 任务分配与协作机制链式流水线之外还需要决策点调度3.1 模式一链式流水线的优势与代价系统早期我们用的是最简单的链式流水线上一个Agent的输出就是下一个Agent的输入中间没有任何分支。这种模式的好处非常突出逻辑清晰、容易排查、状态维护简单。每个工单都走同一条路出了问题只需要看卡在哪个环节。缺点也明显一是不灵活所有任务不管难度差异都走同样的工序二是阻塞如果上一个Agent因为外部API超时卡住了后面所有环节都得等着。后来我在设计里增加了一个超时旁路超过一定时限的任务不再傻等而是先把状态标记为“超时待处理”等Agent恢复后再继续。这样链路不会因为一个节点卡死而整条瘫痪。3.2 模式二决策点调度的关键场景随着业务复杂化固定链路渐渐不够用了。我们引入了决策点调度就是在固定流程上加了几个判断节点由编排层根据当前上下文决定下一步往哪走。最典型的一个决策点是质检环节。方案Agent提交方案后质检Agent会给出通过或打回的意见。如果打回需要判断打回原因信息缺失说明检索Agent可能没抓全需要重新检索补充信息后再生成方案。格式错误信息已经够了只是方案结构不对不需要重新检索直接让方案Agent改格式。严重幻觉可能是模型本身的问题这时候要把方案完全作废从零开始重新生成并考虑切换模型或温度参数。这三个分支的执行成本完全不同。如果不去做决策点调度所有的打回一律走“重新检索→重新生成→再次质检”的完整链路成本至少多出三倍。决策点的实现逻辑并不复杂核心是让编排层能读取任务级上下文里的质检意见字段然后根据意见里的结构化标签做分支。这里面最有价值的是让质检Agent输出机器可读的判断标签不要只输出一段自然语言意见。3.3 分歧处理不是所有问题都适合投票多Agent协作的另一个常见场景是同一个任务让多个Agent各自做一遍然后对比结果。我们叫交叉验证。这在处理高风险工单时特别有用比如金额较大的赔付申请。早期我犯过一个错误为了追求准确率让五个Agent同时做同一个赔付审核然后投票决定。结果是成本翻了五倍准确率提升却很有限而且当三个Agent支持通过、两个支持拒绝时到底该听谁的反而成了一个更难的问题。后来我们调整了策略只在两类场景才做交叉验证第一类是事实分歧。Agent之间回答案不一致且差异可以通过数据来核实这种情况下我们直接调数据接口验证不靠投票。第二类是高风险决策。涉及金额、法律、安全等无法轻松承担错误的场景启用21模式两个Agent独立审核如果意见不一致由第三个仲裁Agent做最终判断。多说一句只要模型有概率差异交叉验证就有效果但绝大多数业务场景根本不需要五个Agent投票。成本是LLM时代最容易被忽视的敌人后面我会专门展开。3.4 并发控制与任务队列多个Agent并行跑起来之后一个新问题出现了瞬时并发把所有外部API连接数打满。有段时间一到高峰期订单查询接口就报限流错误因为五个Agent同时在拉数据。后来我们给编排层加了一个简单的并发控制组件每个Agent分配一个并发窗口超过窗口的任务先进入队列排队等前面的请求返回了再放行。任务队列本身还解决了一个重要问题可恢复性。如果某个Agent进程崩溃了队列里的任务不会丢重启后可以继续消费。第一版我们用的是内存队列重启后队列清空靠任务状态机从数据库恢复现场。后来流量大了换了Redis队列任务加上过期时间和重试次数才算真正放心中。4. 协作的底层是通信消息协议、幂等性与超时重试4.1 多个Worker和多Agent系统的本质区别很多教程会演示“用LangGraph跑三个Worker”看起来像多Agent但其实就是三个独立的进程各自处理一段输入。它们之间没有围绕同一个目标交换过任何信息这不叫协作。多智能体协作系统必须有两条纽带一条是共享的目标状态另一条是节点间的信息流动。没有了通信协议Agent再多也只是一群散兵游勇。实际项目中最容易被低估的就是通信协议的设计。一开始我们图省事直接让Agent之间用函数调用方式互相调用方案Agent直接调质检Agent的Python函数。这在小规模Demo里完全没有问题但一旦系统大了就出问题——跨服务的时候函数调用变成了HTTP调用你要开始考虑超时、重试、消息确认。最尴尬的是这些问题的排查难度远大于写业务逻辑本身。4.2 我们使用的消息信封结构最终我们统一了所有Agent之间的消息格式给每条消息加了一个“信封”。这个信封不复杂但是非常重要{ msg_id: msg_20250928_001, trace_id: trace_3f9a2c8e, task_id: task_10492, stage: solution_generating, from_role: planner_agent, to_role: quality_agent, payload: { plan_id: plan_00231, content: ..., request_id: req_9527 }, expires: 1710000000 }trace_id是用来做全链路追踪的一个工单从进入到结束携带同一个trace_id排查问题的时候只需要按它把所有日志拉出来。stage标记当前消息属于哪个阶段方便状态机对齐。from_role和to_role明确谁发的、发给谁避免消息投递错乱。expires是消息过期时间超过这个时间还没有被消费就算重试也没有意义了。字段都是用JSON自协商的没有引入重量级的Schema定义平台。原因很简单自协商轻量改动成本低系统初期最重要的不是协议的严谨性而是迭代速度。等跑通了再引入版本管理也不迟。4.3 幂等、确认与超时重试通信可靠性的三个底线通信这块我们踩过最痛的坑就是重复执行。当时的情况是这样下游执行Agent调一个发送邮件的接口邮件服务偶发超时。执行Agent等了10秒没有返回就自动重试了一次。结果第一次调用其实已经成功了用户的邮箱里躺着两封一模一样的邮件。这就是典型的缺乏幂等控制。之后我们定下了三条底线幂等优先。给每个外部调用带上request_id下游服务通过这个ID去重。被调用方必须保证同一个ID只处理一次。确认隔离。消息“送达”不代表“成功处理”。消费方必须返回明确的成功或失败信号不能让生产方猜。超时必重。一个Agent卡住了不能无限等我们会设置一个动态超时时间根据历史平均耗时乘以系数超时后触发重试。但重试次数上限设为两次再不行就转入人工队列。这三条里面幂等是最容易被忽略又最致命的。下游接口哪怕有一点不确定性都必须假设它会重复调用。你要么在下游做去重要么在上游做状态锁二选一不能都没有。5. 共享状态的三个坑脏读、覆盖与上下文膨胀5.1 脏读下游看到了上游写到一半的数据多Agent系统里Agent A写数据Agent B读数据如果A还没写完B就开始读B拿到的就是残缺数据。我们管这叫脏读。真实案例是这样的质检Agent需要拿到方案Agent的完整方案文本但方案Agent是流式输出的文本还在生成中就被推到了共享上下文。质检Agent一读到这个半成品立刻启动了校验流程结果发现方案缺少执行时间字段于是打回。可问题是方案Agent本来马上就会补上这个字段只是还没有写完。后来我们的解决办法是给任务级上下文的每个数据区加状态标记from enum import Enum class DataStatus(str, Enum): INIT init # 占位尚未填写 DRAFT draft # 正在写入下游不可读 READY ready # 写入完成下游可读 DEPRECATED deprecated # 已废弃等待重新生成任何Agent在读取前必须检查状态是READY读取后应快速消费写入方写完必须置为READY才能通知下游。状态字段虽然是约定但最好由编排层统一校验不允许下游直接读取非READY数据。这么一改脏读问题基本绝迹。5.2 覆盖后写的覆盖先写的先写的数据消失另一个经典事故是覆盖。检索Agent和方案Agent同时往任务上下文里写了一个叫summary的字段。检索Agent写的是“工单摘要”方案Agent写的是“方案摘要”两个都把同一个键覆盖了。最后质检看到的summary是方案摘要但它需要的是工单摘要整个流程就错了。这个问题的根源是上下文设计得不够细。不同角色写的字段必须分开不能都是一个泛泛的summary。我们按照“角色前缀字段名”的命名方式把所有共享字段重命名了一遍retriever_data.order_inforetriever_data.inventory_infoplanner_data.plan_contentquality_data.check_result除了命名隔离还在写操作上加了版本号校验。写入时必须携带预期版本号如果在写入前版本已经被其他人改了就拒绝写入并让当前Agent重新拉取最新数据。本质上就是数据库里的乐观锁。5.3 上下文膨胀所有Agent都变成“什么都记一点的大嘴巴”做多Agent的人最容易忽视的问题是Token转换。单个Agent上下文长度没问题但如果每个Agent都把上游传下来的原始内容保留下来再叠加自己的输出再传给下游消息会像滚雪球一样膨胀。我们的一个工单任务早期传了四层检索结果约2千token方案约3千质检意见约1千执行结果约500字。看起来不大但如果是批量处理每个Agent都要把这个消息体存在自己的记忆里几百个工单同时跑内存和Token消耗都在快速上涨。更麻烦的是到了最后一个Agent手里它读到的信息里80%跟它要做的事情无关反而干扰判断。后来我们执行了一个很关键的原则Agent之间的信息传递只传摘要不传原文。跨Agent协作时生产方必须形成一段结构化摘要下游需要原文时再通过工具去取而不是把原文塞进上下文里。这就好比你周一开项目例会不可能让每个人把上周所有邮件的全文念一遍你只需要说明结论、问题和下一步计划。人这样协作效率最高Agent也是一样。6. 多Agent系统的联调与问题定位日志要能“回放”6.1 黑盒调试有多痛苦多Agent系统最折磨人的一点是问题往往发生在Agent与Agent的接缝处而不是某个Agent内部。单看任何一方的日志都找不到问题根因。有一次线上任务卡在“待质检”状态我查了方案Agent的日志显示已经把方案提交出去了查了编排层的日志却没看到质检任务被创建。两边的日志都对但整个链路就是断的。最后把日志拉到一起才发现问题出在方案Agent提交的消息里payload少了一个质检需要的字段。质检Agent的消费者解析消息时抛了异常但这个异常被一个catch-all捕获后静默忽略了。消息既没有进入重试队列也没有产生一条可检索的错误日志直接被吞掉了。这就是典型的黑盒调试的噩梦。6.2 从一次卡死问题看链路排查的正确姿势那次之后我认真做了一轮可观测性改造。核心就三件事第一所有日志必须带trace_id、stage、role三个字段。日志格式统一为标准JSON方便采集和检索。第二异常绝对不能吞。每一条异常都要有错误日志并且归类成可枚举的错误码。任何Agent调用失败不管是否重试都先留痕。第三做一个简单的“消息流回放”工具。我们当时用Redis Stream存了每个任务的完整消息序列排查问题时只需要按trace_id把消息按时间顺序拉出来一眼就能看出哪一步没有执行、哪一步消息格式不对。排查那次卡死问题时我用回放工具看到编码Agent的消息确实进入了队列但消费线程在处理到payload.interaction_list字段时抛了一个“字段缺失”的异常。因为该任务对应的问题类型是投诉类投诉类工单本身就不带交互记录字段但方案生成模板却强制要求这个字段。真实原因是这样而不是谁没干活。事后我们做的修复有两层模板生成时根据工单类型动态调整必填字段消费端解析失败时直接进入“失败待稽查”队列绝不静默忽略。6.3 最小回归测试集改完不慌的底气多Agent系统改动一次牵一发而动全身。方案Agent的提示词改了一句可能影响质检通过率进而影响执行Agent的行为。没有回归验证谁也不敢上线。后来我们建了一个只有10条工单的冒烟测试集覆盖四类情况正常工单、带超时的工单、空结果的工单、重复消息的工单。每条工单都有预期结果和预期耗时上限。每次改动先跑这10条全部通过才放行。这个动作看起来很笨但确实帮我们挡住了至少三次回归事故。有一次改完质检Agent的评判规则正常工单全部通过但重复消息那条出了问题——因为质检逻辑接收消息时没有做幂等检查把同一份方案校验了两次。冒烟测试秒级暴露了问题我们才没有把它带到线上。7. 复盘一次真实事故重复执行、死锁与不必要的大模型调用7.1 事故现场一套迁移方案用户收到两封邮件这是我们系统上线后最严重的一次故障。现象是某个客户工单生成了两套几乎一样的迁移方案用户邮箱里收到了两封内容相同的邮件后台还建了两条重复的任务记录。排查链路从用户反馈开始先看编排层日志这个工单确实被创建了两次执行任务task_id不一样。再往下追发现原因是上游服务调用Agent网关的时候发生了超时网关自动重试了一次。但重试请求带的task_id变了等于同一个工单被当成了两个新任务处理两套方案生成流程并行跑起来了。根子在于我们当时的任务接收接口没有做好幂等。网关只认参数里的task_id去重但上层系统重试的时候重新生成了一次ID去重自然就失效了。7.2 修复方案我们加了四道锁那次事故之后我们做了四件事上游重试时透传原始请求ID不允许重试时重新生成任务ID。任务表中对业务工单号加唯一约束同一个工单号只能存在一个未完成的任务。任务状态加上“执行中”锁只有处于“新建”状态的任务才能被执行。重复消息过来时看到状态不是“新建”就直接返回已完成。消费端做声明式处理处理前先查是否已经处理过处理过则直接返回不再重复执行。这套组合其实不新鲜跟后端接口幂等设计的思路完全一致。但放到多Agent系统里很多人就会被“Agent很智能”这个标签迷惑觉得它们自己能判断要不要重复做事。事实是只要底层还是人来写的代码就必须假设任何一步都可能被重复触发。7.3 量化收获修复重复执行后成本直线下降修复重复执行的意外收获是月底账单明显好看了。之前我们以为系统消耗大是正常的但修完重复执行后下游API调用量下降了30%左右原因是很多任务在重试链路上被无意义地执行了两遍甚至三遍。这给了我一个特别深的体会多Agent系统的成本问题很多时候不是出在“模型太贵”而是出在架构设计不合理导致的不必要调用。每多一次无意义的LLM调用都在浪费钱也在加剧下游系统的压力。8. 关于选型与成本我的几条实在建议8.1 不要为了Agent而Agent如果一个任务用固定规则加状态机就能解决就不要硬上多Agent。我们在系统里有一个节点专门做工单分类早期用LLM做用的是小模型准确性也不错。但后来发现大量工单的类别其实可以用简单的关键词规则覆盖掉只有少数边界情况需要语义理解。改造之后规则引擎处理了约90%的分类请求LLM只负责剩下10%的模糊场景。结果更稳定成本也降了下去而且分类这个环节可解释性反而更强了。8.2 把LLM放在真正需要判断的位置多Agent系统里不是每个节点都需要大模型。检索用ES够了格式转换用模板够了数据校验用规则引擎够了。LLM应该只放在两类位置一类是需要理解意图的位置比如判断用户到底想干什么另一类是需要生成内容的位置比如方案撰写、质检评判。我们的系统里5个Agent节点真正消耗LLM调用只有2个。这样做下来系统的平均单任务成本大概只有全LLM方案的45%左右而整体效果几乎没差别。逻辑很简单大部分Token花在了确定性任务上本身就是浪费。8.3 上线节奏小流量灰度加半自动验证最后说上线策略。多Agent系统和普通后端服务不一样它的结果质量很难预判没法写死断言。我们前两周采取的是“影子模式”系统跑在和线上并行的环境里但AI生成的结果不会直接发给客户而是由人工客服审核判断AI方案的合格率。等到合格率稳定超过人工历史水平的90%才允许系统在低流量5%下直出。然后逐步提升到30%、50%、100%。每一档都跑至少一天观察人工介入率和投诉率。整个过程大概用了两周虽然慢但很稳。现在回想起来做出多Agent协作系统方向技术上并没有哪个单点特别高深最难的是平衡设计、成本、可观测性和稳定性这几件事。如果让我给刚起步的团队一个建议先把通信协议、状态机和日志回放这三样做好再讨论角色分工和模型选型。Agent的数量和价值不成正比可控的价值才成正比。最后分享一个留了很深的印象的细节我们编排层最后加了一个“人工接管”开关。不管系统设计得多周全总会出现模型犯傻的时候。这个开关存在的意义是让系统在极端情况下仍然可以被一个普通人一键停下来。多Agent系统再复杂兜底的永远是最简单的那句——出了问题人说了算。
返回列表