springAI二 SpringAI实战
前言 SpringAI整合了全球(主要是国外)的大多数大模型,而且对于大模型开发的三种技术架构都有比较好的封装和支持,开发起来非常方便。不同的模型能够接收的输入类型、输出类型不一定相同。SpringAI根据模型的输入和输出类型不同对模型进行了分类:大模型应用开发大多数情况下使用的都是基于对话模型(Chat Model),也就是输出结果为自然语言或代码的模型。目前SpringAI支持的大约19种对话
前言 SpringAI整合了全球(主要是国外)的大多数大模型,而且对于大模型开发的三种技术架构都有比较好的封装和支持,开发起来非常方便。
不同的模型能够接收的输入类型、输出类型不一定相同。SpringAI根据模型的输入和输出类型不同对模型进行了分类:

大模型应用开发大多数情况下使用的都是基于对话模型(Chat Model),也就是输出结果为自然语言或代码的模型。
目前SpringAI支持的大约19种对话模型,以下是一些功能对比:

其中功能最完整的就是OpenAI和Ollama平台的模型了。接下来,我们就以这两个平台为例给大家讲解SpringAI的应用。
一 SpringAI入门(对话机器人)
1.1.快速入门
1.1.1.创建工程
创建一个新的SpringBoot工程,勾选Web、MySQL驱动即可:

工程结构如图:

原始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.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>ai-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ai-demo</name>
<description>ai-demo</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.1.2.引入依赖
SpringAI完全适配了SpringBoot的自动装配功能,而且给不同的大模型提供了不同的starter,比如:
我们可以根据自己选择的平台来选择引入不同的依赖。这里我们先以Ollama为例。
首先,在项目pom.xml中添加spring-ai的版本信息:
<spring-ai.version>1.0.0-M6</spring-ai.version>
然后,添加spring-ai的依赖管理项:
<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>
最后,引入spring-ai-ollama的依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
为了方便后续开发,我们再手动引入一个Lombok依赖,注意,不要在新建工程时勾选Lombok依赖,那样不生效:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
最终,完整依赖如下:
<?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.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>ai-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ai-demo</name>
<description>ai-demo</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.1.3.配置模型信息
接下来,我们还要在配置文件中配置模型的参数信息。
以ollama为例,我们将application.properties修改为application.yaml,然后添加下面的内容:
spring:
application:
name: ai-demo
ai:
ollama:
base-url: http://localhost:11434 # ollama服务地址, 这就是默认值
chat:
model: deepseek-r1:7b # 模型名称
options:
temperature: 0.8 # 模型温度,影响模型生成结果的随机性,越小越稳定
1.1.4.ChatClient
ChatClient中封装了与AI大模型对话的各种API,同时支持同步式或响应式交互。
不过,在使用之前,首先我们需要声明一个ChatClient。
在com.itheima.ai.config包下新建一个CommonConfiguration类:

完整代码如下:
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工厂对象,利用它可以自由选择模型、添加各种自定义配置
- OllamaChatModel:如果你引入了ollama的starter,这里就可以自动注入OllamaChatModel对象。同理,OpenAI也是一样的用法。
1.1.5.同步调用
接下来,我们定义一个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=你好

1.1.6.流式调用
同步调用需要等待很长时间页面才能看到结果,用户体验不好。为了解决这个问题,我们可以改进调用方式为流式调用。
在SpringAI中使用了WebFlux技术实现流式调用。
修改刚才ChatController中的chat方法:
// 注意看返回值,是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();
}
重启测试,再次访问:
1.1.7.System设定
可以发现,当我们询问AI你是谁的时候,它回答自己是DeepSeek-R1,这是大模型底层的设定。如果我们希望AI按照新的设定工作,就需要给它设置System背景信息。
在SpringAI中,设置System信息非常方便,不需要在每次发送时封装到Message,而是创建ChatClient时指定即可。
我们修改CommonConfiguration中的代码,给ChatClient设定默认的System信息:
@Bean
public ChatClient chatClient(OllamaChatModel model) {
return ChatClient.builder(model) // 创建ChatClient工厂实例
.defaultSystem("您是一家名为“麦芽糖程序员”的职业教育公司的客户聊天助手,你的名字叫小黑。请以友好、乐于助人和愉快的方式解答学生的各种问题。")
.defaultAdvisors(new SimpleLoggerAdvisor())
.build(); // 构建ChatClient实例
}
我们再次询问“你是谁?”

注意,前面是DeepSeek的深度思考内容,绿色高亮部分才是最终的回答。
现在,AI已经能够以麦芽糖客服小黑的身份来回答问题了~
1.2 日志功能
默认情况下,应用于AI的交互时不记录日志的,我们无法得知SpringAI组织的提示词到底长什么样,有没有问题。这样不方便我们调试。
1.2.1 Advisor
SpringAI基于AOP机制实现与大模型对话过程的增强、拦截、修改等功能。所有的增强通知都需要实现Advisor接口。

Spring提供了一些Advisor的默认实现,来实现一些基本的增强功能:

- SimpleLoggerAdvisor:日志记录的Advisor
- MessageChatMemoryAdvisor:会话记忆的Advisor
- QuestionAnswerAdvisor:实现RAG的Advisor
当然,我们也可以自定义Advisor,具体可以参考:https://docs.spring.io/spring-ai/reference/1.0/api/advisors.html#_implementing_an_advisor
1.2.2 添加日志Advisor
首先,我们需要修改CommonConfiguration,给ChatClient添加日志Advisor:
@Bean
public ChatClient chatClient(OllamaChatModel model) {
return ChatClient.builder(model) // 创建ChatClient工厂实例
.defaultSystem("你是一个热心、可爱的智能助手,你的名字叫小团团,请以小团团的身份和语气回答问题。")
//添加日志Advisor
.defaultAdvisors(new SimpleLoggerAdvisor()) // 添加默认的Advisor,记录日志
.build(); // 构建ChatClient实例
}
1.2.3.修改日志级别
logging:
level:
org.springframework.ai: debug # AI对话的日志级别
com.itheima.ai: debug # 本项目的日志级别
重启项目,再次聊天,IDEA控制台就能看到AI对话的日志信息了~
1.3.对接前端
在浏览器通过地址访问,非常麻烦,也不够优雅。如果能有一个优美的前端页面就好了。我们可以使用cursor来开发一个前端即可。
解决CORS跨域问题
前后端在不同域名,存在跨域问题,因此我们需要在服务端解决cors问题。在com.itheima.ai.config包中添加一个MvcConfiguration类:

内容如下:
package com.itheima.ai.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("Content-Disposition");
}
}
重启服务,如果你的服务端接口正确,那么应该就可以聊天了。
注意:
前端访问服务端的默认路径是:http://localhost:8080
聊天对话的接口是:POST /ai/chat
请确保你的服务端接口也是这样。
启动前端后,访问 http://localhost:5173即可看到页面:

点击第一个卡片《AI聊天》进入对话机器人页面:

恭喜您,你的第一个AI对话机器人就完成了。
1.4 会话记忆功能
现在,我们的AI聊天机器人是没有记忆功能的,上一次聊天的内容,下一次就忘掉了。
我们之前说过,让AI有会话记忆的方式就是把每一次历史对话内容拼接到Prompt中,一起发送过去。是不是还挺麻烦的。
别担心,好消息是,我们并不需要自己来拼接,SpringAI自带了会话记忆功能,可以帮我们把历史会话保存下来,下一次请求AI时会自动拼接,非常方便。
1.4.1.ChatMemory
会话记忆功能同样是基于AOP实现,Spring提供了一个MessageChatMemoryAdvisor的通知,我们可以像之前添加日志通知一样添加到ChatClient即可。
不过,要注意的是,MessageChatMemoryAdvisor需要指定一个ChatMemory实例,也就是会话历史保存的方式。
ChatMemory接口声明如下:
public interface ChatMemory {
// TODO: consider a non-blocking interface for streaming usages
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:会话历史保存在内存中
- CassandraChatMemory:会话保存在Cassandra数据库中(需要引入额外依赖,并且绑定了向量数据库,不够灵活)
我们暂时选择用InMemoryChatMemory来实现。
1.4.2.添加会话记忆Advisor
在CommonConfiguration中注册ChatMemory对象
@Bean
public ChatMemory chatMemory() {
//如果要存储在mysql等持久数据库的话,我们需要自定义类实现ChatMemory接口,下边返回我们自定义类即可
return new InMemoryChatMemory();
}
然后添加MessageChatMemoryAdvisor到ChatClient:
@Bean
public ChatClient chatClient(OllamaChatModel model, ChatMemory chatMemory) {
return ChatClient.builder(model) // 创建ChatClient工厂实例
.defaultSystem("您是一家名为“黑马程序员”的职业教育公司的客户聊天助手,你的名字叫小黑。请以友好、乐于助人和愉快的方式解答学生的各种问题。")
.defaultAdvisors(new SimpleLoggerAdvisor()) // 添加默认的Advisor,记录日志
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))//添加会话记忆
.build(); // 构建ChatClient实例
}
OK,现在聊天会话已经有记忆功能了,不过现在的会话记忆还是不完善的,我们还有继续补充。
1.5.会话历史
会话历史与会话记忆是两个不同的事情:
会话记忆:是指让大模型记住每一轮对话的内容,不至于前一句刚问完,下一句就忘了。
会话历史:是指要记录总共有多少不同的对话
以DeepSeek为例,页面上的会话历史:

在ChatMemory中,会记录一个会话中的所有消息,记录方式是以conversationId为key,以List为value,根据这些历史消息,大模型就能继续回答问题,这就是所谓的会话记忆。
而会话历史,就是每一个会话的conversationId,将来根据conversationId再去查询List。
比如上图中,有3个不同的会话历史,就会有3个conversationId,管理会话历史,就是记住这些conversationId,当需要的时候查询出conversationId的列表。
注意,在接下来业务中,我们以chatId来代指conversationId.
1.5.1.管理会话id(会话历史)
由于会话记忆是以conversationId来管理的,也就是会话id(以后简称为chatId)。将来要查询会话历史,其实就是查询历史中有哪些chatId.
因此,为了实现查询会话历史记录,我们必须记录所有的chatId,我们需要定义一个管理会话历史的标准接口。
我们定义一个com.itheima.ai.repository包,然后新建一个ChatHistoryRepository接口:
package com.itheima.ai.repository;
import java.util.List;
public interface ChatHistoryRepository {
/**
* 保存会话记录
* @param type 业务类型,如:chat、service、pdf
* @param chatId 会话ID
*/
void save(String type, String chatId);
/**
* 获取会话ID列表
* @param type 业务类型,如:chat、service、pdf
* @return 会话ID列表
*/
List<String> getChatIds(String type);
}
然后定义一个实现类InMemoryChatHistoryRepository:
package com.itheima.ai.repository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
@RequiredArgsConstructor
public class InMemoryChatHistoryRepository implements ChatHistoryRepository {
private Map<String, List<String>> chatHistory;
@Override
public void save(String type, String chatId) {
/*if (!chatHistory.containsKey(type)) {
chatHistory.put(type, new ArrayList<>());
}
List<String> chatIds = chatHistory.get(type);*/
List<String> chatIds = chatHistory.computeIfAbsent(type, k -> new ArrayList<>());
if (chatIds.contains(chatId)) {
return;
}
chatIds.add(chatId);
}
@Override
public List<String> getChatIds(String type) {
/*List<String> chatIds = chatHistory.get(type);
return chatIds == null ? List.of() : chatIds;*/
return chatHistory.getOrDefault(type, List.of());
}
}
注意:
目前我们业务比较简单,没有用户概念,但是将来会有不同业务,因此简单采用内存保存type与chatId关系。
将来大家也可以根据业务需要把会话id持久化保存到Redis、MongoDB、MySQL等数据库。
如果业务中有user的概念,还需要记录userId、chatId、time等关联关系
1.5.2.保存会话id
接下来,修改ChatController中的chat方法,做到3点:
- 添加一个请求参数:chatId,每次前端请求AI时都需要传递chatId
- 每次处理请求时,将chatId存储到ChatRepository
- 每次发请求到AI大模型时,都传递自定义的chatId
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
@CrossOrigin("*")
@RequiredArgsConstructor
@RestController
@RequestMapping("/ai")
public class ChatController {
private final ChatClient chatClient;
private final ChatMemory chatMemory;
private final ChatHistoryRepository chatHistoryRepository;
@RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")
public Flux<String> chat(@RequestParam(defaultValue = "讲个笑话") String prompt, String chatId) {
chatHistoryRepository.addChatId(chatId);
return chatClient
.prompt(prompt)
.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
.stream()
.content();
}
}
注意,这里传递chatId给Advisor的方式是通过AdvisorContext,也就是以key-value形式存入上下文:
chatClient.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
其中的CHAT_MEMORY_CONVERSATION_ID_KEY是AbstractChatMemoryAdvisor中定义的常量key,将来MessageChatMemoryAdvisor执行的过程中就可以拿到这个chatId了。
1.5.3.查询会话历史
接着,我们定义一个新的Controller,专门实现会话历史的查询。包含两个接口:
- 根据业务类型查询会话历史列表(我们将来有3个不同业务,需要分别记录历史。大家的业务可能是按userId记录,根据UserId查询)
- 根据chatId查询指定会话的历史消息
其中,查询会话历史消息,也就是Message集合。但是由于Message并不符合页面的需要,我们需要自己定义一个VO.
定义一个com.itheima.entity.vo包,在其中定义一个MessageVO类:
package com.itheima.ai.entity.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.ai.chat.messages.Message;
@NoArgsConstructor
@Data
public class MessageVO {
private String role;
private String content;
public MessageVO(Message message) {
this.role = switch (message.getMessageType()) {
case USER -> "user";
case ASSISTANT -> "assistant";
case SYSTEM -> "system";
default -> "";
};
this.content = message.getText();
}
}
然后在com.itheima.ai.controller包下新建一个ChatHistoryController:
package com.itheima.ai.controller;
import com.itheima.ai.entity.vo.MessageVO;
import com.itheima.ai.repository.ChatHistoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RequiredArgsConstructor
@RestController
@RequestMapping("/ai/history")
public class ChatHistoryController {
private final ChatHistoryRepository chatHistoryRepository;
private final ChatMemory chatMemory;
/**
* 查询会话历史列表
* @param type 业务类型,如:chat,service,pdf
* @return chatId列表
*/
@GetMapping("/{type}")
public List<String> getChatIds(@PathVariable("type") String type) {
return chatHistoryRepository.getChatIds(type);
}
/**
* 根据业务类型、chatId查询会话历史
* @param type 业务类型,如:chat,service,pdf
* @param chatId 会话id
* @return 指定会话的历史消息
*/
@GetMapping("/{type}/{chatId}")
public List<MessageVO> getChatHistory(@PathVariable("type") String type, @PathVariable("chatId") String chatId) {
List<Message> messages = chatMemory.get(chatId, Integer.MAX_VALUE);
if(messages == null) {
return List.of();
}
return messages.stream().map(MessageVO::new).toList();
}
}
OK,重启服务,现在AI聊天机器人就具备会话记忆和会话历史功能了!
更多推荐



所有评论(0)