
1. 为什么要转向Multi-Agent单Agent到底卡在哪1.1 单Agent的天花板不是“智能不够”而是“状态折叠”这两年聊LLM应用绕不开一个词Multi-Agent系统。很多人拿“你是研究员”“你是编辑”“你是评审”这类角色切换来宣称自己在做多Agent但真正跑过复杂任务的人都知道那只是一种提示词层面的“精神分裂”离系统还差得远。我最近在模拟项目X里完整实践了一遍Multi-Agent从原理到开发的全链路用三Agent流水线做“调研—撰写—评审”的自动化内容生产期间踩了不少坑这篇就当一次完整复盘。先说我为什么一开始没有直接用单个Agent硬怼任务。现在的大模型能力确实强一份三五千字的行业分析报告你给它一个提纲它能写出来甚至格式还挺漂亮。但如果你要求它先做事实检索再基于检索结果梳理逻辑再检查数据来源是否可信还要回溯每一步修改过程……单Agent很快就会“精神涣散”。原因并不是模型变笨了而是所有中间状态被强行折叠在同一个上下文窗口里。我用一个生活类比解释这件事你让一个人同时当市场调研员、数据分析师和报告撰稿人他当然也能干但每切换一次任务脑子里都要重新加载之前关于另一个角色任务的信息。如果期间还查了十几次网页、贴了一大堆原始JSON回来上下文窗口很快被无关内容占满。最终写出来的报告前面几章还行越到后面越容易开始自说自话甚至把前面已经废弃的结论又拿回来用。这不是模型能力不足而是“状态折叠”带来的必然结果。LLM本身的注意力有限当任务目标、历史消息、工具返回结果、修改意见全部堆在一个对话流里时真正重要的信息会被大量低信噪比内容稀释。Multi-Agent系统解决的核心问题就是把一个复杂的任务状态拆开分发到多个独立上下文里让每个Agent只需要关心自己那一亩三分地。1.2 多角色提示词不等于Multi-Agent我见过不少分享说自己在系统里定义了“项目经理、程序员、测试员”三个Agent实际上代码里只是三段不同的System Prompt然后让同一个模型一顿输出。这种做法当然也能产出看起来分工明确的内容但我不认为它算Multi-Agent系统。关键区别在于多个角色提示词共享的是同一个上下文。你让模型先扮演研究员它输出了十条结论。接着让它切换成撰稿人这十条结论确实还在上下文里但研究员身份的临时草稿、它纠结过但放弃的备选方案、它搜索过程中遇到的无效信息也全在里面。后续的“撰稿人”分不清哪些是最终结论哪些是过程中的垃圾信息。真正的Multi-Agent系统里每个Agent有独立的上下文、独立的工具权限、独立的输出空间。Agent之间通过结构化消息交互只传递必要结论不共享原始思考过程。这样带来的三个实际好处隔离性一个Agent的混乱状态不会污染另一个Agent的干净上下文。链路可审计每一条结论都能追溯到是哪个Agent、用了哪些工具、在哪一轮产出的。可针对性优化调研Agent质量不行就只换它的提示词/模型/工具不需要重跑整条链路。这三个好处就是“多角色提示词”永远做不到的。1.3 什么场景才真正需要Multi-Agent也不是所有任务都得拆成多Agent。我自己的判断标准很简单如果任务可以一次性描述清楚且不需要分阶段验证那单Agent就够了。比如让模型写一篇产品介绍、做一段代码注释、翻译一篇文章这些任务用Multi-Agent纯属给自己增加调试成本。需要上Multi-Agent的场景一般具备以下特征中的一个或几个场景特征说明典型例子角色技能差异大不同阶段需要完全不同的领域知识和输出格式检索事实、写正文、做质量审查、排版输出需要内部验证闭环一个角色产出的结果要经过另一个角色审查才能放行代码生成后必须经过代码审查Agent检查边界条件需要并行探索同一时间可以尝试多条候选路径再汇总并行生成5个方案让评审Agent选择最优项结果必须可追溯最终结论要求能追溯到每一步的证据来源行业研究报告、投资分析摘要另一个判断信号是“当你发现单Agent的修改过程开始互相打架”。比如你让它“写一个章节然后检查并修改”它改着改着会把前面正确的部分也改掉因为同一个上下文里同时存在“当前版本”和“历史版本”模型已经分不清哪个是基准了。这时候引入多Agent通过独立的Reviewer来反馈比反复让同一个Agent自己改自己的效果好得多。2. Multi-Agent系统的核心设计三件套消息、记忆、收敛2.1 消息协议要像“接口文档”而不是“聊天记录”很多第一次设计Multi-Agent系统的人最自然的想法是让Agent之间“像人一样聊天”。于是每个Agent的输出都是自然语言段落然后直接把这段文字传给下一个Agent。跑两轮就发现下游Agent根本不知道哪句话是结论、哪句话是推理过程、哪句话是随口提的注意事项。正确的做法是Agent之间的通信走结构化消息协议而不是靠自然语言聊天。我在模拟项目X里定义的消息格式不算复杂大致长这样{ source: researcher, target: writer, message_type: findings_report, payload: { topic: 大模型推理优化技术综述, key_points: [ {point: 稀疏注意力可以降低长文本推理开销, source_id: ref-003} ], missing_info: [近三年的工程化落地案例] }, timestamp: 1712304000000 }你不需要完全套用这个结构但至少要保证几个字段是稳定的来源角色、目标角色、消息类型、核心载荷、时间戳。有了这个结构后续做日志排查、消息过滤、权限校验才有抓手。如果所有Agent之间传的都是“一段优美但无结构的话”那你就只能靠LLM自己蒙出了问题也没法定位是哪个字段丢了。我建议在架构层面把“消息协议”当成一份内部接口文档来维护每个Agent声明自己消费哪些消息类型、产出哪些消息类型。这就类似于微服务之间的API契约谁违反了契约谁就背锅。这比让Agent之间“自由聊天”可控得多。2.2 记忆分层共享黑板 私有上下文Multi-Agent系统里“记忆”是个大坑。Agent不是人没有天然的记忆分层所有信息都靠开发者决定往哪里塞、传不传。我的做法是两层共享黑板Shared Blackboard和私有上下文Private Context。共享黑板所有Agent都能读取的全局状态只放“已经收敛的结论”。比如调研Agent最终整理出的5条核心事实、Writer提交的正文版本、Reviewer给出的最终评审意见。私有上下文每个Agent自己的完整工作空间。比如Researcher检索时看到的原文片段、它排除掉的无效结果、它自己中间整理但是后来又推翻的草稿。这些内容绝不进共享黑板。这样做的好处是显而易见的下游Agent不会被研究过程中的噪音干扰。它有足够的上下文理解“这个调研结论是怎么来的”但不需要看“调研Agent翻过的每一篇网页”。经验教训是几乎所有上下文污染事故都来自开发者图省事把Agent的完整中间输出直接塞进全局状态。比如让Researcher返回search_results字段里面带着20条检索结果的标题、URL和摘要原文。Writer拿到这个字段后LLM会忍不住去读那20条内容最终写出来的正文反而偏离了调研Agent精心提炼的结论。正确的做法是只让Researcher返回conclusion和evidence_list原始检索结果留在私有上下文里。2.3 工具权限Agent能调用的API必须显式授权Multi-Agent系统天然伴随一个风险你会本能地给每个Agent都挂上工具结果权限边界越来越模糊。比如调研Agent负责搜索它可能不需要发邮件写作Agent负责生成文本它可能不需要调用数据库写入接口。如果所有工具对所有Agent开放那么一个被提示词带偏的Agent就可能在错误时机做错误操作。我的建议是建一个“工具注册表”每个Agent在启动时声明自己允许调用的工具集合。架构上就是一个简单的映射表AGENT_TOOLS { researcher: [web_search, fetch_url, vector_db_query], writer: [drafting_tool, citation_formatter], reviewer: [fact_check_db, read_reports] }这套设计在模拟项目X里帮我挡住了一次事故有一版印刷稿的Writer Agent因为提示词引导问题生成了“需要向客户发送交付邮件”的指令但由于该Agent根本没有邮件工具权限系统直接拒绝了这步动作而不是真的把邮件发出去。权限隔离不花多少成本但能避免很多无法挽回的副作用。2.4 收敛判定协作不能无限循环Multi-Agent系统最常见的失控方式不是“跑不起来”而是“跑不停”。Reviewer总是不满意Writer反复改改完Reviewer又有新意见……这种循环在对话里看似在推进实际已经陷入死循环每一轮都在烧token。收敛判定至少要包含三层控制最大循环次数。我一般设为2到3轮超过就直接走兜底逻辑。阶段性质量门禁。比如Reviewer看完草稿后必须输出一个score字段和一组具体的modification_requestsscore超过阈值且修改意见数量大于0时允许继续score低于阈值直接判“返回给Writer重写”或“标记为低质量但放行”避免卡死。全局预算上限。给整条流水线设置一个总token预算超过预算就冻结所有Agent并输出当前最优状态。如果你的Agent之间是自由对话模式收敛条件会格外难写。因为自由对话里的“满意”是模糊的两个Agent可能围绕同一件事反复确认。所以更稳妥的方案是给最终裁决权给一个专门的“收敛Agent”或“路由函数”——它不参与内容生成只看结构化状态里有没有达标的标记。3. 从零跑通一个“调研—撰写—评审”三Agent流水线3.1 角色设计与目标拆解我用一个具体例子来说明整个落地过程让系统自动生成一份主题为“大模型推理优化技术综述”的长文。传统做法是一个人写完整篇但我们拆成三个AgentResearcher调研者负责检索和整理事实。输出必须是结构化的findings包含“结论”“证据来源”“缺失信息”三个子字段。它不负责写正文。Writer撰写者基于findings写正文正文可以分章节但每一处关键结论都要引用调研Agent给出的source_id。Reviewer评审者不直接改稿只输出评审意见。意见必须是可执行的、具体的不允许出现“内容不够充实请优化”这种无法量化的反馈。这个角色拆解其实就是前面说的“状态隔离”调研Agent的草稿、检索记录不会污染WriterWriter的中间版本不会影响Reviewer的判断。每个Agent只管自己的那一层状态。3.2 为什么我用LangGraph做编排选编排框架时我犹豫过三条路直接用LangGraph、用CrewAI、或者干脆自己写一套状态机。模拟项目X的结论是如果主流程是明确的流水线加条件分支LangGraph这类图编排框架最合适。不选CrewAI的原因不是它不好而是它的角色定义更偏向“组队对话”每个Agent之间靠任务委派和数据传参协作适合open ended的探索但对“固定步骤加评审循环”这种流程反而要写不少黑魔法才能精确控制。AutoGen类似它的核心交互方式是对话轮次驱动适合让Agent讨论方案不太适合“一条链做完再统一评审”的结构。LangGraph的优势是它把状态转换过程显式地表达成一张有向图每个节点是一个Agent或函数每个边的条件由代码决定。这样流程可读性很强也方便做循环上限和状态回退。这不是说LangGraph是唯一答案。如果你的业务本身就是一个“Leader不断分派任务多个Worker并行干活然后回报”的模式那CrewAI或自研的任务队列可能更自然。选型问题放到第5章细聊。3.3 核心代码片段状态、节点与路由我在模拟项目X里用的简化代码长这样依赖库就不展开了只展示关键结构from typing import TypedDict, Annotated from operator import add from langgraph.graph import StateGraph, END class AgentState(TypedDict): topic: str findings: list draft: str review: list rewrite_count: int max_rewrites: int accepted: bool def research_node(state: AgentState) - dict: topic state[topic] findings research_agent.invoke({query: topic}) return {findings: findings} def write_node(state: AgentState) - dict: draft writer_agent.invoke({findings: state[findings]}) return {draft: draft, rewrite_count: state.get(rewrite_count, 0) 1} def review_node(state: AgentState) - dict: review_result reviewer_agent.invoke({draft: state[draft]}) return { review: review_result[modifications], accepted: review_result[score] 0.8 } def should_rewrite(state: AgentState) - bool: if state[accepted]: return False if state[rewrite_count] state[max_rewrites]: return False return True builder StateGraph(AgentState) builder.add_node(research, research_node) builder.add_node(write, write_node) builder.add_node(review, review_node) builder.set_entry_point(research) builder.add_edge(research, write) builder.add_edge(write, review) builder.add_conditional_edge( review, should_rewrite, {True: write, False: END} ) app builder.compile()这段代码的逻辑很直观先调研再写作再评审。评审不通过且重写次数没到上限就回到写作节点继续改否则终止。max_rewrites默认设成2超过次数无论评审分数如何都直接输出当前版本。这里有个容易忽略的细节rewrite_count的维护必须在状态里完成而且最好用Annotated加operator.add来合并累加而不是像普通字典一样直接覆盖。我当时第一次跑的时候每次回到write节点都用同一个变量覆盖导致计数一直停留在1评审永远不通过还循环了三次才发现。3.4 跑通之后怎么评估评估一个三Agent流水线不能只看最终文章质量还要看迭代次数从run开始到收敛的平均轮数。一般2轮以内正常超过4轮说明评审意见质量有问题。修改点采纳率Reviewer提出的意见里有多少条被Writer真正落实。如果采纳率很低大概率是意见太模糊。引用完整性Writer生成的正文里关键数据是否有对应的source_id且这些source_id能不能在Ref里找到。人工抽检通过率抽检正文的事实准确性观察是否出现“调研内容被写歪”的情况。模拟项目X里连续跑了20个不同topic平均每篇会经历1.8轮评审抽检通过率从单Agent版本的55%提升到了82%。代价是延迟和token平均翻倍但这笔开销换来了稳定性和可追溯性在业务上是值得的。4. 实战最容易翻车的四个场景我从调试日志里看到的真相4.1 上下文污染下游Agent读了不该读的内容第一个坑是上下文污染。前面提到过Researcher如果返回全量检索结果Writer会被大量原始信息带偏。我实际遇到的情况更严重有一版实现里Researcher为了“方便后续核对”把搜索结果的前100条URL和摘要全部放进共享状态。Writer写正文时LLM在几十条摘要里挑了几个看似有关但实际过时的数字导致正文出现内容与调研结论不一致的问题。修复方案很简单共享状态里只允许传递结论性数据。我给Researcher的返回结构做了约束key_points最多5条每条必须有source_id多余内容全部留在私有上下文。调试时用日志检查每个Agent实际消费到的state字段大小如果某个字段超过预期基本可以判定是污染源。4.2 死循环与重复调用预算失控的根因第二个坑是死循环。Reviewer给Writer提了一堆意见Writer改了Reviewer仍不通过理由是“内容可以更好”。这类无法量化的意见会无限循环。我一度以为是模型太笨后来看了日志才发现Reviewer返回的是自然语言长文Writer从中提取不到“具体修改动作”于是只能把整段重写一遍自然越改越差。修复的关键不是让Reviewer“更聪明”而是约束它的输出结构。我要求Reviewer返回如下JSON{ score: 0.7, issues: [ {type: fact_check, description: 第2章引用的推理延迟数据与source_idref-003不一致, suggestion: 删除或修正这处引用}, {type: coherence, description: 第3章与第2章的体系分类存在重复, suggestion: 合并两章第2节} ] }只有issues数组里的意见才会被透传给Writer。这样Writer的每次修改都有明确目标不会做“全文重写”这种无意义动作。另外循环上限必须写成边界条件而不是“等到满意为止”否则任何一次意见漂移都可能让预算失控。4.3 幻觉交叉传染一个Agent的错误被另一个Agent放大第三个坑是幻觉交叉传染。单Agent也会幻觉但多Agent系统里的幻觉更隐蔽调研Agent如果检索不到信息会在输出里硬编一个“20%的性能提升”之类的数据。Writer拿到这个数据后会主动给它包装一套看似合理的解释。Reviewer审稿时又因为数据已经被写进了正文语境判断其“看起来合理”直接放行。这就是典型的交叉传染错误经过多个Agent的加工后可信度反而被抬高了。我的对策分两层第一强制事实溯源。Researcher的key_points里如果没有source_id整条流水线直接报错不许进入下一步。第二Reviewer要抽查证据。Reviewer的fact_check逻辑不只是看文本还要拿着source_id去缓存里拉取原文摘要做比对。如果发现某个数字在原文里根本没有就生成“无法验证”的issue。这一步可以拦住大部分交叉幻觉。4.4 成本与延迟每多一个Agent性能问题不只是相加第四个坑是成本。很多人低估了Multi-Agent系统的链条长度。一个调研Agent可能内部已经循环了三轮每次循环里又调用了若干次搜索工具Writer一次生成可能输出几千个tokenReviewer也不便宜。整条链路跑完开销经常是单Agent的五到十倍。我建议上线前做三件事给每个节点单独设max_token限制避免长文本失控。尽量复用缓存。同一个topic下调研结果、评审历史都可以缓存短期重复测试时成本能砍掉一大半。模型分层。调研Agent和Reviewer不一定需要用最强的模型可以换成速度快、便宜很多的模型只有生成最终正文的Writer用强模型。这样质量和成本能取得一个更好的平衡。5. 框架选型与自研编排边界在哪里5.1 三条路线的真实差异现在市面上常见的选择有三条路线图编排框架以LangGraph为代表、对话协作框架以AutoGen和CrewAI为代表、自研编排。它们不是互相替代的关系而是各自适合不同形态的任务。路线核心模型适合任务需要付出的成本图编排框架把Agent工作流定义成有向图由代码控制流转和分支流水线型任务、评审循环、状态机复杂的流程需要写状态定义不适合无预设流程对话协作框架让多个Agent像群聊一样讨论由对话轮次推进开放探索、方案辩论、灵活分工收敛困难需要额外控制“聊到什么时候为止”自研编排自己写状态机、消息队列、工具注册表深度定制、已有框架满足不了、内部需强审计工作量大但控制力和可观测性最强我在模拟项目X里最终用了图编排框架核心原因是我们的流程足够固定调研、写作、评审三个节点的顺序和条件分支都很清晰。如果换成开放式的“让两个Agent自由讨论出一个方案”我可能会选对话协作框架。5.2 我的选型公式给一个简单粗暴的选型建议任务有明确步骤且步骤之间只有条件跳转优先考虑LangGraph这类图编排。任务本身是“让多个Agent合作探讨一个没有标准答案的问题”优先考虑AutoGen/CrewAI。你需要严格控制每一步的输入输出结构并且有独立的调试平台可以考虑自研。只想快速验证Multi-Agent思路是否行得通用CrewAI的Role配置跑一个Demo两小时就能出结果。这里我想强调一点框架只是手段理解消息、记忆、收敛这三个核心设计才是关键。我见过用自研代码写得乱七八糟但效果还不错的系统也见过框架堆了一堆概念但跑不出稳定结果的项目。差别不在工具在于设计者有没有想清楚为什么需要隔离、怎么收敛。5.3 上线前必须补的三件事最后聊一点运营层面的经验。Multi-Agent系统从demo到上线不只是把代码部署到服务器那么简单。至少三件事必须在发版前做完成本上限在系统外部加一个硬性上限比如单任务总费用超出就强制终止并告警。不能只靠Agent内部的max_rewrites。失败降级如果多Agent链路中途出问题是否能优雅降级到单个Agent直接生成结果我一般会在最后兜底路由里放一个最简单的单Agent生成逻辑虽然质量差一点但至少不会让整个业务不可用。可观测性每个Agent的输入输出、所用工具、token消耗、耗时都要有结构化日志。Troubleshooting时如果只看到“生成了这段文本”而没有上下文排查效率会非常低。在做模拟项目X的后期我把每次运行的所有消息都落成JSON日志然后用检索工具直接搜某一条source_id的完整链路。定位问题的速度比早期“盯着终端输出改代码”快得多。最后再分享一个我反复踩坑后总结的小技巧在Agent的输出JSON里永远加一个confidence字段让Agent在无法确定的地方显式标记置信度。Reviewer看到低置信度的内容会自动触发事实核查而不是默认相信。这个字段帮我拦住了不少隐蔽的幻觉交叉传染你可以直接抄进自己的协议里。