
2、Act 执行一个动作,通常是工具调用?
3、Observe 读取动作结果,并把结果反馈回下一轮推理
也就是最经典的:
Reason -> Act -> Observe -> Reason -> ...
所以从理论最小闭环看,ReAct 其实只需要 3 个原子节点就够了。
01、标准ReAct Agent节点设计
三个原子节点只是基础的ReAct Agent,大部门工程里会长成6个,甚至更多。因为一旦从论文原理走到生产系统,光有 Reason / Act / Observe 通常不够。你会马上遇到这些现实问题:
1、上下文越来越长怎么办?
2、工具无限调用怎么办?
3、什么时候该结束?
4、超过轮次怎么办?
5、最终答案如何单独收口?
6、是否要做审批、回放、容错?
于是很多团队会在3个原子节点之外,再补上“工程节点”。一般工程里会有6个节点,其实可以分成两类:
1. 原理核心节点:ReasoningNode、ActionNode、ObservationNode这 3 个才最接近 ReAct 的理论原型。
2. 工程增强节:SummarizingNode、LimitExceededNode、FinalAnswerNode
这 3 个不是 ReAct 理论本身必需的,而是为了让 ReAct 更能在真实系统里稳定运行。

02、ReAct 底层原理只有“必要结构”和“常见增强结构”
一、必要结构
这部分才是 ReAct 的本体:推理结构:用来判断当前状态、决定下一步行为。行动结构:执行一个动作或工具。观察结构:消化行动结果,并反馈给推理层。
这是 ReAct 的不可再少骨架。
二、常见增强结构
这部分不是理论强制,但在工程里常见:
1、终止结构:判断什么时候结束,有的系统把它写成条件分支,不单独做节点,有的系统会做成 FinalAnswerNode。
2、超限兜底结构:防止无限循环,有的系统单独做 LimitExceededNode。
3、上下文压缩结构:长任务常常需要摘要,所以会有 SummarizingNode。
4、答案验收结构:判断答案是否真的满足任务要求,有的系统会单独加 validator 节点。
5、人工审批结构:高风险动作要人工确认,有些系统会单独做 approval 节点。
6、错误恢复结构:工具失败、状态异常、重试分流,有时也会做成独立节点。
所以从底层看,ReAct 不是一个固定 6 节点模板,而是一种“围绕 Reason-Act-Observe 的循环式决策范式”。
你可以把它理解成两层
第一层:论文级 ReAct
最小闭环只有:Reason、Act、Observe
第二层:系统级 ReAct
为了可上线、可治理、可持续运行,会扩成:推理节点、执行节点、观察节点、终止节点、摘要节点、超限节点、校验节点、审批节点、错误恢复节点、记忆更新节点。
03、ReAct 是原理,节点拆分是工程设计
因为很多人容易把“某个项目里的节点划分”误认为“ReAct 的标准定义”。其实不是。比如不同系统可能这么拆:
方案 A:极简型
Reason、Act、Observe
方案 B:常规工程型
Reason、Act、Observe、FinalAnswer、LimitExceeded
方案 C:长任务增强型
Reason、Act、Observe、Summarize、Validate、FinalAnswer、LimitExceeded。
方案 D:企业治理型
Reason、Action、Plan、Approval、Act、Observe、Validate、Audit、FinalAnswer。
它们都可以是 ReAct,只是工程粒度不同。它已经从“理论 ReAct”进化到了“生产可控 ReAct”。
从底层原理上,ReAct 的本体只有 3 个核心环节:Reason、Act、Observe。
但每个工程里的多个个节点,不是 ReAct 的唯一标准答案,而是围绕这 3 个核心环节做出的工程化扩展。
04、如何实现?
以企业治理型为例,采用SpringAiAlibaba来实现对应的多个节点,从而达到工程级别ReActAgent。
这类“企业治理型 ReAct”非常适合用 spring ai alibaba 的 StateGraph 来实现。
核心思路就是:
1、每个节点实现一个 NodeAction。
2、所有中间变量放进 OverAllState。
3、用 Dispatcher 决定下一跳。
4、把治理能力拆成显式节点,而不是塞进 Prompt。
一、节点怎么定义
先定义状态键:
public interface GovStateKeys {
String USER_MESSAGE = "user_message";
String MESSAGES = "messages";
String ACTION_PLAN = "action_plan";
String NEED_APPROVAL = "need_approval";
String APPROVAL_GRANTED = "approval_granted";
String TOOL_CALLS = "tool_calls";
String TOOL_RESULTS = "tool_results";
String OBSERVATION = "observation";
String VALIDATION_PASSED = "validation_passed";
String AUDIT_RECORD = "audit_record";
String FINAL_ANSWER = "final_answer";
String REASON_NODE = "reason";
String ACTION_PLAN_NODE = "action_plan";
String APPROVAL_NODE = "approval";
String ACT_NODE = "act";
String OBSERVE_NODE = "observe";
String VALIDATE_NODE = "validate";
String AUDIT_NODE = "audit";
String FINAL_NODE = "final";
}二、各节点怎么实现
1. ReasonNode
作用:判断当前任务要不要拆计划、要不要直接答、要不要走工具。
public class ReasonNode implements NodeAction {
private final ChatModel chatModel;
public ReasonNode(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public Map<String, Object> apply(OverAllState state) {
String userMessage = state.value(GovStateKeys.USER_MESSAGE, "");
// 这里可以调用 LLM,让它输出结构化决策
boolean needPlan = userMessage.length() > 50; // 示例
return Map.of(
"need_plan", needPlan,
"current_phase", "reasoning"
);
}
}2. ActionPlanNode
作用:把复杂目标拆成可执行动作计划,不是最终任务计划,而是当前执行计划。
public class ActionPlanNode implements NodeAction {
private final ChatModel chatModel;
public ActionPlanNode(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public Map<String, Object> apply(OverAllState state) {
String userMessage = state.value(GovStateKeys.USER_MESSAGE, "");
String actionPlan = """
1. 查询相关数据
2. 判断是否涉及高风险动作
3. 如需审批则等待审批
4. 执行工具
5. 校验结果
""";
return Map.of(
GovStateKeys.ACTION_PLAN, actionPlan,
"current_phase", "action_plan"
);
}
}3. ApprovalNode
作用:判断当前计划是否涉及高风险动作,是否需要人工审批。
public class ApprovalNode implements NodeAction {
@Override
public Map<String, Object> apply(OverAllState state) {
String plan = state.value(GovStateKeys.ACTION_PLAN, "");
boolean needApproval = plan.contains("删除") || plan.contains("写文件") || plan.contains("执行命令");
if (needApproval) {
return Map.of(
GovStateKeys.NEED_APPROVAL, true,
GovStateKeys.APPROVAL_GRANTED, false,
"current_phase", "awaiting_approval"
);
}
return Map.of(
GovStateKeys.NEED_APPROVAL, false,
GovStateKeys.APPROVAL_GRANTED, true,
"current_phase", "approval_skipped"
);
}
}4. ActNode
作用:真正执行工具。
public class GovActionNode implements NodeAction {
private final ToolExecutionExecutor executor;
public GovActionNode(ToolExecutionExecutor executor) {
this.executor = executor;
}
@Override
public Map<String, Object> apply(OverAllState state) throws Exception {
if (!state.value(GovStateKeys.APPROVAL_GRANTED, false)) {
return Map.of("current_phase", "blocked");
}
var toolCalls = state.<List<AssistantMessage.ToolCall>>value(GovStateKeys.TOOL_CALLS).orElse(List.of());
var result = executor.execute(toolCalls, "conv-1", "agent-1", false, "user-1", "");
return Map.of(
GovStateKeys.TOOL_RESULTS, result.responses(),
"current_phase", "act"
);
}
}5. ObserveNode
作用:把工具结果变成结构化观察。
public class GovObservationNode implements NodeAction {
@Override
public Map<String, Object> apply(OverAllState state) {
var toolResults = state.value(GovStateKeys.TOOL_RESULTS, List.of());
String observation = "已获取工具结果,共 " + toolResults.size() + " 条";
return Map.of(
GovStateKeys.OBSERVATION, observation,
"current_phase", "observe"
);
}
}6. ValidateNode
作用:校验结果是否满足任务要求,是否要继续补证据。
public class ValidateNode implements NodeAction {
@Override
public Map<String, Object> apply(OverAllState state) {
String observation = state.value(GovStateKeys.OBSERVATION, "");
boolean passed = observation != null && !observation.isBlank();
return Map.of(
GovStateKeys.VALIDATION_PASSED, passed,
"current_phase", "validate"
);
}
}7. AuditNode
作用:记录整个动作链,满足审计要求。
public class AuditNode implements NodeAction {
@Override
public Map<String, Object> apply(OverAllState state) {
String record = """
{
"phase":"audit",
"approved": %s,
"validated": %s
}
""".formatted(
state.value(GovStateKeys.APPROVAL_GRANTED, false),
state.value(GovStateKeys.VALIDATION_PASSED, false)
);
return Map.of(
GovStateKeys.AUDIT_RECORD, record,
"current_phase", "audit"
);
}
}8. FinalAnswerNode
作用:统一收口输出。
public class GovFinalAnswerNode implements NodeAction {
@Override
public Map<String, Object> apply(OverAllState state) {
String observation = state.value(GovStateKeys.OBSERVATION, "");
String finalAnswer = "最终结论:" + observation;
return Map.of(
GovStateKeys.FINAL_ANSWER, finalAnswer,
"current_phase", "final"
);
}
}三、Dispatcher 怎么写
ReasonDispatcher
public class ReasonDispatcher implements EdgeAction {
@Override
public String apply(OverAllState state) {
boolean needPlan = state.value("need_plan", false);
return needPlan ? GovStateKeys.ACTION_PLAN_NODE : GovStateKeys.FINAL_NODE;
}
}
ApprovalDispatcher
public class ApprovalDispatcher implements EdgeAction {
@Override
public String apply(OverAllState state) {
boolean granted = state.value(GovStateKeys.APPROVAL_GRANTED, false);
return granted ? GovStateKeys.ACT_NODE : GovStateKeys.FINAL_NODE;
}
}
ValidateDispatcher
public class ValidateDispatcher implements EdgeAction {
@Override
public String apply(OverAllState state) {
boolean passed = state.value(GovStateKeys.VALIDATION_PASSED, false);
return passed ? GovStateKeys.AUDIT_NODE : GovStateKeys.REASON_NODE;
}
}四、整个图怎么挂起来
这部分最关键,和你当前工程的 buildReActGraph(...) 是同一种写法。
StateGraph graph = new StateGraph("governed-react-agent", keyStrategyFactory)
.addNode(GovStateKeys.REASON_NODE, AsyncNodeAction.node_async(new ReasonNode(chatModel)))
.addNode(GovStateKeys.ACTION_PLAN_NODE, AsyncNodeAction.node_async(new ActionPlanNode(chatModel)))
.addNode(GovStateKeys.APPROVAL_NODE, AsyncNodeAction.node_async(new ApprovalNode()))
.addNode(GovStateKeys.ACT_NODE, AsyncNodeAction.node_async(new GovActionNode(executor)))
.addNode(GovStateKeys.OBSERVE_NODE, AsyncNodeAction.node_async(new GovObservationNode()))
.addNode(GovStateKeys.VALIDATE_NODE, AsyncNodeAction.node_async(new ValidateNode()))
.addNode(GovStateKeys.AUDIT_NODE, AsyncNodeAction.node_async(new AuditNode()))
.addNode(GovStateKeys.FINAL_NODE, AsyncNodeAction.node_async(new GovFinalAnswerNode()))
.addEdge(StateGraph.START, GovStateKeys.REASON_NODE)
.addConditionalEdges(GovStateKeys.REASON_NODE,
AsyncEdgeAction.edge_async(new ReasonDispatcher()),
Map.of(
GovStateKeys.ACTION_PLAN_NODE, GovStateKeys.ACTION_PLAN_NODE,
GovStateKeys.FINAL_NODE, GovStateKeys.FINAL_NODE
))
.addEdge(GovStateKeys.ACTION_PLAN_NODE, GovStateKeys.APPROVAL_NODE)
.addConditionalEdges(GovStateKeys.APPROVAL_NODE,
AsyncEdgeAction.edge_async(new ApprovalDispatcher()),
Map.of(
GovStateKeys.ACT_NODE, GovStateKeys.ACT_NODE,
GovStateKeys.FINAL_NODE, GovStateKeys.FINAL_NODE
))
.addEdge(GovStateKeys.ACT_NODE, GovStateKeys.OBSERVE_NODE)
.addEdge(GovStateKeys.OBSERVE_NODE, GovStateKeys.VALIDATE_NODE)
.addConditionalEdges(GovStateKeys.VALIDATE_NODE,
AsyncEdgeAction.edge_async(new ValidateDispatcher()),
Map.of(
GovStateKeys.AUDIT_NODE, GovStateKeys.AUDIT_NODE,
GovStateKeys.REASON_NODE, GovStateKeys.REASON_NODE
))
.addEdge(GovStateKeys.AUDIT_NODE, GovStateKeys.FINAL_NODE)
.addEdge(GovStateKeys.FINAL_NODE, StateGraph.END);这 8 个节点不是为了“把图画复杂”,而是为了把不同责任显式拆开:
Reason 决定理解与分流ActionPlan 决定当前动作计划Approval 决定能不能做Act 真正去做Observe 看结果Validate 判断结果够不够好Audit 留痕FinalAnswer 统一交付这套拆法的最大价值是:
治理能力不再藏在 Prompt 里,而是变成可控、可审计、可插拔的节点。