SpringAI 从零到落地实践

一、SpringAI 概述

1.1 背景与定位

目前大模型应用开发最常见的框架是 LangChain,但它基于 Python 语言。虽然存在 LangChain4j,可对于大量使用 Spring 生态的 Java 项目来说,适配性稍差。

Spring 官方推出的 SpringAI 框架,充分利用了 Spring 框架中 AOP、IOC 的能力,可以与现有 Java 项目无缝融合,上手方便。

1.2 环境要求

框架最低 JDK 版本SpringBoot 要求
SpringAIJDK 17SpringBoot 3.x
LangChain4jJDK 8无强制要求

如果是比较老的项目(JDK 8),可使用 LangChain4j;若要使用 SpringAI,必须先升级 JDK 与 SpringBoot 版本。

1.3 大模型的使用方式

大模型应用开发并不是在浏览器中跟 AI 聊天,而是通过访问模型对外暴露的 API 接口实现与大模型的交互。企业通常有三种选择:

  1. 使用开放的大模型 API(本次演示方式):选择合适的大模型,申请自己的 API Key 即可免费试用较长时间。
  2. 在云平台部署私有大模型。
  3. 在本地服务器部署私有大模型。

二、SpringAI 新手入门

2.1 配置 pom.xml 文件

(1)引入起步依赖

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

(2)声明版本号

 <spring-ai.version>1.0.0-M6</spring-ai.version>

(3)添加依赖管理

 <dependencyManagement>
     <dependencies>
         <dependency>
             <groupId>org.springframework.ai</groupId>
             <artifactId>spring-ai-bom</artifactId>
             <version>${spring-ai.version}</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>
     </dependencies>
 </dependencyManagement>

(4)完整 pom.xml 示例

 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 ​
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>3.4.5</version>
         <relativePath/>
     </parent>
 ​
     <groupId>com.xbs</groupId>
     <artifactId>springAI-openAi</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <name>springAI-openAi</name>
     <description>springAI-openAi</description>
 ​
     <properties>
         <java.version>17</java.version>
         <spring-ai.version>1.0.0-M6</spring-ai.version>
     </properties>
 ​
     <dependencyManagement>
         <dependencies>
             <dependency>
                 <groupId>org.springframework.ai</groupId>
                 <artifactId>spring-ai-bom</artifactId>
                 <version>${spring-ai.version}</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
         </dependencies>
     </dependencyManagement>
 ​
     <dependencies>
         <!-- Web -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
 ​
         <!-- Lombok -->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>1.18.22</version>
         </dependency>
 ​
         <!-- SpringAI OpenAI 起步依赖 -->
         <dependency>
             <groupId>org.springframework.ai</groupId>
             <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
         </dependency>
 ​
         <!-- MySQL -->
         <dependency>
             <groupId>com.mysql</groupId>
             <artifactId>mysql-connector-j</artifactId>
             <scope>runtime</scope>
         </dependency>
 ​
         <!-- Test -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
 ​
         <!-- MyBatis-Plus(SpringBoot3) -->
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
             <version>3.5.10.1</version>
         </dependency>
 ​
         <!-- PDF 文档读取器(RAG 用) -->
         <dependency>
             <groupId>org.springframework.ai</groupId>
             <artifactId>spring-ai-pdf-document-reader</artifactId>
         </dependency>
     </dependencies>
 ​
     <build>
         <plugins>
             <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
         </plugins>
     </build>
 </project>

除 SpringAI 依赖外,额外引入了 Web、Lombok、MySQL、MyBatis-Plus、PDF Reader 等后续开发会用到的依赖。

2.2 配置 application.yaml 文件

以阿里云 DashScope(兼容 OpenAI 接口模式)为例:

 spring:
   application:
     name: ai-demo
   ai:
     openai:
       base-url: https://dashscope.aliyuncs.com/compatible-mode
       api-key: 在这里写你自己申请的 API-Key
       chat:
         options:
           model: qwen-max-latest   # 选择模型:https://help.aliyun.com/zh/model-studio/getting-started/models
           temperature: 0.8        # 模型温度,影响生成结果随机性,越小越稳定
       embedding:
         options:
           model: text-embedding-v3
           dimensions: 1024
   datasource:
     driver-class-name: com.mysql.cj.jdbc.Driver
     url: jdbc:mysql://localhost:3144/ai?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowPublicKeyRetrieval=true&allowMultiQueries=true&useServerPrepStmts=false
     username: root
     password: 你猜猜看
 ​
 logging:
   level:
     org.springframework.ai: debug          # AI 对话日志级别
     com.xbs.springaiopenai: debug           # 本项目日志级别
 ​
 server:
   port: 8087
 ​
 mybatis:
   configuration:
     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2.3 配置 ChatClient

 package com.itheima.ai.config;
 ​
 import org.springframework.ai.chat.client.ChatClient;
 import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
 import org.springframework.ai.ollama.OllamaChatModel;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 ​
 @Configuration
 public class CommonConfiguration {
 ​
     // 参数中的 model 即所用模型,这里用 Ollama,也可换成 OpenAIChatModel
     @Bean
     public ChatClient chatClient(OllamaChatModel model) {
         return ChatClient.builder(model)   // 创建 ChatClient 工厂
                 .build();                  // 构建 ChatClient 实例
     }
 }

代码解读:

  • ChatClient.builder(...):得到一个 ChatClient.Builder 工厂对象,利用它可以自由选择模型、添加各种自定义配置。

2.4 同步调用

定义一个 Controller,接收用户提示词,发送给大模型,拿到结果后返回:

 package com.itheima.ai.controller;
 ​
 import lombok.RequiredArgsConstructor;
 import org.springframework.ai.chat.client.ChatClient;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 ​
 @RequiredArgsConstructor
 @RestController
 @RequestMapping("/ai")
 public class ChatController {
 ​
     private final ChatClient chatClient;
 ​
     // 请求方式和路径不要改动,将来要与前端联调
     @RequestMapping("/chat")
     public String chat(@RequestParam(defaultValue = "讲个笑话") String prompt) {
         return chatClient
                 .prompt(prompt)   // 传入 user 提示词
                 .call()           // 同步请求,等待 AI 全部输出完才返回
                 .content();       // 返回响应内容
     }
 }

基于 call() 方法的调用属于同步调用,必须等所有响应结果全部返回后才能返回给前端。

启动项目,浏览器访问:

 http://localhost:8080/ai/chat?prompt=你好

2.5 流式调用

同步调用需要等待较长时间,用户体验差。SpringAI 基于 WebFlux 实现流式调用:

// 返回值是 Flux<String>,即流式结果;
// 另外需要设定响应类型和编码,否则前端会乱码
@RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")
public Flux<String> chat(@RequestParam(defaultValue = "讲个笑话") String prompt) {
    return chatClient
            .prompt(prompt)
            .stream()     // 流式调用
            .content();
}

重启项目后再次访问即可看到逐字输出效果。

2.6 System 设定

询问 AI “你是谁”时,它会按底层模型设定回答(如 DeepSeek-R1)。若希望 AI 按新设定工作,需要为其设置 System 背景信息。

在 SpringAI 中,设置 System 信息非常方便——不需要每次发送时封装到 Message,而是在创建 ChatClient 时指定:

@Bean
public ChatClient chatClient(OllamaChatModel model) {
    return ChatClient.builder(model)
            .defaultSystem("你的名字叫小板。请以友好、乐于助人和愉快的方式解答学生的各种问题。")
            .defaultAdvisors(new SimpleLoggerAdvisor())
            .build();
}

2.7 日志功能

默认情况下,与 AI 的交互不记录日志,无法得知 SpringAI 组织的提示词长什么样,不利于调试。

SpringAI 基于 AOP 机制实现对大模型对话过程的增强、拦截、修改等功能。所有增强通知都需要实现 Advisor 接口。Spring 提供了一些默认实现:

Advisor作用
SimpleLoggerAdvisor日志记录
MessageChatMemoryAdvisor会话记忆
QuestionAnswerAdvisor实现 RAG

在配置 ChatClient 时添加日志 Advisor:

@Bean
public ChatClient chatClient(OllamaChatModel model) {
    return ChatClient.builder(model)
            .defaultSystem("你是一个热心、可爱的智能助手")
            .defaultAdvisors(new SimpleLoggerAdvisor())   // 添加默认 Advisor,记录日志
            .build();
}

同时修改日志级别:

logging:
  level:
    org.springframework.ai: debug     # AI 对话的日志级别
    com.itheima.ai: debug             # 本项目的日志级别

重启项目后,再次与 AI 聊天即可在控制台看到完整的请求/响应日志。

2.8 会话记忆功能

默认情况下聊天机器人没有记忆,上一轮对话的内容下一轮就会被遗忘。传统做法是把每次历史对话拼接到 Prompt 中一起发送,比较繁琐。SpringAI 自带会话记忆功能,会自动保存并拼接历史会话。

2.8.1 ChatMemory

会话记忆同样基于 AOP 实现,由 MessageChatMemoryAdvisor 提供。该 Advisor 需要指定一个 ChatMemory 实例,即会话历史的保存方式。

ChatMemory 接口声明如下:

public interface ChatMemory {

    default void add(String conversationId, Message message) {
        this.add(conversationId, List.of(message));
    }

    // 添加会话信息到指定 conversationId 的会话历史中
    void add(String conversationId, List<Message> messages);

    // 根据 conversationId 查询历史会话
    List<Message> get(String conversationId, int lastN);

    // 清除指定 conversationId 的会话历史
    void clear(String conversationId);
}

所有会话记忆都与 conversationId(会话 ID) 关联,不同会话 ID 的记忆自然分开管理。

目前 SpringAI 提供两个 ChatMemory 实现:

实现类存储方式说明
InMemoryChatMemory内存简单易用,重启丢失
CassandraChatMemoryCassandra 数据库需额外依赖,且绑定向量数据库,不够灵活

2.8.2 添加会话记忆功能

第一步:注册 chatMemory Bean

@Bean
public ChatMemory chatMemory() {
    return new InMemoryChatMemory();
}

第二步:添加到 ChatClient

@Bean
public ChatClient chatClient(OllamaChatModel model, ChatMemory chatMemory) {
    return ChatClient.builder(model)
            .defaultSystem("你的名字叫小板。请以友好、乐于助人和愉快的方式解答学生的各种问题。")
            .defaultAdvisors(new SimpleLoggerAdvisor())              // 记录日志
            .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))  // 会话记忆
            .build();
}

第三步:调用时传入 chatId 区分用户

为区分每个用户对应的不同会话记录,需要在请求时传入用户 ID:

@RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")
public Flux<String> chat(@RequestParam(defaultValue = "讲个笑话") String prompt, String chatId) {

    return chatClient
            .prompt(prompt)
            .advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
            .stream()
            .content();
}

通过 CHAT_MEMORY_CONVERSATION_ID_KEY 将 chatId 传递给 MessageChatMemoryAdvisor,SpringAI 会自动根据该 ID 加载历史对话并拼接到本次请求中。

THE END
文章版权归Tinsur.cn所有,转载分享请标注原链接
点赞0 分享
评论 抢沙发
头像-TinsurBlog
欢迎您留下宝贵的见解!
提交
头像-TinsurBlog

昵称

取消
昵称代码图片链接

    暂无评论内容