
按理说做到第七期大家应该已经对状态图、节点、边这些基础概念很熟了但前阵子我复盘一个实际项目时发现真正把人卡住的往往不是某个 API 不会用而是对整个工作流的组织方式没想清楚。尤其是当你手里捏着一个能干很多事的大模型却越用越别扭的时候——那大概率不是模型的问题而是你试图让一个智能体包办所有事。这一篇我拿一个具体的多智能体协作场景来展开模拟一个需要外部知识库支持的智能调研任务。一个协调中枢负责拆任务几个专职智能体分别做资料查证、内容整合、质量复审全部跑在一套 LangGraph 编排图上。你会发现LangGraph 做 AI 工作流编排的真正价值不在于“把多个提示词串起来”而在于它给了你一套表达分工、并行、条件转移和状态沉淀的语法让原本不可控的智能体协作变得可以设计、可以调试、可以复盘。1. 为什么要把单 Agent 拆成一支“多人团队”1.1 单智能体的瓶颈状态膨胀与角色混叠先说说我为什么开始拆。之前有个需求很简单输入一批文档让模型根据外部知识库生成一份带出处的调研综述。最初我只做了一个 Agent把“检索资料、归纳要点、撰写报告、自查引用”全塞进一套系统提示词里。坏处刚开始不明显等文档规模上来之后全都暴露了。第一是上下文窗口被无效信息挤占。单个 Agent 为了兼顾检索和写作必须把检索到的候选片段全部留在上下文里而这些片段几十条加起来往往比正稿还长真正用于推理的空间反而变小了。第二是角色混叠。你让同一个模型既当检索员又当审查员它会倾向于“自己的产出总是对的”自查环节形同虚设。第三是权限没法收敛。工具一旦绑定在唯一一个 Agent 上就等同于所有能力对所有任务开放你很难在流程上限制“这一步绝对不能调用外部写入工具”。这些问题的本质不是提示词写得不好而是职责没有被结构化。一个人同时干五件事的时候管理精力会指数级上升换成五个各司其职的人每一件事的上下文和验收标准都变简单了。1.2 分工之后的收益可观测性、独立扩展、容错拆成多智能体之后我拿到的最直接收益是过程可视化。单 Agent 模式下模型心里在想什么只能靠日志猜测拆开之后每个节点产出的中间结果都会落到共享状态里我可以随时检查“检索阶段到底返回了什么”“分析节点有没有遗漏关键条目”出问题第一时间就能定位到哪一步。收益二是单个环节可以独立迭代。比如检索节点最终决定用一个更便宜的模型来负担因为它的任务只是“从知识库里找条目并给出摘要”不需要顶尖推理能力而最终的整合节点继续使用能力更强的模型。这种“按角色分配不同模型、不同温度、不同工具列表”的做法在单 Agent 里几乎没法干净地实现。收益三是容错边界清晰。某一次任务中调用外部知识库超时以前整个 Agent 的会话就废了现在我可以单独给检索节点配置重试和降级逻辑其他节点根本感知不到底层出了什么岔子。1.3 什么时候不该拆多智能体不是银弹当然也得泼盆冷水。如果任务本身只有两步比如“读一段文本然后润色”强行拆成两个 Agent 只会白白增加延迟和 token 消耗。多智能体架构适合的是存在明显子任务边界、子任务之间存在依赖或并行关系、且每一步都有可验证产出的场景。我在设计前会问自己三个问题拆出来的每个角色是否拥有独立且不可被其他角色替代的工具或信息面每个角色完成后的产出是否能在状态里被清晰地记录和验证整个流程是否真的需要并行执行还是线性跑一遍就够了如果把这些问题答清楚了还觉得该拆才值得动手。2. 团队的原型设计角色、共享状态与外部知识库对接2.1 角色定义调度中枢、检索员、整合员与质检员这次示例项目里我把智能体团队设计成了四个角色角色核心职责持有工具产出物调度中枢拆解用户任务、判断下一步交给谁任务规划器子任务列表与执行顺序检索员查询外部知识库、整理候选条目知识库检索工具带来源编号的资料条目整合员综合资料撰写最终调研报告无外部工具仅读状态报告初稿质检员核对引用与观点一致性决定是否退回知识库复核工具通过或退回意见调度中枢是一个值得琢磨的设计。它本身不一定非要是一个完整的大模型调用在很多场景里一个轻量级的分类模型甚至一组规则就能完成路由。但既然这一期强调的是架构表达我选择用一个大模型节点来做意图拆解因为它还需要处理用户输入中那些含糊的表达规则路由很容易被绕晕。2.2 状态 Schema 设计共享工作区里到底放什么LangGraph 的核心是全局状态对象团队里所有节点读写同一份状态所以状态怎么设计直接决定协作是否顺畅。我见过最常见的错误是把状态当成“聊天消息列表”的简单包装结果所有中间结果都堆在消息里越往后越乱。这次我用的状态结构大致是from typing import Annotated, TypedDict, List import operator class ResearchState(TypedDict): user_request: str subtasks: Annotated[List[dict], operator.add] # 调度中枢拆解出的子任务 source_items: Annotated[List[dict], operator.add] # 检索员产出的资料条目 draft_report: str # 整合员产出的报告初稿 review_verdict: str # 质检员的结论 iteration_count: int # 当前修订轮数 messages: Annotated[List[dict], operator.add] # 各节点的记录性输出关键有两点一是用Annotated[List[dict], operator.add]表达“追加语义”多个节点可以并行往里加条目而互不覆盖二是把“状态”和“消息历史”分开。子任务清单、资料条目、报告草稿这些结构化字段才是节点真正决策要用到的信息messages只是留给人工排查时看的流水账两者不要混为一谈。2.3 外部知识库接入为什么不能让每个智能体各自连一遍知识库的接入方式也是个常见翻车点。许多人会顺手给每个 Agent 都挂上“知识库检索”工具看起来很方便实际上问题很大多个节点同时检索会重复消耗额度更麻烦的是不同节点对“相关资料”的理解不一致导致整合员拿到的条目来源五花八门引用格式都对不上。我的做法是只把检索工具下放给检索员节点。检索员统一把外部知识库返回的条目做清洗、去重、编号存进source_items字段。后续的整合员和质检员都不直接访问原始知识库而是基于这份清洗后的结构化资料工作。这样知识库的连接只需要维护一个节点后续如果要切换检索策略比如从关键词检索换成向量检索也只影响一个角色。2.4 工具注册的边界哪些能力下放哪些能力锁死在团队协作里工具权限不是“能给就给”而是要遵循最小权限原则。我通常只给以下三类工具只读型检索工具查知识库、查文件、纯计算型工具解析日期、计算统计量、以及流程必需的写工具比如把最终报告保存到指定目录。任何可能产生外部副作用的工具都会被锁在单独的管理节点里而不是让普通角色持有。这样做的好处是安全边界很清晰即使某个角色的提示词被误导它能造成的破坏也被限制在只读范围内。多智能体系统的不可控性很大程度上可以通过工具权限来收敛。3. LangGraph 编排机制分支、并行与条件路由的实现要点3.1 核心节点拓扑从线性到有向图的转变很多教程会从“节点串成链”开始教但那其实是退化的图。编排多智能体协作至少要画出这样的有向图结构入口节点接收用户请求调用调度中枢进行意图拆解拆解完成后通过条件边判断如果用户只是简单查询直接走轻量回答路径如果需要调研进入检索阶段。检索阶段可以并行展开多个检索子节点各自查询不同子主题汇聚后再交给整合员。整合员产出初稿后质检员复审如果复审不通过流程会回到整合员节点进行修订并记录修订次数。这个图跑起来之后你会发现“线性”只是它的一种特殊情况。真正好用的编排一定是带分支、有循环、能并行的。3.2 并行执行让不同子任务同时跑LangGraph 支持多个节点并行执行。方式是让一个节点连接多条出边指向多个下游节点这些节点在同一轮执行时会被并发调度。我在这里用了一个真实的“扇出/汇聚”模式调度中枢拆解出三个子主题后三个检索子节点各自负责一个子主题带着独立的关键词去访问外部知识库。这些检索子节点互不依赖因此可以并行。汇聚时它们通过operator.add把各自的资料条目追加到source_items里顺序不重要因为整合员只关心汇聚后的完整集合。并行看似简单真正需要注意的是汇聚节点的等待语义。LangGraph 的机制是上游所有并行分支都执行完毕后下游节点才会触发。也就是说你不用自己写“等待所有任务完成”的代码框架已经保证了下游节点只会看到完整的汇聚结果。这个特性让并行分支的代码保持干净不需要额外的人工同步逻辑。3.3 条件边与路由函数如何判断下一步交给谁条件边是编排的灵魂。前面提到调度中枢决定下一步交给谁本质就是一条从“调度中枢”节点出发、终点不同的条件边。路由函数可以直接基于状态里的结构化字段做判断而不是去“读心”。比如我会写这样的逻辑def route_after_scheduling(state: ResearchState) - str: if any(urgent in st.get(priority, ) for st in state[subtasks]): return quick_answer return research_branch再比如整合员之后接质检员质检员的 verdict 决定是否退回def route_after_review(state: ResearchState) - str: if state[review_verdict] approve: return finalize if state[iteration_count] 3: return finalize # 不能无限修订 return integrate_again注意这里我把“达到最大修订次数作为最终出口”的路由也写进去了。任何带循环的图都必须在图上设置明确的退出条件否则一遇到边缘情况流程会陷入自我循环。3.4 图内循环与上限控制避免“对话死循环”多智能体流程里最常见的死循环是质检员永远不满意、整合员永远改不完。单 Agent 模式下模型可能会因为上下文里的对话惯性一路循环下去但 LangGraph 是显式的图执行我可以通过两层机制锁死它。第一层是上文的显式路由判断iteration_count达到上限后强制进入终态。第二层是传入recursion_limit这是整个图执行的硬边界。任何一条执行路径的节点调用次数超过这个上限运行时会抛出异常防止流程无限空转。我一般把iteration_count的业务上限设成 3而把recursion_limit留出余量比如设为 20 甚至 50。这个余量要够一次正常复杂任务跑完可一旦出现异常循环又会及时爆炸让你发现。4. 状态管理与持久化的几个深坑4.1 Reducer 的设计谁在累加数据谁在覆盖数据状态字段的行为由 reducer 决定这是 LangGraph 和普通字典最大的不同。没有声明 reducer 的字段每次节点写入都是覆盖式更新声明了operator.add的列表字段更新是追加式的。这个设计要配合语义使用source_items和subtasks是累积型数据来自不同节点的产出需要合并所以用operator.adddraft_report和review_verdict是单一型数据只保留最近一次写入所以不加 reducer。我踩过的坑是把一个本该“单个最新值”的字段设计成了追加列表结果整合员每修订一次报告旧版本就全堆在状态里不仅占 token还容易让路由函数读到过期结果。反过来把“资料条目”设计成了覆盖字段几个并行检索子节点跑完只剩下最后一个节点的产出前半程的工作全白干了。Reducer 的选择不是语法细节它代表你对数据模型的最终决策。4.2 Checkpointer 与断点恢复关键节点必须可回放团队协作流程一旦跑起来人工干预是常态而不是异常。LangGraph 的 checkpointer 机制允许在指定节点暂停执行、检查状态、甚至修改状态后再恢复。我把断点设在三个位置调度中枢完成之后、质检员完成之后、最终报告输出之前。这样做的目的是给人工留出“闸门”。比如调度中枢拆出的子任务列表不符合预期我可以在执行检索之前直接修改subtasks字段然后从暂停点继续而不是杀掉整个进程从头再来。实现上只需要配置好持久化后端并在图执行时传入thread_id。同一个线程内的执行历史会被完整保存这让“上次跑到一半这次接着跑”成为可能。在实际项目里长耗时任务几乎离不开这个能力。4.3 跨节点通信不该直接在消息列表里塞系统指令我在 2.2 里强调过消息列表只是流水账这里再补充一个操作层面的经验节点之间的信息传递要走结构化字段不要靠改动系统提示词或插入消息文本。我们很容易写出这样的实现整合员节点从messages里找最后一条用户消息把它包装成新的提示语。短看没问题但一旦并行分支存在messages的追加顺序是不确定的根本无法保证“最后一条消息”就是你想要的上下文。正确做法是任何需要跨节点传递的决策信息在写入时就放进独立字段比如subtasks、source_items。节点只从这些字段取数完全依赖消息列表的节点注定会在复杂流程里翻车。4.4 并发写同一个状态字段的冲突处理并行节点写共享字段是另一个容易出事的角落。虽然operator.add解决了“追加合并”的问题但如果你两个并行分支各自都对同一个普通字段做覆盖式写入最终谁能留下完全取决于调度顺序结果不可预测。遇到这种需求有两种处理思路。第一种是拆分字段给每个并行分支分配独立的字段槽位最后再用汇聚节点统一合并。第二种是收敛写权限并行分支只读公共字段把写操作集中在汇聚节点里执行。我倾向于第二种因为它的状态流转更清晰并行阶段只负责产出中间结果合并逻辑集中在一处也好做单元测试。5. 真实跑起来的调试方法论与性能观察5.1 用状态快照还原每一步决策多智能体流程出问题时单看日志往往不够因为日志是一连串字符串很难定位到具体某个节点写入的中间数据。我的调试习惯是在关键节点开启状态快照把每次执行完之后的完整状态导出来看。具体做法很简单在图的回调或节点返回时把state序列化成 JSON 存档文件名带thread_id和节点名。出问题时我可以直接从“调度中枢刚拆完任务”这个时间点开始回放看它是怎么判断的、检索结果是什么、整合员读了什么、质检员基于什么结论打回的。这种“状态时间线”比任何自然语言日志都更接近问题本质。5.2 常见失败模式及应对跑了几十次之后我总结出四个高频失败模式失败模式表现应对手段检索结果噪声大资料条目太多、整合员抓不住重点限制单节点返回条目上限并让检索员在返回前做一轮轻量过滤质检标准漂移同一批内容有时通过有时退回给质检员提供显式的核对清单要求逐项输出判断理由修订不收敛每轮都改但改不到点上把修订差分限制在“仅修改被质疑部分”并设置最多三轮外部工具超时检索节点频繁失败在节点内部封装重试与降级先重试两次再降级为更宽泛的关键词查询每一项的根因都不完全一样但共同点是不要把智能体当黑盒要给每个节点定义明确的失败出口。比如检索节点连续失败三次后不是直接报错而是返回一个标记“资料不全”的占位结果让调度中枢决定是换一种检索策略还是直接生成带有局限说明的报告。5.3 延迟、Token 消耗与预算控制多智能体编排不是免费的。一个带两个并行分支的调研任务总 token 消耗可能是单 Agent 方案的两倍以上。这也正常毕竟上下文更集中、步骤可追溯。但如果不加控制成本会很快失控。我的控制手段有三项按角色选模型检索员用便宜快速的模型只有调度中枢和整合员用高能力模型限制上下文里传递的资料规模检索员返回前先做摘要压缩只保留与任务最相关的字段在状态里记录累计消耗每跑完一个阶段比对预算超了就提前终止进入人工处理。实测下来并行执行虽然增加了单时刻的资源开销但因为整体耗时显著下降比如三个子主题并行检索耗时接近单个子主题对实时交互场景的价值很大。关键是要把 token 消耗纳入状态设计而不是事后看账单。6. 系列走向从“团队协作”到“可插拔角色平台”6.1 动态注册新角色把角色变成配置而不是代码这一期做完之后我还原了一个重要问题角色不能写死在代码里。每加一个新角色就要改路由函数、改节点类、改状态字段这违背了使用图编排的初衷。更合理的思路是把角色定义成配置角色名称、系统提示词模板、可调用工具范围、默认模型、输入输出字段映射全都放进一个配置结构里。图的结构仍然由代码控制但“这个角色是不是存在”“它被路由到哪个环节”由配置驱动。比如想增加纠错角色我只需要在配置里定义它并在路由表里加一条分支规则图的整体骨架不用变。这种做法的价值在于它让多智能体系统的“演进成本”大幅下降。运营同学可以调整角色分工策略而不需要会写代码开发者只需要维护那几张配置表。6.2 与评估体系结合多智能体团队的回归测试多智能体系统的质量评估比单 Agent 更难因为整体输出质量取决于多个节点的协作效果。我目前采用的是分层评估先对单个节点做单元指标检索员看召回率、整合员看文本质量评分、质检员看重合率再对端到端流程做综合指标答案完整度、引用准确率、修订收敛率。分层评估的意义在于它能帮助定位“到底是哪个环节坏了”。如果单节点指标都正常但端到端指标崩了问题大概率出在节点之间的状态衔接上这时候再回去检查字段设计和路由逻辑。6.3 我的下一步实验方向沿着这个成果我下一步想尝试把一个“工具型智能体”和一个“决策型智能体”彻底分开决策型只负责判断任务意图、拆解步骤、校验结果工具型只负责执行具体操作并返回结构化结果。这样一套基于事件驱动的协作框架也许可以支持更复杂的异步工作流。另一个方向是给角色加上“记忆分区”。目前共享状态是全局的但某些角色的中间结论其实不需要被其他角色看到。给状态增加分层可见性会让我们离开“全局共享一切”的简单模式更接近真实组织里的信息流边界。说到底这套团队协作架构能不能跑好一半靠 LangGraph 的编排能力另一半靠你对自己任务结构的理解。先把角色的职责边界画清楚再去写图这一期内容才算真正落地了。