1. 单Agent扛不住的时候,就是拆分的信号
1.1 我第一次真实的翻车现场
做AI应用开发这几年,我踩过最狠的一次坑,是让一个"全知全能"的Agent去自动生成一份行业研究报告。当时我想得很简单:大模型上下文窗口够大,让它自己搜资料、自己分析、自己写,权限都给足,问题不大吧?结果跑了不到三个小时,输出就开始"精神分裂"。前半部分还在认真分析市场规模,后半部分突然开始引用完全不相关的网页内容;给它指定的标题规范,写到第五章就彻底丢了;最离谱的是,它把我在系统提示词里写的一条引导语当成正文内容直接搬进了报告里。
那次之后我彻底明白一件事:单Agent的瓶颈往往不是模型能力本身,而是上下文里的"信息浑浊"。任务越长、涉及的工具越多、中间环节越杂,这个浑浊就越严重。要解决这个问题,不能靠堆提示词,只能靠架构层面的调整,也就是把Multi-Agent真正用起来。
1.2 单Agent到底卡在哪三个地方
我把当时的问题归纳成三类,你可以对照自己的项目看:
- 记忆污染。对话历史越积越长,Agent开始分不清"早期自己说过的话"和"用户真实需求"谁的优先级更高。早期结论和后期结论互相打架,指令被淹没在历史数据里,模型逐渐丢失主线。
- 成本失控。每次调用都要把全部历史都塞给模型。假设一个任务迭代30步,每步新增约1500 token的中间产物,那么第20步时单次调用可能要带上3万token的旧上下文。跑完整个任务,大量token都花在反复重复旧内容上。
- 职责漂移。一个Agent同时扮演研究员、写作员、审核员,角色切换多了以后,系统提示词里那句"你现在是数据分析师"的影响力会被越来越长的历史稀释。我就亲眼看着它从"分析师"慢慢变成了"复读机"。
所以Multi-Agent的真正价值,不是"多几个模型一起跑",而是把一个大而模糊的任务拆成多个小而清晰的任务,分配给职责明确、上下文干净、能够互相协作的执行单元。要做成这件事,核心是解决三个问题:怎么拆任务、怎么隔离上下文、怎么设计协作机制。这篇文章围绕这三件事展开,最后附一个完整的实战复盘,希望能给正在做Agent应用的同学一点可复用的思路。
2. 拆任务:任务树、依赖关系与角色岗位说明书
2.1 任务树怎么画才不容易散架
拆任务的第一步是画任务树。我推荐的方法是目标倒推法:从最终交付物出发,反复问"要产出这个东西,必须先得到什么",逐层往下拆到叶子节点。拿我刚才说的"行业研究报告"为例,任务树大概是这样的:
生成行业研究报告 ├── 确定报告大纲 │ └── 规划子章节与每个章节的要点 ├── 数据采集 │ ├── 市场规模数据 │ ├── 竞争格局信息 │ ├── 技术趋势资料 │ └── 典型玩家动态 ├── 数据核验与清洗 │ ├── 冲突信息交叉验证 │ └── 来源可信度评估 ├── 分析结论生成 │ ├── 市场趋势判断 │ └── 竞争格局分析 └── 报告撰写与质检 ├── 章节成稿 └── 事实一致性审校画到叶子节点时,我用三个问题来做收敛判断:
- 可完成性:这个子任务能不能由单一Agent在少数几次LLM调用内完成?如果需要"先查再想再写"好几个来回,说明还要继续拆。
- 输入输出清晰度:它的输入来源是谁、输出交给谁,中间有没有明确的数据格式?如果说不清,任务边界就是模糊的。
- 可验收性:任务完成后怎么验证做得对不对?比如"采集Agent"的验收条件是"返回N条带来源链接的事实卡",而不是抽象的"搞一些资料回来"。
2.2 依赖关系与三种拆法
任务树的节点不是孤立的,常见的依赖关系有三种:串行依赖(A完成才能开始B,比如"先采集再核验")、并行依赖(互不依赖可以同时跑,比如竞品分析和市场规模测算)、条件依赖(B走哪条路径取决于A的结果,比如数据如果显示行业处于衰退期就得追加原因分析)。
对应这三种依赖,常用的拆分策略有三种:
- 流水线拆法:按阶段切成A→B→C的链式结构,适合阶段边界清晰、顺序固定的任务。
- 扇出拆法:一个任务裂成多个并行子任务,完成后统一汇总。适合管理、研究、采集这类可以同时铺开的环节。
- 分支拆法:根据中间结果动态决定后续任务,通常需要编排层介入,由Manager Agent做路由判断。
我实际使用时的组合拳是:整体走流水线,局部用扇出,遇到条件依赖就让Manager做路由。这样既有确定性,又不失灵活。
2.3 角色定义比提示词重要
拆完任务,就要给每个Agent定义"岗位说明书"。这一步很多人会偷懒,只写一句"你是一个数据分析师"。但实际跑起来你会发现,Agent之间的协作质量靠的不是提示词里那点性格描述,而是清晰的输入输出契约。
我给每个Agent的岗位说明书固定包含五要素:
| 要素 | 含义 | 示例 |
|---|---|---|
| 职责范围 | 只做什么、明确不做什么 | "只负责采集,不进行分析" |
| 输入契约 | 接收什么格式的数据 | JSON数组,每个元素含keyword、max_results |
| 输出契约 | 返回什么格式的交付物 | 含source、claim、confidence的事实卡数组 |
| 可用工具 | 能调用哪些工具 | 搜索API、网页抓取工具 |
| 成功标准 | 什么算完成、什么算失败 | facts数组为空视为失败并上报 |
举个例子,"数据采集Agent"的输出契约我会写成这样:
{ "task_id": "string", "facts": [ { "source": "string", "claim": "string", "confidence": 0.0 } ], "status": "done | partial | failed" }有了这个契约,下游Agent完全不需要关心上游是怎么搜索、怎么抽信息的,拿到JSON就能干活。这比任何精心堆砌的提示词都更能决定系统上限。任务拆得细、契约定得清,后面做上下文隔离和协作编排才会顺。
3. 隔离上下文:每个Agent的"记忆边界"到底划在哪
3.1 不隔离的代价有多大
上下文隔离是Multi-Agent和"多角色一体化提示词"最本质的区别。一体化提示词里所有角色共享一份完整对话历史;而Multi-Agent必须让每个Agent拥有自己的记忆空间。为什么必须这么做?除了前面说的记忆污染,还有两个硬理由。
第一个是token经济学。假设整个任务链路的原始上下文是100个单元,分成5个Agent后,每个Agent实际只关心其中20个单元。隔离之后,每次调用只需要带20个单元;不隔离的话,每次调用都要带100个单元。任务规模越大、迭代轮数越多,两者的成本差距就是指数级的。我在一个中等规模的项目里实测过,做上下文隔离后整体token消耗降低了约60%,延迟也明显下降。
第二个是事实一致性。采集Agent读到的100篇原始网页里,必然存在噪音、过时信息和互相矛盾的表述。如果这些原始内容全部进入写作Agent的上下文,写作Agent很容易写出前后打架的句子。隔离的真正意义,是让每个Agent只看到被加工过、可信且与当前任务相关的那一小部分信息。链条越往下游,信息浓度应该越高,噪音越少。
3.2 隔离的三种落地方式
具体怎么实现隔离,我整理过三种从轻到重的落地方式:
- 会话级隔离:每个Agent维护独立的session/对话历史,Agent之间只通过方法调用的返回值传递信息。这是最简单的一种,适合流水线模式,实现成本极低。
- 存储级隔离:每个Agent访问独立的向量数据库集合或命名空间,只能检索到自己的领域数据。适合需要长期记忆、跨多轮任务的场景。我之前用Qdrant做过按Agent名拆collection的方案,排查问题时非常清爽。
- 白板级隔离:系统有一个共享存储区,Agent只被授权读写某些分区或键值域。公共分区放最终结果,私有分区放过程数据。
我目前的主力方案是**"会话级隔离+共享白板"的组合**:每个Agent的对话历史严格私有;任务完成后的交付物写入白板,供有权限的下游Agent读取。这个组合简单、可控、出问题时边界清楚。
3.3 "共享白板"的正确打开方式
共享白板是上下文隔离里的"例外区域",设计不好就容易又变成一锅粥。我给自己定了三条使用规则:
- 只写结果,不写过程:中间思考、失败尝试、冗余草稿一律留在私有上下文里,白板上只允许出现对外可用的交付成果。
- 按命名空间分区:比如
research/raw_facts、analysis/conclusions、report/final_draft。每个Agent只能写自己所属的分区,对其他分区只有只读权限。 - 带版本和状态:每次写入都附上
version、updated_at、status字段。下游Agent通过status判断数据是否可用,避免读到写到一半的脏数据。
提示:最容易翻车的地方是"让Agent手写一个超长总结传给下游"。这种自然语言接力本质上还是在传递原始上下文,只是换了个位置。正确的做法是让Agent把关键信息结构化,把原文引用或工件ID传给下游;下游需要细节时再按ID去读源数据。隔离的目的不是不传信息,而是只传"该传的那部分"。
4. 协作机制:编排四种模式与消息协议设计
4.1 四种主流协作模式怎么选
拆完任务、划好上下文,就到了Agent之间怎么协作的环节。我整理过四种主流模式,各有各的适用场景。
串行流水线(Pipeline):A完成把输出交给B,B交给C。适合阶段边界清晰、顺序固定的任务,比如"采集→清洗→分析→写稿"。优点是链路简单、好排查,缺点是慢,且任何一个环节卡住整条链就卡住。
管理者和执行者(Hierarchical):一个Manager Agent负责拆任务、派发、收集结果、汇总,下面挂多个Worker Agent。适合任务结构会动态变化的场景。Manager不亲自干活,它更像一个有调度权的路由器。我习惯让Manager只做三件事:分解、派发、汇总,具体执行一律下放给Worker。
黑板模式(Blackboard):所有Agent围绕一个共享区域工作,谁发现新信息就写入,其他Agent被触发后读取并继续推进。适合探索型任务,比如"让多个Agent集体研究一个开放性问题"。难点是触发机制不好设计,容易空转或重复劳动。
对抗/评审模式(Debate/Review):多个Agent对同一结果评审、挑错、修改,循环迭代直到收敛。适合代码审查、内容质检这类准确性要求极高的场景。缺点是成本高,而且如果Agent能力不够,会出现低质量的互相抬杠。
实际项目里这些模式经常混用。比如我的研究报告项目就是:整体流水线、采集环节扇出并行、末尾挂一个质检Agent做评审。别拘泥于选一种模式,按任务树上的结构去组合才是正道。
4.2 消息协议:让Agent之间说"结构化的话"
不管选什么协作模式,Agent之间的消息格式都建议走结构化协议,而不是"用一段话把所有信息说完"。我一般至少约定这几个字段:
| 字段 | 说明 |
|---|---|
| task_id | 全局唯一任务标识,贯穿全链路 |
| from / to | 发送方、接收方 |
| payload | 业务数据,按约定的JSON Schema校验 |
| status | pending / done / failed / needs_input |
| trace_id | 链路追踪ID,用于排查问题 |
有了这套字段,做日志分析、链路追踪、失败重放都很方便。我见过不少项目让Agent直接"把结果用一段话说给下一个Agent听",结果出问题时日志里全是自然语言,根本定位不了是哪一步把数据搞坏的。消息协议是你给Agent系统上的第一个保险丝。
4.3 异常处理与降级路径
Multi-Agent系统真正跑到生产环境后,最常见的三个异常是:Agent输出格式不符合契约(说好返回JSON结果给了段散文)、Agent在失败后进入死循环式重试、下游Agent拿到上游数据却解析失败。
我的应对思路分三层:
- 契约校验层:在每个Agent输出的入口做JSON Schema校验,不合格就自动附加"格式错误反馈"让它再试一次;仍失败就标记失败并上报,绝不放行脏数据。
- 超时与熔断:给每个Agent调用设置超时时间和最大重试次数。超过阈值不再盲目重试,而是把控制权交还给编排层,由Manager决定是重派还是标记失败。
- 降级路径:关键链路连续失败时,主动降级成"单Agent直出"模式。牺牲一点质量,保证整个流程不卡死。
很多Agent框架解决的问题是"怎么让Agent跑起来",但没有回答"跑坏了怎么兜底"。恰恰是这个兜底设计,决定了你的系统是demo还是能稳定跑的生产系统。
5. 实战复盘:行业研究报告自动生成的Multi-Agent设计
5.1 需求拆解与Agent拓扑
我拿一个实际做过的项目完整过一遍设计流程:自动生成某新兴行业的深度研究报告。这个系统从用户给一个行业关键词开始,到交付一份带数据来源、分析结论、格式规范的完整报告。
我把任务拆成了六个Agent,岗位拓扑如下:
| Agent | 职责 | 上游输入 | 下游输出 |
|---|---|---|---|
| 规划Agent | 拆报告结构、定每章要点 | 用户需求 | 大纲JSON |
| 采集Agent | 搜索并抽取原始事实 | 大纲关键词 | 事实卡列表 |
| 验证Agent | 核验冲突数据与来源可信度 | 事实卡 | 验证通过的事实集 |
| 分析Agent | 产出市场趋势、竞争格局结论 | 事实集 | 分析结论JSON |
| 写作Agent | 按大纲组织成文 | 大纲+分析结论 | 章节草稿 |
| 质检Agent | 审校事实一致性、格式规范 | 章节草稿 | 终稿+修改意见 |
协作模式是:规划Agent → [采集Agent(扇出并行)→ 验证Agent] → 分析Agent → 写作Agent → 质检Agent。整体是流水线,中间采集环节做扇出,末尾挂评审。编排层用一段简化的Python逻辑可以写成这样:
def run_pipeline(requirement): outline = planner.run({"requirement": requirement}) fact_tasks = fan_out(collector, outline["keywords"]) facts = validate.run(fact_tasks) conclusions = analyst.run({"facts": facts}) draft = writer.run({"outline": outline, "conclusions": conclusions}) final, review = qa.run({"draft": draft, "outline": outline}) return final每一步的输出都走契约校验,校验不过就返回给原Agent重试一次。
5.2 上下文划分表:谁该看到什么
这是整个设计里我最看重的部分。每个Agent的上下文边界必须提前写清楚,不能含糊:
- 采集Agent:只看搜索关键词和检索结果摘要,看不到报告大纲全貌。这样做是为了避免它"带着结论去找论据"——一旦它知道报告结论是"行业高速增长",它搜索时就会只挑支持增长的资料。
- 验证Agent:只看事实卡和来源URL,不做行业分析,只做来源可信度交叉验证。
- 分析Agent:只看验证通过的事实集,不看原始网页。保证分析基于可信数据,而不是被原始噪音干扰。
- 写作Agent:只看大纲和分析结论,不看事实明细。避免写作时被原始数据带偏,也避免它擅自修改数据。
- 质检Agent:只看成稿和大纲,对照检查事实一致性和格式规范。
这样划分之后,整条链路的信息浓度是逐级上升的:从几十条原始URL,到若干条事实卡,再到几条分析结论,最后变成报告。每个Agent只接触自己该接触的那层,上下文干净,token开销可控,职责边界也清晰。
5.3 踩过的坑和修复手段
系统上线后我踩了三个很典型的坑,每个都值得拿出来说。
第一个坑:采集Agent返回"空结果"却报成功。排查发现它的输出契约里status字段默认值是done——即使没抓到任何事实,它也按成功返回。结果下游单个分析Agent拿着空事实集硬写出了两页看似合理的空泛分析。修复办法是让契约校验强制要求"facts数组非空才允许返回done",同时在编排层加了空结果拦截。
第二个坑:写作Agent悄悄"脑补"数据。写作Agent拿到的分析结论里有"市场规模约为X亿元",它写进正文时擅自改成了更精确的"X亿元,较上年增长Y%",没有任何依据。修复办法是在结论字段里增加confidence和source_id,并明确告诉写作Agent:数据一律照抄,不要润色、不要推断,保持原样。
第三个坑:上下文隔离做得太死,报告前后风格不一致。并行扇出的多个写作子任务分别完成后拼起来,读着像拼凑文章。后来我在共享白板里加了一个"全局风格规范"分区,由规划Agent在启动时写入风格要求,写作Agent运行时都读取这份规范,风格才统一下来。
这三个坑说明一个道理:隔离不是目的,可控才是。什么该隔离、什么该共享,要以信息正确、风格统一、成本可控三个目标来做权衡。
6. 最后几点经验
6.1 别为了多Agent而多Agent
我见过不少项目,任务其实很简单,强行拆成四五个Agent,结果光协调消息就占了大部分token,效果反而不如单Agent加一套好提示词。如果你的任务在单Agent下能稳定完成,那就先别拆。等你真的遇到记忆污染、上下文超限、职责漂移这三个问题之一,再认真考虑拆。
6.2 从"单体+结构化"开始迁移
先做单体Agent,把输入输出协议定义清楚,整体跑通后,再按协议拆分成多个Agent和编排层,迁移成本极低。我几乎所有Multi-Agent项目都是从单体提示词迭代过来的,这条路最稳。别一上来就画一个庞大的Agent拓扑,那是给自己挖坑。
6.3 可观测性是排查故障的命根子
每个Agent的输入输出、token消耗、重试次数、链路耗时,都要打日志。排查"哪个Agent把数据搞坏了"时,一条完整trace比任何脑补推理都管用。我在生产系统里给消息协议加了trace_id之后,定位问题的平均时间从小时级降到了分钟级。
6.4 给Agent认错的权利
在系统提示词里明确允许Agent输出"我需要额外的信息",让它在拿不准的时候主动上报,而不是硬编一个表面合理的答案。这一点在验证类、质检类Agent上尤其好用——它们经常需要判断"这里的证据不足以得出结论"。给Agent一条"认错"的出口,比让它硬着头皮生成一个看似完整实则错误的答案,要划算得多。
如果你正在设计自己的多Agent系统,建议从一个你正在头疼的单Agent任务开始:先画出任务树,再定义每个节点的岗位说明书,然后划上下文边界,最后选协作模式。把这四步走完,你的系统已经比大多数demo型项目扎实了。后面每加一个Agent,都先问一次:它真的有必要独立存在吗?它的上下文边界和消息契约定清楚了吗?回答好这两个问题,你的Multi-Agent系统就能长期稳定地运转。