大模型默认只能单轮对话,每次对话完成后就会丢失当前对话记忆,我们之前了解过可以通过AssistantMessage把大模型回复结果存储起来下次提问时在发送给大模型,不过使用过于麻烦和受限,Spring AI 和Spring AI Alibaba都实现了更好实现记忆化的方式:
1. 内存存储
java
@Configuration
public class SaaLLMConfig {
private final String QWEN_MODEL = "qwen-plus";
@Bean
public DashScopeApi dashScopeApi() {
return DashScopeApi.builder().apiKey(System.getenv("AliQWenAPIKey")).build();
}
@Bean
public ChatModel qwenChatModel(DashScopeApi dashScopeApi) {
return DashScopeChatModel
.builder()
.dashScopeApi(dashScopeApi)
.defaultOptions(DashScopeChatOptions.builder().model(QWEN_MODEL).build())
.build();
}
@Bean
public ChatClient qwenChatClient(@Qualifier("qwenChatModel") ChatModel qwenChatModel) {
MessageWindowChatMemory windowChatMemory = MessageWindowChatMemory.builder()
.maxMessages(10)
.chatMemoryRepository(new InMemoryChatMemoryRepository())
.build();
return ChatClient
.builder(qwenChatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(windowChatMemory).build())
.build();
}
}
- MessageWindowChatMemory:维护固定容量的消息窗口(默认 20 条)。当消息超限时,自动移除较早的对话消息(始终保留系统消息)。
- InMemoryChatMemoryRepository: 基于 ConcurrentHashMap 实现内存存储。默认情况下,若未配置其他 Repository,Spring AI 将自动配置 InMemoryChatMemoryRepository 类型的 ChatMemoryRepository Bean供直接使用。
2. 源码解析
关键在于MessageChatMemoryAdvisor这个类,可以看到我们在配置ChatClient时配置了一个该对象,这个就是生效的关键,虽然名字叫 Advisor ,但它和 Spring AOP 一点关系都没有 。它是 Spring AI 自己实现的一套独立拦截器机制。
java
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
String conversationId = getConversationId(chatClientRequest.context(), this.defaultConversationId);
// 1. Retrieve the chat memory for the current conversation.
List<Message> memoryMessages = this.chatMemory.get(conversationId);
// 2. Advise the request messages list.
List<Message> processedMessages = new ArrayList<>(memoryMessages);
processedMessages.addAll(chatClientRequest.prompt().getInstructions());
// 3. Create a new request with the advised messages.
ChatClientRequest processedChatClientRequest = chatClientRequest.mutate()
.prompt(chatClientRequest.prompt().mutate().messages(processedMessages).build())
.build();
// 4. Add the new user message to the conversation memory.
UserMessage userMessage = processedChatClientRequest.prompt().getUserMessage();
this.chatMemory.add(conversationId, userMessage);
return processedChatClientRequest;
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
List<Message> assistantMessages = new ArrayList<>();
if (chatClientResponse.chatResponse() != null) {
assistantMessages = chatClientResponse.chatResponse()
.getResults()
.stream()
.map(g -> (Message) g.getOutput())
.toList();
}
this.chatMemory.add(this.getConversationId(chatClientResponse.context(), this.defaultConversationId),
assistantMessages);
return chatClientResponse;
}
在该类源码中我们可以看到关键的两个方法,before和after
- before:在发送请求前执行先从memoryMessages中获取历史对话,再把本次对话的用户问题也存入chatMemory中。
- after:在大模型返回后执行,把大模型返回内容存储到chatMemory中
3. Redis存储
配置redis:
XML
spring:
data:
redis:
host: localhost
port: 6379
database: 0
要使用Redis需要引入依赖:
XML
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Alibaba Spring AI Redis 记忆模块 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
</dependency>

可以看到这个依赖适配了Jedis和RedissonRedis以及springBoot的Lettuce
java
@Configuration
public class SaaLLMConfig {
private final String DEEPSEEK_MODEL = "deepseek-v3.2";
@Bean
public ChatModel deepseekChatModel() {
return DashScopeChatModel
.builder()
.dashScopeApi(DashScopeApi.builder().apiKey(System.getenv("AliQWenAPIKey")).build())
.defaultOptions(DashScopeChatOptions.builder().model(DEEPSEEK_MODEL).build())
.build();
}
@Bean
public ChatClient deepseekChatClient(@Qualifier("deepseekChatModel") ChatModel deepseekChatModel) {
LettuceRedisChatMemoryRepository redisCMR = LettuceRedisChatMemoryRepository.builder().build();
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(10)
.chatMemoryRepository(redisCMR)
.build();
return ChatClient
.builder(deepseekChatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
}
同时,一个大模型通常会服务多个用户,对于不同用户的对话应该分开存储,我们可以通过设置CONVERSATION_ID来实现:
java
@RestController
public class ChatMemoryController {
@Resource(name = "deepseekChatClient")
ChatClient deepseekChatClient;
@GetMapping("/chatmemory/chat2")
public Flux<String> chat2(String userId, String question) {
return deepseekChatClient
.prompt()
.user(question)
.advisors(
advisorSpec -> advisorSpec.param(CONVERSATION_ID, userId)
)
.stream()
.content();
}
}
通过.advisors方法设置,如果不设置会使用默认的,这样所有用户的对话是共享的:


可以看到是按id分别存储的
4. 关系型数据库存储

导入依赖:
XML
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--Spring AI JDBC 聊天记忆启动器-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
增加配置:
XML
spring:
ai:
chat:
#开启Schema初始化,会自动创建表用
memory:
repository:
jdbc:
initialize-schema: always
# 数据库连接
datasource:
url: jdbc:mysql://localhost:3306/ai_chat?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# JDBC 自动建表
sql:
init:
mode: always
配置ChatClient:
java
@Configuration
public class SaaLLMConfig {
private final String DEEPSEEK_MODEL = "deepseek-v3.2";
private final String QWEN_MODEL = "qwen-plus";
private final String GLM5 = "glm-5";
@Bean
public DashScopeApi dashScopeApi() {
return DashScopeApi.builder().apiKey(System.getenv("AliQWenAPIKey")).build();
}
@Bean
public ChatModel glmChatModel() {
return DashScopeChatModel
.builder()
.dashScopeApi(DashScopeApi.builder().apiKey(System.getenv("AliQWenAPIKey")).build())
.defaultOptions(DashScopeChatOptions.builder().model(GLM5).build())
.build();
}
@Bean
public ChatClient glmChatClient(@Qualifier("glmChatModel") ChatModel glmChatModel, JdbcChatMemoryRepository jdbcChatMemoryRepository) {
MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(10)
.chatMemoryRepository(jdbcChatMemoryRepository)
.build();
return ChatClient
.builder(glmChatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
Spring AI 为 JdbcChatMemoryRepository 提供自动配置,可直接注入。
java
@RestController
public class ChatMemoryController {
@Resource(name = "glmChatClient")
ChatClient glmChatClient;
@GetMapping("/chatmemory/chat3")
public Flux<String> chat3(String userId, String question) {
return glmChatClient
.prompt()
.user(question)
.advisors(
advisorSpec -> advisorSpec.param(CONVERSATION_ID, userId)
)
.stream()
.content();
}
}
