第 02-0558 页

> 标题:[AI工程] Spring AI第六篇·杏和舒筋堂

杏和舒筋堂文章中心首页在线预约
> 来源:https://mp.weixin.qq.com/s/CqFamqNSqAGhDkS4ZcAD4A > 号:gh_81733e34bf5d > 专辑:4698158649133875201 > 标题:[AI工程] Spring AI第六篇: 对话记忆—数据库和Redis不同的实现 > 真正文:8658 字 真图:1 张 真视频:0 个 id="js_content" style="visibility: hidden; opacity: 0; "> 💡 做大模型应用开发的朋友一定遇到过这个问题:你跟 AI 说"我叫张三",下一轮对话问它"我叫什么",它一脸茫然地回你"抱歉,我不知道你的名字"。 原因很简单 —— LLM 是无状态的。每次请求都是一次全新的开始,它不会记得上一轮你们聊了什么。要实现"多轮对话",你得把之前的聊天记录每次都打包发给大模型。 Spring AI 提供了一套 ChatMemory 组件来帮你干这件事。但存储方案怎么选?内存、数据库、Redis、向量库,各自的取舍是什么?这篇就来把对话记忆的实现方案一次讲透。 [AI工程] Spring AI第六篇_ 对话记忆—数据库和Redis不同的实现_素材/图01.jpg 一、ChatMemory 架构:Spring AI 2.0 的记忆体系 先看一个最直观的例子。没有记忆的时候,大模型的表现是这样的: @Testpublic void testChatOptions() {    String content = chatClient.prompt()            .user("我叫张三")            .call()            .content();    System.out.println(content);    content = chatClient.prompt()            .user("我叫什么?")            .call()            .content();    System.out.println(content);}第一轮告诉它名字,第二轮问它,它答不上来。因为两次调用之间没有任何状态共享。 Spring AI 2.0 的解法是 ChatMemory + MessageWindowChatMemory,核心架构可以简单理解成: ChatClient --> MessageChatMemoryAdvisor --> ChatMemory(MessageWindowChatMemory) --> ChatMemoryRepository(存储实现)ChatMemory 是接口,MessageWindowChatMemory 是默认实现,负责对话轮次的窗口淘汰策略。底层真正的存储交给 ChatMemoryRepository,自动配置类会注入默认的 InMemoryChatMemoryRepository: @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();    }}1.1 基本使用 2.0 中推荐使用 MessageChatMemoryAdvisor(替代 1.0 的 PromptChatMemoryAdvisor): @SpringBootTestpublic class ChatMemoryTest {    ChatClient chatClient;    @BeforeEach    public void init(@Autowired DeepSeekChatModel chatModel,                     @Autowired ChatMemory chatMemory) {        chatClient = ChatClient.builder(chatModel)                .defaultAdvisors(                        MessageChatMemoryAdvisor.builder(chatMemory).build()                )                .build();    }    @Test    public void testChatOptions() {        String content = chatClient.prompt()                .user("我叫张三")                .call()                .content();        System.out.println(content);        content = chatClient.prompt()                .user("我叫什么?")                .call()                .content();        System.out.println(content);    }}这次大模型能记住你叫徐庶了。 2.0 变化:PromptChatMemoryAdvisor → MessageChatMemoryAdvisor。后者以消息方式注入记忆,更自然且与工具调用兼容性更好。 1.2 配置最大存储数量 聊天的消息发给大模型是算 token 的,token 有上限。DEFAULT_MAX_MESSAGES 默认 20 条(即 10 轮对话),超出后按先进先出淘汰最早的消息: @BeanChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {    return MessageWindowChatMemory.builder()            .maxMessages(10)            .chatMemoryRepository(chatMemoryRepository)            .build();}1.3 多用户隔离 不同用户的对话不能串记忆,通过 CONVERSATION_ID 隔离: @Testpublic void testChatOptions() {    // 用户1的对话    String content = chatClient.prompt()            .user("我叫张三")            .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, "1"))            .call()            .content();    content = chatClient.prompt()            .user("我叫什么?")            .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, "1"))            .call()            .content();    // 用户2的对话 —— 记忆隔离    content = chatClient.prompt()            .user("我叫什么?")            .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, "2"))            .call()            .content();}不同的 CONVERSATION_ID,各自独立记忆,互不干扰。 1.4 ChatMemoryRepository 存储实现一览 2.0 中 ChatMemoryRepository 支持的存储实现: 实现类存储介质特点适用场景InMemoryChatMemoryRepositoryJVM 内存默认实现,零配置开发测试、单机 DemoJdbcChatMemoryRepositoryJDBC 数据库持久化,支持 MySQL/PostgreSQL 等生产环境、需要可靠存储RedisChatMemoryRepositoryRedis读写快,天然过期机制高并发、短时记忆场景CassandraChatMemoryRepositoryCassandra分布式、高可用大规模分布式场景Neo4jChatMemoryRepositoryNeo4j图数据库,关系推理需要记忆关联推理的场景MongoDbChatMemoryRepositoryMongoDB文档型存储灵活 schema 场景注:2026 年还有一个比较值得关注的动态 —— Spring AI 正在孵化全新的 Session API,采用事件溯源 + 上下文压缩的方式管理短期记忆,目标是 Spring AI 2.1(预计 2026 年 11 月),届时 ChatMemory 将被标记为 deprecated。如果你正在做技术选型,这个时间节点值得关注。 二、数据库存储:让记忆不再昙花一现 默认的 InMemoryChatMemoryRepository 有两个致命问题:一是 JVM 内存有限,聊多了会 OOM;二是重启就全丢了。生产环境必须上持久化存储。 2.1 添加依赖     org.springframework.ai    spring-ai-starter-model-chat-memory-repository-jdbc    org.springframework.boot    spring-boot-starter-jdbc    com.mysql    mysql-connector-j    runtime2.2 配置 spring.ai.chat.memory.repository.jdbc.initialize-schema=alwaysspring.ai.chat.memory.repository.jdbc.schema=classpath:/schema-mysql.sqlspring:  datasource:    username: root    password: 123456    url: jdbc:mysql://localhost:3306/springai?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&    driver-class-name: com.mysql.cj.jdbc.Driver2.3 配置类 @Configurationpublic class ChatMemoryConfig {    @Bean    ChatMemory chatMemory(JdbcChatMemoryRepository chatMemoryRepository) {        return MessageWindowChatMemory.builder()                .maxMessages(10)                .chatMemoryRepository(chatMemoryRepository)                .build();    }}2.4 建表 SQL resources/schema-mysql.sql: CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (    `conversation_id` VARCHAR(36) NOT NULL,    `content` TEXT NOT NULL,    `type` VARCHAR(10) NOT NULL,    `timestamp` TIMESTAMP NOT NULL,    INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX` (`conversation_id`, `timestamp`));2.5 测试验证 @SpringBootTestpublic class ChatMemoryTest {    ChatClient chatClient;    @BeforeEach    public void init(@Autowired DeepSeekChatModel chatModel,                     @Autowired ChatMemory chatMemory) {        chatClient = ChatClient.builder(chatModel)                .defaultAdvisors(                        MessageChatMemoryAdvisor.builder(chatMemory).build()                )                .build();    }    @Test    public void testChatOptions() {        String content = chatClient.prompt()                .user("你好,我叫张三!")                .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, "1"))                .call()                .content();        System.out.println(content);        content = chatClient.prompt()                .user("我叫什么?")                .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, "1"))                .call()                .content();        System.out.println(content);    }}跑完测试去数据库里看一眼 SPRING_AI_CHAT_MEMORY 表,对话记录已经持久化进去了。如果你设置了 maxMessages(1),数据库里每个 conversation_id 只会存最后一条消息 —— 这就是窗口淘汰策略的效果。 Q1:为什么我设置了 maxMessages(10),但数据库里存的消息超过 10 条? maxMessages 控制的是发送给大模型的消息窗口大小,不是数据库存储条数。数据库会完整记录对话历史,但 MessageWindowChatMemory 在加载记忆时会按 maxMessages 截取最近的 N 条。所以你看到的数据库条数 > 窗口大小是正常的。 三、Redis 存储:更快的读写,更简单的部署 数据库方案虽然可靠,但如果你需要更快的读写速度、或者想利用 Redis 天然的 TTL 过期机制来管理记忆生命周期,Redis 是更好的选择。 3.1 方案选择 Spring AI 官方没有直接提供 RedisChatMemoryRepository,你需要自己实现 ChatMemoryRepository 接口(增、删、查)。但 alibaba-ai 有现成的实现,还包括 ES 版本。 3.2 添加依赖     5.2.0    com.alibaba.cloud.ai    spring-ai-alibaba-starter-memory-redis    redis.clients    jedis    {spring.ai.memory.redis.host}")    private String redisHost;    @Value("{spring.ai.memory.redis.password}")    private String redisPassword;    @Value("${spring.ai.memory.redis.timeout}")    private int redisTimeout;    @Bean    public RedisChatMemoryRepository redisChatMemoryRepository() {        return RedisChatMemoryRepository._builder_()                .host(redisHost)                .port(redisPort)                // .password(redisPassword)                .timeout(redisTimeout)                .build();    }}Q2:数据库 vs Redis,到底怎么选? 维度JDBC 数据库Redis读写性能中等(磁盘 IO)高(内存)持久化强持久化,不会丢依赖 RDB/AOF,有丢失风险过期机制需自己实现定时清理天然 TTL 支持部署成本中等额外维护 Redis 实例适用场景对话需要长期保存高并发、可接受记忆丢失我的建议是:如果你的应用对记忆持久性要求高(比如客服系统需要保留对话记录做质检),用数据库;如果只是让 AI 在会话期间"记住"上下文,会话结束后可以丢弃,Redis 更轻量。 四、2.0 新特性:向量记忆与工具调用的博弈 4.1 VectorStoreChatMemoryAdvisor 2.0 新增了 VectorStoreChatMemoryAdvisor,把对话记忆存入向量数据库,通过语义检索召回相关历史对话: ChatClient chatClient = ChatClient.builder(chatModel)        .defaultAdvisors(                VectorStoreChatMemoryAdvisor.builder(vectorStore)                        .build()        )        .build();这个思路跟前面的窗口淘汰完全不同。MessageWindowChatMemory 是"最近 N 条",不管相不相关;VectorStoreChatMemoryAdvisor 是"语义最相关的 N 条",跨越任意时间。 简单理解: MessageWindowChatMemory       --> 近期记忆(按时间窗口截取)VectorStoreChatMemoryAdvisor  --> 语义记忆(按相关性检索)Q3:窗口记忆和向量记忆能不能一起用? 可以,而且推荐一起用。这引出了"多层次记忆架构"的概念,后面会详细讲。 4.2 Memory 与 Tool Calling 的 order 博弈 当 Memory Advisor 和 Tool Calling 一起使用时,执行顺序很关键: AdvisorOrder 值说明ToolCallingAdvisorHIGHEST_PRECEDENCE + 300管理工具调用循环MessageChatMemoryAdvisorHIGHEST_PRECEDENCE + 200管理记忆注入和持久化因为 MessageChatMemoryAdvisor 的 order(200)小于 ToolCallingAdvisor 的 order(300),Memory 会在工具调用循环 外部 执行 —— 即记忆只在循环开始前加载一次、循环结束后持久化一次。这是推荐的行为,避免了每次工具调用都刷新记忆。 如果你确实需要在循环内部每次工具调用后都更新记忆,可以调整 order 让 Memory Advisor 的 order > 300,或者使用 disableInternalConversationHistory() 来精细控制。 五、多层次记忆架构:模仿人类大脑 痛点 无论你用什么存储方案,都只能保证"存"的性能。但一旦聊天记录多了,依然会超过大模型的 token 上限。这是个根本矛盾: 记忆多 = 聪明  -->  token 超限 = 报错记忆少 = 安全  -->  记不住上下文 = 笨解法:三层记忆 模仿人类的记忆机制,把记忆分层: 层级实现方案容量作用近期记忆ChatMemory(MessageWindowChatMemory)~10 条保留最近几轮对话,直接在上下文窗口中中期记忆VectorStoreChatMemoryAdvisor~5 条通过 RAG 检索相关历史对话长期记忆定时批处理 / 关键点实时处理无限制关键信息的固化总结架构关系: 用户对话 --> 近期记忆(ChatMemory, 10条) --> 中期记忆(VectorStore RAG, 5条) --> 长期记忆(总结提炼)              ↑ 上下文窗口内                    ↑ 语义检索召回                      ↑ 关键信息固化长期记忆有两种实现方式: 方式一:定时批处理 —— 通过定时任务对积累的对话进行总结和提炼,适合非实时场景方式二:关键点实时处理 —— 在对话中识别出关键信息点时立即提取并存储,适合需要即时固化的场景说白了,这就是在 token 上限和记忆完整性之间找平衡:近期记忆保证上下文连贯,中期记忆保证语义相关性,长期记忆保证关键信息不丢失。 最后总结 开发测试阶段:直接用默认的 InMemoryChatMemoryRepository,零配置开箱即用生产环境、需持久化:JdbcChatMemoryRepository + MySQL,数据可靠不丢失高并发、短时记忆:RedisChatMemoryRepository,读写快、天然 TTL 过期需要语义召回:VectorStoreChatMemoryAdvisor,跨越时间的上下文关联终极方案:三层记忆架构,近期 + 中期 + 长期,在 token 上限和记忆完整性之间找平衡需要关注的是,Spring AI 2.1(预计 2026 年 11 月)将引入全新的 Session API,届时 ChatMemory 可能会被 deprecated。但核心思路 —— 窗口淘汰、持久化存储、语义检索、分层记忆 —— 不会变,变的只是 API 形态。 参考资料 & 致谢 [1] Spring AI 官方文档 - Chat Memory  [2] Spring AI 官方文档 - Advisors API  [3] MessageWindowChatMemory API 文档  [4] VectorStoreChatMemoryAdvisor API 文档  [5] Spring AI Session API - 官方博客  [6] Spring AI 2.0 中文文档 - 聊天记忆  [7] alibaba-ai memories - GitHub  [8] Spring2.0 - 语雀
不知道自己偏哪种体质?对一对就知道——九种体质,照镜子勾49题,1分钟出结果。
九种体质自测 →
小马
小马 · 杏和舒筋堂中医健康助手 · 真人当面看更准
您好,我是小马。哪里不舒服,跟我说说——是酸、是胀、是麻,还是睡不好,我在。
杏和舒筋堂 · 中医健康助手
本页回答只作健康参考,不替代当面看诊