当大模型只会"说"不会"做",它只是一个聊天机器人。ReAct 范式赋予 AI 自主思考与行动的能力------本文从理论到代码,完整拆解 ReAct 如何在 Spring AI Alibaba Graph Runtime 上运转。
一、为什么需要 ReAct?
先看一个场景:你问 AI "北京今天的空气质量指数是多少?"
-
普通大模型的回答:
根据我的训练数据,北京空气质量指数通常在...... ------ 胡说八道。它不知道"今天"的数据,但还是会编一个(幻觉)。
-
ReAct Agent 的回答:
-
思考(Reasoning):我需要查询实时空气质量数据,调用天气 API。
-
行动(Acting) :调用
get_air_quality("北京")\\rightarrow 返回 AQI=78。 -
观察(Observation):AQI=78,空气质量良好。
-
输出:北京今天的空气质量指数为 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:需要先搜索阿里巴巴最近的新闻。
-
Acting :
search_news(query="阿里巴巴 最新", limit=5) -
Observation:拿到 5 条关于财报发布和出海进展的新闻。
-
-
第2轮 - Reasoning:新闻提到了业绩增长,需要具体的财务数据来支撑。
-
Acting :
get_finance_data(ticker="BABA", metrics=["revenue","profit","marketCap"]) -
Observation:精确财务指标数据返回。
-
-
第3轮 - Reasoning:结合财务数据,还需要摸清它目前的行业竞争格局。
-
Acting :
get_company_info(name="阿里巴巴") -
Observation:企业画像及核心竞争对手情况返回。
-
-
第4轮 - Reasoning:所有拼图收集完毕,开始整合信息并生成最终的结构化报告。
- 输出 :返回拼装好的
MarketAnalysisReport对象。
- 输出 :返回拼装好的
七、ReAct 的局限与最佳实践
7.1 局限性分析与应对策略
| 局限 | 说明 | 应对策略 |
|---|---|---|
| Token 消耗高 | 每一轮推理和观察都会将完整上下文重新塞给 LLM | 设置合理的 maxIterations + 定期消息修剪 |
| 延迟(Latency)较高 | 属于多轮串行交互,比较耗时 | 开启工具并行化调用 + 结果缓存(Cache) |
| 可能陷入死循环 | 工具频繁报错或信息不全可能导致模型卡死重试 | 引入 ModelCallLimitHook + 设定失败自闭环 |
| 严重依赖模型推理质量 | 参数量较小的模型容易"忘记"工具调用格式 | 强化 systemPrompt,在提示词中提供 Few-Shot 示例 |
7.2 生产环境最佳实践
-
System Prompt 要下死命令:必须在提示词中明确强调"先查后答","宁可报错,绝不瞎猜"。
-
工具的描述(Description)要字字珠玑:LLM 选工具全看 Description,模糊的描述会导致模型误调用或乱传参。
-
永远要有兜底机制 :生产环境必须设置
maxIterations或者是调用拦截器,控制算力成本。 -
统一结构化输出 :使用
outputType约束 Agent 的最终吐出格式,方便后端业务代码直接解析承接。
八、总结
ReAct 是 AI Agent 走向实用的底座范式,它让大模型真正实现了"边想边做,知行合一":
java
Reasoning (想清楚下一步) ──► Acting (执行工具拿结果) ──► Observation (总结并继续想)
在 Spring AI Alibaba 的生态中,基于 Graph Runtime 的 Model/Tool/Hook 三类节点极好地封装了这一复杂的决策循环,配合强大的 MCP 协议和拦截器机制,为 Java 开发者提供了一套生产级、开箱即用的 Agent 解决方案。