1. 前言

Spring AI 使用 ChatClient 进行模型交互,在 SSA 中用的是 ReactAgent ,一个是客户端对象,一个是 React 智能体对象,那么 ReactAgent 到底是啥呢?

官网有提到 ReactAgent 用于构建具有推理和行动能力的智能代理,遵循 ReAct(推理 + 行动)范式,用于迭代解决问题。

2. ReAct 范式

天才少年姚顺雨想必大多数人搞 AI 的都听说过,高考总分 704 分,安徽省理科第三,清华大学姚班…

2.1 核心思想

人类智能的真正独特之处,不在于只会想,也不在于只会做,而在于能把「思考)」和「行动」结合起来,用思考指导行动,用行动补全思考。例如,在厨房做一个番茄炒蛋时,你先思考要先炒鸡蛋更嫩,便行动打散鸡蛋、开火热油;炒番茄时发现太酸,又思考加少许糖,再行动加糖,全程思考与行动相互配合、彼此补全。

202210 月,正在普林斯顿大学读博的姚顺雨发表了论文《ReAct: Synergizing Reasoning and Acting in Language Models》(第一作者),提出了 ReAct 范式解决大型语言模型在处理语言理解和决策任务时的两方面局限性:

  • 仅推理Reasoning-only):擅长逻辑拆解、多步推导、抽象问题解决。但无法获取外部实时信息,容易产生「幻觉」。
  • 仅行动Acting-only):能调用工具、查询数据、与环境交互。缺乏复杂推理能力,只能执行简单指令,难以处理需要多步规划的任务

ReAct 通用范式的核心思想,就是把推理(Reasoning)和行动(Acting)结合进大语言模型,边思考规划、边行动取证,用推理指导行动,用行动支撑推理。既解决推理不足、易产生幻觉的问题,又避免盲目行动、无法适配复杂场景的短板。

2.2 核心流程

ReAct 的核心流程就是一个可循环的三段式闭环:思考 → 行动 → 观察 → 再思考 → 再行动…… 直到模型判断已经得到最终答案,结束流程:

  • 思考是大模型的内部推理,需要将大任务拆成小目标,制定执行计划,检查当前进度,调整下一步方案。
  • 行动是执行的操作,由前面的思考直接指导,比如搜索网页、查询知识库、点击按钮、移动对象、调用工具等。执行完动作后,返回真实结果,比如搜索摘要、查询到的数据等。
  • 观察是分析工具返回结果,作为下一轮推理(思考)的输入,来修正下一轮思考,直到问题解决。

整个循环的流程如下:

在这里插入图片描述

示例,在豆包中提问:周杰伦的出生地的市花是什么?
在这里插入图片描述

整个过程经过了三次循环:

轮次 Thought(推理) Action(行动) Observation(观察)
第1轮 用户询问「周杰伦出生地的市花是什么」,需先确定周杰伦的出生地,再查询该城市的市花,以此作为解题路径。 调用搜索工具,关键词:
「周杰伦 出生地」「周杰伦出生城市 市花」
参考12篇资料
确认周杰伦出生于中国台湾省新北市(前身为台北县,2010年升格为直辖市)。
第2轮 已知出生地为新北市,需进一步精确检索该地区的市花信息,以完善解答。 调用搜索工具,关键词:
「新北市 市花」「台北县 市花 新北市」
参考13篇资料
发现搜索结果中市花信息存在矛盾:部分资料显示为杜鹃花,也有资料提及山茶花(茶花),需进一步权威验证。
第3轮 市花信息存在差异,需通过更精准的权威关键词搜索,确认新北市的官方市花。 调用搜索工具,关键词:
「新北市 官方 市花」「新北市 升格直辖市 市花 更改」
参考14篇资料
最终确认:新北市(含原台北县)的官方市花为杜鹃花,升格为直辖市后市花未变更,仅市树由樟树改为山樱。

最终,生成结果:

在这里插入图片描述

这是典型的 ReAct 解题范式:

  • 推理:先拆解问题,规划解决路径。
  • 行动:调用搜索工具获取信息。
  • 观察:分析结果,发现矛盾或补充信息。
  • 迭代:基于观察结果修正推理,发起新的行动,直到得到准确结论。

2.3 缺点

ReAct 虽然在复杂问题上表现出色,但也存在明显的局限性,主要体现在以下几个方面:

  • 效率低:依赖多轮循环,每一步都要生成推理文本、调用工具、处理返回结果,相比单次生成回答,延迟大幅增加。
  • 成本高昂:每轮的推理内容、工具请求 / 响应都会占用上下文 Token,多轮迭代后 Token 成本急剧上升。
  • 推理错误连锁反应AI 的「思考」环节本身可能出错,比如错误规划解题路径、误读工具返回结果,会直接导致后续循环偏离正确方向,甚至陷入死循环。
  • 终止条件模糊:很难精准定义 “问题已解决” 的终止条件,可能出现过早终止(结论不完整)或过度循环(重复调用工具却无新信息)的情况。
  • 强工具依赖性,容错率低:依赖外部工具,如果工具不可用、接口不稳定或无对应工具,流程会直接中断,无法解决问题。
  • 结果一致性差:不同 Prompt、不同模型版本下,AI 的推理路径和工具调用选择可能差异极大,导致同一问题的答案稳定性不足,难以用于对可靠性要求高的场景(如医疗、法律)。

ReAct 更适合需要外部信息、多步骤推理的复杂任务(如信息检索、工具协作、复杂问题拆解),但在效率、成本、稳定性上存在明显短板,且高度依赖工具生态。实际应用中,通常会结合任务复杂度,选择「纯生成」「单步工具调用」「ReAct 多轮循环」「工作流」等不同范式,而非一刀切使用 ReAct

3. ReactAgent

Spring AI Alibaba 提供了 ReAct 智能体范式的实现类 ReactAgent ,在循环中通过运行工具来实现目标,会一直运行直到满足停止条件,即当模型输出最终答案或达到迭代限制时。

3.1 工作原理

ReactAgent 基于 Graph 运行时执行,底层实际是一个 Graph 工作流,由节点和边组成,定义了 Agent 如何处理信息,或从开始节点执行,直到结束节点。

ReactAgent 的执行节点有:

  • Model Node (模型节点):调用 LLM 进行推理和决策
  • Tool Node (工具节点):执行工具调用
  • Hook Nodes (钩子节点):在关键位置插入自定义逻辑

核心执行流程:
在这里插入图片描述

核心循环逻辑

  1. 初始推理:Query 进入 Model,模型先对问题进行拆解、规划,判断是否需要调用外部工具。
  2. 行动(Acting):若模型判断需要外部信息,通过 Acting 分支调用 Tools 执行具体操作。
  3. 观察与再推理(Reasoning):工具返回结果后,通过 Reasoning 反馈给 Model,模型结合新信息修正推理、调整下一步计划,形成循环。
  4. 终止(Finish):当模型判断已获取足够信息、可以给出最终结论时,通过 Finish 分支输出 Answer,结束流程。

3.2 继承关系

ReactAgent 有两个抽象父类:

  • Agent :定义 Agent 的核心契约和通用能力,身份标识、Graph 生命周期管理 、执行 API (invoke/stream)、定时任务、输入标准化等能力
  • BaseAgent :抽象中间类,输入/输出 Schema 定义、输出 Key 策略 、内容包含控制、子图嵌套等能力。

在这里插入图片描述

3.3 核心属性

public class ReactAgent extends BaseAgent {
    // 线程状态管理
    private final ConcurrentMap<String, Map<String, Object>> threadIdStateMap;
    // LLM 节点 - 模型调用
    private final AgentLlmNode llmNode;
    // 工具节点 - 工具执行
    private final AgentToolNode toolNode;
    // 扩展钩子
    private List<? extends Hook> hooks;
    // 拦截器
    private List<ModelInterceptor> modelInterceptors;
    private List<ToolInterceptor> toolInterceptors;
    // 系统指令
    private String instruction;
    // 状态序列化器
    private StateSerializer stateSerializer;
    // 是否包含工具
    private final Boolean hasTools;
}

3.4 核心 API

同步调用:

// 返回 AssistantMessage
public AssistantMessage call(String message);
public AssistantMessage call(UserMessage message);
public AssistantMessage call(List<Message> messages);
public AssistantMessage call(Map<String, Object> inputs);

// 返回完整状态
public Optional<OverAllState> invoke(String message);
public Optional<OverAllState> invoke(Map<String, Object> inputs);

流式调用:

// 返回 NodeOutput 流
public Flux<NodeOutput> stream(String message);
public Flux<NodeOutput> stream(Map<String, Object> inputs);

// 返回 Message 流
public Flux<Message> streamMessages(String message);
public Flux<Message> streamMessages(Map<String, Object> inputs);

中断与恢复:

// 中断执行
public void interrupt(RunnableConfig config);
public void interrupt(List<Message> messages, RunnableConfig config);

// 更新状态
public void updateAgentState(Object state, RunnableConfig config);

3.5 Graph 结构

initGraph() 是一个懒加载方法,在首次需要使用 Graph 时才会被触发:

protected Flux<NodeOutput> doStream(Map<String, Object> input, RunnableConfig runnableConfig) {
    CompiledGraph compiledGraph = getAndCompileGraph();
    return compiledGraph.stream(input, buildStreamConfig(runnableConfig));
}
public synchronized CompiledGraph getAndCompileGraph() {
    if (compiledGraph != null) {
        return compiledGraph;
    }

    StateGraph graph = getGraph();
    try {
        if (this.compileConfig == null) {
            this.compiledGraph = graph.compile();
        }
        else {
            this.compiledGraph = graph.compile(this.compileConfig);
        }
    } catch (GraphStateException e) {
        throw new RuntimeException(e);
    }
    return this.compiledGraph;
}

触发后,会构建一个完整的 ReAct 循环图,包含模型推理节点、工具执行节点、工具执行节点、边。

/**
 * 初始化 ReactAgent 的状态图
 * <p>
 * 构建一个完整的 ReAct 循环图,包含:
 * - 核心节点:模型推理节点(__model__)、工具执行节点(__tool__)
 * - 扩展节点:各位置的 Hook 节点
 * - 边:连接各节点的普通边和条件边
 * <p>
 * 图结构示例(包含 Hook 时):
 * <pre>
 * START → [beforeAgent Hooks] → [beforeModel Hooks] → __model__ → [afterModel Hooks]
 *                                                                          │
 *                                          ┌───────────────────────────────┘
 *                                          ▼
 *                                    是否有工具调用?
 *                                    /            \
 *                                  是              否
 *                                  ▼                ▼
 *                            __tool__          [afterAgent Hooks] → END
 *                                  │
 *                                  └──→ [beforeModel Hooks] → __model__ (循环)
 * </pre>
 *
 * @return 构建完成的状态图
 * @throws GraphStateException 图状态异常
 */
@Override
protected StateGraph initGraph() throws GraphStateException {

    // ==================== 1. Hook 初始化与验证 ====================
    
    // 确保 hooks 列表不为 null
    if (hooks == null) {
        hooks = new ArrayList<>();
    }

    // 构建有效的 Hook 列表
    // 始终添加默认的 InstructionAgentHook,用于处理 Agent 作为子图时的指令注入
    // InstructionAgentHook 会在 beforeAgent 阶段将 instruction 注入到消息中
    List<Hook> effectiveHooks = new ArrayList<>();
    effectiveHooks.add(InstructionAgentHook.create());  // 默认 Hook,优先级最高
    effectiveHooks.addAll(hooks);                        // 用户自定义 Hooks

    // 验证 Hook 唯一性,防止同一个 Hook 实例被重复添加
    // 每个 Hook 通过 "类名.名称" 作为唯一标识
    Set<String> hookNames = new HashSet<>();
    for (Hook hook : effectiveHooks) {
        // Set.add() 返回 false 表示元素已存在,即发现重复 Hook
        if (!hookNames.add(Hook.getFullHookName(hook))) {
            throw new IllegalArgumentException("Duplicate hook instances found: " + Hook.getFullHookName(hook));
        }

        // 为每个 Hook 设置 Agent 名称和引用,便于 Hook 内部访问 Agent 上下文
        hook.setAgentName(this.name);
        hook.setAgent(this);
    }

    // ==================== 2. 创建状态图 ====================
    
    // 创建 StateGraph,配置:
    // - name: Graph 名称,用于标识和调试
    // - keyStrategyFactory: 状态键策略工厂,定义如何合并状态(如 messages 使用 AppendStrategy)
    // - stateSerializer: 状态序列化器,用于持久化
    StateGraph graph = new StateGraph(name, buildMessagesKeyStrategyFactory(effectiveHooks), stateSerializer);

    // ==================== 3. 添加核心节点 ====================
    
    // 添加模型推理节点(必需)
    // __model__ 节点负责调用 LLM 进行推理
    graph.addNode(AGENT_MODEL_NAME, node_async(this.llmNode));
    
    // 如果配置了工具,添加工具执行节点
    // __tool__ 节点负责执行模型请求的工具调用
    if (hasTools) {
        graph.addNode(AGENT_TOOL_NAME, node_async(this.toolNode));
    }

    // ==================== 4. 为 Hook 注入工具 ====================
    
    // 某些 Hook 可能需要使用工具(如 SkillsAgentHook)
    // 在 Agent 循环开始/结束时进行初始化/清理操作
    setupToolsForHooks(effectiveHooks, toolNode);

    // ==================== 5. 按 HookPosition 分类 ====================
    
    // 根据 @HookPositions 注解将 Hooks 分类到不同位置
    // HookPosition 定义了 Hook 在 ReAct 循环中的执行时机
    List<Hook> beforeAgentHooks = filterHooksByPosition(effectiveHooks, HookPosition.BEFORE_AGENT);   // Agent 开始前
    List<Hook> afterAgentHooks = filterHooksByPosition(effectiveHooks, HookPosition.AFTER_AGENT);     // Agent 结束后
    List<Hook> beforeModelHooks = filterHooksByPosition(effectiveHooks, HookPosition.BEFORE_MODEL);   // 每次模型调用前
    List<Hook> afterModelHooks = filterHooksByPosition(effectiveHooks, HookPosition.AFTER_MODEL);     // 每次模型调用后

    // ==================== 6. 添加 Hook 节点 ====================
    
    // 6.1 添加 beforeAgent Hook 节点
    // 在 Agent 整个生命周期开始前执行,通常用于:
    // - 初始化状态
    // - 注入系统指令
    // - 设置上下文
    for (Hook hook : beforeAgentHooks) {
        if (hook instanceof AgentHook agentHook) {
            // AgentHook 直接使用 beforeAgent 方法作为节点动作
            graph.addNode(Hook.getFullHookName(hook) + ".before", agentHook::beforeAgent);
        } else if (hook instanceof MessagesAgentHook messagesAgentHook) {
            // MessagesAgentHook 需要适配为操作消息列表的形式
            graph.addNode(Hook.getFullHookName(hook) + ".before", MessagesAgentHook.beforeAgentAction(messagesAgentHook));
        }
    }

    // 6.2 添加 afterAgent Hook 节点
    // 在 Agent 整个生命周期结束后执行,通常用于:
    // - 清理资源
    // - 记录日志
    // - 输出结果处理
    for (Hook hook : afterAgentHooks) {
        if (hook instanceof AgentHook agentHook) {
            graph.addNode(Hook.getFullHookName(hook) + ".after", agentHook::afterAgent);
        } else if (hook instanceof MessagesAgentHook messagesAgentHook) {
            graph.addNode(Hook.getFullHookName(hook) + ".after", MessagesAgentHook.afterAgentAction(messagesAgentHook));
        }
    }

    // 6.3 添加 beforeModel Hook 节点
    // 在每次模型调用前执行,通常用于:
    // - 修改发送给模型的消息
    // - 动态调整 ChatOptions
    // - 中断执行
    for (Hook hook : beforeModelHooks) {
        if (hook instanceof ModelHook modelHook) {
            if (hook instanceof InterruptionHook interruptionHook) {
                // InterruptionHook 特殊处理:支持中断执行,用于 human-in-the-loop
                // 它是一个可中断的节点,可以在执行时暂停等待外部输入
                graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", interruptionHook);
            } else {
                graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", modelHook::beforeModel);
            }
        } else if (hook instanceof MessagesModelHook messagesModelHook) {
            graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", MessagesModelHook.beforeModelAction(messagesModelHook));
        }
    }

    // 6.4 添加 afterModel Hook 节点
    // 在每次模型调用后执行,通常用于:
    // - 处理模型输出
    // - 人工审核
    // - 条件跳转控制
    for (Hook hook : afterModelHooks) {
        if (hook instanceof ModelHook modelHook) {
            if (hook instanceof HumanInTheLoopHook humanInTheLoopHook) {
                // HumanInTheLoopHook 特殊处理:支持人工介入审核
                // 在模型输出后暂停,等待人工确认或修改
                graph.addNode(Hook.getFullHookName(hook) + ".afterModel", humanInTheLoopHook);
            } else {
                graph.addNode(Hook.getFullHookName(hook) + ".afterModel", modelHook::afterModel);
            }
        } else if (hook instanceof MessagesModelHook messagesModelHook) {
            graph.addNode(Hook.getFullHookName(hook) + ".afterModel", MessagesModelHook.afterModelAction(messagesModelHook));
        }
    }

    // ==================== 7. 确定节点流向 ====================
    
    // 根据配置的 Hooks 确定图的入口、循环入口、循环出口、出口节点
    // 如果某个位置没有 Hook,则使用默认节点
    
    // 入口节点:图的第一个节点
    // 优先级:beforeAgent → beforeModel → __model__
    String entryNode = determineEntryNode(beforeAgentHooks, beforeModelHooks);
    
    // 循环入口节点:ReAct 循环的入口(工具执行后返回的位置)
    // 优先级:beforeModel → __model__
    String loopEntryNode = determineLoopEntryNode(beforeModelHooks);
    
    // 循环出口节点:模型调用后的出口(用于判断是否需要工具调用)
    // 优先级:afterModel → __model__
    String loopExitNode = determineLoopExitNode(afterModelHooks);
    
    // 出口节点:图的最后一个节点
    // 优先级:afterAgent → END
    String exitNode = determineExitNode(afterAgentHooks);

    // ==================== 8. 设置边 ====================
    
    // 添加从 START 到入口节点的边
    graph.addEdge(START, entryNode);
    
    // 设置所有 Hook 节点之间的边,以及核心节点的边
    // 包括:
    // - Hook 链的串联
    // - 条件边的路由逻辑
    // - 工具调用的循环边
    setupHookEdges(graph, beforeAgentHooks, afterAgentHooks, beforeModelHooks, afterModelHooks,
            entryNode, loopEntryNode, loopExitNode, exitNode, this);
    
    return graph;
}

一个完整的 ReAct 循环图如下:

在这里插入图片描述

流程说明:

  1. BEFORE_AGENTAgent 启动前执行一次(注入指令、初始化)
  2. BEFORE_MODEL:每轮 LLM 调用前执行
  3. model:大模型推理,判断是否需要工具
  4. AFTER_MODEL:模型输出后,路由判断
  5. tool:有工具调用则执行,执行完回到模型节点循环
  6. AFTER_AGENTAgent 结束前执行一次
  7. 无工具调用 → 直接结束流程
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐