第一部分:概述

一、 ReAct

ReActReasoning(推理)和 Acting(行动)的缩写,是一种让大语言模型(LLM)像人类一样"边想边做"的架构模式。

1.1 为什么需要 ReAct?

想象你问朋友"明天去杭州西湖穿什么衣服合适?",他不会直接瞎猜,而是会:

  1. 思考(Reasoning):“需要知道杭州明天的天气”
  2. 行动(Acting):打开手机查天气预报
  3. 观察(Observation):看到明天杭州 15-22°C,小雨
  4. 再思考:“气温适中但有雨,需要带伞,穿薄外套”
  5. 回答:“建议穿薄外套,记得带伞”

ReAct 就是让 AI 复现这个**“思考 → 行动 → 观察 → 再思考”**的循环过程,直到得出最终答案。

1.2 ReAct 的核心循环

需要工具

无需工具

用户提问

LLM 推理

调用工具

直接回答

观察结果

返回用户

关键洞察:ReAct 不是一次性生成答案,而是通过多轮交互让 LLM 逐步逼近正确答案。每轮 LLM 都会输出一个"想法"(Thought),决定是继续调用工具还是给出最终回答。


二、项目定位与能力

本项目是 Spring AI Alibaba Graph 框架的官方示例,演示如何构建一个具备工具调用能力的 AI 智能体。

2.1 它能做什么?
场景 示例 AI 行为
天气查询 “杭州天气怎么样?” 调用天气工具 → 获取数据 → 组织回答
多城市对比 “北京和上海哪个更热?” 分别查询两地 → 对比温度 → 给出结论
闲聊 “你好呀” 直接友好回复,不调用工具
2.2 技术栈全景

基础设施层

AI 编排层

应用层

Spring Boot 3.x

ReactController
REST API

CompiledGraph
ReAct Agent 图

LLM Node
Qwen-Max

Tool Node
WeatherService

阿里云 DashScope

WeatherAPI.com

层级 组件 作用说明
应用层 Spring Boot 3.x 提供 Web 容器和依赖注入
AI 框架 Spring AI Alibaba 统一封装 LLM 调用、工具注册、提示词管理
大模型 Qwen-Max 负责推理决策和文本生成
图编排 spring-ai-alibaba-graph-core 用"图"的方式编排 ReAct 循环逻辑
工具层 WeatherService 封装天气查询能力,供 AI 调用

三、核心组件深度解析

3.1 启动入口:ReactApplication
@SpringBootApplication
@EnableConfigurationProperties(WeatherProperties.class)
public class ReactApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReactApplication.class, args);
    }
}

作用拆解

  • @SpringBootApplication:开启自动配置、组件扫描
  • @EnableConfigurationProperties:将 application.yml 中以 spring.ai.alibaba.toolcalling.weather 开头的配置自动绑定到 WeatherProperties 对象

为什么需要配置绑定? 因为天气服务的 API Key 需要从外部注入,而不是硬编码在代码里。

3.2 核心配置:ReactAutoconfiguration

这是整个项目的"心脏",负责把各个零件组装成能工作的 ReAct Agent。

@Configuration
public class ReactAutoconfiguration {

    @Bean
    public ReactAgent normalReactAgent(ChatModel chatModel, ToolCallbackResolver resolver) {
        // 步骤 1:构建 ChatClient(LLM 调用客户端)
        ChatClient chatClient = ChatClient.builder(chatModel)
            .defaultToolNames("getWeatherFunction")  // 注册可用工具
            .defaultAdvisors(new SimpleLoggerAdvisor())  // 添加日志切面
            .defaultOptions(OpenAiChatOptions.builder()
                .internalToolExecutionEnabled(false)  // ⚠️ 关键:关闭内部工具执行
                .build())
            .build();

        // 步骤 2:构建 ReactAgent(ReAct 逻辑封装)
        return ReactAgent.builder()
            .name("React Agent Demo")
            .chatClient(chatClient)
            .resolver(resolver)  // 工具解析器:根据名称找到具体工具
            .build();
    }

    @Bean
    public CompiledGraph reactAgentGraph(@Qualifier("normalReactAgent") ReactAgent reactAgent) {
        // 步骤 3:编译图(将 ReAct 逻辑编译成可执行的状态机)
        CompiledGraph compiledGraph = reactAgent.getAndCompileGraph();

        // 步骤 4:打印 PlantUML 图(调试用,可视化图结构)
        GraphRepresentation graphRepresentation = compiledGraph.getGraph(GraphRepresentation.Type.PLANTUML);
        System.out.println(graphRepresentation.content());

        return compiledGraph;
    }
}

关键配置解析

配置项 原因
internalToolExecutionEnabled(false) 关闭 如果开启,Spring AI 会在内部自动执行工具调用,但我们希望由 Graph 框架来编排整个 ReAct 循环,所以需要关闭
defaultToolNames("getWeatherFunction") 工具名 告诉 LLM 有哪些工具可用,LLM 会根据用户问题决定是否调用
SimpleLoggerAdvisor 日志切面 记录每次 LLM 调用的输入输出,方便调试

⚠️ 潜在问题 1:超时配置不合理

// 原代码(问题)
RequestConfig.custom()
    .setConnectTimeout(Timeout.of(10, TimeUnit.MINUTES))  // ❌ 10分钟太长
    .setResponseTimeout(Timeout.of(10, TimeUnit.MINUTES))

问题分析:天气查询是轻量级调用,10 分钟超时会导致故障时长时间挂起,浪费资源。

修复建议

RequestConfig.custom()
    .setConnectTimeout(Timeout.of(30, TimeUnit.SECONDS))      // 连接超时 30 秒
    .setResponseTimeout(Timeout.of(60, TimeUnit.SECONDS))     // 响应超时 60 秒
    .build();
3.3 HTTP 接口:ReactController
@RestController
@RequestMapping("/react")
public class ReactController {

    private final CompiledGraph compiledGraph;

    ReactController(@Qualifier("reactAgentGraph") CompiledGraph compiledGraph) {
        this.compiledGraph = compiledGraph;
    }

    @GetMapping("/chat")
    public String simpleChat(String query) {
        // 1. 构建用户消息
        Optional<OverAllState> result = compiledGraph.invoke(
            Map.of("messages", new UserMessage(query))
        );

        // 2. 提取 AI 回复(⚠️ 有风险,见下方)
        List<Message> messages = (List<Message>) result.get().value("messages").get();
        AssistantMessage assistantMessage = (AssistantMessage) messages.get(messages.size() - 1);

        return assistantMessage.getText();
    }
}

⚠️ 潜在问题 2:空指针风险

// 原代码(问题)
List<Message> messages = (List<Message>) result.get().value("messages").get();
// 如果 result 是 empty,.get() 抛出 NoSuchElementException

修复建议

@GetMapping("/chat")
public String simpleChat(String query) {
    // 参数校验
    if (query == null || query.trim().isEmpty()) {
        return "请提供查询内容";
    }

    // 调用 ReAct 图
    Optional<OverAllState> result = compiledGraph.invoke(
        Map.of("messages", new UserMessage(query))
    );

    // 安全取值
    OverAllState state = result.orElseThrow(() -> 
        new RuntimeException("ReAct 图执行失败,未返回结果"));

    List<Message> messages = (List<Message>) state.value("messages")
        .orElse(Collections.emptyList());

    if (messages.isEmpty()) {
        return "未获取到有效回复";
    }

    AssistantMessage assistantMessage = (AssistantMessage) messages.get(messages.size() - 1);
    return assistantMessage.getText();
}

⚠️ 潜在问题 3:GET 方法限制

// 原代码:GET 请求,参数通过 URL 传递
@GetMapping("/chat")
public String simpleChat(String query)  // query 暴露在 URL 中

问题:URL 长度有限制(通常 2KB-8KB),且中文需要编码,不适合长文本。

修复建议

@PostMapping("/chat")
public String simpleChat(@RequestBody ChatRequest request) {
    // 使用 POST + JSON Body,支持更复杂的内容
}

public record ChatRequest(String query) {}

四、天气工具组件

4.1 WeatherService:AI 的"手"

WeatherService 实现了 Java 的 Function 接口,这是 Spring AI 定义工具的标准方式。

public class WeatherService implements Function<WeatherService.Request, WeatherService.Response> {

    // 输入参数:LLM 调用时会自动填充
    public record Request(
        @JsonProperty(required = true, value = "city") 
        @JsonPropertyDescription("城市名称,如:杭州、Beijing") 
        String city,

        @JsonProperty(required = true, value = "days") 
        @JsonPropertyDescription("预报天数,范围 1-14 天") 
        int days
    ) {}

    // 输出参数:工具执行后返回给 LLM
    public record Response(
        @JsonProperty(required = true, value = "city") String city,
        @JsonProperty(required = true, value = "current") Map<String, Object> current,
        @JsonProperty(required = true, value = "forecastDays") List<Map<String, Object>> forecastDays
    ) {}

    @Override
    public Response apply(Request request) {
        // 实际调用逻辑...
    }
}

注解解析

注解 作用 给谁看
@JsonProperty 定义 JSON 字段名和是否必填 序列化框架
@JsonPropertyDescription 描述字段含义 LLM(这是关键!LLM 根据这个描述理解参数怎么用)

为什么需要描述? LLM 看到工具定义时,并不知道 “city” 是城市名,“days” 是天数。@JsonPropertyDescription 就是给 LLM 看的"说明书"。

4.2 自动配置:WeatherAutoConfiguration
@Configuration
@ConditionalOnClass(WeatherService.class)  // 只有当 WeatherService 类存在时才生效
@ConditionalOnProperty(  // 只有当配置中 enabled=true 时才生效
    prefix = "spring.ai.alibaba.toolcalling.weather", 
    name = "enabled", 
    havingValue = "true"
)
public class WeatherAutoConfiguration {

    @Bean(name = "getWeatherFunction")  // ⚠️ 名称必须与 Agent 注册的一致
    @ConditionalOnMissingBean  // 只有当用户没有自定义时才创建
    @Description("查询指定城市的天气信息,支持未来 1-14 天预报")
    public WeatherService getWeatherServiceFunction(WeatherProperties properties) {
        return new WeatherService(properties);
    }
}

⚠️ 潜在问题 4:Mock 数据陷阱

// 当前代码实际走的是这个方法(假数据)
private Response doGetWeatherMock(Request request) {
    if (Objects.equals("杭州", request.city())) {
        return new Response(request.city(), 
            Map.of("temp", 25, "condition", "Sunny"), 
            List.of(...));
    }
    // ... 其他城市也是硬编码
}

问题:虽然代码里有 doGetWeather() 方法(真实 API 调用),但实际执行的是 doGetWeatherMock()。这意味着无论问哪个城市,返回的都是预设的假数据。

修复建议(带降级策略):

@Override
public Response apply(Request request) {
    try {
        // 优先调用真实 API
        return doGetWeather(request);
    } catch (Exception e) {
        logger.error("天气 API 调用失败,降级到 Mock 数据: {}", e.getMessage());
        // 降级返回 Mock 数据,保证服务可用性
        return doGetWeatherMock(request);
    }
}

private Response doGetWeather(Request request) {
    String location = WeatherUtils.preprocessLocation(request.city());
    String url = UriComponentsBuilder.fromHttpUrl(WEATHER_API_URL)
        .queryParam("q", location)
        .queryParam("days", request.days())
        .queryParam("key", properties.getApiKey())
        .toUriString();

    return restTemplate.getForObject(url, Response.class);
}

⚠️ 潜在问题 5:Bean 名称硬编码

// Agent 中硬编码了工具名
.defaultToolNames("getWeatherFunction")

// 配置类中定义了 Bean 名
@Bean(name = "getWeatherFunction")

问题:两边名称必须完全一致,改名时容易遗漏,导致工具找不到。

修复建议:使用常量或配置中心管理:

public class ToolNames {
    public static final String WEATHER = "getWeatherFunction";
}

// 两边都引用常量
.defaultToolNames(ToolNames.WEATHER)
@Bean(name = ToolNames.WEATHER)
4.3 WeatherUtils:城市名转换
public class WeatherUtils {
    /**
     * 将中文城市名转换为拼音
     * 因为 WeatherAPI 需要英文城市名
     */
    public static String preprocessLocation(String location) {
        if (containsChinese(location)) {
            return PinyinUtil.getPinyin(location, "");  // 杭州 -> hangzhou
        }
        return location;  // Beijing -> Beijing
    }
}

为什么需要转换? 国外的天气 API 通常不支持中文城市名,所以需要先把"杭州"转成"hangzhou"。


五、配置详解

5.1 application.yml 完整配置
server:
  port: 8080

spring:
  application:
    name: react-agent

  ai:
    # ========== 天气工具配置 ==========
    alibaba:
      toolcalling:
        weather:
          enabled: true                    # 启用天气工具
          api-key: ${WEATHER_API_KEY}      # 从环境变量读取

    # ========== 大模型配置 ==========
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}     # DashScope API Key

    # ========== OpenAI 兼容配置 ==========
    # Spring AI 使用 OpenAI 协议与 DashScope 通信
    openai:
      base-url: https://dashscope.aliyuncs.com/compatible-mode
      api-key: ${AI_DASHSCOPE_API_KEY}
      chat:
        options:
          model: qwen-max-latest           # 使用最新版 Qwen-Max
5.2 环境变量设置
# Linux / Mac
export AI_DASHSCOPE_API_KEY="sk-xxxxxxxxxxxxxxxx"
export WEATHER_API_KEY="xxxxxxxxxxxxxxxx"

# Windows PowerShell
$env:AI_DASHSCOPE_API_KEY="sk-xxxxxxxxxxxxxxxx"
$env:WEATHER_API_KEY="xxxxxxxxxxxxxxxx"

# 验证
echo $AI_DASHSCOPE_API_KEY

六、ReAct 工作流程深度剖析

6.1 一次完整的请求旅程

以用户提问"杭州明天天气怎么样?"为例:

WeatherAPI Tool Node WeatherService LLM Node Qwen-Max CompiledGraph ReactController 用户 WeatherAPI Tool Node WeatherService LLM Node Qwen-Max CompiledGraph ReactController 用户 第 1 轮循环 第 2 轮循环 GET /react/chat?query=杭州明天天气怎么样? invoke({messages: [UserMessage]}) 分析用户意图 Thought: 需要查询杭州天气 Action: 调用 getWeatherFunction Action Input: {"city": "杭州", "days": 2} 执行工具调用 HTTP GET /weather?q=hangzhou&days=2 JSON 数据 Observation: {temp: 25, condition: "Sunny", ...} 传入工具结果,要求生成回答 Thought: 已获取数据,可以回答 Final Answer: 杭州明天天气晴朗... OverAllState {messages: [...]} "杭州明天天气晴朗,气温 25°C..."
6.2 图结构详解

开始

LLM Node
分析意图

需要
工具?

Tool Node
调用天气 API

结束

LLM Node
基于结果生成回答

还需要
工具?

关键理解

  • 节点(Node):图中的每个步骤,如 LLM Node、Tool Node
  • 边(Edge):节点之间的流转关系,可以是条件分支
  • 状态(State):每次循环都会更新共享状态,包含消息历史、工具结果等
  • 循环:ReAct 的核心就是"LLM → Tool → LLM"的循环,直到 LLM 决定结束

七、接口规范

7.1 接口详情
属性
路径 /react/chat
方法 GET(建议生产环境改为 POST)
参数 query: String,用户问题
返回值 String,AI 回复文本
7.2 请求示例
# 天气查询
curl "http://localhost:8080/react/chat?query=杭州天气怎么样"

# 多轮意图(当前不支持,需要扩展)
curl "http://localhost:8080/react/chat?query=北京呢?"

# 闲聊
curl "http://localhost:8080/react/chat?query=你好,请自我介绍"
7.3 响应示例
杭州今天天气晴朗,气温 25°C,体感舒适。未来 3 天预报:
• 明天:多云,18°C ~ 26°C
• 后天:小雨,16°C ~ 22°C
• 大后天:晴,17°C ~ 25°C

建议:后天有雨,出门记得带伞 🌂

八、问题汇总与改进建议

序号 问题 严重程度 影响 建议方案
1 HTTP 超时 10 分钟 ⭐⭐⭐ 故障时资源挂起 改为 30s 连接 / 60s 响应
2 空指针风险(双重 .get()) ⭐⭐⭐⭐ 直接抛异常,用户体验差 使用 orElseThrow + 空值检查
3 GET 请求限制 ⭐⭐⭐ URL 长度受限,中文编码问题 改为 POST + JSON Body
4 使用 Mock 数据 ⭐⭐⭐⭐ 返回假数据,误导用户 接入真实 API,Mock 作为降级
5 Bean 名称硬编码 ⭐⭐ 维护困难,易遗漏 使用常量类或配置中心

九、设计总结

ReAct Agent

核心思想

推理 + 行动

循环直到解决

技术亮点

Spring AI 统一抽象

Graph 可视化编排

工具自动注册

适用场景

需要实时数据

多步骤推理

工具调用链

注意事项

超时配置

错误处理

真实 API 接入

本项目展示了

  • ✅ 如何基于 Spring AI Alibaba 构建 ReAct Agent
  • ✅ 如何注册和调用自定义工具
  • ✅ 如何使用 Graph 框架编排复杂 AI 流程
  • ⚠️ 生产环境需解决超时、容错、真实数据等问题


第二部分:部署实操指南

手把手教你把 ReAct 智能问答从代码跑起来,直到上线 运行🚀


一、前置准备

1.1 环境检查清单

在开始前,请确认你的环境满足以下要求:

开始部署

Java 21?

安装 JDK 21

Maven 3.9+?

安装 Maven

API Key?

申请 DashScope Key

可以开始!

检查 Java

java -version

预期输出(版本号可能不同,但必须 ≥ 17):

openjdk version "21.0.2" 2024-01-16
OpenJDK Runtime Environment (build 21.0.2+13-58)

如果没有 Java 21

  • Ubuntu/Debiansudo apt install openjdk-21-jdk
  • Macbrew install openjdk@21
  • Windows:下载 Eclipse Temurin 安装包

检查 Maven

mvn -version

预期输出:

Apache Maven 3.9.6

如果没有 Maven

  • Ubuntusudo apt install maven
  • Macbrew install maven
  • Windows:下载 Maven 官方包
1.2 获取阿里云 DashScope API Key(必须!)

这是整个项目的"钥匙",没有它 AI 无法工作。

申请步骤

  1. 打开 阿里云 DashScope 控制台
  2. 登录阿里云账号(没有就注册一个)
  3. 左侧菜单找到 “API-KEY 管理”
  4. 点击 “创建新的 API Key”
  5. 给 Key 起个名字(如"react-demo")
  6. 立即复制保存! Key 只显示一次

Key 长这样sk-abc123def456ghi789jkl012mn345op

💡 小贴士:建议创建后先测试一下 Key 是否有效:

curl https://dashscope.aliyuncs.com/api/v1/models >   -H "Authorization: Bearer sk-你的Key"
1.3 获取天气 API Key(可选但建议)

项目内置了天气查询功能,需要 WeatherAPI.com 的 Key。

申请步骤

  1. 访问 WeatherAPI.com
  2. 点击 “Sign Up Free”(免费版足够用)
  3. 填写邮箱注册
  4. 登录后进入 Dashboard
  5. 找到 API Key,复制保存

⚠️ 重要说明:如果不配置天气 Key,项目也能跑,但返回的是假数据(Mock)。详见第一部分"潜在问题 4"。


二、本地开发环境运行

2.1 进入项目目录
# 假设你已经在 examples 仓库目录
cd spring-ai-alibaba-graph-example/react

# 查看项目结构
ls -la

预期看到:

pom.xml
src/
  main/
    java/.../ReactApplication.java
    resources/
      application.yml
  test/
2.2 配置 API Key

方式一:环境变量(推荐,安全)

# Linux / Mac(当前终端会话有效)
export AI_DASHSCOPE_API_KEY="sk-你的DashScopeKey"
export WEATHER_API_KEY="你的天气Key"

# 验证是否设置成功
echo $AI_DASHSCOPE_API_KEY
echo $WEATHER_API_KEY
# Windows PowerShell(当前会话有效)
$env:AI_DASHSCOPE_API_KEY="sk-你的DashScopeKey"
$env:WEATHER_API_KEY="你的天气Key"

方式二:写入配置文件(仅本地开发,切勿提交到 Git!)

编辑 src/main/resources/application.yml

spring:
  ai:
    alibaba:
      toolcalling:
        weather:
          api-key: "你的天气Key"        # ⚠️ 生产环境不要这样写
    dashscope:
      api-key: "sk-你的DashScopeKey"   # ⚠️ 生产环境不要这样写

🔒 安全警告:将 API Key 写入配置文件并提交到代码仓库是严重安全隐患!可能导致 Key 泄露被盗用,产生高额费用。

2.3 编译并启动服务
# 编译(第一次需要下载依赖,约 1-3 分钟,取决于网络)
mvn clean compile

# 启动服务
mvn spring-boot:run

启动过程解析

mvn spring-boot:run

下载依赖

编译代码

启动 Spring Boot

初始化 ChatClient

编译 ReAct 图

打印 PlantUML 图

监听 8080 端口

启动成功的标志

  .   ____          _            __ _ _
 /\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \  \/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.x)

Started ReactApplication in 8.456 seconds
Tomcat started on port(s): 8080 (http)

💡 观察 PlantUML 图:启动时控制台会打印 ReAct Agent 的图结构,可以复制到 PlantUML 在线编辑器 查看可视化流程。

2.4 接口测试

打开新的终端窗口(保持服务运行),执行:

# 测试 1:天气查询(会触发工具调用)
curl "http://localhost:8080/react/chat?query=杭州天气怎么样"

# 测试 2:另一个城市
curl "http://localhost:8080/react/chat?query=上海今天多少度"

# 测试 3:闲聊(不会触发工具调用)
curl "http://localhost:8080/react/chat?query=你好呀"

# 测试 4:空参数(观察错误处理)
curl "http://localhost:8080/react/chat"

预期响应

  • 天气查询:返回天气描述(注意:当前可能是 Mock 数据)
  • 闲聊:友好的问候回复
  • 空参数:根据代码实现,可能返回空或报错

三、生产环境部署

3.1 打包应用
# 清理并打包(跳过测试加速)
mvn clean package -DskipTests

# 查看生成的 JAR 包
ls -lh target/

预期生成:

target/react-1.0.0.jar  (约 30-50MB,包含所有依赖)
3.2 服务器准备

最低配置要求

资源 最低要求 推荐配置
CPU 1 核 2 核
内存 2 GB 4 GB
磁盘 10 GB 20 GB
系统 Ubuntu 20.04+ / CentOS 8+ Ubuntu 22.04 LTS
网络 能访问 dashscope.aliyuncs.com 带宽 ≥ 5Mbps

云服务器推荐

  • 阿里云 ECS(同地域访问 DashScope 更快)
  • 腾讯云 CVM
  • AWS EC2
3.3 上传 JAR 包到服务器
# 使用 scp 命令(本地终端执行)
scp target/react-1.0.0.jar root@你的服务器IP:/opt/react/

# 如果目录不存在,先创建
ssh root@你的服务器IP "mkdir -p /opt/react"

或者使用图形化工具

  • Windows:WinSCP、FileZilla
  • Mac:Cyberduck、Transmit
3.4 启动方式对比
方式 适用场景 优点 缺点
直接运行 临时测试 简单 关闭终端即停止
nohup 简单后台 脱离终端 重启后需手动启动
Systemd 生产环境 开机自启、自动重启、状态管理 配置稍复杂
Docker 容器化部署 环境隔离、一次构建多处运行 需要 Docker 知识
3.5 Systemd 部署(推荐用于生产)

步骤 1:创建服务文件

sudo vim /etc/systemd/system/react-agent.service

写入以下内容(根据你的实际路径修改):

[Unit]
Description=Spring AI Alibaba ReAct Agent
After=network.target

[Service]
Type=simple
User=ubuntu                    # 运行服务的用户(不要用 root)
WorkingDirectory=/opt/react    # JAR 包所在目录
ExecStart=/usr/bin/java -jar /opt/react/react-1.0.0.jar

# 环境变量(重要!)
Environment="AI_DASHSCOPE_API_KEY=sk-你的DashScopeKey"
Environment="WEATHER_API_KEY=你的天气Key"
Environment="SERVER_PORT=8080"

# JVM 参数(根据服务器内存调整)
Environment="JAVA_OPTS=-Xms512m -Xmx1024m"

# 自动重启策略
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

步骤 2:启动并启用服务

# 重新加载 Systemd 配置
sudo systemctl daemon-reload

# 启动服务
sudo systemctl start react-agent

# 设置开机自启
sudo systemctl enable react-agent

# 查看状态
sudo systemctl status react-agent

# 查看实时日志
sudo journalctl -u react-agent -f

常用命令速查

sudo systemctl start react-agent     # 启动
sudo systemctl stop react-agent      # 停止
sudo systemctl restart react-agent   # 重启
sudo systemctl status react-agent    # 查看状态

四、Docker 部署(最简单)

如果你熟悉 Docker,这是最快的部署方式。

4.1 编写 Dockerfile

在项目根目录创建 Dockerfile

# ========== 构建阶段 ==========
FROM eclipse-temurin:21-jdk-alpine AS builder

WORKDIR /app
COPY pom.xml .
COPY src ./src

# 安装 Maven 并构建
RUN apk add --no-cache maven     && mvn clean package -DskipTests

# ========== 运行阶段 ==========
FROM eclipse-temurin:21-jre-alpine

WORKDIR /app

# 从构建阶段复制 JAR 包
COPY --from=builder /app/target/*.jar app.jar

# 创建非 root 用户(安全最佳实践)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3     CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

# 启动命令(环境变量从外部传入)
ENTRYPOINT ["java", "-jar", "app.jar"]

Dockerfile 解析

指令 作用
multi-stage build 分阶段构建,最终镜像只包含 JRE 和 JAR,体积更小
non-root user 安全最佳实践,防止容器逃逸攻击
HEALTHCHECK Docker 自动检测服务是否健康
4.2 构建镜像
# 构建镜像(注意最后的点)
docker build -t react-agent:latest .

# 查看构建好的镜像
docker images | grep react-agent
4.3 运行容器
docker run -d   --name react-agent   -p 8080:8080   -e AI_DASHSCOPE_API_KEY="sk-你的DashScopeKey"   -e WEATHER_API_KEY="你的天气Key"   --restart unless-stopped   react-agent:latest

参数说明

参数 含义
-d 后台运行(detached)
--name 容器名称
-p 8080:8080 端口映射(主机端口:容器端口)
-e 设置环境变量
--restart unless-stopped 除非手动停止,否则自动重启

验证运行

# 查看容器状态
docker ps

# 查看日志
docker logs -f react-agent

# 测试接口
curl "http://localhost:8080/react/chat?query=杭州天气"
4.4 Docker Compose 部署(推荐)

创建 docker-compose.yml

version: '3.8'

services:
  react-agent:
    build: .
    container_name: react-agent
    ports:
      - "8080:8080"
    environment:
      - AI_DASHSCOPE_API_KEY=${AI_DASHSCOPE_API_KEY}
      - WEATHER_API_KEY=${WEATHER_API_KEY}
      - JAVA_OPTS=-Xms512m -Xmx1024m
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/react/chat?query=ping"]
      interval: 30s
      timeout: 10s
      retries: 3

使用:

# 启动
docker-compose up -d

# 查看日志
docker-compose logs -f

# 停止
docker-compose down

五、Nginx 反向代理与 HTTPS

5.1 为什么需要 Nginx?

HTTP

反向代理

静态文件

HTTPS

用户

Nginx

React Agent
:8080

前端页面

Nginx 的作用

  • 反向代理:隐藏后端服务,统一入口
  • 负载均衡:多台服务时分发请求
  • HTTPS:SSL 证书管理
  • 静态资源:缓存、压缩
5.2 安装 Nginx
# Ubuntu / Debian
sudo apt update
sudo apt install nginx

# 启动
sudo systemctl start nginx
sudo systemctl enable nginx

# 验证
sudo systemctl status nginx
5.3 配置反向代理

创建配置文件:

sudo vim /etc/nginx/sites-available/react-agent

写入:

server {
    listen 80;
    server_name your-domain.com;  # 改成你的域名或 IP

    # 日志
    access_log /var/log/nginx/react-access.log;
    error_log /var/log/nginx/react-error.log;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;

        # 转发真实 IP 和 Host
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

启用配置:

# 创建软链接
sudo ln -s /etc/nginx/sites-available/react-agent /etc/nginx/sites-enabled/

# 检查配置语法
sudo nginx -t

# 重载配置
sudo systemctl reload nginx
5.4 配置 HTTPS(强烈推荐)

使用 Let’s Encrypt 免费证书:

# 安装 certbot
sudo apt install certbot python3-certbot-nginx

# 获取并自动配置证书
sudo certbot --nginx -d your-domain.com

# 按照提示操作,选择 redirect HTTP to HTTPS

# 测试自动续期
sudo certbot renew --dry-run

Certbot 会自动

  • 生成 SSL 证书
  • 修改 Nginx 配置添加 443 端口
  • 设置 HTTP 自动跳转 HTTPS
  • 配置证书自动续期(每 90 天)

六、部署验证清单

部署完成后,逐项检查:

检查项 命令 预期结果
服务运行状态 sudo systemctl status react-agent Active: active (running)
端口监听 sudo ss -tlnp | grep 8080 LISTEN
本地接口 curl "http://localhost:8080/react/chat?query=测试" 返回 AI 回复
外网接口 curl "http://你的IP:8080/react/chat?query=测试" 返回 AI 回复
Nginx 代理 curl "http://你的域名/react/chat?query=测试" 返回 AI 回复
HTTPS curl -k "https://你的域名/react/chat?query=测试" 返回 AI 回复
日志 sudo tail -f /var/log/react-agent.log 无 ERROR
资源使用 htopfree -h CPU < 50%, 内存 < 80%

七、常见问题排查

问题 1:启动报错 “API Key 为空”

现象

IllegalArgumentException: API Key must not be empty

排查步骤

# 1. 检查环境变量
echo $AI_DASHSCOPE_API_KEY

# 2. 如果为空,重新设置
export AI_DASHSCOPE_API_KEY="sk-xxx"

# 3. 如果使用 Systemd,检查服务文件中的 Environment 配置
sudo cat /etc/systemd/system/react-agent.service | grep Environment

# 4. 修改后重载并重启
sudo systemctl daemon-reload
sudo systemctl restart react-agent
问题 2:端口 8080 被占用

现象

Web server failed to start. Port 8080 was already in use.

解决

# 查看谁占用了 8080
sudo lsof -i:8080
# 或
sudo ss -tlnp | grep 8080

# 方式 1:杀掉占用进程
kill -9 <PID>

# 方式 2:修改服务端口
# 在 application.yml 或环境变量中设置:
export SERVER_PORT=8081
问题 3:外网无法访问

排查流程

外网访问不了

服务器本地能访问?

服务未启动

防火墙放行?

配置防火墙

安全组放行?

配置云安全组

Nginx 配置问题

命令

# 检查防火墙
sudo ufw status
sudo ufw allow 8080/tcp

# 检查云服务器安全组(阿里云/腾讯云/AWS 控制台)
# 确保入站规则允许 8080 或 80/443 端口
问题 4:LLM 调用报错
错误码 含义 解决
401 API Key 无效 检查 Key 是否正确,是否过期
429 请求太频繁 降低调用频率,或升级 DashScope 套餐
503 服务暂时不可用 等待几分钟后重试,或查看 DashScope 状态页
连接超时 网络问题 检查服务器能否访问 dashscope.aliyuncs.com

测试网络连通性

curl -v https://dashscope.aliyuncs.com/compatible-mode/v1/models   -H "Authorization: Bearer sk-你的Key"
问题 5:返回空回复或异常

可能原因

  1. API Key 没有权限(新 Key 可能需要等待几分钟生效)
  2. 网络不通(服务器需要能访问外网)
  3. 请求格式错误

查看详细日志

# Systemd 方式
sudo journalctl -u react-agent -n 100 --no-pager

# Docker 方式
docker logs react-agent --tail 100
问题 6:天气数据是假的

确认方法

curl "http://localhost:8080/react/chat?query=杭州天气"
# 如果返回的温度总是 25°C,无论实际天气如何,就是 Mock 数据

解决:修改代码,让 apply() 方法调用 doGetWeather() 而不是 doGetWeatherMock()。详见第一部分"潜在问题 4"的修复建议。


八、与 chatflow 示例的对比

对比维度 chatflow react(本项目)
功能定位 智能待办助手 天气问答助手
难度等级 ⭐⭐ 中级 ⭐ 入门级
核心流程 多意图识别 + 子图路由 单 ReAct 循环
交互方式 POST + Session 保持多轮 GET + 单轮问答
状态管理 复杂状态机 简单状态传递
工具数量 多个(增删改查) 单个(天气查询)
适用学习阶段 进阶 入门

学习建议:先掌握 react(简单直观),再挑战 chatflow(复杂但功能更强)。


九、一键部署脚本

为了方便快速部署,提供以下脚本:

#!/bin/bash
set -e

# ========== 配置区(修改为你的实际值)==========
JAR_FILE="react-1.0.0.jar"
SERVICE_NAME="react-agent"
PORT=8080
DASHSCOPE_KEY="sk-你的DashScopeKey"
WEATHER_KEY="你的天气Key"
INSTALL_DIR="/opt/react"
JAVA_VERSION="21"

# ========== 颜色输出 ==========
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }

# ========== 检查 root 权限 ==========
if [ "$EUID" -ne 0 ]; then 
    log_error "请使用 sudo 运行此脚本"
    exit 1
fi

# ========== 安装 Java ==========
if ! command -v java &> /dev/null; then
    log_info "安装 OpenJDK ${JAVA_VERSION}..."
    apt update
    apt install -y openjdk-${JAVA_VERSION}-jdk
else
    JAVA_CURRENT=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)
    if [ "$JAVA_CURRENT" -lt "$JAVA_VERSION" ]; then
        log_warn "当前 Java 版本过低,建议升级到 ${JAVA_VERSION}"
    fi
fi

# ========== 创建目录 ==========
log_info "创建应用目录..."
mkdir -p ${INSTALL_DIR}

# ========== 复制 JAR 包 ==========
if [ ! -f "${JAR_FILE}" ]; then
    log_error "找不到 JAR 文件: ${JAR_FILE}"
    log_info "请先运行: mvn clean package -DskipTests"
    exit 1
fi

cp ${JAR_FILE} ${INSTALL_DIR}/
log_info "JAR 包已复制到 ${INSTALL_DIR}"

# ========== 创建 Systemd 服务 ==========
log_info "创建 Systemd 服务..."
cat > /etc/systemd/system/${SERVICE_NAME}.service <<EOF
[Unit]
Description=Spring AI Alibaba ReAct Agent
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=${INSTALL_DIR}
ExecStart=/usr/bin/java -Xms512m -Xmx1024m -jar ${INSTALL_DIR}/${JAR_FILE}
Environment="AI_DASHSCOPE_API_KEY=${DASHSCOPE_KEY}"
Environment="WEATHER_API_KEY=${WEATHER_KEY}"
Environment="SERVER_PORT=${PORT}"
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

# ========== 启动服务 ==========
log_info "启动服务..."
systemctl daemon-reload
systemctl start ${SERVICE_NAME}
systemctl enable ${SERVICE_NAME}

# ========== 验证 ==========
sleep 3
if systemctl is-active --quiet ${SERVICE_NAME}; then
    log_info "✅ 服务启动成功!"
    log_info "📍 本地访问: http://localhost:${PORT}/react/chat?query=测试"
    log_info "📊 查看状态: systemctl status ${SERVICE_NAME}"
    log_info "📖 查看日志: journalctl -u ${SERVICE_NAME} -f"
else
    log_error "❌ 服务启动失败,请检查日志"
    journalctl -u ${SERVICE_NAME} -n 50 --no-pager
    exit 1
fi

使用方法

# 1. 保存为 deploy.sh
# 2. 修改配置区的变量
# 3. 执行
chmod +x deploy.sh
sudo ./deploy.sh

十、部署总结

简单

生产

容器化

本地开发

打包 JAR

选择部署方式

直接运行

Systemd

Docker

验证

配置 Nginx

配置 HTTPS

上线完成!

部署方式选择建议

场景 推荐方式 理由
本地开发测试 mvn spring-boot:run 最快,热更新
内部演示 nohup java -jar 简单,临时使用
生产环境 Systemd 稳定,自动恢复
微服务架构 Docker + K8s 弹性伸缩,环境一致
快速验证 Docker Compose 一键启动,易于清理

下一步可以做的事

  • 🔧 修改代码接入真实天气 API(替换 Mock 数据)
  • 🔒 添加接口鉴权(Spring Security + JWT)
  • 📊 接入监控(Prometheus + Grafana)
  • 🚀 学习更复杂的 chatflow 示例
  • 🌐 配置域名和 CDN,开放给更多人使用
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐