集成SpringAI
引入依赖
在项目的父工程中的pom文件中导入springai的依赖:
<!-- 依赖声明 -->
<dependencyManagement>
<dependencies>
<!-- SpringAI坐标,版本统一管理-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.1.8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>在使用的业务模块中的pom.xml文件中添加依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>很多大模型的对接都兼容了openai的api,所以添加的是openai的依赖 , 典型的百炼大模型就已经集成了openai。
配置
在application.yml文件中增加如下配置:
spring:
ai:
# 使用openai规范连接阿里云百炼平台模型
openai:
base-url: ${DASHSCOPE_BASE_URL}
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen3.8-flash使用注意
使用openai规范调用阿里云百炼平台, SpringAI会自动在base-url后面拼接/v1/chat/completions
所以base-url后面不能带 /v1 后缀
聊天对话
在SpringAI中,ChatClient是核心关键点,负责与大模型交互,而得到ChatClient对象是通过ChatClient.Builder构建得到的,并且需要将ChatClient对象放入到Spring容器,方便注入使用。
所以,需要写一个配置类,来完成上述的事情。
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Bean : 在Spring容器创建的时候会自动执行被@Bean注解标注的方法,将方法的返回值对象装入到Spring容器中
* 方法执行过程中如果需要参数 , 会自动从Spring容器中查询匹配类型的Bean进行注入(与@Autowired规则一样)
*/
@Configuration
public class SpringAIConfig {
@Bean
// 注意:这里是将返回的返回值对象也就是ChatClient对象装入到Spring容器中
public ChatClient openAiChatClient(OpenAiChatModel openAiChatModel){
return ChatClient.builder(openAiChatModel).build();
}
}代码解读:
ChatClient.builder:会得到一个ChatClient.Builder工厂对象,利用它可以自由选择模型、添加各种自定义配置OpenAiChatModel:如果你引入了openai的starter,这里就可以自动注入OpenAiChatModel对象。同理,Ollama也是一样的用法。
接下来,我们定义一个Controller,在其中接收用户发送的提示词,然后把提示词发送给大模型,交给大模型处理,拿到结果后返回,代码如下:
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/ai")
public class ChatController {
@Autowired
private ChatClient chatClient;
@PostMapping(path = "/chat", produces = "text/html;charset=utf-8")
public String chat(String prompt) {
String content = chatClient
.prompt()
.user(prompt) // 设置用户提示词
.call() // 调用大模型
.content();
return content;
}
}流式对话
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/ai")
public class ChatController {
@Autowired
private ChatClient chatClient;
@RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")
public Flux<String> chat(String prompt) {
return chatClient.prompt()
.user(prompt)
.stream()
.content();
}
}System角色设定
我们希望AI按照新的设定工作,就需要给它设置System背景信息。
在SpringAI中,设置System信息非常方便,不需要在每次发送时封装到Message,而是创建ChatClient时指定即可。
我们修改SpringAIConfig中的代码,给ChatClient设定默认的System信息:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringAIConfig {
/**
* 创建并返回一个ChatClient的Spring Bean实例。
* @param openAiChatModel
* @return
*/
@Bean
public ChatClient chatClient(OpenAiChatModel openAiChatModel) {
return ChatClient
.builder(openAiChatModel)
.defaultSystem("您是一个优秀的智能助手,名字叫小聪。请以友好乐于助人和愉快的方式解答用户的各种问题。")
.build();
}
}SpringAI对接其他模型
集成Ollama
导入依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>在application-dev.yml中集成ollama
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: deepseek-r1:8b核心配置类
@Bean
public ChatClient ollamaChatClient(OllamaChatModel ollamaChatModel) {
return ChatClient
.builder(ollamaChatModel)
.build();
}集成DeepSeek
导入依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>在application-dev.yml中集成DeepSeek
spring:
ai:
deepseek:
base-url: https://api.deepseek.com
api-key: ${DEEPSEEK_API_KEY}
chat:
options:
model: deepseek-flash需要在电脑的环境变量中添加DEEPSEEK_API_KEY
核心配置类
@Bean
public ChatClient deepSeekChatClient(DeepSeekChatModel deepSeekChatModel) {
return ChatClient
.builder(deepSeekChatModel)
.build();
}ToolCalling
toolcalling运行的原理如下图:

流程解读:
- 提前把这些操作定义为Function(SpringAI中叫Tool)
- 然后将Function的名称、作用、需要的参数等信息都封装为Prompt提示词与用户的提问一起发送给大模型
- 大模型在与用户交互的过程中,根据用户交流的内容判断是否需要调用Function
- 如果需要则返回Function名称、参数等信息
- Java解析结果,判断要执行哪个函数,代码执行Function,把结果再次封装到Prompt中发送给AI
- AI继续与用户交互,直到完成任务
听起来是不是挺复杂,还要解析响应结果,调用对应函数。
不过,有了SpringAI,中间这些复杂的步骤大家就都不用做了!
下面是SpringAI中ToolCalling的使用流程:
角色设定
public class SystemConstants {
public static final String prompt = """
您是一个优秀的聊天助手,你的名字叫小智。请以友好、乐于助人和愉快的方式解答用户的各种问题。
如果需要查询护理项目时,要调用find_project_tool工具
""";
}引入提示词:
@Bean
public ChatClient openAiChatClient(OpenAiChatModel openAiChatModel) {
return ChatClient
.builder(openAiChatModel)
.defaultSystem(SystemConstants.prompt)
.build();
}定义Tool
import com.xhzb.nursing.domain.vo.NursingProjectVo;
import com.xhzb.nursing.service.INursingProjectService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class NursingProjectTool {
@Autowired
private INursingProjectService nursingProjectService;
@Tool(name = "find_project_tool", description = "查询护理项目")
public List<NursingProjectVo> findAllProjectVo() {
//查询所有护理项目
return nursingProjectService.listAll();
}
}注册Tool
@Configuration
public class SpringAIConfig {
/**
* 创建并返回一个ChatClient的Spring Bean实例。
* @param openAiChatModel
* @return
*/
@Bean
public ChatClient openAiChatClient(OpenAiChatModel openAiChatModel, NursingProjectTool nursingProjectTool) {
return ChatClient
.builder(openAiChatModel)
.defaultTools(nursingProjectTool)
.defaultSystem(SystemConstants.prompt)
.build();
}
}会话记忆和历史记录
Advisors 运行原理
Spring AI Advisors提供了一种灵活且强大的方式,可以在 Spring 应用中轻松拦截、调整和增强基于AI的交互操作。通过使用Advisors,可以构建更复杂、可重用且易于维护的AI组件,从而提升应用的功能性和简化开发流程,使项目更加高效和整洁。
SpringAI基于AOP机制实现与大模型对话过程的增强、拦截、修改等功能。所有的增强通知都需要实现Advisor接口。

Spring提供了一些Advisor的默认实现,来实现一些基本的增强功能:
- SimpleLoggerAdvisor:日志记录的Advisor
- MessageChatMemoryAdvisor:会话记忆的Advisor
- QuestionAnswerAdvisor:实现RAG的Advisor
日志Advisor
首先,我们需要修改SpringAIConfig,给ChatClient添加日志Advisor:
@Configuration
public class SpringAIConfig {
/**
* 创建并返回一个ChatClient的Spring Bean实例。
* @param openAiChatModel
* @return
*/
@Bean
public ChatClient openAiChatClient(OpenAiChatModel openAiChatModel, NursingProjectTool nursingProjectTool) {
return ChatClient
.builder(openAiChatModel)
.defaultTools(nursingProjectTool)
.defaultSystem(SystemConstants.prompt)
.defaultAdvisors(new SimpleLoggerAdvisor()) //添加默认的advisor 记录日志
.build();
}
}接下来,我们在application-dev.yml中添加日志配置,更新日志级别:
# 日志配置
logging:
level:
com.xhzb: debug
org.springframework: warn
org.springframework.ai: debug # AI对话的日志级别重启项目,再次聊天就能看到AI对话的日志信息了~
会话聊天记忆
我们的AI聊天机器人是没有记忆功能的,上一次聊天的内容,下一次就忘掉了。
SpringAI自带了会话记忆功能,可以帮我们把历史会话保存下来,下一次请求AI时会自动拼接,非常方便。
会话记忆功能同样是基于AOP实现,Spring提供了一个MessageChatMemoryAdvisor的通知,我们可以像之前添加日志通知一样添加到ChatClient即可。
不过,要注意的是,MessageChatMemoryAdvisor需要指定一个ChatMemory实例,也就是会话历史保存的方式。
ChatMemory接口声明如下()
public interface ChatMemory {
String DEFAULT_CONVERSATION_ID = "default";
String CONVERSATION_ID = "chat_memory_conversation_id";
default void add(String conversationId, Message message) {
Assert.hasText(conversationId, "conversationId cannot be null or empty");
Assert.notNull(message, "message cannot be null");
this.add(conversationId, List.of(message));
}
void add(String conversationId, List<Message> messages);
List<Message> get(String conversationId);
void clear(String conversationId);
}可以看到,所有的会话记忆都是与conversationId有关联的,也就是会话Id,将来不同会话id的记忆自然是分开管理的。
目前,在SpringAI中有两个ChatMemory的实现:
MessageWindowChatMemory:RedisChatMemory:会话保存在Redis非关系数据库中
开启会话记忆和会话隔离
开启基于内存的会话记忆
@Configuration
public class SpringAIConfig {
/**
* 创建并返回一个ChatClient的Spring Bean实例。
* @param openAiChatModel
* @return
*/
@Bean
public ChatClient openAiChatClient(OpenAiChatModel openAiChatModel,
NursingProjectTool nursingProjectTool
,ChatMemory chatMemory) {
return ChatClient
.builder(openAiChatModel)
.defaultTools(nursingProjectTool)
.defaultSystem(SystemConstants.prompt)
.defaultAdvisors(new SimpleLoggerAdvisor(), MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
}还要记得要进行会话隔离,不同会话直接的数据是相互隔离互不影响的
如何隔离呢,怎么实现会话隔离呢?做法就是修改ChatController,传递上下文的会话id
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/ai")
public class ChatController {
@Autowired
private ChatClient openAiChatClient;
@RequestMapping(value = "/chat",produces = "text/html;charset=UTF-8")
public Flux<String> chat(String prompt,String chatId) {
Flux<String> content = openAiChatClient
.prompt()
.user(prompt)
.advisors(a->a.param(ChatMemory.CONVERSATION_ID,chatId))
.stream()
.content();
return content;
}
}注意,这里传递chatId给Advisor的方式是通过AdvisorContext,也就是以key-value形式存入上下文:
chatClient...advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId))基于Redis的会话记忆
由于基于内存保存会话记忆和会话历史会导致数据丢失,所以我们可以选择更好的存储方式,用上redis方式,这样既可以保证效率也能兼顾数据的可靠性。

不过,前面的方案,都面临一个问题,SpringAI中的Message类未实现Serializable接口,也没提供public的构造方法,无法实现序列化。所以一般的最佳实践是定义一个可序列化的Message类,方便后续持久化。
现在在vo包中新建一个Msg类:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.ai.chat.messages.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Msg {
MessageType messageType;
String text;
Map<String, Object> metadata;
private LocalDateTime createTime;
public Msg(Message message) {
this.messageType = message.getMessageType();
this.text = message.getText();
this.metadata = message.getMetadata();
createTime = LocalDateTime.now();
}
public Message toMessage() {
return switch (messageType) {
case SYSTEM -> new SystemMessage(text);
case USER -> UserMessage.builder().text(text).media(List.of()).metadata(metadata).build();
case ASSISTANT -> AssistantMessage.builder().content(text).properties(metadata).media(List.of()).build();
default -> throw new IllegalArgumentException("Unsupported message type: " + messageType);
};
}
}这个类中有两个关键方法:
- 构造方法:实现将SpringAI的Message转为我们的Msg的功能
- toMessage方法:实现将我们的Msg转为SpringAI的Message
自定义ChatMemory
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.xhzb.nursing.domain.vo.Msg;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 基于Redis的ChatMemory实现
* 数据类型 : List<Msg的JSON字符串>
* Key的规则 : chat:memory:{conversationId}
*/
@Service
public class RedisChatMemory implements ChatMemory {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String KEY_TEMPLATE = "chat:memory:%s";
/**
* 新增聊天消息
*
* @param conversationId
* @param messages
*/
@Override
public void add(String conversationId, List<Message> messages) {
// 1. 判断消息列表是否为空
if (CollUtil.isEmpty(messages)) {
return;
}
// 2. 将聊天消息转化为JSON保存到Redis中
String key = String.format(KEY_TEMPLATE, conversationId);
// 2.1 将消息列表中的每个Message对象 先转化为 Msg对象 再转化为JSON字符串
List<String> msgList = messages.stream()
.map(message -> new Msg(message))
.map(msg -> JSONUtil.toJsonStr(msg))
.toList();
redisTemplate.opsForList().leftPushAll(key, msgList);
}
/**
* 获取聊天消息
*
* @param conversationId
* @return
*/
@Override
public List<Message> get(String conversationId) {
// 再Redis中根据会话id查询会话历史
String key = String.format(KEY_TEMPLATE, conversationId);
// 获取会话记录列表
List<String> msgList = redisTemplate.opsForList().range(key, 0, -1);
// 如果为空直接返回空集合
if (CollUtil.isEmpty(msgList)) {
return List.of();
}
// 如果不为空,将数据先转化为 Msg对象 再转化为Message对象
List<Message> messageList = msgList.stream()
.map(str -> JSONUtil.toBean(str, Msg.class))
.sorted((msg1, msg2) -> msg1.getCreateTime().compareTo(msg2.getCreateTime()))
.map(msg -> msg.toMessage())
.toList();
return messageList;
}
/**
* 清空聊天消息
* @param conversationId
*/
@Override
public void clear(String conversationId) {
String key = String.format(KEY_TEMPLATE, conversationId);
redisTemplate.delete(key);
}
}