☰
多Agent系统实战:从单Agent困境到生产级架构落地
2026/10/5 16:22:19 网站建设 项目流程

1. 从单Agent到多Agent:为什么"一个全能助手"的路线走不通

我最早接触Agent开发的时候,和大多数人一样,脑子里想的是"造一个什么都能干的超级助手"。给它挂上搜索、代码执行、文件读写、浏览器操作,再配一个足够长的系统提示词,理论上它就能处理所有任务。这个思路在Demo阶段非常爽,一个Agent跑通全流程,演示效果拉满。但一旦往生产环境推,问题就集中爆发了。

最典型的症状是上下文污染。一个Agent既要记住用户的原始意图,又要记住中间调用了哪些工具、返回了什么结果、当前进行到哪一步,还要在每一轮对话里保持人设和格式约束。当任务链路超过七八步之后,提示词里塞进去的历史信息开始互相干扰,模型会"忘记"早期的关键约束,或者把某个工具的返回结果误当成用户指令。我实测过一个客服场景,单Agent在处理"退款+换货+查询物流"三件事混在一起时,错误率比拆成三个专职Agent高了将近40%。

第二个症状是能力耦合导致的调试地狱。当搜索、计算、写文件全在一个Agent里,一旦输出格式出错,你根本不知道是提示词的问题、工具描述的问题,还是模型在某一步推理跑偏了。每次改一处提示词,可能把另一个本来正常的流程搞崩。这种"牵一发动全身"的体验,做过复杂Prompt工程的人都懂。

多智能体系统(Multi-Agent System)的核心思路,就是把"一个全能选手"拆成"一支分工明确的团队"。每个Agent只负责一个相对窄的领域,有自己的系统提示词、自己的工具集、自己的输出格式约束。Agent之间通过编排器(Orchestrator)来协调,谁先干、谁后干、结果怎么传递,全部显式定义。这样做的好处是:每个Agent的上下文窗口更干净,职责边界清晰,出问题能快速定位到具体是哪个环节。

但这里有个常见的误解需要先澄清:多Agent不等于多个模型实例。很多人以为多Agent就是开三个GPT-4的API并行跑,其实不是。多Agent的本质是角色分离与流程编排,底层可以是同一个模型,甚至可以是不同厂商、不同规模的模型混用。一个负责意图识别的Agent可以用小模型,负责复杂推理的Agent用大模型,负责格式校验的Agent甚至可以用规则引擎代替。这种异构组合才是多Agent架构真正的成本优势所在。

提示:如果你现在的单Agent系统已经出现"改A坏B"、上下文越堆越长、错误无法归因的情况,那就是该考虑多Agent拆分的信号了。不要等到系统彻底不可维护才动手。

2. 编排器到底在编排什么:三种主流协作拓扑的取舍

编排器(Orchestrator)是多智能体系统的大脑,它决定了Agent之间怎么协作。市面上常见的协作拓扑大致分三类,我按实际落地难度和适用场景逐个拆解。

2.1 流水线式(Pipeline):最稳但最不灵活

流水线式就是A做完交给B,B做完交给C,像工厂装配线一样。每个Agent的输入是上一个Agent的输出,职责极其清晰。这种拓扑的优点是可预测性极强,每一步的输入输出都能打日志、做校验,出问题一眼就能定位。缺点是无法处理需要回退或分支的任务。比如用户问"帮我查一下上周的订单,如果还没发货就取消",流水线式就很难处理"如果"这个条件分支。

我一般建议新手从流水线式入手,因为它最容易调试。你可以先把一个复杂任务拆成固定的N步,每步一个Agent,跑通之后再考虑引入分支。

2.2 主管-工人式(Supervisor-Worker):灵活但有调度开销

这种拓扑里有一个"主管Agent"负责理解任务、拆解子任务、分派给不同的"工人Agent",最后汇总结果。主管Agent本质上是一个路由器加协调者。它的优势是能处理动态任务——用户的需求千变万化,主管可以实时决定调用哪些工人。

但这里有个坑:主管Agent本身也会犯错。如果主管把任务分错了,后面的工人再努力也是白搭。我踩过一次坑,主管Agent把"查询账户余额"的任务分给了负责"发送邮件"的工人,结果工人拿着查询请求去调邮件API,直接报错。后来我在主管的提示词里加了强约束:每个工人Agent必须有一段明确的"能力描述",主管只能从能力描述里选,不能自由发挥。

2.3 黑板式(Blackboard):适合复杂协作但实现成本高

黑板式是指所有Agent共享一块"黑板"(通常是一个共享的内存或数据库),每个Agent都可以读写黑板上的信息,根据黑板状态决定自己要不要行动。这种模式适合需要多轮协商、信息共享的场景,比如多个Agent共同分析一份财报,各自贡献不同维度的分析,最后汇总。

但黑板式的实现复杂度最高,因为你要处理并发读写冲突和状态一致性。多个Agent同时往黑板上写,谁先谁后、冲突怎么解决,都需要额外设计。我个人的经验是,除非任务真的需要多Agent反复协商,否则不要轻易上黑板式,维护成本会让你怀疑人生。

拓扑类型适用场景实现难度调试难度我的推荐指数
流水线式步骤固定的任务,如文档处理、数据清洗低低新手首选
主管-工人式需求动态变化,如客服、任务分派中中生产环境主流
黑板式多轮协商、联合分析高高按需使用

选拓扑的核心原则是:能用流水线就别用主管-工人,能用主管-工人就别用黑板。每增加一层灵活性,就多一层不确定性和调试成本。很多团队一上来就搞最复杂的架构,结果连最基本的流程都跑不稳。

3. Agent之间的通信协议:消息传递里藏着最多的坑

多Agent系统里,Agent之间怎么"说话"是决定系统稳定性的关键。我见过太多项目,Agent单独跑都没问题,一串联就各种格式错乱、信息丢失。问题基本都出在通信协议上。

3.1 结构化消息是底线,自由文本是灾难

最原始的做法是让Agent直接输出自然语言,下一个Agent去解析。这种做法在Demo里能跑,在生产里必崩。因为自然语言有歧义,A Agent说"订单已处理",B Agent可能理解成"已发货",也可能理解成"已取消"。

正确的做法是强制结构化输出。每个Agent的输出必须是一个JSON对象,包含固定的字段,比如:

{ "task_id": "order_123", "status": "completed", "result": { "action": "cancel_order", "reason": "not_shipped", "confidence": 0.95 }, "next_agent": "notification_agent" }

这样下一个Agent拿到的是明确的字段,而不是一段需要猜的自然语言。我在项目里强制要求所有Agent的输出必须通过JSON Schema校验,校验不通过就重试,重试三次还不行就报错。这个约束看起来死板,但把线上事故率降了一个数量级。

3.2 上下文传递:传什么、不传什么

Agent之间传递上下文时,最容易犯的错误是把整个对话历史全传下去。这样做的后果是上下文窗口迅速膨胀,后面的Agent被无关信息干扰。正确的做法是只传必要信息。

我通常会把上下文分成三层:

  • 任务层:当前任务的ID、目标、约束条件,这些必须传。
  • 结果层:上一个Agent的输出结果,按需传。
  • 历史层:之前的交互记录,默认不传,除非当前Agent明确需要。

举个例子,用户问"帮我订一张明天去北京的机票,要靠窗"。意图识别Agent输出{intent: "book_flight", destination: "北京", date: "明天", preference: "靠窗"}。订票Agent只需要这些字段,不需要知道用户之前还问过天气。如果把完整对话历史传过去,订票Agent可能会被"天气"这个无关词干扰。

3.3 错误传递与重试机制

多Agent系统里,一个Agent失败会级联影响后面的Agent。所以必须设计错误传递协议。我的做法是每个Agent的输出里都带一个status字段,取值success、retry、fail。如果上游Agent返回retry,编排器就重新调用它;如果返回fail,编排器要么走降级流程,要么直接终止并通知用户。

这里有个细节:重试不能无脑重试。如果Agent是因为输入格式错误而失败,重试一百次也没用。所以我在重试逻辑里加了判断:如果是格式错误,先让一个"修复Agent"尝试修正输入;如果是超时或临时故障,才直接重试。

注意:Agent之间的通信协议一定要在项目初期就定死,不要等到系统跑起来再改。改通信协议等于把所有Agent的提示词和解析逻辑全部重写,成本极高。

4. 并发与状态管理:多Agent系统最容易翻车的地方

单Agent系统基本不涉及并发问题,但多Agent系统一旦上生产,并发就是绕不开的坎。我见过一个团队,Demo阶段用单线程跑得好好的,一上线遇到十个用户同时请求,系统直接雪崩。问题就出在状态管理和并发控制上。

4.1 无状态Agent与有状态编排

一个重要的设计原则是:Agent本身尽量无状态,状态由编排器统一管理。Agent每次被调用时,输入是完整的上下文,输出是结果,它自己不保存任何跨请求的状态。这样做的好处是Agent可以水平扩展,十个请求可以分给十个Agent实例并行处理。

但编排器必须是有状态的,它要记住每个任务的进度、当前走到哪一步、下一步该调谁。编排器的状态通常存在数据库或Redis里,key是任务ID,value是当前状态。这样即使编排器重启,任务也能从断点恢复。

4.2 并发冲突:同一个任务被重复处理

最常见的并发问题是同一个任务被多个编排器实例同时处理。比如用户点了两次提交,或者消息队列重复投递,导致同一个任务ID被处理了两遍。如果这个任务是"扣款",那就出大事了。

解决办法是加锁。编排器在处理任务前,先尝试获取任务ID对应的分布式锁,拿到锁才处理,处理完释放锁。拿不到锁说明已经有实例在处理了,直接跳过。这个锁的粒度要细,锁的是单个任务,不是整个系统。

4.3 超时与死锁:Agent卡住了怎么办

Agent调用外部API时可能超时,如果编排器没有超时机制,整个任务就会一直挂着。我的做法是给每个Agent调用设置硬超时,比如30秒。超时后编排器记录日志,然后决定是重试还是走降级流程。

还有一种更隐蔽的问题是死锁。比如Agent A等Agent B的结果,Agent B又等Agent A的结果,两个都卡住。这种情况通常出现在黑板式架构里。避免死锁的方法是设计单向依赖,A依赖B,B就不能依赖A。如果业务上确实需要双向依赖,就拆成两个阶段,第一阶段A产出,第二阶段B产出,不要在同一阶段互相等待。

并发问题典型表现解决方案
任务重复处理同一任务被执行多次分布式锁 + 幂等设计
Agent超时任务长时间挂起硬超时 + 重试/降级
循环等待多个Agent互相等待单向依赖 + 分阶段设计
状态不一致编排器状态与实际不符状态机 + 定期对账

4.4 幂等性:让重复执行不出错

幂等性是并发场景下的保命符。所谓幂等,就是同一个操作执行一次和执行多次,结果一样。比如"查询余额"天然幂等,"扣款"就不幂等。对于不幂等的操作,要在执行前检查是否已经执行过。我的做法是在数据库里建一张操作记录表,每次执行前先查这个操作ID是否已存在,存在就直接返回上次的结果。

5. 记忆与上下文工程:多Agent系统的"共享大脑"怎么建

多Agent系统里,记忆管理比单Agent复杂得多。单Agent只需要维护一份对话历史,多Agent则要处理"哪些记忆共享、哪些记忆私有、记忆怎么在Agent之间传递"的问题。

5.1 短期记忆与长期记忆的分层

我把记忆分成两层:短期记忆是当前任务执行过程中的临时信息,任务结束就丢弃;长期记忆是跨任务需要保留的信息,比如用户偏好、历史交互记录。

短期记忆通常放在编排器的内存或Redis里,key是任务ID。每个Agent执行时,编排器把相关的短期记忆注入到它的上下文里。长期记忆则存在向量数据库里,需要时通过语义检索召回。

这里有个关键决策:哪些信息进长期记忆。我的原则是只存"对未来任务有用"的信息。比如用户说"我不喜欢靠过道的座位",这个偏好值得存;用户说"今天天气不错",这个就不用存。判断标准是:如果下次用户再来,这条信息能不能帮我更好地服务他。

5.2 记忆的写入时机与冲突处理

记忆写入最容易出的问题是冲突。比如用户上次说"我喜欢靠窗",这次说"我喜欢靠过道",两条记忆冲突了怎么办。我的做法是给每条记忆加时间戳和置信度,检索时优先返回最新的、置信度最高的。如果两条记忆明显矛盾,就在注入上下文时都带上,让Agent自己判断,或者直接问用户确认。

另一个坑是记忆污染。如果Agent把错误的信息写进了长期记忆,后面所有任务都会受影响。所以我在写入长期记忆前加了一道校验:只有置信度超过阈值的记忆才允许写入,低置信度的先放短期记忆,等确认后再转长期。

5.3 上下文窗口的预算管理

每个Agent的上下文窗口是有限的,多Agent系统里,上下文要在多个Agent之间分配。我的做法是给每个Agent设定上下文预算,比如意图识别Agent只给2000 token,复杂推理Agent给8000 token。编排器在注入上下文时,按预算裁剪,优先保留任务相关的信息,历史信息按时间倒序保留。

这个预算管理听起来简单,但实际做起来很考验对业务的理解。你得知道每个Agent真正需要什么信息,才能合理分配。我一般会先让Agent跑一段时间,统计它实际用到的上下文比例,再反过来调整预算。

6. 落地实战:从零搭一个可用的多Agent系统

前面讲的都是原理和设计,这一节讲具体怎么落地。我以一个"智能客服工单处理"场景为例,完整走一遍搭建流程。

6.1 场景拆解与Agent划分

假设需求是:用户提交工单,系统自动分类、自动处理简单问题、复杂问题转人工。我把它拆成四个Agent:

  • 分类Agent:读取工单内容,判断是"退款"、"换货"还是"咨询"。
  • 信息提取Agent:从工单里提取订单号、用户ID、问题描述等结构化信息。
  • 处理Agent:根据分类结果,调用对应的业务API处理。
  • 回复Agent:生成给用户的回复文案。

编排器负责按顺序调用这四个Agent,并在处理Agent失败时决定是否转人工。

6.2 每个Agent的提示词设计要点

分类Agent的提示词要窄而明确,只让它做分类,不要让它顺便提取信息。我见过有人把分类和信息提取塞进一个Agent,结果分类准确率下降。原因是模型在同时做两件事时,注意力被分散了。

信息提取Agent的提示词要给出明确的字段定义和示例。比如"订单号格式为ORD开头加8位数字",这样模型提取时有个参照。不给示例的话,模型可能把用户随口说的数字当成订单号。

处理Agent的提示词要强调工具调用的前置条件。比如"调用退款API前,必须先确认订单状态为已支付"。这个约束能避免很多无效调用。

回复Agent的提示词要控制语气和格式。我一般会给出几个正例和反例,让模型模仿正例的语气。

6.3 编排器的状态机设计

编排器本质上是一个状态机。我用的是最朴素的状态机:每个任务有一个状态字段,取值classifying、extracting、processing、replying、done、failed。编排器根据当前状态决定调哪个Agent,Agent返回后更新状态。

状态机的转移规则要写死,不要让模型来决定下一步。模型只负责在当前Agent内部做决策,跨Agent的流程由代码控制。这样能保证流程的确定性。

6.4 日志与可观测性

多Agent系统没有日志就是瞎子。我在每个Agent的输入输出都打了日志,包括:任务ID、Agent名称、输入上下文、输出结果、耗时、token消耗。这些日志存在Elasticsearch里,出问题时能快速检索。

除了日志,我还加了链路追踪。每个任务有一个trace_id,所有Agent的调用都带上这个ID,这样能在追踪系统里看到完整的调用链路,一眼看出是哪一步慢、哪一步错。

提示:可观测性不是上线后才补的,要在开发阶段就建好。我一般会在第一个Agent跑通后就把日志和追踪加上,后面每加一个Agent都自动接入。

7. 踩过的坑与性能优化:让系统真正扛住生产流量

系统能跑通和能扛住生产流量是两回事。这一节分享几个我在生产环境踩过的坑和对应的优化手段。

7.1 模型调用延迟:并行化能省一半时间

多Agent系统里,模型调用是最大的延迟来源。如果四个Agent串行调用,每个平均2秒,总延迟就是8秒。用户等8秒基本就流失了。

优化的关键是找出可以并行的步骤。比如分类Agent和信息提取Agent其实可以并行,因为它们都只依赖原始工单内容,互不依赖。并行之后,这两个步骤的延迟从4秒降到2秒。处理Agent和回复Agent有依赖关系,必须串行,但回复Agent可以在处理Agent返回后立即开始,不用等日志写完。

我实测下来,合理的并行化能把端到端延迟降低40%到60%。但并行化也有代价:并发调用会增加API的QPS压力,如果用的是有速率限制的API,可能触发限流。所以并行度要根据API的限额来定,不能无脑并行。

7.2 缓存:哪些结果可以复用

有些Agent的输出是可以缓存的。比如分类Agent,如果两个工单内容完全一样,分类结果肯定一样,没必要重复调用。我在分类Agent前面加了一层缓存,key是工单内容的哈希,命中缓存直接返回。

但缓存要小心时效性。比如"查询订单状态"这种Agent,结果随时会变,不能缓存。我一般只缓存那些输入相同则输出稳定的Agent,比如分类、信息提取、格式转换。

7.3 降级策略:模型挂了怎么办

生产环境必须考虑模型服务不可用的情况。我的降级策略分三级:

  • 一级降级:切换到备用模型。比如主用大模型,备用小模型,虽然效果差一点,但能保证服务不中断。
  • 二级降级:走规则引擎。对于分类这种任务,如果模型不可用,可以用关键词匹配兜底。
  • 三级降级:转人工。如果前两级都不可用,直接把任务转给人工客服,并给用户一个明确的提示。

降级策略要提前设计好,不能等故障发生了再临时想。我一般会在系统里加一个开关,运维可以手动切换降级级别。

7.4 成本控制:不是所有Agent都需要大模型

多Agent系统的一个隐性成本是token消耗。如果每个Agent都用最贵的模型,成本会高得吓人。我的做法是按任务复杂度分配模型:

Agent类型任务特点推荐模型成本占比
分类/提取简单判断,输出结构化小模型低
推理/决策复杂逻辑,需要理解大模型高
格式转换机械转换规则引擎极低
回复生成需要自然语言中等模型中

这样分配下来,整体成本能比全用大模型降低60%以上,而效果下降不明显。关键是要对每个Agent的任务难度有准确判断,不能一刀切。

8. 多Agent系统的安全边界与权限隔离

多Agent系统里,Agent能调用的工具越多,风险越大。一个被恶意输入操控的Agent,可能调用它本不该调用的工具。所以权限隔离是多Agent系统必须做的功课。

8.1 最小权限原则:每个Agent只给必要的工具

我在设计Agent时,严格遵循最小权限原则。分类Agent只给文本分类工具,不给任何写操作的工具。处理Agent才给业务API的调用权限,而且只给当前任务需要的API。比如退款Agent只给退款API,不给转账API。

这样做的好处是,即使某个Agent被提示词注入攻击,它能造成的破坏也有限。我见过一个案例,一个Agent被用户输入诱导调用了删除数据的API,就是因为那个Agent的权限给太大了。

8.2 工具调用的二次确认

对于高风险操作,比如退款、删除、修改用户信息,我在编排器层面加了二次确认。Agent调用这类工具时,编排器先拦截,检查是否满足预设条件(比如金额是否超过阈值、是否已经确认过),满足才放行。

这个二次确认不是让用户确认,而是系统内部的校验。比如退款金额超过1000元,编排器就要求处理Agent提供额外的确认信息,否则拒绝执行。

8.3 输入输出的安全过滤

Agent的输入可能包含恶意内容,输出可能包含敏感信息。我在编排器的入口和出口都加了过滤。入口过滤是检测提示词注入的常见模式,比如"忽略之前的指令"这类话术。出口过滤是检测输出里是否包含手机号、身份证号等敏感信息,包含就脱敏。

这些过滤规则不可能覆盖所有情况,但能挡住大部分低级攻击。安全是个持续对抗的过程,规则要定期更新。

9. 写在最后:一些不那么技术但很重要的经验

做多Agent系统这一年多,技术上的坑踩了不少,但真正让我印象深刻的是一些非技术层面的经验。

第一,不要为了多Agent而多Agent。我见过一些团队,明明一个Agent加几个工具就能解决的问题,非要拆成五个Agent,结果复杂度上去了,效果反而下降。多Agent的价值在于职责分离和流程可控,如果一个任务本身很简单,单Agent完全够用,就别折腾。

第二,Agent的粒度要反复调。拆得太粗,等于没拆;拆得太细,Agent之间通信开销比干活还大。我一般会先粗拆,跑一段时间看哪些Agent经常一起被调用,就把它们合并;哪些Agent内部逻辑太复杂,就再拆。这个粒度不是一次定死的,要迭代。

第三,提示词版本管理很重要。多Agent系统里,每个Agent的提示词都是一份配置。改提示词等于改代码,必须版本化。我用Git管理所有Agent的提示词,每次改动都记录原因和效果,出问题能快速回滚。

第四,测试要覆盖Agent之间的交互。单Agent测试只能保证单个Agent正常,多Agent系统的bug往往出在交互上。我专门写了一套集成测试,模拟完整的任务流程,覆盖各种异常情况,比如某个Agent超时、返回格式错误、返回空结果等。

第五,别忽视人工兜底。再好的多Agent系统也有搞不定的情况。我在每个关键节点都留了转人工的入口,用户不满意可以随时转人工,Agent处理不了的也自动转人工。人工兜底不是失败,而是系统成熟度的体现。

最后分享一个我常用的调试技巧:当多Agent系统出问题时,我会把整个调用链路的输入输出打印出来,按时间顺序排列,然后从最后一个出错的Agent往前倒推,看是哪一步的输入不对。这个方法看起来笨,但比盲目改提示词有效得多。多Agent系统的调试,本质上就是沿着数据流找断点,找到断点,问题就解决了一半。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询