Spring AI实战系列(七):Chat Memory对话记忆实战,基于Redis实现持久化多轮对话
一、系列回顾与本篇定位1.1 系列回顾第一篇完成Spring AI与阿里云百炼的基础集成基于ChatModel实现同步对话与API Key安全注入。第二篇解锁ChatClient实现全局统一配置与链式调用告别重复样板代码。第三篇实现DeepSeek/Qwen双模型无缝共存与动态切换完成双版本流式输出。第四篇深度拆解Prompt工程全体系从底层Message到模板化动态生成掌握与大模型高效沟通的方法论。第五篇掌握结构化输出能力实现大模型输出到 Java 实体的自动映射解决业务系统对接痛点。第六篇掌握Tool Calling核心能力让大模型自动调用业务接口实现 “大模型 业务系统” 的深度融合。1.2 本篇定位大模型有一个缺陷它是无状态的。每次请求对大模型来说都是 “全新的开始”它不会记住之前的对话内容 —— 你刚告诉它 “我叫张三”下一句问 “我叫什么”它就会一脸茫然。而Chat Memory对话记忆正是解决这个问题的核心能力它通过存储和检索历史对话消息让大模型 “记住” 之前的交互实现上下文感知的多轮对话。更重要的是生产环境中我们不仅需要记忆还需要持久化记忆—— 服务重启后记忆不能丢失同时需要会话隔离—— 不同用户的对话记忆必须完全独立不能串线。本篇是系列企业级落地核心篇我们将深度拆解 Spring AI Chat Memory 的全体系从核心原理出发彻底理解 Chat Memory 的工作流程与 Spring AI 的核心抽象。基于 Redis 实现持久化对话记忆完成从配置、集成到会话隔离的全流程实战。覆盖消息窗口控制、内存记忆与持久化记忆对比、会话管理等企业级高频场景。补充生产环境最佳实践与高频踩坑避坑指南解决 Chat Memory 落地的核心问题。二、核心概念拆解Spring AI Chat Memory2.1 什么是Chat MemoryChat Memory是Spring AI提供的对话记忆抽象核心作用是存储历史对话在每次对话完成后自动将用户消息和大模型响应保存到记忆仓库。检索历史对话在每次新请求发起前自动从记忆仓库中检索历史消息注入到当前 Prompt 中。会话隔离通过conversationId会话 ID区分不同用户的对话确保记忆完全独立。简单来说Chat Memory就是给大模型 “装了个记忆大脑”让它能记住之前的对话内容实现连贯的多轮交互。2.2 Spring AI Chat Memory 核心组件Spring AI 的 Chat Memory 体系设计非常优雅核心组件只有四个职责清晰学习成本低核心组件作用典型实现ChatMemory对话记忆的顶层抽象接口定义了记忆的存储、检索、清除能力MessageWindowChatMemory基于消息窗口的记忆ChatMemoryRepository记忆的持久化仓库抽象负责将对话历史持久化到外部存储RedisChatMemoryRepositoryRedis 持久化实现MessageChatMemoryAdvisorAdvisor 机制的核心实现将记忆能力动态注入到 ChatClient的调用流程中MessageChatMemoryAdvisor.builder(chatMemory).build()conversationId会话 ID用于隔离不同用户的对话记忆是实现多用户独立对话的关键通常使用UUID或用户ID作为会话ID2.3 核心工作流程Spring AI Chat Memory 的完整工作流程如下请求发起用户发送新问题同时传入conversationId。历史检索MessageChatMemoryAdvisor自动根据conversationId从ChatMemoryRepository中检索历史消息。Prompt 组装将历史消息注入到当前 Prompt 中放在用户新问题之前。模型调用ChatClient 将包含历史消息的 Prompt 发送给大模型。结果生成大模型基于历史消息和当前问题生成响应。记忆保存MessageChatMemoryAdvisor自动将当前用户消息和大模型响应保存到ChatMemoryRepository中。整个流程完全自动开发者无需手动处理记忆的存储和检索只需关注业务逻辑即可。三、实战落地基于 Redis 实现持久化多轮对话为了满足生产环境的需求我们不再使用简单的内存记忆服务重启后丢失而是使用Redis 作为持久化记忆仓库同时实现会话隔离确保不同用户的对话记忆完全独立。3.1 环境前提已完成 JDK 17、Spring Boot 3.2.x 环境搭建已完成 Spring AI 基础配置项目中已注册ChatModel和ChatClient实例已配置阿里云百炼 API Key 环境变量DASHSCOPE_API_KEY已安装并启动 Redis 服务本地或远程均可已在pom.xml中引入 Redis 依赖!-- Spring Boot Redis 依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- Spring AI Alibaba 依赖已包含RedisChatMemoryRepository -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId version1.0.0.2/version /dependency3.2 第一步配置 Redis 连接与持久化仓库首先在application.yml中配置 Redis 连接信息然后编写RedisMemoryConfig配置类注册RedisChatMemoryRepositoryBean。application.yml 配置spring: data: redis: host: localhost # Redis主机地址 port: 6379 # Redis端口 password: 123456 # Redis密码生产环境请从环境变量读取 database: 0 # Redis数据库索引 timeout: 3000ms # 连接超时时间 lettuce: pool: max-active: 8 # 连接池最大连接数 max-idle: 8 # 连接池最大空闲连接数 min-idle: 0 # 连接池最小空闲连接数RedisMemoryConfig 配置类import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Redis 持久化记忆仓库配置类 */ Configuration public class RedisMemoryConfig { // 从配置文件读取Redis连接信息 Value(${spring.data.redis.host}) private String redisHost; Value(${spring.data.redis.port}) private int redisPort; Value(${spring.data.redis.password:}) // 生产环境建议从环境变量读取 private String redisPassword; /** * 注册RedisChatMemoryRepository Bean * 负责将对话历史持久化到Redis中 */ Bean public RedisChatMemoryRepository redisChatMemoryRepository() { return RedisChatMemoryRepository.builder() .host(redisHost) .port(redisPort) .password(redisPassword) // 生产环境建议使用System.getenv()读取 .build(); } }关键说明生产环境中Redis 密码不要硬编码在配置类中建议通过System.getenv(REDIS_PASSWORD)从环境变量读取或者使用 Spring Cloud Config 等配置中心管理。RedisChatMemoryRepository会自动将对话历史序列化为 JSON 格式存储到 Redis 中无需开发者手动处理序列化。3.3 第二步将Chat Memory集成到ChatClient修改我们之前的LLMConfig配置类给qwenChatClient添加MessageChatMemoryAdvisor同时配置MessageWindowChatMemory基于消息窗口的记忆实现防止历史消息占用过多 Token。import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * ChatModelChatClient多模型共存Redis持久化记忆配置类 */ Configuration public class LLMConfig { // 模型名称常量统一管理 private final String DEEPSEEK_MODEL deepseek-v3; private final String QWEN_MODEL qwen-plus; // ChatModel 实例注册 Bean(name deepseek) public ChatModel deepSeekChatModel() { return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .build()) .defaultOptions(DashScopeChatOptions.builder() .withModel(DEEPSEEK_MODEL) .withTemperature(0.7) .build()) .build(); } Bean(name qwen) public ChatModel qwenChatModel() { return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .build()) .defaultOptions(DashScopeChatOptions.builder() .withModel(QWEN_MODEL) .withTemperature(0.7) .build()) .build(); } // 带Redis记忆的ChatClient 实例注册 Bean(name qwenChatClient) public ChatClient qwenChatClient( Qualifier(qwen) ChatModel qwenChatModel, RedisChatMemoryRepository redisChatMemoryRepository) { // 1. 配置基于消息窗口的记忆只保留最近10条消息防止Token超限 MessageWindowChatMemory windowChatMemory MessageWindowChatMemory.builder() .chatMemoryRepository(redisChatMemoryRepository) // 绑定Redis持久化仓库 .maxMessages(10) // 最大保留消息数包含用户消息和助手响应 .build(); // 2. 配置MessageChatMemoryAdvisor将记忆能力注入到ChatClient MessageChatMemoryAdvisor memoryAdvisor MessageChatMemoryAdvisor.builder(windowChatMemory) .build(); // 3. 构建ChatClient通过defaultAdvisors全局启用记忆 return ChatClient.builder(qwenChatModel) .defaultOptions(ChatOptions.builder().model(QWEN_MODEL).build()) .defaultAdvisors(memoryAdvisor) // 全局默认启用记忆 .build(); } // 家庭作业给DeepSeek也加上Redis记忆 Bean(name deepseekChatClient) public ChatClient deepseekChatClient( Qualifier(deepseek) ChatModel deepSeekChatModel, RedisChatMemoryRepository redisChatMemoryRepository) { MessageWindowChatMemory windowChatMemory MessageWindowChatMemory.builder() .chatMemoryRepository(redisChatMemoryRepository) .maxMessages(10) .build(); return ChatClient.builder(deepSeekChatModel) .defaultOptions(ChatOptions.builder().model(DEEPSEEK_MODEL).build()) .defaultAdvisors(MessageChatMemoryAdvisor.builder(windowChatMemory).build()) .build(); } }关键说明MessageWindowChatMemory的作用大模型的上下文窗口是有限的如果历史消息太多会占用大量Token导致请求失败。MessageWindowChatMemory通过maxMessages参数只保留最近的 N 条消息自动丢弃更早的消息防止 Token 超限。defaultAdvisors的作用通过defaultAdvisors将MessageChatMemoryAdvisor设置为 ChatClient 的全局默认 Advisor这样所有通过该 ChatClient 发起的请求都会自动启用记忆能力无需每次调用时手动配置。家庭作业完成我们已经给deepseekChatClient也加上了 Redis 记忆实现了双模型的持久化多轮对话。3.4 第三步实现带会话隔离的对话接口创建ChatMemoryRedisController实现带会话隔离的多轮对话接口通过userId或conversationId隔离不同用户的对话记忆。import jakarta.annotation.Resource; import org.springframework.http.HttpStatus; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID; import java.util.UUID; /** * Redis持久化多轮对话接口 */ RestController public class ChatMemory4RedisController { Resource(name qwenChatClient) private ChatClient qwenChatClient; /** * 带会话隔离的多轮对话接口 */ GetMapping(/chatmemory/chat) public String chat( RequestParam(name msg) String msg, RequestParam(name userId, required false) String userId) { // 1. 参数校验 if (msg null || msg.isBlank()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, Missing required query param: msg); } // 2. 生成/获取会话ID如果传入userId则使用userId否则生成随机UUID String conversationId (userId null || userId.isBlank()) ? UUID.randomUUID().toString() : userId.trim(); // 3. 调用ChatClient通过advisors传入会话ID实现会话隔离 return qwenChatClient .prompt(msg) // 核心传入CONVERSATION_ID参数隔离不同用户的对话记忆 .advisors(advisorSpec - advisorSpec.param(CONVERSATION_ID, conversationId)) .call() .content(); } }接口测试说明第一次访问http://localhost:xxx/chatmemory/chat?msg我叫张三系统会自动生成一个会话 ID大模型会记住 “你叫张三”。第二次访问传入第一次返回的会话ID或使用固定的 userId访问http://localhost:xxx/chatmemory/chat?msg我叫什么userId你的会话ID大模型会回答 “你叫张三”说明记忆生效了。重启服务后再次访问记忆仍然存在说明Redis持久化生效了。四、进阶场景高频需求实现4.1 清除特定会话的记忆生产环境中我们经常需要清除特定用户的对话记忆如用户主动清除、会话过期。Spring AI 的ChatMemory接口提供了clear()方法可以轻松实现记忆清除。import jakarta.annotation.Resource; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * 会话管理接口 */ RestController public class ChatMemoryManageController { Resource private ChatMemory chatMemory; // 直接注入ChatMemory /** * 清除特定会话的记忆 * 访问示例http://localhost:xxx/chatmemory/clear?userId你的用户ID */ DeleteMapping(/chatmemory/clear) public String clearMemory(RequestParam(name userId) String userId) { chatMemory.clear(userId); return String.format(会话【%s】的记忆已清除, userId); } }4.2 内存记忆vsRedis持久化记忆对比Spring AI 提供了两种记忆实现内存记忆和持久化记忆我们可以根据场景灵活选择对比维度内存记忆InMemoryChatMemoryRedis 持久化记忆RedisChatMemoryRepository存储位置JVM 内存Redis 数据库持久化服务重启后丢失服务重启后仍然存在性能极高无需网络 IO高有少量网络IO会话隔离支持支持适用场景开发测试、单实例服务、临时对话生产环境、多实例集群、需要持久化的对话内存记忆的实现方式如果不需要持久化只想在开发测试时使用内存记忆可以这样配置// 内存记忆配置无需Redis Bean public ChatClient qwenChatClient(Qualifier(qwen) ChatModel qwenChatModel) { // 使用InMemoryChatMemory服务重启后记忆丢失 MessageWindowChatMemory windowChatMemory MessageWindowChatMemory.builder() .maxMessages(10) .build(); return ChatClient.builder(qwenChatModel) .defaultAdvisors(MessageChatMemoryAdvisor.builder(windowChatMemory).build()) .build(); }4.3 基于Token数量的记忆窗口除了MessageWindowChatMemory基于消息数量的窗口Spring AI还提供了TokenWindowChatMemory基于 Token 数量的窗口可以更精确地控制历史消息占用的 Token 数量避免浪费模型的上下文窗口。import org.springframework.ai.chat.memory.TokenWindowChatMemory; import org.springframework.ai.chat.model.ChatModel; // 基于Token数量的记忆窗口配置 Bean(name qwenChatClient) public ChatClient qwenChatClient( Qualifier(qwen) ChatModel qwenChatModel, RedisChatMemoryRepository redisChatMemoryRepository) { // 基于Token数量的记忆最多保留8000 Token的历史消息 TokenWindowChatMemory tokenWindowChatMemory TokenWindowChatMemory.builder() .chatModel(qwenChatModel) // 传入ChatModel用于计算Token数量 .chatMemoryRepository(redisChatMemoryRepository) .maxTokens(8000) // 最大保留Token数 .build(); return ChatClient.builder(qwenChatModel) .defaultAdvisors(MessageChatMemoryAdvisor.builder(tokenWindowChatMemory).build()) .build(); }五、实践建议5.1 Redis 配置最佳实践使用 Redis 集群生产环境建议使用 Redis 主从复制或 Redis Cluster避免单点故障保证高可用。连接池配置合理配置 Redis 连接池的max-active、max-idle、min-idle参数避免连接泄漏和性能瓶颈。密码安全Redis 密码必须从环境变量或配置中心读取绝对不要硬编码在代码或配置文件中。内存过期策略给 Redis 设置合理的内存过期策略如allkeys-lru避免对话历史占用过多内存导致 Redis OOM。序列化优化Spring AI 默认使用 JSON 序列化确保序列化后的消息大小合理避免过大的消息占用过多 Redis 内存。5.2 会话管理最佳实践会话 ID 生成策略对于已登录用户直接使用用户 ID 作为conversationId确保用户的对话记忆永久关联。对于未登录用户使用 UUID 生成临时会话 ID同时设置会话过期时间如 24 小时过期后自动清除记忆。会话过期机制生产环境建议实现会话过期机制定期清除长时间未使用的对话记忆避免 Redis 内存无限增长。会话安全会话 ID 必须与用户身份绑定防止未授权访问他人的对话记忆。不要在 URL 中明文传递会话 ID建议通过请求头或 Cookie 传递如果是 Web 应用。5.3 记忆窗口配置最佳实践根据模型上下文窗口设置模型上下文窗口为 32K TokenmaxMessages设置为 10-20或maxTokens设置为 8000-16000。模型上下文窗口为 128K TokenmaxMessages设置为 30-50或maxTokens设置为 32000-64000。预留足够的 Token 空间不要把记忆窗口设置得太大要预留足够的 Token 空间给当前用户问题和大模型响应避免请求失败。动态调整记忆窗口可以根据用户等级动态调整记忆窗口大小如 VIP 用户保留更多历史消息提升用户体验。5.4 监控与运维最佳实践记忆存储监控监控 Redis 的内存使用情况、对话历史的存储数量、平均每条对话的大小及时发现内存增长异常。调用次数监控监控每个会话的对话次数、记忆的检索和保存次数识别高频使用的会话。异常监控监控 Redis 连接失败、记忆检索失败、记忆保存失败等异常及时告警和处理。备份与恢复定期备份 Redis 中的对话历史数据防止数据丢失。六、避坑指南6.1 坑点 1Redis 连接失败现象启动服务时报错提示无法连接到 Redis。原因Redis 服务未启动。application.yml中的 Redis 主机、端口、密码配置错误。防火墙阻止了 Redis 连接。解决方案检查 Redis 服务是否正常启动。核对application.yml中的 Redis 配置信息。检查防火墙规则确保应用服务器能访问 Redis。6.2 坑点 2会话ID丢失记忆不生效现象每次请求都是新的开始大模型记不住之前的对话。原因没有在请求中传入conversationId。conversationId每次都不一样没有复用。传入conversationId的方式错误没有使用advisorSpec.param(CONVERSATION_ID, conversationId)。解决方案确保每次请求都传入相同的conversationId或使用固定的 userId。确保通过advisors(advisorSpec - advisorSpec.param(CONVERSATION_ID, conversationId))传入会话 ID。6.3 坑点 3记忆窗口太大导致 Token 超限现象请求报错提示 “Prompt too large” 或 “Token limit exceeded”。原因maxMessages设置得太大历史消息占用了过多 Token。没有预留足够的 Token 空间给当前用户问题和大模型响应。解决方案调小maxMessages或maxTokens参数。优先使用TokenWindowChatMemory更精确地控制 Token 数量。6.4 坑点 4多个 ChatClient共享记忆现象不同模型的对话记忆串线了Qwen的对话出现在DeepSeek的记忆中。原因多个ChatClient 使用了相同的ChatMemory实例且没有区分会话ID前缀。解决方案给不同模型的会话ID加上不同的前缀如 Qwen 用qwen_开头DeepSeek 用deepseek_开头。或者给不同模型使用独立的ChatMemory实例。6.5 坑点 5Redis 内存溢出现象Redis内存占用持续增长最终导致OOM。原因没有设置会话过期机制对话历史无限增长。Redis 的内存过期策略设置不合理。解决方案实现会话过期机制定期清除长时间未使用的对话记忆。给 Redis 设置合理的内存过期策略如allkeys-lru。监控 Redis 内存使用情况及时扩容或清理数据。七、本篇总结本篇我们深度掌握了Spring AI Chat Memory的全体系从核心原理出发理解了 Chat Memory 的工作流程与 Spring AI 的核心抽象。基于 Redis 实现了持久化对话记忆完成了从配置、集成到会话隔离的全流程实战。覆盖了记忆窗口控制、内存记忆与持久化记忆对比、会话管理等企业级高频场景。补充了生产环境最佳实践与高频踩坑避坑指南为 Chat Memory 的生产落地提供了完整的解决方案。Chat Memory 是实现连贯多轮对话的核心能力只有掌握了持久化记忆与会话隔离才能真正开发出面向多用户的企业级 AI 应用。八、下篇预告本篇我们掌握了Chat Memory核心能力实现了带持久化记忆的多轮对话。至此Spring AI 的核心基础能力基础集成、ChatClient、Prompt 工程、结构化输出、Tool Calling、Chat Memory我们已经全部掌握。在本系列的下一篇中我们将进入Spring AI企业级高级能力的最后一块拼图 ——RAG检索增强生成如果本文对你有帮助欢迎点赞、收藏、评论跟着系列教程一步步完成 Spring AI 企业级应用的全流程落地。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2474063.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!