502-Spring AI Alibaba React Agent 功能完整案例

本案例将引导您一步步构建一个 Spring Boot 应用,演示如何利用 Spring AI Alibaba 的 React Agent 功能,实现基于 Graph 的智能问答,并集成天气查询工具。

1. 案例目标

我们将创建一个包含核心功能的 Web 应用:

  1. React Agent 问答 (/react/chat):通过 Graph 架构,让 AI 能够自主决定是否需要调用工具来回答用户的问题,实现更智能的交互体验。
  2. 天气查询工具集成:集成天气查询功能,当用户询问天气相关问题时,AI 能够自动调用天气工具获取实时数据。

2. 技术栈与核心依赖

  • Spring Boot 3.x
  • Spring AI Alibaba (用于对接阿里云 DashScope 通义大模型)
  • Spring AI Alibaba Graph Core (用于构建 Graph 架构的 Agent)
  • Maven (项目构建工具)

pom.xml 中,你需要引入以下核心依赖:

复制代码
<dependencies>
    <!-- Spring AI Alibaba 核心启动器,集成 DashScope -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
    </dependency>
    
    <!-- Spring AI Chat Client 自动配置 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
    </dependency>
    
    <!-- Spring AI Alibaba Graph Core -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-graph-core</artifactId>
        <version>1.0.0.3</version>
    </dependency>
    
    <!-- Spring Web 用于构建 RESTful API -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- HTTP Client -->
    <dependency>
        <groupId>org.apache.httpcomponents.client5</groupId>
        <artifactId>httpclient5</artifactId>
        <version>5.4.1</version>
    </dependency>
    
    <!-- OpenAI 兼容模式 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai</artifactId>
    </dependency>
    
    <!-- Hutool 工具类 -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-extra</artifactId>
        <version>5.8.38</version>
    </dependency>
</dependencies>

3. 项目配置

src/main/resources/application.yml 文件中,配置你的 DashScope API Key 和天气 API Key。

复制代码
server:
  port: 8080
spring:
  application:
    name: react
  ai:
    alibaba:
      toolcalling:
        weather:
          enabled: true
          api-key: ${WEATHER_API_KEY}
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}
    openai:
      base-url: https://dashscope.aliyuncs.com/compatible-mode
      api-key: ${AI_DASHSCOPE_API_KEY}
      chat:
        options:
          model: qwen-max-latest
      embedding:
        options:
          model: text-embedding-v1

重要提示 :请将 AI_DASHSCOPE_API_KEYWEATHER_API_KEY 环境变量设置为你从阿里云获取的有效 API Key。你也可以直接将其写在配置文件中,但这不推荐用于生产环境。

4. 项目结构

React Agent 项目的结构如下:

复制代码
src/main/java/com/alibaba/cloud/ai/graph/react/
├── ReactApplication.java          # 应用程序主类
├── ReactAutoconfiguration.java     # React Agent 自动配置
├── ReactController.java           # REST API 控制器
└── function/                      # 工具函数目录
    ├── WeatherAutoConfiguration.java # 天气服务自动配置
    ├── WeatherProperties.java      # 天气服务配置属性
    ├── WeatherService.java        # 天气服务实现
    └── WeatherUtils.java          # 天气工具类

5. 核心组件实现

5.1 应用程序主类 ReactApplication.java

复制代码
package com.alibaba.cloud.ai.graph.react;

import com.alibaba.cloud.ai.graph.react.function.WeatherProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableConfigurationProperties(WeatherProperties.class)
public class ReactApplication {

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

5.2 React Agent 自动配置 ReactAutoconfiguration.java

复制代码
package com.alibaba.cloud.ai.graph.react;

import java.util.concurrent.TimeUnit;

import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.GraphRepresentation;
import com.alibaba.cloud.ai.graph.exception.GraphStateException;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.util.Timeout;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.tool.resolution.ToolCallbackResolver;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestClient;

@Configuration
public class ReactAutoconfiguration {

    @Bean
    public ReactAgent normalReactAgent(ChatModel chatModel, ToolCallbackResolver resolver) throws GraphStateException {
        ChatClient chatClient = ChatClient.builder(chatModel)
            .defaultToolNames("getWeatherFunction")
            .defaultAdvisors(new SimpleLoggerAdvisor())
            .defaultOptions(OpenAiChatOptions.builder().internalToolExecutionEnabled(false).build())
            .build();

        return ReactAgent.builder()
            .name("React Agent Demo")
            .chatClient(chatClient)
            .resolver(resolver)
            .maxIterations(10)
            .build();
    }

    @Bean
    public CompiledGraph reactAgentGraph(@Qualifier("normalReactAgent") ReactAgent reactAgent)
            throws GraphStateException {

        GraphRepresentation graphRepresentation = reactAgent.getStateGraph()
            .getGraph(GraphRepresentation.Type.PLANTUML);

        System.out.println("\n\n");
        System.out.println(graphRepresentation.content());
        System.out.println("\n\n");

        return reactAgent.getAndCompileGraph();
    }

    @Bean
    public RestClient.Builder createRestClient() {
        // 配置 HTTP 客户端超时
        RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(Timeout.of(10, TimeUnit.MINUTES))
            .setResponseTimeout(Timeout.of(10, TimeUnit.MINUTES))
            .setConnectionRequestTimeout(Timeout.of(10, TimeUnit.MINUTES))
            .build();

        HttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(requestConfig).build();
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);

        return RestClient.builder().requestFactory(requestFactory);
    }
}

5.3 REST API 控制器 ReactController.java

复制代码
package com.alibaba.cloud.ai.graph.react;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;

import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.messaging.Message;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@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) throws GraphRunnerException {
        Optional<OverAllState> result = compiledGraph.invoke(Map.of("messages", new UserMessage(query)));
        List<Message> messages = (List<Message>) result.get().value("messages").get();
        AssistantMessage assistantMessage = (AssistantMessage) messages.get(messages.size() - 1);

        return assistantMessage.getText();
    }
}

5.4 天气服务实现

5.4.1 天气服务配置属性 WeatherProperties.java
复制代码
package com.alibaba.cloud.ai.graph.react.function;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "spring.ai.alibaba.toolcalling.weather")
public class WeatherProperties {

    private String apiKey;

    public String getApiKey() {
        return apiKey;
    }

    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }
}
5.4.2 天气服务实现类 WeatherService.java
复制代码
package com.alibaba.cloud.ai.graph.react.function;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;

import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;

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

    private static final Logger logger = LoggerFactory.getLogger(WeatherService.class);

    private static final String WEATHER_API_URL = "https://api.weatherapi.com/v1/forecast.json";

    private final WebClient webClient;

    private final ObjectMapper objectMapper = new ObjectMapper();

    public WeatherService(WeatherProperties properties) {
        this.webClient = WebClient.builder()
            .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
            .defaultHeader("key", properties.getApiKey())
            .build();
    }

    public static Response fromJson(Map<String, Object> json) {
        Map<String, Object> location = (Map<String, Object>) json.get("location");
        Map<String, Object> current = (Map<String, Object>) json.get("current");
        Map<String, Object> forecast = (Map<String, Object>) json.get("forecast");
        List<Map<String, Object>> forecastDays = (List<Map<String, Object>>) forecast.get("forecastday");
        String city = (String) location.get("name");
        return new Response(city, current, forecastDays);
    }

    @Override
    public Response apply(Request request) {
        if (request == null || !StringUtils.hasText(request.city())) {
            logger.error("Invalid request: city is required.");
            return null;
        }
        String location = WeatherUtils.preprocessLocation(request.city());
        String url = UriComponentsBuilder.fromHttpUrl(WEATHER_API_URL)
            .queryParam("q", location)
            .queryParam("days", request.days())
            .toUriString();
        logger.info("url : {}", url);
        try {
            return doGetWeatherMock(request);
        }
        catch (Exception e) {
            logger.error("Failed to fetch weather data: {}", e.getMessage());
            return null;
        }
    }

    @NotNull
    private Response doGetWeatherMock(Request request) throws JsonProcessingException {
        if (Objects.equals("杭州", request.city())) {
            return new Response(request.city(), Map.of("temp", 25, "condition", "Sunny"),
                    List.of(Map.of("date", "2025-05-27", "high", 28, "low", 20)));
        }
        else if (Objects.equals("上海", request.city())) {
            return new Response(request.city(), Map.of("temp", 26, "condition", "Sunny"),
                    List.of(Map.of("date", "2025-05-27", "high", 29, "low", 21)));
        }
        else if (Objects.equals("南京", request.city())) {
            return new Response(request.city(), Map.of("temp", 18, "condition", "cloudy"),
                    List.of(Map.of("date", "2025-05-27", "high", 18, "low", 10)));
        }
        else {
            return new Response(request.city(), Map.of("temp", -20, "condition", "Snowy"),
                    List.of(Map.of("date", "2025-05-27", "high", -10, "low", -30)));
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonClassDescription("Weather Service API request")
    public record Request(
            @JsonProperty(required = true, value = "city") @JsonPropertyDescription("city name") String city,

            @JsonProperty(required = true,
                    value = "days") @JsonPropertyDescription("Number of days of weather forecast. Value ranges from 1 to 14") int days) {
    }

    @JsonClassDescription("Weather Service API response")
    public record Response(
            @JsonProperty(required = true, value = "city") @JsonPropertyDescription("city name") String city,
            @JsonProperty(required = true,
                    value = "current") @JsonPropertyDescription("Current weather info") Map<String, Object> current,
            @JsonProperty(required = true,
                    value = "forecastDays") @JsonPropertyDescription("Forecast weather info") List<Map<String, Object>> forecastDays) {
    }
}
5.4.3 天气工具类 WeatherUtils.java
复制代码
package com.alibaba.cloud.ai.graph.react.function;

import cn.hutool.extra.pinyin.PinyinUtil;

public class WeatherUtils {

    public static String preprocessLocation(String location) {
        if (containsChinese(location)) {
            return PinyinUtil.getPinyin(location, "");
        }
        return location;
    }

    public static boolean containsChinese(String str) {
        return str.matches(".*[\u4e00-\u9fa5].*");
    }
}
5.4.4 天气服务自动配置 WeatherAutoConfiguration.java
复制代码
package com.alibaba.cloud.ai.graph.react.function;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;

@Configuration
@ConditionalOnClass(WeatherService.class)
@ConditionalOnProperty(prefix = "spring.ai.alibaba.toolcalling.weather", name = "enabled", havingValue = "true")
public class WeatherAutoConfiguration {

    @Bean(name = "getWeatherFunction")
    @ConditionalOnMissingBean
    @Description("Use api.weather to get weather information.")
    public WeatherService getWeatherServiceFunction(WeatherProperties properties) {
        return new WeatherService(properties);
    }
}

6. 运行与测试

  1. 启动应用:运行你的 Spring Boot 主程序。
  2. 使用浏览器或 API 工具(如 Postman, curl)进行测试

测试 1:普通问答

访问以下 URL,询问一个不需要调用工具的问题。

复制代码
http://localhost:8080/react/chat?query=你好,请介绍一下你自己

预期响应

你好!我是一个基于 React Agent 架构的 AI 助手,可以帮助你回答各种问题。我还可以使用工具来获取实时信息,比如天气数据。有什么我可以帮助你的吗?

测试 2:天气查询

访问以下 URL,询问天气相关问题。

复制代码
http://localhost:8080/react/chat?query=杭州今天的天气怎么样?

预期响应

根据查询结果,杭州今天的天气情况是:温度25摄氏度,天气晴朗(Sunny)。今天最高温度28度,最低温度20度。

测试 3:其他城市天气查询

访问以下 URL,查询其他城市的天气。

复制代码
http://localhost:8080/react/chat?query=南京今天天气如何?

预期响应

根据查询结果,南京今天的天气情况是:温度18摄氏度,天气多云(cloudy)。今天最高温度18度,最低温度10度。

7. 实现思路与扩展建议

实现思路

本案例的核心思想是**"基于 Graph 的 Agent 架构"**。通过 React Agent,我们实现了:

  • 自主决策:AI 能够自主决定是否需要调用工具来回答用户的问题,无需预先定义调用规则。
  • 工具集成:通过 Spring AI 的工具调用机制,轻松集成外部 API,如天气查询服务。
  • 状态管理:通过 Graph 架构,管理 Agent 的状态和决策流程,使整个交互过程更加可控和可观测。

扩展建议

  • 集成更多工具:可以集成更多外部 API,如股票查询、新闻获取、地图服务等,扩展 Agent 的能力范围。
  • 多轮对话优化:通过添加记忆机制,使 Agent 能够记住之前的对话内容,提供更连贯的交互体验。
  • 错误处理增强:增强工具调用的错误处理机制,当工具调用失败时,提供友好的错误提示和替代方案。
  • 可视化监控:集成可视化工具,展示 Agent 的决策过程和工具调用情况,便于调试和优化。
  • 多 Agent 协作:构建多个专业 Agent,每个 Agent 专注于特定领域,通过协作机制解决复杂问题。
相关推荐
F2的AI学习笔记6 小时前
AI智能体工具调用终极指南:从Function Calling到MCP的三大方案详解
人工智能
北辰alk6 小时前
边缘端AI部署全面指南:原理、方案与实战代码
人工智能
噜~噜~噜~7 小时前
LSTM(Long Short-Term Memory)个人理解
人工智能·lstm·双层lstm·多层lstm
翔云 OCR API7 小时前
基于深度学习与OCR研发的报关单识别接口技术解析
人工智能·深度学习·ocr
wwlsm_zql7 小时前
京津冀工业智能体赋能:重构产业链升级新篇章
人工智能·重构
lzjava20247 小时前
Spring AI实现一个智能客服
java·人工智能·spring
hweiyu007 小时前
数据挖掘 miRNA调节网络的构建(视频教程)
人工智能·数据挖掘
飞哥数智坊7 小时前
AI Coding 新手常见的3大误区
人工智能·ai编程
3Bronze1Pyramid7 小时前
深度学习参数优化
人工智能·深度学习