Java开发者AI转型第七课!AI失忆症克星!ChatMemory对话历史管理与上下文实战
大家好我是直奔標杆欢迎各位Java同仁来到《Spring AI 零基础到实战》专栏的第七课咱们继续并肩前行一步步搞定Spring AI实战技能共同实现AI转型在上一节《Java开发者AI转型第六课Spring AI 灵魂架构 Advisor 切面拦截与自定义实战》中咱们一起吃透了现代Spring AI的核心——Advisor顾问/切面拦截器。掌握了这个高阶技能咱们终于能直面AI应用开发中最头疼的难题大模型的“7秒钟失忆症”今天就带大家彻底解决它相信很多同仁在实际开发中都遇到过这样的问题用代码调用大模型聊天时你问“我叫张三今年 6 岁。”AI 答“你好张三小朋友。”你紧接着问“我几岁了”AI 答“抱歉我不知道你的年龄。”是不是很困惑网页版的Deepseek/ChatGPT能轻松记住上下文为啥咱们通过API调用的大模型就成了“失忆症患者”还有更实际的问题如果同时有100个用户使用你的AI客服怎么确保张三的聊天记忆不串到李四那里服务器重启后之前的聊天记录还能找回来吗这节课咱们就借助上一节学到的Advisor机制结合Spring AI强大的ChatMemory组件手把手教大家给大模型装上“长久记忆”同时实现多租户隔离和数据库持久化直接适配生产环境干货拉满建议收藏跟着实操本节学习目标共勉认知破局搞懂大语言模型LLM的无状态Stateless本质避开初学者常见误区自动化记忆掌握MessageWindowChatMemory与MessageChatMemoryAdvisor的用法实现零代码侵入的上下文自动拼接多租户隔离巧用ChatMemory.CONVERSATION_ID实现多用户会话的物理隔离避免记忆串线记忆持久化告别内存丢失问题实战JdbcChatMemoryRepository将对话记录永久存入MySQL数据库适配生产级需求。多租户记忆检索与装配引擎核心原理拆解在动手写代码之前咱们先通过一张逻辑图搞清楚多用户场景下Spring AI底层的记忆切面是如何工作的这张图的核心关键的就是ChatMemoryRepository咱们可以把它类比成一个大型文件柜方便大家记忆抽屉Key就是咱们传入的conversationId会话ID比如user_001可对应用户唯一标识内容Value就是该用户与AI之前所有的聊天记录ListMessage。每次收到用户的提问时MemoryAdvisor会自动根据请求中的会话ID从这个“文件柜”里取出该用户的历史聊天数据自动拼接上下文彻底省去咱们手动处理的繁琐操作这就是Spring AI的强大之处深入理解大模型为什么会“失忆”在动手写代码之前咱们先纠正一个很多初学者都会踩的误区不少同仁觉得大模型是一个能随时记住数据的“智能伙伴”其实不然。无论是ChatGPT、DeepSeek还是通义千问咱们调用它们的API时其本质就是一个无状态Stateless的数学函数f(prompt) response。简单说就是你的HTTP请求发过去它只识别本次发送的文本内容一旦请求结束你的聊天数据就会被彻底清除不会在它的内存中留存。那网页版的Deepseek为什么能记住咱们之前说的话答案很简单它的前端或后端会悄悄帮咱们保存所有聊天记录每次发送新消息时系统会自动把“历史聊天记录本次新问题”拼接成一个完整的Prompt再打包发给大模型——说白了就是手动帮大模型“记笔记”。在Spring AI中这些聊天记录被抽象成了两个核心接口咱们重点掌握即可不用死记原理结合实操理解更高效ChatMemory负责记忆管理策略比如只保留最近10条消息防止记忆过长撑爆Token避免不必要的损耗ChatMemoryRepository负责物理存储决定聊天记录存在内存里还是存入MySQL等数据库。实战上手引入ChatMemory与Advisor零代码侵入实现自动记忆咱们先回顾一下不使用Spring AI框架时AI开发中处理聊天记忆有多繁琐需要手动创建ListMessage每次对话后还要手动add()用户提问和AI回答重复工作多还容易出错。而Spring AI框架已经帮咱们封装好了一切借助ChatClient的流式API和内置Advisor就能省去这些繁琐操作咱们直接上手实操步骤清晰跟着做就能搞定第一步注册全局记忆组件Spring自动装配无需手动写大量代码要让系统拥有记忆能力首先需要在Spring容器中注册一个ChatMemory。最简洁的方式是使用MessageWindowChatMemory滑动窗口记忆法它会维护一个消息窗口窗口大小不超过指定上限默认20条消息当消息数量超出上限时会自动删除较旧的消息但会保留系统消息。重点来了这一步咱们完全不用手动实现当咱们引入模型相关的starter比如spring-ai-starter-model-deepseek时Spring会自动向容器中注册ChatMemoryRepository和MessageWindowChatMemory底层默认使用InMemoryChatMemoryRepository本质就是ConcurrentHashMap将数据存在JVM内存中核心自动装配代码如下大家可以参考理解不用手动编写/** * 自动装配逻辑Spring默认实现供大家参考学习 * * 账号直奔標杆CSDN专注Java AI转型实战分享 */ AutoConfiguration ConditionalOnClass({ChatMemory.class, ChatMemoryRepository.class}) public class ChatMemoryAutoConfiguration { Bean ConditionalOnMissingBean ChatMemoryRepository chatMemoryRepository() { return new InMemoryChatMemoryRepository(); } Bean ConditionalOnMissingBean ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) { return MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).build(); } }这里重点提醒一下ConditionalOnMissingBean注解的作用是“当容器中没有该Bean时才自动注册”这意味着咱们可以根据自己的需求在项目配置中自行替换组件灵活性拉满。第二步挂载Memory Advisor将记忆组件绑定到ChatClient注册好全局记忆组件后咱们需要将它通过拦截器挂载到ChatClient上这样ChatClient就能自动调用记忆功能了直接上Controller代码注释详细大家可以直接复制实操/** * 自动记忆功能实战可直接复制到项目中测试 * * author 直奔標杆CSDN专注Java AI转型实战与大家共同进步 */ RestController public class AutoMemoryController { private final ChatClient chatClient; // 构造器注入 Builder 和 ChatMemory推荐构造器注入更规范 public AutoMemoryController(ChatClient.Builder builder, ChatMemory chatMemory) { this.chatClient builder // 为ChatClient挂载全局记忆拦截器实现自动记忆 .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .defaultSystem(你是一个贴心的私人助理你需要努力记住用户的偏好。) .build(); } GetMapping(/api/chat) public String chatWithMemory(RequestParam String msg) { // 重点配置Advisor后每次call()前后都会自动读写chatMemory无需手动处理 return chatClient.prompt() .user(msg) .call() .content(); } }测试验证必做加深理解咱们分两步测试就能看到效果非常直观先访问接口/api/chat?msg我叫张三我最喜欢吃火锅。再访问接口/api/chat?msg我是谁我喜欢吃什么大家会发现AI终于不再失忆能准确回答出你的名字和喜好——这就是Advisor和ChatMemory的作用自动帮咱们拼接了上下文关键优化解决记忆“串线”问题多租户隔离生产级必备上面的代码虽然能实现自动记忆但在真实服务器环境中会出现致命问题咱们只注册了一个全局ChatMemory如果张三和李四同时访问/api/chat接口就会出现“串记忆”的情况——张三能看到李四的聊天记录李四也能看到张三的直接导致业务异常这是生产环境绝对不能容忍的。要实现多租户多用户的物理隔离核心就是给每个用户分配一个专属的“记忆抽屉”关键操作就是每次请求时通过ChatMemory.CONVERSATION_ID告诉Advisor当前对话属于哪个用户。咱们稍微改造一下Controller就能完美解决这个问题代码如下重点修改部分已标注可直接替换测试/** * 多租户隔离版聊天接口生产级可用 * param userId 用户唯一标识比如登录用户的token、uuid等确保唯一 * param msg 用户的新问题 */ GetMapping(/api/chat/isolated) public String chatWithIsolatedMemory( RequestParam String userId, RequestParam String msg) { return chatClient.prompt() .user(msg) // 重点动态传入用户专属会话ID实现记忆物理隔离取值参考BaseChatMemoryAdvisor#getConversationId .advisors(a - a.param(ChatMemory.CONVERSATION_ID, userId)) // 备注参数设置在call()时生效可参考DefaultChatClientUtils#toChatClientRequest源码 .call() .content(); }测试验证多租户隔离效果按以下步骤测试就能确认隔离效果建议大家实际操作一遍访问/api/chat/isolated?userIdzhangsanmsg我叫张三访问/api/chat/isolated?userIdlisimsg我叫李四交叉访问/api/chat/isolated?userIdlisimsg你知道张三吗测试后会发现李四的对话中无法获取张三的信息多用户记忆物理隔离完美生效再也不用担心串线问题进阶实战从内存到JDBC数据库持久化生产级落地关键咱们前面用到的InMemoryChatMemory是将数据存在JVM的ConcurrentHashMap中这就意味着只要Spring Boot服务重启所有的聊天记录都会丢失——这在开发测试环境没问题但在生产环境中是绝对不可接受的。Spring AI提供了官方JDBC存储方案咱们只需几行代码就能将聊天记录永久存入MySQL或PostgreSQL数据库彻底解决内存丢失问题直接适配生产环境咱们一步步实操1. 引入JDBC相关依赖pom.xml中添加首先引入JDBC记忆库的官方Starter和MySQL驱动版本可根据自己的项目需求调整代码如下!-- JDBC 记忆库官方 StarterSpring AI官方提供直接引入即可 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-chat-memory-repository-jdbc/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope version${mysql.version}/version /dependency2. 配置数据库连接application.yml中配置在配置文件中填写MySQL连接信息同时配置memory的初始化策略重点是initialize-schema的设置代码如下注释详细可直接复制修改spring: ai: chat: memory: repository: jdbc: # 初始化数据库默认值为embedded仅适配H2、HSQL等嵌入式数据库 # 改为alwaysSpring AI启动时会自动创建表无需手动建表 initialize-schema: always datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springai?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf-8allowPublicKeyRetrievaltrueuseSSLfalseallowMultiQueriestrue username: root # 替换为自己的MySQL用户名 password: 123456 # 替换为自己的MySQL密码重点提醒配置initialize-schema: always后Spring AI启动时会自动在MySQL中创建名为spring_ai_chat_memory的表无需咱们手动建表省去繁琐操作。3. 将Memory库切换为JDBC驱动手动配置可选咱们可以手动构建ChatMemory将存储方式切换为JDBC代码如下可直接复制到项目中注释详细方便理解package com.uka.springai.demo; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository; import org.springframework.ai.chat.memory.repository.jdbc.MysqlChatMemoryRepositoryDialect; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; /** * JDBC 存储记忆配置生产级实战可直接复用 * * author 直奔標杆CSDN分享Java AI转型实战经验与大家共同成长 */ Configuration public class AiMemoryConfig { Bean public ChatMemory chatMemory(JdbcTemplate jdbcTemplate) { // 1. 构建基于MySQL语法的JDBC记忆库也可通过Bean覆盖默认的内存存储 JdbcChatMemoryRepository repository JdbcChatMemoryRepository.builder() .jdbcTemplate(jdbcTemplate) // 重点设置MySQL方言适配MySQL数据库 .dialect(new MysqlChatMemoryRepositoryDialect()) .build(); // 2. 将JDBC记忆库注入到滑动窗口策略中 return MessageWindowChatMemory.builder() .chatMemoryRepository(repository) .build(); } }配置完成后再调用之前的/api/chat/isolated接口所有的对话记录都会被永久存入MySQL数据库——即使服务器断电、重启只要传入相同的userId大模型就能精准回忆起之前的所有对话彻底解决记忆丢失问题补充知识点JDBC自动装配无需手动配置更便捷上面咱们演示的是手动构建ChatMemory其实还有更便捷的方式由于咱们引入了spring-ai-starter-model-chat-memory-repository-jdbcSpring会自动帮咱们装配JdbcChatMemoryRepository因此上面第3步的手动替换操作咱们可以选择忽略Spring会自动覆盖默认的内存存储。给大家贴出Spring的自动装配代码方便大家理解底层逻辑不用手动编写参考学习即可AutoConfiguration( after {JdbcTemplateAutoConfiguration.class}, // 关键在ChatMemoryAutoConfiguration之前执行实现对内存存储的覆盖 before {ChatMemoryAutoConfiguration.class} ) ConditionalOnClass({JdbcChatMemoryRepository.class, DataSource.class, JdbcTemplate.class}) EnableConfigurationProperties({JdbcChatMemoryRepositoryProperties.class}) public class JdbcChatMemoryRepositoryAutoConfiguration { Bean ConditionalOnMissingBean JdbcChatMemoryRepository jdbcChatMemoryRepository(JdbcTemplate jdbcTemplate, DataSource dataSource) { // 根据数据库配置自动获取对应的数据库方言 JdbcChatMemoryRepositoryDialect dialect JdbcChatMemoryRepositoryDialect.from(dataSource); return JdbcChatMemoryRepository.builder().jdbcTemplate(jdbcTemplate).dialect(dialect).build(); } // 其余自动装配逻辑省略大家可自行查看Spring AI源码学习 }本节课总结共勉复盘本节课咱们重点攻克了大模型“失忆”的核心难题结合Spring AI的Advisor机制和ChatMemory组件实现了AI的长久记忆同时完成了多租户隔离和数据库持久化直接达到生产级标准咱们一起复盘一下核心要点依托MessageChatMemoryAdvisor切面机制彻底省去了手动拼接聊天历史的繁琐代码实现零代码侵入的自动记忆通过ChatMemory.CONVERSATION_ID轻松实现多用户会话的物理隔离解决了记忆串线的生产级痛点引入JdbcChatMemoryRepository无需修改核心业务代码就能将记忆从内存平滑迁移到MySQL数据库彻底解决记忆丢失问题。建议大家课后多动手实操一遍把代码跑起来感受Spring AI的便捷性遇到问题可以在评论区留言咱们一起交流解决共同进步下节预告精彩继续随着用户与AI的聊天次数增多记忆中的上下文会越来越多这会导致传递给大模型的Prompt越来越大——不仅会让API计费暴涨Token成本太高还可能超出大模型的窗口限制直接报错OOM这也是生产环境中必须解决的问题。下一节课咱们将带来《Java开发者AI转型第八课告别Token刺客Spring AI 记忆裁剪源码解密与Token级防溢出终极奥义》深入剖析Token的本质教大家如何在Spring AI中精确计算和动态裁剪长文本守住钱包和服务器的底线干货持续输出咱们下节不见不散往期实战内容连贯学习效果更佳Java开发者AI转型第四课告别硬编码Spring AI 提示词模板 (Prompt) 注入与多模态识图全攻略Java开发者AI转型第五课让AI懂规矩Spring AI 结构化输出 (DTO) 映射与 Flux 流式打字机极速响应Java开发者AI转型第六课Spring AI 灵魂架构 Advisor 切面拦截与自定义实战我是直奔標杆专注Java开发者AI转型实战分享每天进步一点点一起向标杆靠拢觉得本文有用的话欢迎点赞、收藏、关注评论区交流实操心得