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 机制,让我们能更专注于业务逻辑。希望本文能帮助大家快速落地多轮对话功能,如有疑问欢迎在评论区交流。

相关推荐
JAVA面经实录9171 小时前
网络编程基础(Java Web/分布式前置·完整版)(十一)
java·前端·网络
Escalating_xu1 小时前
【Linux】基础 I/O 深度解析:FILE、文件描述符、open/read/write、重定向与缓冲区
java·linux·服务器
城管不管1 小时前
MySQL 慢查询完整排查
java·服务器·jvm·数据库·mysql·spring·面试
ZC跨境爬虫1 小时前
LeetCode 88. 合并两个有序数组(双指针详解 + Java Python 实现)
java·python·算法·leetcode
北风toto1 小时前
研发效能与后端核心技术全景指南:从CI/CD到共性组件实战
java·开发语言·ci/cd
01二进制代码漫游日记1 小时前
C++基础入门速通
java·开发语言·c++
Herbert_hwt1 小时前
第六章 Java深入理解接口、函数式接口与lambda表达式
java·开发语言·算法
API快乐传递者2 小时前
电商竞品分析接口实战指南:从数据采集到决策洞察的全链路方案
java·python
程序员黑豆9 小时前
Java类型推断完全指南:从var到菱形运算符,掌握使用限制与最佳实践
java·前端·ai编程