java实现Agent+ReAct demo(Spring Boot + Spring AI Alibaba ReAct Agent)

下面给你一套完整的、可直接运行的 Spring Boot + Spring AI Alibaba ReAct Agent 项目代码

二、pom.xml
<?xml version="1.0" encoding="UTF-8"?>

4.0.0

复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.4</version>
    <relativePath/>
</parent>

<groupId>com.example</groupId>
<artifactId>react-agent-demo</artifactId>
<version>1.0.0</version>
<name>react-agent-demo</name>
<description>Java ReAct Agent Demo</description>

<properties>
    <java.version>17</java.version>
    <spring-ai.version>1.0.0-M3</spring-ai.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

<repositories>
    <repository>
        <id>spring-milestones</id>
        <url>https://repo.spring.io/milestone</url>
    </repository>
</repositories>

如果你的模型不是 OpenAI 兼容接口,可以把 spring-ai-openai-spring-boot-starter 换成对应模型实现。

三、application.yml

server:

port: 8080

spring:

ai:

openai:

base-url: https://dashscope.aliyuncs.com/compatible-mode

api-key: ${AI_API_KEY}

chat:

options:

model: qwen-plus

启动前配置环境变量:

export AI_API_KEY=你的模型API密钥

四、启动类

package com.example.agent;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class ReactAgentApplication {

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

}

五、定义工具

ReAct Agent 的核心能力来自工具。这里定义两个示例工具:订单查询 和 天气查询。

OrderTools.java

package com.example.agent.tool;

import org.springframework.ai.tool.annotation.Tool;

import org.springframework.ai.tool.annotation.ToolParam;

import org.springframework.stereotype.Component;

import java.util.Map;

@Component

public class OrderTools {

复制代码
private static final Map<String, String> MOCK_ORDERS = Map.of(
        "1001", "已发货",
        "1002", "待支付",
        "1003", "已完成"
);

@Tool(description = "根据订单号查询订单状态")
public String queryOrderStatus(
        @ToolParam(description = "订单号,例如 1001") String orderId) {

    String status = MOCK_ORDERS.get(orderId);

    if (status == null) {
        return "未找到订单:" + orderId;
    }

    return "订单号:" + orderId + ",状态:" + status;
}

}

WeatherTools.java

package com.example.agent.tool;

import org.springframework.ai.tool.annotation.Tool;

import org.springframework.ai.tool.annotation.ToolParam;

import org.springframework.stereotype.Component;

import java.util.Map;

@Component

public class WeatherTools {

复制代码
private static final Map<String, String> MOCK_WEATHER = Map.of(
        "北京", "晴,26℃",
        "上海", "多云,24℃",
        "杭州", "小雨,22℃"
);

@Tool(description = "查询指定城市的天气信息")
public String getWeather(
        @ToolParam(description = "城市名称,例如 北京") String city) {

    String weather = MOCK_WEATHER.get(city);

    if (weather == null) {
        return "暂不支持查询城市:" + city;
    }

    return city + "的天气:" + weather;
}

}

六、Agent 配置

AgentConfig.java

package com.example.agent.config;

import com.example.agent.tool.OrderTools;

import com.example.agent.tool.WeatherTools;

import org.springframework.ai.chat.client.ChatClient;

import org.springframework.ai.chat.model.ChatModel;

import org.springframework.ai.tool.ToolCallbackProvider;

import org.springframework.ai.tool.method.MethodToolCallbackProvider;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class AgentConfig {

复制代码
@Bean
public ToolCallbackProvider agentTools(OrderTools orderTools,
                                       WeatherTools weatherTools) {
    return MethodToolCallbackProvider.builder()
            .toolObjects(orderTools, weatherTools)
            .build();
}

@Bean
public ChatClient chatClient(ChatModel chatModel,
                             ToolCallbackProvider agentTools) {
    return ChatClient.builder(chatModel)
            .defaultSystem("你是一个智能助手,能够通过工具查询订单状态和天气信息。" +
                    "请先分析用户需求,再决定是否调用工具,最后给出完整回答。")
            .defaultToolCallbacks(agentTools.getToolCallbacks())
            .build();
}

}

这里的关键是:

ChatClient chatModel -> tools

模型负责推理,工具负责执行。模型判断需要工具时,会自动触发工具调用。

七、Agent 服务层

ReactAgentService.java

package com.example.agent.agent;

import org.springframework.ai.chat.client.ChatClient;

import org.springframework.stereotype.Service;

@Service

public class ReactAgentService {

复制代码
private final ChatClient chatClient;

public ReactAgentService(ChatClient chatClient) {
    this.chatClient = chatClient;
}

public String run(String userMessage) {
    return chatClient.prompt()
            .user(userMessage)
            .call()
            .content();
}

}

这个类就是 ReAct Agent 的入口。

它背后的执行逻辑是:

用户输入

模型思考是否需要工具

需要工具 → 调用工具 → 拿到结果 → 继续思考

不需要工具 → 输出最终答案

八、Controller 接口

AgentController.java

package com.example.agent.controller;

import com.example.agent.agent.ReactAgentService;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

@RestController

@RequestMapping("/agent")

public class AgentController {

复制代码
private final ReactAgentService reactAgentService;

public AgentController(ReactAgentService reactAgentService) {
    this.reactAgentService = reactAgentService;
}

@PostMapping("/chat")
public AgentResponse chat(@RequestBody AgentRequest request) {
    String answer = reactAgentService.run(request.getMessage());
    return new AgentResponse(answer);
}

public record AgentRequest(String message) {
}

public record AgentResponse(String answer) {
}

}

九、测试请求

启动项目后,调用接口:

curl -X POST http://localhost:8080/agent/chat

-H "Content-Type: application/json"

-d '{"message":"帮我查询订单号1001的状态"}'

返回示例:

{

"answer": "订单号1001的状态是已发货。"

}

再测试天气:

curl -X POST http://localhost:8080/agent/chat

-H "Content-Type: application/json"

-d '{"message":"北京今天天气怎么样?"}'

返回示例:

{

"answer": "北京的天气是晴,26℃。"

}

十、ReAct 执行流程

这个项目的执行流程如下:

用户输入:帮我查询订单号1001的状态

ChatClient 发送请求给大模型

模型判断需要调用 queryOrderStatus 工具

框架自动执行 OrderTools.queryOrderStatus("1001")

工具返回:订单号:1001,状态:已发货

模型结合工具结果生成最终回答

返回给前端

这就是一个典型的 ReAct 流程:

Thought → Action → Observation → Final Answer

你说得对,上面那版确实没有体现 ReAct 的核心循环,只是用 Spring AI 的 ChatClient 做了一次工具调用,本质上是"模型调工具",不是真正的 ReAct。

真正的 ReAct 必须显式体现:

Thought

Action

Observation

Final Answer

并且是循环执行的。

下面给你一个真正体现 ReAct 循环的 Java 版本,适合博客讲解原理。

一、ReAct 的核心循环

ReAct 不是普通工具调用,而是这样一个循环:

模型思考:我需要做什么?

模型决定调用工具:Action

系统执行工具:Observation

把 Observation 放回上下文

模型继续思考

直到模型输出 Final Answer

伪代码是这样的:

while (true) {

模型思考并输出下一步;

复制代码
if (输出的是 Final Answer) {
    返回最终答案;
}

if (输出的是 Action) {
    执行工具;
    把工具结果加入上下文;
    继续下一轮循环;
}

}

这才是 ReAct。

二、项目结构

react-agent-demo/

├── ReactAgentApp.java

├── ReActAgent.java

├── Tool.java

├── ToolRegistry.java

├── tools/

│ ├── OrderQueryTool.java

│ └── WeatherQueryTool.java

└── LlmClient.java

三、工具接口

Tool.java

public interface Tool {

复制代码
String getName();

String getDescription();

String execute(String input);

}

每个工具都要实现这个接口。

四、工具注册中心

ToolRegistry.java

import java.util.HashMap;

import java.util.List;

import java.util.Map;

public class ToolRegistry {

复制代码
private final Map<String, Tool> tools = new HashMap<>();

public ToolRegistry(List<Tool> toolList) {
    for (Tool tool : toolList) {
        tools.put(tool.getName(), tool);
    }
}

public Tool get(String name) {
    return tools.get(name);
}

public String buildToolDescription() {
    StringBuilder sb = new StringBuilder();

    for (Tool tool : tools.values()) {
        sb.append("- ")
          .append(tool.getName())
          .append(": ")
          .append(tool.getDescription())
          .append("n");
    }

    return sb.toString();
}

}

它负责:

注册工具

查找工具

生成工具描述给模型

五、订单查询工具

OrderQueryTool.java

public class OrderQueryTool implements Tool {

复制代码
@Override
public String getName() {
    return "query_order";
}

@Override
public String getDescription() {
    return "根据订单号查询订单状态,输入参数是订单号,例如 1001";
}

@Override
public String execute(String input) {
    if ("1001".equals(input)) {
        return "订单1001状态:已发货";
    }

    if ("1002".equals(input)) {
        return "订单1002状态:待支付";
    }

    return "未找到订单:" + input;
}

}

六、天气查询工具

WeatherQueryTool.java

public class WeatherQueryTool implements Tool {

复制代码
@Override
public String getName() {
    return "get_weather";
}

@Override
public String getDescription() {
    return "查询城市天气,输入参数是城市名,例如 北京";
}

@Override
public String execute(String input) {
    if ("北京".equals(input)) {
        return "北京天气:晴,26℃";
    }

    if ("上海".equals(input)) {
        return "上海天气:多云,24℃";
    }

    return "暂不支持查询城市:" + input;
}

}

七、ReAct 核心 Agent

ReActAgent.java

import java.util.ArrayList;

import java.util.List;

public class ReActAgent {

复制代码
private final LlmClient llmClient;
private final ToolRegistry toolRegistry;

private static final int MAX_STEPS = 5;

public ReActAgent(LlmClient llmClient, ToolRegistry toolRegistry) {
    this.llmClient = llmClient;
    this.toolRegistry = toolRegistry;
}

public String run(String question) {

    List<String> history = new ArrayList<>();
    history.add("Question: " + question);

    for (int step = 1; step <= MAX_STEPS; step++) {

        String prompt = buildPrompt(history);

        System.out.println("========== Step " + step + " ==========");
        System.out.println("Prompt:n" + prompt);

        String llmOutput = llmClient.call(prompt);

        System.out.println("LLM Output:n" + llmOutput);

        if (llmOutput.contains("Final Answer:")) {
            return extractFinalAnswer(llmOutput);
        }

        if (llmOutput.contains("Action:")) {
            String action = extractAction(llmOutput);
            String actionInput = extractActionInput(llmOutput);

            Tool tool = toolRegistry.get(action);

            if (tool == null) {
                history.add("Observation: 工具不存在:" + action);
                continue;
            }

            String observation = tool.execute(actionInput);

            history.add("Thought: " + extractThought(llmOutput));
            history.add("Action: " + action);
            history.add("Action Input: " + actionInput);
            history.add("Observation: " + observation);

            continue;
        }

        history.add("Thought: " + llmOutput);
    }

    return "超过最大推理步数,未能得到最终答案。";
}

private String buildPrompt(List<String> history) {

    StringBuilder sb = new StringBuilder();

    sb.append("你是一个 ReAct Agent。n");
    sb.append("你可以通过工具解决用户问题。nn");

    sb.append("可用工具:n");
    sb.append(toolRegistry.buildToolDescription());
    sb.append("n");

    sb.append("你必须按以下格式输出:nn");

    sb.append("Thought: 思考下一步该做什么n");
    sb.append("Action: 工具名称n");
    sb.append("Action Input: 工具输入参数nn");

    sb.append("或者:nn");
    sb.append("Thought: 我已经获得足够信息n");
    sb.append("Final Answer: 最终答案nn");

    sb.append("历史对话:n");

    for (String line : history) {
        sb.append(line).append("n");
    }

    return sb.toString();
}

private String extractFinalAnswer(String output) {
    return output.substring(output.indexOf("Final Answer:") + "Final Answer:".length())
                 .trim();
}

private String extractThought(String output) {
    return extractBetween(output, "Thought:", "Action:");
}

private String extractAction(String output) {
    return extractBetween(output, "Action:", "Action Input:");
}

private String extractActionInput(String output) {
    if (output.contains("Action Input:")) {
        return output.substring(output.indexOf("Action Input:") + "Action Input:".length())
                     .trim();
    }
    return "";
}

private String extractBetween(String text, String start, String end) {
    int startIndex = text.indexOf(start);
    if (startIndex == -1) {
        return "";
    }

    startIndex += start.length();

    int endIndex = text.indexOf(end, startIndex);
    if (endIndex == -1) {
        return text.substring(startIndex).trim();
    }

    return text.substring(startIndex, endIndex).trim();
}

}

这个类就是 ReAct 的核心。

它的关键逻辑是:

把问题放进 history

构造 prompt 发给模型

解析模型输出

如果是 Final Answer,结束

如果是 Action,执行工具

把 Observation 放回 history

继续下一轮

八、LLM 客户端

LlmClient.java

public interface LlmClient {

复制代码
String call(String prompt);

}

这里先写一个模拟实现,方便博客演示。

MockLlmClient.java

public class MockLlmClient implements LlmClient {

复制代码
private int callCount = 0;

@Override
public String call(String prompt) {
    callCount++;

    if (callCount == 1) {
        return "Thought: 用户想查询订单1001的状态,我需要调用订单查询工具。n" +
               "Action: query_ordern" +
               "Action Input: 1001";
    }

    return "Thought: 我已经获得了订单状态信息,可以回答用户。n" +
           "Final Answer: 订单1001的状态是已发货。";
}

}

真实项目中,这里应该调用大模型接口,比如 OpenAI、通义千问、DeepSeek、GLM 等。

九、启动类

ReactAgentApp.java

import java.util.List;

public class ReactAgentApp {

复制代码
public static void main(String[] args) {

    ToolRegistry toolRegistry = new ToolRegistry(
            List.of(
                    new OrderQueryTool(),
                    new WeatherQueryTool()
            )
    );

    LlmClient llmClient = new MockLlmClient();

    ReActAgent agent = new ReActAgent(llmClient, toolRegistry);

    String answer = agent.run("帮我查询订单1001的状态");

    System.out.println("最终答案:" + answer);
}

}

十、运行效果

输出大致如下:

========== Step 1 ==========

Prompt:

你是一个 ReAct Agent。

你可以通过工具解决用户问题。

可用工具:

query_order: 根据订单号查询订单状态,输入参数是订单号,例如 1001

get_weather: 查询城市天气,输入参数是城市名,例如 北京

你必须按以下格式输出:

Thought: 思考下一步该做什么

Action: 工具名称

Action Input: 工具输入参数

或者:

Thought: 我已经获得足够信息

Final Answer: 最终答案

历史对话:

Question: 帮我查询订单1001的状态

LLM Output:

Thought: 用户想查询订单1001的状态,我需要调用订单查询工具。

Action: query_order

Action Input: 1001

执行工具:query_order,输入:1001

========== Step 2 ==========

Prompt:

...

历史对话:

Question: 帮我查询订单1001的状态

Thought: 用户想查询订单1001的状态,我需要调用订单查询工具。

Action: query_order

Action Input: 1001

Observation: 订单1001状态:已发货

LLM Output:

Thought: 我已经获得了订单状态信息,可以回答用户。

Final Answer: 订单1001的状态是已发货。

最终答案:订单1001的状态是已发货。

十一、博客里可以这样解释

这个版本真正体现了 ReAct 的循环:

用户提问

模型思考

模型决定调用工具

系统执行工具

工具结果进入上下文

模型继续思考

输出 Final Answer

它和 Spring AI ChatClient 自动工具调用的区别是:

方式 特点

Spring AI 自动工具调用 框架帮你处理循环,代码简洁

手写 ReAct 循环 能清楚看到 Thought、Action、Observation、Final Answer

十二、真实项目改造建议

如果要接真实大模型,只需要替换 LlmClient:

public class QwenLlmClient implements LlmClient {

复制代码
@Override
public String call(String prompt) {
    // 调用通义千问、OpenAI、DeepSeek 等接口
    return "模型返回内容";
}

}

然后在启动类里替换:

LlmClient llmClient = new QwenLlmClient();

这样整套 ReAct Agent 就能跑起来了。

相关推荐
看浪的路人1 小时前
第6讲:SQL 解析器
java·开发语言·数据库
weixin_440784111 小时前
【OkHttp实现原理】
android·java·okhttp
a3535413821 小时前
C++项目如何架构优化
java·c++·架构
AC赳赳老秦1 小时前
官方技术文档聚合实践:用 OpenClaw 批量抓取开源项目文档,构建离线可检索技术知识库
java·运维·服务器·python·信息可视化·deepseek·openclaw
略略略咯咯1 小时前
ActiveMQ
java·activemq·java-activemq
万亿少女的梦1681 小时前
基于Spring Boot、Java与MySQL的同城宠物服务系统设计与实现
java·spring boot·mysql·系统设计·协同过滤
不才不才不不才1 小时前
Spring 源码系列(20): @SpringBootApplication 三注解拆解
java·后端·spring
罗小爬EX1 小时前
AI Chat 多类型流式事件处理方案(以AgentScope Java 2为例)
java·人工智能·状态模式
zhanghaha13142 小时前
Python进阶教程:5_XML 解析 —— 新手完全指南
java·前端·数据库