LangGraph4j多智能体编排实战:Supervisor模式核心组件与状态流转填坑指南
打开编辑器之前我先说一句这不是一篇科普文这是一篇填坑实录。网上关于 LangGraph4j 的资料少得可怜中文社区里 Multi-Agent Supervisor 的落地案例更是稀缺大部分教程还停留在Hello Agent的阶段。我花了两周时间啃源码、调试状态流转最终把一个带 Supervisor 的三智能体系统跑通了中间踩过的坑、绕过的弯我觉得值得写出来。如果你正准备在 Java 技术栈里做多智能体编排这篇文章应该能帮你省下不少试错成本。1. 为什么需要一个 Supervisor多智能体不是“多就是好”1.1 单 Agent 的边界在哪里先用一个具体场景说清楚问题。假设你要做一个客服工单系统用户提交的问题是我的订单延迟了想退款但又怕影响会员等级。单 Agent 拿到这句话要么把它当作退款问题处理要么当作物流问题处理模型一犹豫结果就容易两头不靠。我最早的做法是给一个 Agent 塞进全部工具查订单、算退款、查会员规则、发通知看起来什么都能干实际上 Prompt 越长模型越容易在工具选择上犯迷糊而且每次调用都要把所有工具描述和规则塞进上下文费用和延迟都上来了。本质上是职责没有拆分。一个模型窗口里同时塞下多种专业知识和多种工具调用规范它的注意力是会被稀释的。这就像让一个全科医生同时做心外科和皮肤科的活不是不能干而是效率和质量都不稳定。单 Agent 的边界就在这上下文有限、职责耦合、难以横向扩展。1.2 简单多 Agent 组合的坑拆成多个 Agent 是不是就万事大吉我一开始也这么想拆了客服 Agent、订单 Agent、退款 Agent然后在代码里写 if-else 做路由结果代码变成一团乱麻条件分支越来越多每次新增一个 Agent 都要改路由逻辑而且 Agent 之间如果有信息交互比如退款 Agent 需要知道订单 Agent 查到的物流状态还得自己维护一套消息传递的代码稍不留神就出现数据不一致。更麻烦的是循环依赖。比如售后 Agent 发现需要重新评估用户信用就调用用户 Agent用户 Agent 又需要查订单记录又绕回售后 Agent。没有统一的状态管理和执行控制这种循环很容易变成死循环或者无限调用 API账单刷刷涨。我测试的时候有一次忘了设置最大迭代数十几分钟烧掉几百次模型调用这个教训很深刻。1.3 Supervisor 模式解决的是什么Supervisor 模式说白了就是引入一个调度员专门负责理解用户意图、决定下一步该让哪个 Agent 干活、检查干活结果是否满足需求。执行具体任务的专业 Agent 不需要关心全局流程只需要把自己的工种干好Supervisor 也不需要懂得具体业务细节只需要做好意图判断和流程控制。这个模式的核心价值在于把决策和执行分离。决策集中在 Supervisor执行分散在各专业 Agent模块之间通过定义好的状态和消息交互。每新增一个 Agent不需要改路由逻辑只需要告诉 Supervisor我有一个新 Agent 可以干某件事Supervisor 通过模型理解在运行时动态决定是否调用它。这套思路对应到代码上就是状态图StateGraph和条件路由Conditional Edge而 LangGraph4j 正好把这个范式原封不动搬到 Java 世界里。2. 拓扑选型Supervisor 在 Multi-Agent 拓扑里的位置2.1 四种常见的多智能体拓扑我先捋一下多 Agent 系统里常用的几种组织方式方便你判断该不该用 SupervisorChain链式A 干完传给 BB 干完传给 C流水线式。适合流程高度固定、顺序明确的场景比如提取需求 - 生成代码 - 生成测试用例。缺点是中间任何一步失败整条链断掉没有回退机制。Hub-and-Spoke中心辐射所有 Agent 都直接挂在主 Agent 下主 Agent 分发任务。这是最轻量的编排方式实现简单但主 Agent 要同时承担意图理解和任务执行容易变成之前的全科医生困境。Supervisor / Orchestrator主管协作Supervisor 只做决策专业 Agent 解耦执行工具调用通过 Supervisor 的模型决策完成。适合开放域对话、复杂任务拆解业界 copilot 类产品大多是这种。Hierarchical分层Supervisor 下面还挂 Supervisor形成树状。适合企业级超复杂场景比如一个总主管分管客服、销售、技术三条线每条线又有自己的子 Supervisor。LangGraph4j 对递归图的支持让它也能做这种结构但状态设计复杂度和控制成本明显上升。2.2 什么场景该选 Supervisor我自己判断就两条标准第一用户输入是否开放如果是固定的按钮触发、固定表单流程用 Chain 就够了犯不着搞 Supervisor第二任务是否可能涉及多个专业领域的协作决策如果一个任务大概率命中一个 Agent且边界清晰那简单路由就行。做客服工单系统这类场景用户一句自然语言可能同时涉及订单、退款、活动规则多个领域输入是开放的跨 Agent 协作是常态Supervisor 就是最稳的选择。LangGraph4j 里 Supervisor 本身也是一个 Node它跟普通 Agent 的区别在于多了两条能力一是能调用路由工具决定下一个执行者二是能判断整个任务何时结束比如返回 FINISH。这套能力在 LangGraph4j 里通过条件边实现下面是完整拆解。3. LangGraph4j 里 Supervisor 的核心组件拆解3.1 State所有 Agent 共享的黑板LangGraph4j 借鉴了 LangGraph 的核心抽象整个图执行过程实际上是一个状态机的状态流转过程。State 就是挂在图上的共享数据所有节点都能读按声明好的 reducer 规则写。我设计的状态长这样public class ChatState { // 对话历史所有节点共享追加 private final ListChatMessage messages; // 当前任务上下文例如订单号、用户ID private final MapString, Object context; // Supervisor 最新路由决策 private String nextAgent; // 语义路由的原始结果用于调试 private String lastRouteReason; // 构造器、getter、setter 省略 // reducer 规则messages 使用追加合并 public static ChatState merge(ChatState left, ChatState right) { // 合并 messages 列表、合并 context map } }最关键的是merge方法LangGraph4j 每个节点返回的更新会用 reducer 合并回全局状态。messages 用追加合并context 用覆盖合并nextAgent 每次被 Supervisor 覆盖。如果你的多个 Agent 要各自维护独立的工作记忆就在 State 里加一个MapString, Object agentMemory以 Agent 名为 key 隔离避免状态互相污染。3.2 Agent Node只做一件事的执行者每个专业 Agent 在 LangGraph4j 里就是一个 Node核心是一个函数接收当前 State调用模型干活返回更新后的部分 State。我把公共逻辑抽成一个基类public abstract class BaseAgentNode implements NodeChatState { protected final ChatModel model; Override public StateSnapshotChatState apply(StateSnapshotChatState state) { ChatState current state.getState(); // 读取共享上下文 String orderId (String) current.getContext().get(orderId); // 生成 Agent 专属 Prompt String systemPrompt buildSystemPrompt(current); // 调用模型这里是示例实际用你的 LLM 客户端 String answer model.chat(systemPrompt, latestUserMessage(current)); // 构造更新后的状态 MapString, Object updates new HashMap(); updates.put(messages, List.of(new ChatMessage(agent, answer))); return new StateSnapshot(ChatState.merge(current, new ChatState(updates))); } protected abstract String buildSystemPrompt(ChatState state); }这种基类设计的好处是新增一个 Agent 只需要继承并实现 Prompt路由、状态合并这些逻辑完全不用碰。我在项目里实现了OrderAgentNode、RefundAgentNode、MembershipAgentNode三个每个 Agent 的职责边界用系统 Prompt 写死比如退款 Agent 只处理退款资格计算和退款流程不碰物流查询。3.3 Supervisor Node决策中枢Supervisor 的本质也是一个 Node但它调模型的时候不是直接回答用户而是要求模型输出一个结构化路由决策。我给 Supervisor 的 Prompt 里写明了成员清单你是工单系统的路由主管。你有以下可用成员 1. order_agent负责查询订单状态、物流信息 2. refund_agent负责退款资格判断与退款执行 3. membership_agent负责会员等级、积分规则查询 根据用户的最新消息输出一个 JSON {next: order_agent 或 refund_agent 或 membership_agent 或 FINISH} 其中 FINISH 表示当前任务已解决或需要用户补充信息。为了让输出稳定我用带结构化输出的调用方式强制 JSON Schema而不是让模型自由格式返回。这一步很重要一旦路由结果格式不稳定图就断掉了。Java 里我用 Jackson 把next字段解析出来解析失败就默认走兜底分支重新询问用户。Supervisor 还有一层关键设计它不直接执行工具而是通过选人来完成工作。这里有人会纠结Supervisor 要不要具备全部上下文我的做法是Supervisor 只需要看到最近几轮消息摘要和当前任务状态专业细节全部丢给下游 Agent这样既控制了 Token 成本又避免 Supervisor 的决策被无关细节干扰。3.4 条件边与执行流程有了节点之后最关键的是把节点连成图。LangGraph4j 里用条件边连接 Supervisor 和各个 Agentpublic class SupervisorCondition implements ConditionalEdgeChatState { Override public String evaluate(StateSnapshotChatState state) { String next state.getState().getNextAgent(); // 只有合法目标才能路由非法值兜底到结束 if (order_agent.equals(next)) return order_agent; if (refund_agent.equals(next)) return refund_agent; if (membership_agent.equals(next)) return membership_agent; return finish; } }注意这里有个被很多人忽略的细节在 LangGraph 系里条件边返回的目标必须是你注册过的节点名或特殊结束节点。Supervisor 每轮执行完条件边按nextAgent字段把流程分发给对应 AgentAgent 干完活之后再无条件边回到 Supervisor由 Supervisor 决定下一步直到它输出FINISH。这样一个带环的图结构就形成了。4. 实操从零实现一个工单 Supervisor 系统4.1 工程搭建与依赖引入我用的是 Spring Boot 3.2 JDK 21LangGraph4j 当前版本0.5.x 系列在 Maven Central 上可以直接拉。核心依赖dependency groupIdcom.big-model/groupId artifactIdlanggraph4j/artifactId version0.5.1/version /dependency dependency groupIdcom.big-model/groupId artifactIdlanggraph4j-jackson/artifactId version0.5.1/version /dependency注意LangGraph4j 的版本迭代非常快API 变动频繁我踩过的坑是 0.4.x 的StateGraph构造器签名跟 0.5.x 完全不同升级后编译直接挂。建议固定版本不要轻易跟随 SNAPSHOT。4.2 构建图与编译图的装配是很直观的过程注册所有节点指定起点和条件边StateGraphChatState graph new StateGraph(ChatState.SCHEMA); // 注册节点 graph.addNode(supervisor, new SupervisorNode(chatModel)); graph.addNode(order_agent, new OrderAgentNode(chatModel)); graph.addNode(refund_agent, new RefundAgentNode(chatModel)); graph.addNode(membership_agent, new MembershipAgentNode(chatModel)); // 设置入口 graph.setEntryPoint(supervisor); // 普通边所有 Agent 执行完回到 Supervisor graph.addEdge(order_agent, supervisor); graph.addEdge(refund_agent, supervisor); graph.addEdge(membership_agent, supervisor); // 条件边Supervisor 根据决策路由 graph.addConditionalEdge(supervisor, new SupervisorCondition(), Map.of( order_agent, order_agent, refund_agent, refund_agent, membership_agent, membership_agent, finish, finish ) ); // 结束节点Graph 的 END 特殊标识 graph.addEdge(finish, StateGraph.END); CompiledGraphChatState compiled graph.compile();finish在这里是一个显式终端节点也可以直接用StateGraph.END但我习惯做一个显式节点在结束前写点日志或者做敏感信息脱敏方便排查。4.3 执行与流式输出编译后的图直接调用invokeChatState initialState new ChatState( List.of(new ChatMessage(user, 我的订单 SF2024001 显示已签收但我没收到想退款)), Map.of(userId, U10086, orderId, SF2024001) ); ChatState finalState compiled.invoke(initialState); // 从 finalState 中取出 messages最后一条就是答案如果你要做实时打字机效果LangGraph4j 支持流式执行回调里可以拿到中间步骤的状态快照。实际生产里我强烈建议接流式因为多 Agent 系统响应时间普遍比单 Agent 长S 级延迟下不做流式、用户直接以为服务挂了。客户端展示的时候我还会顺带把当前执行到哪个 Agent 透出给前端比如正在查询订单信息体验会好很多。4.4 一个完整的执行链路示例我拿真实场景走一遍流程你感受下状态是怎么流转的用户消息进来触发 Supervisor。Supervisor 用结构化输出判断订单状态 退款两个意图路由到order_agent。订单 Agent 查数据库拿到物流记录后把结果追加进 messages返回图。图走边回到 SupervisorSupervisor 看到物流确实显示签收但用户说没收到判断还需要核对退款资格于是路由到refund_agent。退款 Agent 根据会员等级和订单金额算出可退金额把结果追加进 messages再次回到 Supervisor。Supervisor 这时判断业务链路已经闭环输出 FINISH图走到 finish 节点结束把最终汇总答案返回给用户。这个链路里消息是按轮次追加的上下文天然累积不会丢。而且每一步都有图的状态快照出问题可以直接回放看是哪一步路由错了。5. 通信机制与状态流转的细节设计5.1 Agent 之间不直接通信只通过 State这是 LangGraph4j 与传统 Agent 框架最大的不同也是很多新手容易搞错的地方。在多 Agent 系统里Agent A 想把信息传给 Agent B多数直觉做法是让 A 直接调 B 的接口结果系统耦合越来越重。在 LangGraph4j 里所有 Agent 之间共享同一个 State 对象A 的产出通过 reducer 合并进 StateB 在下一轮从 State 里读。这种黑板式通信Blackboard Pattern的好处是任意节点掉线或替换不影响其他节点乱序回写也不丢数据所有交互过程都有 State 快照可审计。代价是 State 设计需要提前想清楚哪些字段是会被多个 Agent 读写的哪些是专属某个 Agent 的临时数据如果没有规划很容易出现字段覆盖。我的原则是跨 Agent 共享的数据放context单 Agent 内部工数据放agentMemory的独立 key 下messages 始终全局共享用于累积对话历史。5.2 循环次数与递归限制带环的图最怕的就是 Supervisor 一直在两个 Agent 之间来回不输出 FINISH。LangGraph4j 默认有递归深度限制超过阈值会抛异常。我实际配置时把最大递归次数设置在 10-15 之间同时要求 Supervisor 的 Prompt 里写清楚如果已经执行过同一 Agent 多次必须 FINISH 并向用户总结当前进度。CompiledGraphChatState compiled graph.compile( CompileConfig.builder() .recursionLimit(12) .build() );这里有个隐藏问题一旦触达 recursionLimit用户那边看到的是异常不是结果。生产环境里要捕获图谱异常转换成系统需要人工介入这种友好的提示。更优的做法是在 Supervisor 节点里加一层检查如果nextAgent连续两次路由到同一个 Agent就强制改返回 FINISH避免把宝贵的递归额度浪费在重复劳动上。5.3 结构化输出的强约束Supervisor 的整个路由机制依赖模型输出 JSON而模型输出天生不稳定。我在这个项目里做了三层兜底第一层用供应商的结构化输出能力强制返回 JSON Schema 格式第二层解析失败时用正则提取next后的值第三层实在解析不出来路由到默认兜底 Agent 或直接 FINISH 让用户重新表述。这三层保证图永远不会因为一个坏 JSON 卡死。类似的思想也应该用在各专业 Agent 上。我让所有 Agent 返回统一格式的结果对象比如{status, answer, need_more_info}这样 Supervisor 能从 State 里快速判断当前 Agent 是否真的完成了任务而不是靠猜。6. 常见问题与排查实录6.1 问题速查表我把实际开发中遇到的高频问题整理成了一张表遇到类似症状可以直接对照排查问题现象根因解决思路图执行报NodeNotFoundException条件边返回的节点名未注册检查addConditionalEdge的 Map 里所有目标是否都有对应addNode执行几轮后异常RecursionLimitExceededSupervisor 陷入路由循环调大 recursionLimit、强制连续同 Agent 时 FINISH状态丢失后一个 Agent 读不到前一个 Agent 的数据reducer 合并规则写错或字段覆盖检查 merge 方法是否保留了两边数据context 用覆盖语义单独处理模型返回的 JSON 偶尔解析失败输出不稳定使用结构化输出 正则回退 兜底路由三层防护同一用户多次请求上下文混乱图实例或 State 被并发复用每次请求创建独立的 State图可以单例但 State 不能共享Token 消耗比预期高很多每轮把全部 messages 喂给模型裁剪历史给 Supervisor 只喂最近 N 轮加摘要6.2 两个印象最深的排查经历第一个是幽灵状态问题。我的退款 Agent 在改完用户积分后下一轮订单 Agent 读到的还是旧积分。排查了半天发现我的 merge 方法里把整个 context map 整体替换了退款 Agent 只更新了积分字段但同时带回来了一个空的 context 副本直接覆盖了原来的订单号。修复方式是把 merge 改成逐字段地合并整个 Map而不是整体赋值。第二个是并发错乱。我把编译好的图做成了单例结果两个测试用户同时发起请求互相污染了对方的nextAgent字段A 用户的请求被路由到了 B 用户的业务逻辑里。原因是我把 State 对象设计成了一个共享的可变对象。正确的用法是每次 invoke 传入一个全新的 State 实例图虽共享状态必须隔离。加上 Spring 里 Bean 默认单例这个坑很容易踩到。7. Spring AI 还是 LangGraph4j我的选型建议7.1 两组技术到底各自擅长什么最近社群总在问现在到底用 Spring AI 还是 LangGraph4j我两个都写过说下真实感受不站队。Spring AI 是从模型接入视角设计的。它解决的核心问题是让 Java 开发者像写一个普通 Service 一样调用大模型提供 ChatClient、EmbeddingModel、结构化输出、Prompt 模板这些能力。它的 Agent 能力是基于Advisor链实现的适合一个 Agent 带着一堆工具按固定顺序增强上下文这种模式上手快、代码量少。如果你只是需要一个带工具调用、带记忆的单 AgentSpring AI 是省心的选择。LangGraph4j 是从流程编排视角设计的。它核心是图执行引擎节点、边、状态、条件路由、递归控制这些概念目标就是做复杂多 Agent 系统。代码量比 Spring AI 大学习曲线也更陡但它给了你对执行流程的完全控制权。多 Agent Supervisor、分层路由、人工介入审批这些模式在 LangGraph4j 里是原生支持的在 Spring AI 里你要自己用状态机或工作流框架再包一层。7.2 我实际落地时的搭配方案我的生产实现是两者结合用的底层模型调用、结构化输出、工具调用统一走 Spring AI 的 API编排层用 LangGraph4j 的 StateGraph 承接多 Agent 路由和状态管理。LangGraph4j 的节点函数里调 Spring AI 的 ChatClient两边各干各的擅长事不冲突。这么做还有一个现实原因LangGraph4j 对具体模型供应商的抽象还在完善中直接调某个 SDK 味道不太对而 Spring AI 对各家模型的适配和流式处理已经很成熟了用它垫底以后换模型供应商成本小很多。7.3 什么情况下我不推荐 LangGraph4j不是所有项目都该上 LangGraph4j我列几个反例团队完全没接触过图编排概念业务逻辑本就固定用 Chain 或 Spring AI 就够需要在非常轻量的环境里快速迭代没时间消化状态图的设计成本。多智能体不是越复杂越好你的系统里如果只有一个 Agent 加几个工具没必要引入图框架那是给自己找复杂度。写在最后回看这个项目LangGraph4j 给我的核心价值不是多 Agent本身而是一个清晰的结构化思考框架把业务拆成节点、把通信收敛到状态、把决策收敛到统一出口。这套思路一旦掌握后面加 Agent、加审批、加人工干预都是往图里加节点和边的事结构不塌。最后分享一个小技巧多 Agent 系统上线前一定要做路由 Mock 测试。我用固定 JSON 把 Supervisor 的每种路由结果都跑一遍确认每个分支的 Agent 都能正确处理并回到 Supervisor再接入真实模型。这一步能帮你提前暴露八成以上的图结构问题强烈建议照做。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →