三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Spring AI Alibaba 实现多轮对话记忆:ChatMemory 与 Redis 持久化实战

Spring AI Alibaba 实现多轮对话记忆:ChatMemory 与 Redis 持久化实战

前言

在构建智能对话应用时,多轮对话的上下文记忆能力是刚需。Spring AI Alibaba 提供了一套优雅的 ChatMemory 机制,将对话历史的管理、存储和恢复过程封装得极为简洁。本文将带大家从零掌握如何用 Spring AI Alibaba 集成 ChatMemory,涵盖内存存储、Redis 持久化、流式 SSE 输出等场景,并给出可直接用于生产的配置方案。

一、架构流程与核心组件

整个记忆链路如下:

前端请求 → ChatClient → MessageChatMemoryAdvisor(记忆拦截器)
↓ 自动读取/写入会话历史
ChatMemory(消息窗口策略:滑动窗口,限制消息条数)
↓ 底层存储
ChatMemoryRepository(存储层:内存/Redis/MySQL)
↓ 底层调用
DashScopeChatModel → DashScopeApi → 阿里灵积 DashScope 平台

1. ChatMemoryRepository 存储仓库

负责对话消息持久化存储,Spring AI Alibaba 内置三种实现:

  • InMemoryChatMemoryRepository:内存存储,单机测试专用,重启丢失数据
  • RedisChatMemoryRepository:Redis 持久化,集群/生产推荐,会话自动过期,支持两种构建模式:Jedis 直连、复用 Spring Redis 连接工厂
  • JdbcChatMemoryRepository:数据库持久化,长期留存对话记录

2. ChatMemory 记忆策略层

标准实现:MessageWindowChatMemory,滑动窗口淘汰机制,配置maxMessages限制最大保存消息条数,自动丢弃最早对话,防止上下文 Token 超限、API 计费暴涨。

3. MessageChatMemoryAdvisor 记忆拦截器

挂载在 ChatClient 的 Advisor 链路中,请求前自动根据conversationId加载历史消息,模型返回后自动保存用户/助手消息,业务代码无需手动拼接历史。

4. ConversationId 会话唯一标识

不同用户、不同聊天窗口分配独立 ID,实现会话隔离,同一个 ID 自动复用历史对话,不同 ID 完全互不干扰。

5. ChatModel vs ChatClient 记忆能力区分

  • ChatModel:底层原始接口,无内置记忆管理能力,需要手动拼接消息列表,代码冗余;
  • ChatClient:上层封装,搭配 Advisor 一键集成 ChatMemory,支持全局系统提示词、消息自动管理、流式、函数调用,企业开发首选。

二、Maven 核心依赖与配置

2.1 基础依赖

<!-- 基础 DashScope 对话依赖(必引) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>

内存存储无需额外依赖;Redis 持久化需引入记忆扩展包,并根据连接模式补充对应依赖:

<!-- Redis记忆扩展包,两种模式都必须引入 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
</dependency>

<!-- 方式A:Jedis直连模式,需要该依赖 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

<!-- 方式B:复用Spring Redis连接工厂模式,需要该依赖 -->
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
-->

2.2 application.yml 基础配置

spring:
ai:
dashscope:
api-key: sk-xxxxxxxxxxxxxxxxxxxx
chat:
options:
model: qwen-turbo
temperature: 0.7
# Redis配置,Jedis直连模式通过@Value读取;连接池复用模式由SpringDataRedis自动读取
data:
redis:
host: 127.0.0.1
port: 6379
password: ""
database: 0

三、方案1:内存版 ChatMemory(本地测试首选)

内存实现适用于开发调试,提供两种写法:极简自动装配与手动配置定制窗口大小。

3.1 方式A:极简自动装配(90% 测试场景,零配置类)

Spring AI Alibaba 自动配置了InMemoryChatMemoryRepository,我们只需通过@Bean定义ChatMemoryChatClient即可,无需手动创建仓库。

配置类:

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AiMemoryConfig {

// 使用自动装配的 InMemoryChatMemoryRepository
@Bean
public ChatMemory chatMemory(InMemoryChatMemoryRepository repository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(repository)
.maxMessages(10) // 最多保存10条消息(5轮对话)
.build();
}

@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultSystem("你是资深Java后端专家,结合上下文简洁回答问题")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
}

Controller 示例:

@RestController
@RequestMapping("/ai/chat")
public class MemoryChatController {

@Autowired
private ChatClient chatClient;
@Autowired
private ChatMemory chatMemory;

/**
* 同步多轮对话
* @param conversationId 会话ID,前端生成UUID
* @param userMsg 用户提问
*/
@GetMapping("/sync")
public String syncChat(
@RequestParam String conversationId,
@RequestParam String userMsg) {
return chatClient.prompt()
.user(userMsg)
.advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
}

// 清空指定会话记忆
@DeleteMapping("/memory/clear")
public String clearMemory(@RequestParam String conversationId) {
chatMemory.clear(conversationId);
return "会话[" + conversationId + "]记忆已清空";
}
}

3.2 方式B:手动配置类(自定义窗口上限,推荐工程化写法)

若需完全掌控 Bean 创建过程,可手动声明 Repository 和 ChatMemory:

@Configuration
public class AiMemoryConfig {

@Bean
public InMemoryChatMemoryRepository memoryRepository() {
return new InMemoryChatMemoryRepository();
}

@Bean
public ChatMemory chatMemory(InMemoryChatMemoryRepository repository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(repository)
.maxMessages(20) // 自定义窗口大小
.build();
}

@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultSystem("你是资深Java后端专家,结合上下文简洁回答问题")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
}

四、方案2:Redis 持久化 ChatMemory(生产环境集群推荐)

内存存储致命缺陷:服务重启、多实例集群时会话记忆丢失,线上必须使用 Redis 持久化。RedisChatMemoryRepository提供两套构建方式:

4.1 方式A:Jedis 直连(简单 Demo)

组件内部自建 Jedis 连接,不共享项目 Redis 连接池,yml 配置通过@Value读取。

@Configuration
public class RedisMemoryConfig {

@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Value("${spring.data.redis.password:}")
private String password;
@Value("${spring.data.redis.database:0}")
private int database;

@Bean
public RedisChatMemoryRepository redisChatMemoryRepository() {
return RedisChatMemoryRepository.builder()
.host(host)
.port(port)
.password(password)
.database(database)
.build();
}

@Bean
public ChatMemory chatMemory(RedisChatMemoryRepository redisChatMemoryRepository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(redisChatMemoryRepository)
.maxMessages(20)
.build();
}

@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultSystem("你是通义千问对话助手,记住用户全部历史提问")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
}

4.2 方式B:复用 Spring Data Redis 连接工厂(生产推荐)

项目已引入spring-boot-starter-data-redis时使用,连接池、哨兵、集群配置统一管理。

@Configuration
public class RedisMemoryConfig {

@Bean
public RedisChatMemoryRepository redisChatMemoryRepository(RedisConnectionFactory redisConnectionFactory) {
return RedisChatMemoryRepository.builder()
.redisConnectionFactory(redisConnectionFactory)
.build();
}

@Bean
public ChatMemory chatMemory(RedisChatMemoryRepository redisChatMemoryRepository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(redisChatMemoryRepository)
.maxMessages(20)
.build();
}

@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultSystem("你是通义千问对话助手,记住用户全部历史提问")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
}

Redis 记忆特性:

  • 会话数据序列化存入 Redis,服务重启不丢失;
  • 支持配置 Key 过期时间,自动清理长期未使用会话;
  • 微服务多实例集群共享 Redis,多节点对话上下文统一。

重要区分:

  • Jedis 直连模式:内部新建独立 Jedis 连接,不和业务 Redis 共用连接池,适合简单 Demo;
  • RedisConnectionFactory 模式:复用项目已有 Redis 连接池,适合正式业务系统。

五、流式 SSE 接口实现带记忆打字机效果

结合 ChatMemory 实现带历史上下文的实时流式输出,基于 WebFlux Flux + SSE:

@RestController
@RequestMapping("/ai/chat")
public class StreamMemoryChatController {

@Autowired
private ChatClient chatClient;

/**
* 流式带记忆对话,SSE实时推送
*/
@GetMapping(value = "/stream", produces = "text/event-stream;charset=utf-8")
public Flux<String> streamChatWithMemory(
@RequestParam String conversationId,
@RequestParam String userMsg) {
return chatClient.prompt()
.user(userMsg)
.advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, conversationId))
.stream()
.content();
}
}

六、总结与选型建议

  1. 开发测试:使用内存版InMemoryChatMemoryRepository,极简配置,快速验证多轮对话逻辑。
  2. 单机生产:内存版不持久化,若可接受重启丢失历史,也可用;否则切换至 Redis 持久化。
  3. 集群/微服务:必须选择Redis 持久化,并推荐复用 Spring Data Redis 连接工厂,实现连接池统一管理,保证多实例会话共享。
  4. 流式输出:只需在ChatClient基础上调用.stream().content()即可获得 SSE 流,记忆配置无需额外改动。

Spring AI Alibaba 的 ChatMemory 体系将对话上下文管理复杂度降到最低,配合 ChatClient 的 Advisor 机制,让我们能更专注于业务逻辑。希望本文能帮助大家快速落地多轮对话功能,如有疑问欢迎在评论区交流。

← 返回列表