LangGraph 多智能体协同状态机实战:循环反思与死循环熔断设计
在 LLM 应用向 Agentic Workflow(智能体工作流)演进的今天,很多同行依然停留在“单 Prompt 梭哈”或“简单 Linear Chain(线性链)”的阶段。当面对复杂软件架构设计、多表 SQL 关联生成、企业级合规审查等长流程业务时,这种单向流动的架构弊端显露无疑:幻觉率飙升、上下文漂移、无法自我纠错。
为了解决这些痛点,业界引入了 LangGraph 的状态机思想。本文将基于 Java 17 与 Spring Boot 3.x,深度解析并手把手实现一个生产级的“规划者-执行者-审查者(Planner-Executor-Reviewer)”三角色多智能体协同状态机,并重点攻克循环反思(Reflection)中的死循环熔断这一核心生产难题。
一、 问题背景与业务痛点
在传统的单 Prompt 或线性 Chain 模式下,大模型是一次性输出结果。这种模式存在三大致命缺陷:
- “一步到位”的幻想:复杂任务需要拆解。让 LLM 直接输出最终方案,无异于让程序员不写架构设计、不写伪代码,直接一把梭写出万行免维护代码。
- 缺乏纠错反馈环(No Reflection):当“执行者”生成的代码或方案存在漏洞时,系统没有机会进行自我纠错,只能将错误结果直接交付给用户。
- 状态丢失与上下文膨胀:在线性链中,随着步骤的增加,无用上下文越积越多,导致 LLM 注意力分散(Attention Dilution)。
为了解决这些问题,我们需要构建一个有向有环图(DAG with Loops)。如下图所示,通过规划(Plan)-> 执行(Execute)-> 审查(Review)的循环,不断逼近完美结果。
然而,一旦引入“循环”,就必然伴随着“死循环”的风险。若审查者一直不满意,或者 LLM 陷入逻辑死胡同,API 账单将呈指数级暴涨,甚至拖垮后端线程池。因此,生产级的状态机必须具备“步数熔断(Recursion Limit)”与“退避降级”机制。
二、 核心设计与解决思路
为了让 Java 开发者也能优雅地落地 Agent 状态机,我们不依赖 Python 的 LangGraph,而是基于 LangChain4j 与自定义状态机引擎,设计了一套轻量级、线程安全的 StateGraph 框架。
1. 核心架构设计与组件拓扑图
整个系统由共享状态(State)、节点(Node)、路由器(Router)和熔断器(Circuit Breaker)组成。
▲ 架构图 1:系统核心组件交互拓扑与数据流向
2. 端到端请求执行时序图
以下展示了一次完整的“规划-执行-反思-熔断”的调用链路。当迭代次数达到安全阈值(如 5 次)时,熔断器强行介入,输出当前最优草稿并报警。
▲ 时序图 2:端到端请求处理与调用时序链路
3. 技术选型与方案对比
| 维度 | 单 Prompt 模式 | 线性 Chain 模式 | 本文:多智能体状态机 (StateGraph) |
|---|---|---|---|
| 控制流 | 无法控制,单次输出 | 线性顺序,无法回头 | 有向有环,支持条件分支与循环 |
| 错误自愈 | 无 | 无 | 有(Reviewer 角色提供反思反馈) |
| 上下文控制 | 极差(易产生幻觉) | 中等(上下文随步骤线性累积) | 极佳(各节点仅关注自身所需的 State 字段) |
| 死循环风险 | 无 | 无 | 有(通过内置步数熔断器完美解决) |
| 适用场景 | 简单问答、分类 | 固定的多步工作流 | 复杂决策、代码生成、合规性自检等 |
三、 完整实战代码与配置
下面我们将基于 Java 17、Spring Boot 3.2.x 以及 LangChain4j 0.30.0 核心库,实现这套多智能体状态机。
1. 依赖配置 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
</parent>
<groupId>com.mrwu.agent</groupId>
<artifactId>langgraph-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<langchain4j.version>0.30.0</langchain4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- LangChain4j 核心与 OpenAI 集成 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
2. 共享状态定义 AgentState.java
状态机流转的核心是共享状态,必须保证其可变性或通过 Copy-on-Write 机制维持线程安全。这里我们使用一个标准的 POJO 来承载。
package com.mrwu.agent.state;
import lombok.Builder;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
@Builder
public class AgentState {
private String taskId; // 任务唯一ID
private String taskInput; // 原始输入任务
private String plan; // Planner 生成的步骤规划
private String currentDraft; // Executor 生成的当前草稿
private List<String> feedbacks;// Reviewer 历史反馈记录
private boolean isApproved; // 是否通过审查
private int currentStep; // 当前迭代步数(防死循环计数器)
private boolean isTruncated; // 是否被熔断器强行截断
public void addFeedback(String feedback) {
if (this.feedbacks == null) {
this.feedbacks = new ArrayList<>();
}
this.feedbacks.add(feedback);
}
}
3. 智能体节点实现
我们定义三个核心节点。为了保证 LLM 调用的隔离性,每个节点拥有独立的 System Prompt。
规划者节点 PlannerNode.java
package com.mrwu.agent.node;
import com.mrwu.agent.state.AgentState;
import dev.langchain4j.model.chat.ChatLanguageModel;
import org.springframework.stereotype.Component;
@Component
public class PlannerNode {
private final ChatLanguageModel chatModel;
public PlannerNode(ChatLanguageModel chatModel) {
this.chatModel = chatModel;
}
public AgentState execute(AgentState state) {
System.out.println("[Planner] 开始拆解任务...");
String prompt = """
你是一个资深的系统架构师。请针对以下任务,拆解为 3 个关键的执行步骤,并说明每一步的输出标准。
任务: %s
请直接输出步骤列表,无需多余客套话。
""".formatted(state.getTaskInput());
String plan = chatModel.generate(prompt);
state.setPlan(plan);
System.out.println("[Planner] 规划完成。");
return state;
}
}
执行者节点 ExecutorNode.java
package com.mrwu.agent.node;
import com.mrwu.agent.state.AgentState;
import dev.langchain4j.model.chat.ChatLanguageModel;
import org.springframework.stereotype.Component;
@Component
public class ExecutorNode {
private final ChatLanguageModel chatModel;
public ExecutorNode(ChatLanguageModel chatModel) {
this.chatModel = chatModel;
}
public AgentState execute(AgentState state) {
System.out.println("[Executor] 开始执行/优化草稿... 当前迭代步数: " + state.getCurrentStep());
String prompt;
if (state.getCurrentDraft() == null || state.getCurrentDraft().isEmpty()) {
// 首次执行
prompt = """
你是一个精通代码实现的资深工程师。请根据以下规划,完成代码或方案的编写。
规划: %s
任务: %s
""".formatted(state.getPlan(), state.getTaskInput());
} else {
// 基于反馈进行迭代优化
prompt = """
你是一个精通代码实现的资深工程师。请根据审查者的反馈,对当前的草稿进行针对性优化。
当前草稿: %s
审查反馈: %s
""".formatted(state.getCurrentDraft(), state.getFeedbacks().get(state.getFeedbacks().size() - 1));
}
String draft = chatModel.generate(prompt);
state.setCurrentDraft(draft);
System.out.println("[Executor] 草稿生成完成。");
return state;
}
}
审查者节点 ReviewerNode.java
为了实现高可靠的路由决策,Reviewer 节点必须输出结构化的判定结果(是否通过)。这里我们通过 Prompt 强约束其输出 JSON 格式。
package com.mrwu.agent.node;
import com.mrwu.agent.state.AgentState;
import dev.langchain4j.model.chat.ChatLanguageModel;
import org.springframework.stereotype.Component;
@Component
public class ReviewerNode {
private final ChatLanguageModel chatModel;
public ReviewerNode(ChatLanguageModel chatModel) {
this.chatModel = chatModel;
}
public AgentState execute(AgentState state) {
System.out.println("[Reviewer] 正在审查草稿...");
String prompt = """
你是一个严苛的代码质量审查专家。请评估以下草稿是否完全满足原始任务的要求。
原始任务: %s
当前草稿: %s
你必须严格以下列 JSON 格式输出,不要包含任何 markdown 标记或多余字符:
{
"approved": true/false,
"feedback": "如果未通过,请写明具体的修改意见;如果通过,请写 'PASS'"
}
""".formatted(state.getTaskInput(), state.getCurrentDraft());
String response = chatModel.generate(prompt).trim();
// 简易 JSON 解析(生产环境建议使用 Jackson 或 LangChain4j Structured Outputs)
boolean approved = response.contains("\"approved\": true") || response.contains("\"approved\":true");
String feedback = "审查通过";
if (!approved) {
int startIndex = response.indexOf("\"feedback\":") + 11;
feedback = response.substring(startIndex, response.lastIndexOf("}")).replace("\"", "").trim();
}
state.setApproved(approved);
state.addFeedback(feedback);
state.setCurrentStep(state.getCurrentStep() + 1); // 步数自增
System.out.println("[Reviewer] 审查结束。结果: Approved=" + approved + ", 反馈: " + feedback);
return state;
}
}
4. 状态机引擎与生产级熔断器 StateGraphExecutor.java
这是本方案的核心。我们在这里实现节点调度、条件路由,以及防止 API 账单爆炸的递归步数熔断器。
package com.mrwu.agent.engine;
import com.mrwu.agent.node.ExecutorNode;
import com.mrwu.agent.node.PlannerNode;
import com.mrwu.agent.node.ReviewerNode;
import com.mrwu.agent.state.AgentState;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.UUID;
@Service
public class StateGraphExecutor {
private final PlannerNode plannerNode;
private final ExecutorNode executorNode;
private final ReviewerNode reviewerNode;
// 生产级安全阀:最大允许迭代审查次数
private static final int MAX_RECURSION_LIMIT = 3;
public StateGraphExecutor(PlannerNode plannerNode, ExecutorNode executorNode, ReviewerNode reviewerNode) {
this.plannerNode = plannerNode;
this.executorNode = executorNode;
this.reviewerNode = reviewerNode;
}
public AgentState startWorkflow(String taskInput) {
// 1. 初始化状态
AgentState state = AgentState.builder()
.taskId(UUID.randomUUID().toString())
.taskInput(taskInput)
.feedbacks(new ArrayList<>())
.currentStep(0)
.isApproved(false)
.isTruncated(false)
.build();
System.out.println("====== 启动多智能体状态机 ======");
// 2. 节点 1:规划
state = plannerNode.execute(state);
// 3. 循环迭代:执行 -> 审查 -> 反思
while (!state.isApproved()) {
// 【死循环熔断检查】
if (state.getCurrentStep() >= MAX_RECURSION_LIMIT) {
System.err.println("[WARN] 触发生产级步数熔断!已达到最大迭代次数: " + MAX_RECURSION_LIMIT + ". 强制输出当前最优草稿。");
state.setTruncated(true);
state.setApproved(true); // 强行终止循环
break;
}
// 节点 2:执行
state = executorNode.execute(state);
// 节点 3:审查
state = reviewerNode.execute(state);
}
System.out.println("====== 状态机运行结束 ======");
return state;
}
}
5. 外部接口与 LLM 配置
在 application.yml 中配置大模型密钥:
spring:
application:
name: langgraph-agent-demo
# 大模型 API 配置(以 OpenAI 为例,亦可替换为通义千问、DeepSeek 等国内大模型)
llm:
openai:
api-key: demo # 替换为你的真实 API Key
base-url: https://api.openai.com/v1 # 或者是代理地址
配置类 LlmConfig.java:
package com.mrwu.agent.config;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
@Configuration
public class LlmConfig {
@Value("${llm.openai.api-key}")
private String apiKey;
@Value("${llm.openai.base-url}")
private String baseUrl;
@Bean
public ChatLanguageModel chatLanguageModel() {
return OpenAiChatModel.builder()
.apiKey(apiKey)
.baseUrl(baseUrl)
.modelName("gpt-4o-mini") // 推荐使用性价比较高的模型作为 Agent 节点
.temperature(0.2) // 低温保证输出格式和逻辑的稳定性
.timeout(Duration.ofSeconds(60))
.logRequests(true)
.logResponses(true)
.build();
}
}
暴露 REST 服务 AgentController.java:
package com.mrwu.agent.controller;
import com.mrwu.agent.engine.StateGraphExecutor;
import com.mrwu.agent.state.AgentState;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/agent")
public class AgentController {
private final StateGraphExecutor stateGraphExecutor;
public AgentController(StateGraphExecutor stateGraphExecutor) {
this.stateGraphExecutor = stateGraphExecutor;
}
@PostMapping("/run")
public ResponseEntity<AgentState> runTask(@RequestParam String task) {
AgentState result = stateGraphExecutor.startWorkflow(task);
return ResponseEntity.ok(result);
}
}
四、 避坑指南与总结验证
在实际将这套多智能体状态机推向生产环境时,我们团队踩过了不少坑,总结出以下几条硬核避坑指南:
1. 生产落地避坑指南
- 警惕 Token 暴涨与费用失控:多智能体循环反思非常消耗 Token。每次
Executor重新生成时,最好不要让其重写全部内容,而是让其输出“补丁(Patch)”或仅修改有问题的部分。 - 路由决策的“幻觉”硬伤:
Reviewer节点有时会不按套路输出 JSON,导致解析失败。强烈建议在生产环境使用强 Schema 约束(如 JSON Schema 或 LangChain4j 的StructuredOutputs),并在代码中捕获解析异常,一旦解析失败,默认降级为“未通过”并记录错误。 - 状态并发冲突:在 Web 高并发场景下,
AgentState绝不能定义为 Spring 的单例 Bean。必须采用方法内局部变量、ThreadLocal 或者是分布式缓存(如 Redis)来存储每个请求的 State 状态。
2. 生产效果验证
我们调用 /api/agent/run 接口,提交一个故意带有逻辑漏洞的复杂任务:
请求任务:“写一个 Java 方法,实现计算两个日期之间的天数差。注意:必须考虑时区,且不能引入第三方库(只能用 JDK 8 之后的 API)。”
正常迭代通关日志:
====== 启动多智能体状态机 ======
[Planner] 开始拆解任务...
[Planner] 规划完成。
[Executor] 开始执行/优化草稿... 当前迭代步数: 0
[Executor] 草稿生成完成。(生成了使用 ChronoUnit.DAYS.between 的方案,但忘记处理 ZoneId 转换)
[Reviewer] 正在审查草稿...
[Reviewer] 审查结束。结果: Approved=false, 反馈: "代码未显式处理不同时区(ZoneId)传入时的转换,存在潜在Bug。"
[Executor] 开始执行/优化草稿... 当前迭代步数: 1
[Executor] 草稿生成完成。(根据反馈,加入了 ZonedDateTime 与 ZoneId 的转换)
[Reviewer] 正在审查草稿...
[Reviewer] 审查结束。结果: Approved=true, 反馈: PASS
====== 状态机运行结束 ======
触发熔断降级日志:
若我们故意给出一个“悖论任务”(例如:“写一段代码证明 1+1=3”),Reviewer 将永远判定不通过。此时:
====== 启动多智能体状态机 ======
...
[Reviewer] 审查结束。结果: Approved=false, 反馈: "逻辑错误,1+1不等于3"
[Executor] 开始执行/优化草稿... 当前迭代步数: 2
...
[WARN] 触发生产级步数熔断!已达到最大迭代次数: 3. 强制输出当前最优草稿。
====== 状态机运行结束 ======
返回的 JSON 报文中,isTruncated 为 true,前端可以据此给用户展示人性化的提示:“系统已为您生成当前最接近的方案,但未通过合规性终审,请人工介入核对。”
通过引入 LangGraph 的状态机设计思想,我们成功将复杂的长流程业务拆解为高内聚的智能体协作,并通过步数熔断器守住了生产环境的最后一公里安全底线。

本文针对单 Prompt 无法应对长流程复杂业务的痛点,基于 Java 17 与 Spring Boot 3.x,手把手构建一套“规划者-执行者-审查者”多智能体协同状态机。深入解析状态共享、循环反思机制,并独家实现生产级递归步数熔断器,拒绝死循环。
浙公网安备 33010602011771号