一文读懂 ReAct 范式:让 AI Agent 真正学会“思考+行动“

当大模型只会"说"不会"做",它只是一个聊天机器人。ReAct 范式赋予 AI 自主思考与行动的能力------本文从理论到代码,完整拆解 ReAct 如何在 Spring AI Alibaba Graph Runtime 上运转。

一、为什么需要 ReAct?

先看一个场景:你问 AI "北京今天的空气质量指数是多少?"

  • 普通大模型的回答:

    根据我的训练数据,北京空气质量指数通常在...... ------ 胡说八道。它不知道"今天"的数据,但还是会编一个(幻觉)。

  • ReAct Agent 的回答:

    1. 思考(Reasoning):我需要查询实时空气质量数据,调用天气 API。

    2. 行动(Acting) :调用 get_air_quality("北京") \\rightarrow 返回 AQI=78。

    3. 观察(Observation):AQI=78,空气质量良好。

    4. 输出:北京今天的空气质量指数为 78,空气质量良好。

核心区别ReAct 让 AI 从"瞎猜"变成了"查证后回答"

二、ReAct 的核心理论

2.1 三个关键动作

ReAct(Reasoning + Acting)由三个循环动作组成:

复制代码
┌───────────────────────────────────────────┐
│              ReAct 循环                   │
│                                           │
│    Reasoning ────► Acting ────► Observation
│        ▲                               │  │
│        └───────────────────────────────┘  │
│                                           │
│    直到获得足够信息,生成最终回答         │
└───────────────────────────────────────────┘
  • Reasoning(推理):LLM 分析当前状态,决定下一步做什么。

  • Acting(行动):调用外部工具获取信息或执行操作。

  • Observation(观察):将工具返回的结果注入上下文,进入下一轮推理。

💡 注意 :这不是简单的"调用 API",而是一个思维链(Chain of Thought)驱动的决策循环

2.2 与纯 Chain-of-Thought 的区别

特性 Chain-of-Thought (CoT) ReAct
推理方式 纯文本思考链 思考 + 外部工具交互
知识来源 仅靠模型参数 模型 + 外部实时数据
可验证性 低(可能产生幻觉) 高(基于实际查询结果)
适用场景 数学 / 逻辑推理 需要实时信息 / 外部操作的场景

CoT 是"闭门思考",ReAct 是"边查边想"。

2.3 为什么 ReAct 是 Agent 的核心范式?

吴恩达在 Agentic AI 课程中提出:AI 应用的演进路径是 Prompt Engineering \\rightarrow RAG \\rightarrow Agent

  • Prompt Engineering:精心设计提示词,但模型只能用已有知识。

  • RAG:引入外部知识库,但流程是固定的(检索 \\rightarrow 生成)。

  • Agent:LLM 自主决定何时检索、检索什么、如何综合,这就是 ReAct。

ReAct 是 Agent 的"操作系统"------没有它,Agent 就退化成了普通的 RAG Pipeline。

三、Spring AI Alibaba 中的 ReAct 实现

3.1 Graph Runtime 架构

Spring AI Alibaba 的 Agent Framework 基于 Graph Runtime 构建。ReAct Agent 的执行流程映射到以下节点:

Plaintext

复制代码
┌──────────────┐            
│  Model Node  │ ◄── [Reasoning]: LLM 推理决策
└──────┬───────┘            
       │                    
   需要调用工具?            
    /       \               
  是         否             
  │           │             
┌─▼──────────┐┌─▼─────────┐ 
│ Tool Node  ││  输出结果 │ 
└─┬──────────┘└───────────┘ 
  │                         
┌─▼──────────┐              
│ Hook Node  │ ◄── [Observation]: 后处理/拦截/日志
└─┬──────────┘              
  │                         
  └───► 回到 Model Node(进入下一轮推理)
核心节点职责
节点 职责 对应 ReAct 阶段
Model Node 调用 LLM 生成推理和工具调用请求 Reasoning
Tool Node 执行工具调用,返回结果 Acting
Hook Node 后处理工具结果,注入上下文 Observation

3.2 核心代码实现

下面是一个完整的 Java ReAct Agent 实现示例:

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.alibaba.cloud.ai.agent.Agent; // 示例包名,请根据实际SDK调整
import com.alibaba.cloud.ai.chat.ChatClient;

@SpringBootApplication
public class ReactAgentApplication {

    public static void main(String[] args) {
        SpringApplication.run(ReactAgentApplication.class, args);
    }

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder.build();
    }

    // 定义工具 1:天气查询
    @Bean
    public FunctionToolCallback<WeatherRequest, WeatherResponse> weatherTool() {
        return FunctionToolCallback.builder("get_weather", (input, context) -> {
            // 实际场景中这里调用真实的天气 API
            String city = input.city();
            // 模拟 API 返回
            return new WeatherResponse(city, 25, "晴", 45);
        })
        .description("查询指定城市的天气信息,包括温度、天气状况和湿度")
        .inputType(WeatherRequest.class)
        .build();
    }

    // 定义工具 2:空气质量查询
    @Bean
    public FunctionToolCallback<AqiRequest, AqiResponse> aqiTool() {
        return FunctionToolCallback.builder("get_air_quality", (input, context) -> {
            String city = input.city();
            return new AqiResponse(city, 78, "良好");
        })
        .description("查询指定城市的实时空气质量指数(AQI)")
        .inputType(AqiRequest.class)
        .build();
    }

    // 构建 ReAct Agent
    @Bean
    public Agent reactAgent(ChatClient chatClient, List<FunctionToolCallback<?, ?>> tools) {
        return Agent.builder(chatClient)
            .systemPrompt("""
                你是一个智能助手,可以查询天气和空气质量信息。
                当用户询问天气或空气质量时,请使用相应的工具获取实时数据。
                不要猜测或使用训练数据中的信息,始终查询最新数据。
                如果需要多个信息,请依次调用对应工具。
                """)
            .tools(tools)
            .outputType(WeatherReport.class)  // 结构化输出封装
            .build();
    }
}

// ---------------- 请求/响应数据模型 (Records) ----------------

record WeatherRequest(
    @ToolParam(description = "城市名称") String city
) {}

record WeatherResponse(
    String city, int temperature, String condition, int humidity
) {}

record AqiRequest(
    @ToolParam(description = "城市名称") String city
) {}

record AqiResponse(
    String city, int aqi, String level
) {}

record WeatherReport(
    String city,
    int temperature,
    String weatherCondition,
    int humidity,
    int aqi,
    String airQuality,
    String summary
) {}

3.3 执行流程详解

当用户输入:"北京今天天气怎么样?空气好吗?",Agent 的执行过程如下:

  • 第一轮:Reasoning

    复制代码
    Model Node 输出:
      思考:用户询问北京天气和空气质量,我需要调用两个工具。
      工具调用:get_weather(city="北京")
  • 第二轮:Acting \\rightarrow Observation

    复制代码
    Tool Node 执行 get_weather ──► 返回 {temperature: 25, condition: "晴", humidity: 45}
    Hook Node 注入结果到当前上下文
  • 第三轮:Reasoning(继续循环)

    复制代码
    Model Node 输出:
      思考:已获取天气数据,还需要空气质量数据。
      工具调用:get_air_quality(city="北京")
  • 第四轮:Acting \\rightarrow Observation

    复制代码
    Tool Node 执行 get_air_quality ──► 返回 {aqi: 78, level: "良好"}
    Hook Node 注入结果到当前上下文
  • 第五轮:Reasoning(信息充足,生成最终结构化回答)

    复制代码
    WeatherReport {
      "city": "北京", 
      "temperature": 25, 
      "weatherCondition": "晴",
      "humidity": 45, 
      "aqi": 78, 
      "airQuality": "良好",
      "summary": "北京今天天气晴朗,气温25°C,湿度45%,空气质量良好(AQI 78),适合户外活动。"
    }

四、迭代循环的停止条件

ReAct 不能无限循环下去,Spring AI Alibaba 提供了多种精细化的停止机制:

4.1 自然停止

当 LLM 判断已有信息足够回答用户问题,不再发出工具调用请求时,循环自然结束并输出答案。

4.2 最大迭代次数限制

通过限制最大迭代轮数,防止 Agent 在工具异常时陷入死循环:

java 复制代码
Agent agent = Agent.builder(chatClient)
    .systemPrompt("...")
    .tools(tools)
    .maxIterations(10)  // 强制最多循环 10 轮
    .build();

4.3 ModelCallLimitHook

更精细的控制,可以在每次模型调用前检查:

java 复制代码
@Bean
public ModelCallLimitHook modelCallLimitHook() {
    return ModelCallLimitHook.builder()
        .maxCalls(15)           // 最多调用 15 次模型
        .onLimit((state) -> {
            // 超限时返回一个兜底回答
            return "抱歉,经过多轮尝试仍无法获取完整信息。";
        })
        .build();
}

// 注册到 Agent
Agent agent = Agent.builder(chatClient)
    .systemPrompt("...")
    .tools(tools)
    .hook(modelCallLimitHook())
    .build();

4.4 自定义停止条件

通过 Hook 实现更复杂的业务层停止逻辑:

java 复制代码
Agent agent = Agent.builder(chatClient)
    .systemPrompt("...")
    .tools(tools)
    .hook(new AgentHook() {
        @Override
        public HookPosition getPosition() {
            return HookPosition.BEFORE_MODEL;
        }

        @Override
        public boolean shouldTrigger(AgentState state) {
            // 如果外部工具连续失败 3 次,则触发强行停止
            int failCount = state.getValue("tool_fail_count", 0);
            return failCount < 3;
        }

        @Override
        public AgentState beforeModel(AgentState state) {
            return state;
        }
    })
    .build();

五、Tool Node 深度解析

5.1 工具注册的多种方式

Spring AI Alibaba 支持丰富的工具定义姿势:

方式一:@Tool 注解(最简洁)
java 复制代码
public class WeatherTools {
    @Tool(description = "查询城市天气")
    public WeatherResponse getWeather(
        @ToolParam(description = "城市名") String city
    ) {
        return weatherService.query(city);
    }
}
方式二:FunctionToolCallback(最灵活)
java 复制代码
FunctionToolCallback.<WeatherRequest, WeatherResponse>builder("get_weather", 
    (input, context) -> weatherService.query(input.city()))
    .description("查询城市天气")
    .inputType(WeatherRequest.class)
    .build();
方式三:BiFunction + ToolContext(需要上下文信息)
java 复制代码
BiFunction<WeatherRequest, ToolContext, WeatherResponse> weatherFunc = (input, context) -> {
    // 从 ToolContext 获取全局的用户信息、请求 ID 等
    String userId = context.get("userId");
    String requestId = context.get("requestId");
    log.info("用户 {} 查询天气,请求 ID:{}", userId, requestId);
    return weatherService.query(input.city());
};

5.2 工具错误处理:ToolInterceptor

生产环境中外部 API 随时可能挂掉。Spring AI Alibaba 提供了拦截器链机制:

java 复制代码
@Bean
public ToolInterceptor retryInterceptor() {
    return ToolInterceptor.builder()
        .onError((toolName, input, error) -> {
            log.warn("工具 {} 调用失败: {}", toolName, error.getMessage());
            // 返回一个友好的错误上下文给 LLM,让它自主决定下一步
            return ToolResult.error(
                "工具 " + toolName + " 调用失败:" + error.getMessage() +
                ",请尝试其他方式或告知用户。"
            );
        })
        .onSuccess((toolName, input, result) -> {
            log.info("工具 {} 调用成功", toolName);
            return result;
        })
        .build();
}

💡 核心设计思想错误信息也是 Observation(观察)的一部分 。LLM 收到工具失败的信息后,可以决定重试、更换工具、或者直接认输。这就是 ReAct 具备的自愈能力

5.3 MCP(Model Context Protocol)集成

除了本地定义的工具,Spring AI Alibaba 还支持通过 MCP 协议 连接跨语言、跨服务的外部工具生态:

java 复制代码
# application.yml
spring:
  ai:
    mcp:
      servers:
        - name: weather-mcp-server
          url: http://localhost:3001/sse
          description: 天气数据 MCP 服务
        - name: finance-mcp-server
          url: http://localhost:3002/sse
          description: 金融数据 MCP 服务
java 复制代码
// Agent 自动发现并动态注册生态内的 MCP 工具
Agent agent = Agent.builder(chatClient)
    .systemPrompt("你是一个可以查询天气和金融数据的助手。")
    .mcpServers("weather-mcp-server", "finance-mcp-server")
    .build();

MCP 的引入让 ReAct Agent 的能力圈从"自己写"无缝扩展到"全网接入"。

六、实战:构建一个多步骤推理的市场分析 Agent

让我们来看一个更贴近生产的示例------市场分析 Agent,展示 ReAct 面对复杂业务场景的多步流转能力:

java 复制代码
@Service
public class MarketAnalysisAgent {

    private final Agent agent;

    public MarketAnalysisAgent(ChatClient chatClient) {
        this.agent = Agent.builder(chatClient)
            .systemPrompt("""
                你是一个专业的市场分析助手。当用户询问市场情况时:
                1. 先搜索相关新闻和报告
                2. 查询关键财务数据
                3. 综合多方信息后给出结构化报告
                
                每一步操作都要使用工具获取真实数据,切记不要闭门造车和猜测。
                """)
            .tools(List.of(searchTool(), financeTool(), companyInfoTool()))
            .outputType(MarketAnalysisReport.class)
            .maxIterations(8)
            .build();
    }

    public MarketAnalysisReport analyze(String query) {
        AgentExecution execution = agent.call(query);
        return execution.getResult(MarketAnalysisReport.class);
    }

    private FunctionToolCallback<SearchRequest, SearchResponse> searchTool() {
        return FunctionToolCallback.builder("search_news",
            (input, ctx) -> searchService.search(input.query(), input.limit()))
            .description("搜索新闻和报告,获取最新市场动态")
            .inputType(SearchRequest.class)
            .build();
    }

    private FunctionToolCallback<FinanceRequest, FinanceResponse> financeTool() {
        return FunctionToolCallback.builder("get_finance_data",
            (input, ctx) -> financeService.query(input.ticker(), input.metrics()))
            .description("查询上市公司财务数据(营收、利润、市值等)")
            .inputType(FinanceRequest.class)
            .build();
    }

    private FunctionToolCallback<CompanyRequest, CompanyResponse> companyInfoTool() {
        return FunctionToolCallback.builder("get_company_info",
            (input, ctx) -> companyService.lookup(input.name()))
            .description("查询企业基本信息(行业、规模、竞争对手等)")
            .inputType(CompanyRequest.class)
            .build();
    }
}

执行链实录

用户输入:"分析一下阿里巴巴最近的市场表现"

  • 第1轮 - Reasoning:需要先搜索阿里巴巴最近的新闻。

    • Actingsearch_news(query="阿里巴巴 最新", limit=5)

    • Observation:拿到 5 条关于财报发布和出海进展的新闻。

  • 第2轮 - Reasoning:新闻提到了业绩增长,需要具体的财务数据来支撑。

    • Actingget_finance_data(ticker="BABA", metrics=["revenue","profit","marketCap"])

    • Observation:精确财务指标数据返回。

  • 第3轮 - Reasoning:结合财务数据,还需要摸清它目前的行业竞争格局。

    • Actingget_company_info(name="阿里巴巴")

    • Observation:企业画像及核心竞争对手情况返回。

  • 第4轮 - Reasoning:所有拼图收集完毕,开始整合信息并生成最终的结构化报告。

    • 输出 :返回拼装好的 MarketAnalysisReport 对象。

七、ReAct 的局限与最佳实践

7.1 局限性分析与应对策略

局限 说明 应对策略
Token 消耗高 每一轮推理和观察都会将完整上下文重新塞给 LLM 设置合理的 maxIterations + 定期消息修剪
延迟(Latency)较高 属于多轮串行交互,比较耗时 开启工具并行化调用 + 结果缓存(Cache)
可能陷入死循环 工具频繁报错或信息不全可能导致模型卡死重试 引入 ModelCallLimitHook + 设定失败自闭环
严重依赖模型推理质量 参数量较小的模型容易"忘记"工具调用格式 强化 systemPrompt,在提示词中提供 Few-Shot 示例

7.2 生产环境最佳实践

  1. System Prompt 要下死命令:必须在提示词中明确强调"先查后答","宁可报错,绝不瞎猜"。

  2. 工具的描述(Description)要字字珠玑:LLM 选工具全看 Description,模糊的描述会导致模型误调用或乱传参。

  3. 永远要有兜底机制 :生产环境必须设置 maxIterations 或者是调用拦截器,控制算力成本。

  4. 统一结构化输出 :使用 outputType 约束 Agent 的最终吐出格式,方便后端业务代码直接解析承接。

八、总结

ReAct 是 AI Agent 走向实用的底座范式,它让大模型真正实现了"边想边做,知行合一":

java 复制代码
  Reasoning (想清楚下一步) ──► Acting (执行工具拿结果) ──► Observation (总结并继续想)

Spring AI Alibaba 的生态中,基于 Graph Runtime 的 Model/Tool/Hook 三类节点极好地封装了这一复杂的决策循环,配合强大的 MCP 协议和拦截器机制,为 Java 开发者提供了一套生产级、开箱即用的 Agent 解决方案。

相关推荐
逍遥德2 小时前
MQTT教程详解-04.SpringBoot集成MQTT(告别手动控制)
java·spring boot·物联网·中间件·iot·iotdb
语戚2 小时前
力扣 3161. 块放置查询:线段树解法(Java 实现)
java·算法·leetcode·面试·线段树·力扣·
游九尘2 小时前
JavaScript 实现三段式版本号对比函数(app升级用)
javascript·uni-app
zhiSiBuYu05173 小时前
Claude-Code 新手极速上手指南
javascript·node.js
我命由我123453 小时前
Android 开发问题:MlKitException: An internal error occurred during initialization.
android·java·java-ee·android jetpack·android-studio·androidx·android runtime
888CC++3 小时前
java 并发编程
java·开发语言·python
罗超驿3 小时前
18.Web API 实战:元素与表单属性的获取和修改
开发语言·前端·javascript
无风听海3 小时前
JSON Web Token(JWT)完全指南
java·前端·json
山河已无恙4 小时前
BPF-eBPF 开发路线二:libbpf、CO-RE 与 libbpf-bootstrap认知
javascript·bootstrap·php