当前位置: 首页 > news >正文

Spring AI 生产级实战:记忆管理

一、为什么需要记忆管理?

我们平时使用 ChatGPT、通义千问、DeepSeek 等大模型时,经常会感觉模型“记得”前面聊过的内容。

例如:

用户:我叫张三。 AI:好的,张三。 用户:我叫什么? AI:你叫张三。

从体验上看,模型似乎拥有了记忆。

但从技术原理上看,大语言模型本身通常是无状态的。

也就是说,模型并不会天然记住之前发生过什么。每一次调用模型,本质上都是一次新的请求。模型之所以能回答“你叫张三”,并不是因为它真的记住了,而是因为系统在第二次请求时,把前面的对话内容重新带给了模型。

这就是记忆管理要解决的问题:

如何把前面的关键对话内容保存下来, 并在下一次调用模型时重新注入上下文, 让模型表现得像是“记得”之前的交流。

在简单 Demo 中,我们可以手工把历史消息拼接到 Prompt 里。

但在生产系统中,这种方式很快会遇到问题:

会话怎么区分? 历史消息保存在哪里? 每次带多少历史消息? 消息太多超过模型上下文怎么办? 如何持久化? 如何支持多用户? 如何清理过期记忆? 如何避免把敏感信息长期保存?

所以,记忆管理不是一个简单的“把聊天记录拼起来”的问题,而是一个完整的工程问题。

Spring AI 的 Chat Memory 就是为了解决这个问题。


二、Chat Memory 和 Chat History 的区别

在学习 Spring AI 记忆管理之前,必须先区分两个概念:

Chat Memory:对当前模型调用有用的上下文记忆。 Chat History:完整的聊天历史记录。

这两个概念很容易混淆。

1. Chat Memory 是给模型用的

Chat Memory 的目标是帮助模型理解当前对话上下文。

例如:

用户刚刚说过自己的姓名; 用户之前选择了某个产品; 用户前面已经上传了一份报告; 用户上一轮要求用中文回答; 用户正在讨论某个具体业务问题。

这些信息对下一次模型回答有帮助,所以可以作为 Memory 被带入模型。

2. Chat History 是给系统存档用的

Chat History 更像完整会话日志。

它关注的是:

用户说了什么; AI 回答了什么; 调用了哪些工具; 每次请求的时间; 模型原始输出是什么; 是否发生异常; 最终业务结果是什么。

这些内容适合用于审计、追溯、统计、复盘和合规存档。

3. 为什么不能混用?

因为模型上下文窗口是有限的。

如果把完整历史记录全部塞给模型,会带来几个问题:

Token 成本增加; 响应速度下降; 无关内容干扰模型; 敏感信息暴露风险增加; 超过模型上下文长度限制。

所以,生产系统中建议这样设计:

Chat Memory:只保存和当前回答相关的上下文。 Chat History:完整记录进入数据库或日志系统。

简单说:

Memory 是为了让模型回答得更连贯; History 是为了让系统能够追溯和管理。

三、Spring AI 的 ChatMemory 抽象

Spring AI 提供了ChatMemory抽象,用来管理对话中的记忆内容。

从职责上看,ChatMemory主要负责:

按 conversationId 保存消息; 按 conversationId 获取消息; 控制哪些消息应该被保留; 控制哪些消息应该被移除; 为下一次模型调用提供上下文。

可以把它理解为模型调用前面的“上下文管理器”。

典型使用方式是:

ChatMemorychatMemory=MessageWindowChatMemory.builder().maxMessages(10).build();

这里使用的是MessageWindowChatMemory,表示只保留最近一定数量的消息。

这很符合大多数聊天场景:

最近几轮对话最重要; 很久之前的内容影响较小; 上下文不宜无限增长; 需要控制 Token 成本。

四、MessageWindowChatMemory:最常用的窗口记忆

MessageWindowChatMemory是 Spring AI 中默认使用的记忆类型。

它的核心思想很简单:

只保留最近 N 条消息。

例如:

ChatMemorychatMemory=MessageWindowChatMemory.builder().maxMessages(10).build();

这表示每个会话最多保留 10 条消息。

当消息超过限制后,旧消息会被移除。

这种方式非常适合大多数业务场景,例如:

智能客服; 知识库问答; 报告辅助生成; AI 助手; Agent 对话; 医生报告编辑辅助。

为什么窗口记忆适合生产系统?

因为它在效果和成本之间做了平衡:

既能让模型理解最近上下文; 又不会把所有历史都塞进模型; 还能控制 Token 使用量; 实现简单,稳定可靠。

但是它也有局限。

如果用户在很早之前说过一个重要偏好,例如:

“以后都用中文回答我。” “我所在公司使用 Java 技术栈。” “这个项目叫 wheat-qc。”

这种长期偏好可能会被窗口机制逐渐挤出上下文。

所以,在更复杂的系统中,短期记忆通常还需要配合长期记忆、用户画像、数据库配置或向量检索一起使用。


五、ChatMemoryRepository:记忆存储在哪里?

ChatMemory负责决定保留哪些消息。

ChatMemoryRepository负责把消息存到哪里。

这两个职责要分开理解。

ChatMemory:记忆策略。 ChatMemoryRepository:记忆存储。

Spring AI 提供了多种内置存储实现。


六、InMemoryChatMemoryRepository:适合 Demo,不适合生产

默认情况下,如果没有配置其他存储,Spring AI 会使用内存存储。

也就是:

ChatMemoryRepositoryrepository=newInMemoryChatMemoryRepository();

它的特点是:

实现简单; 无需数据库; 适合快速测试; 应用重启后数据丢失; 不适合分布式部署。

所以,In-Memory 适合以下场景:

本地开发; 功能演示; 单元测试; 概念验证。

但不建议用于生产环境。

原因很简单:

服务重启后记忆丢失; 多实例之间无法共享记忆; 无法做审计和追踪; 无法支持长期会话。

七、JDBC ChatMemoryRepository:多数业务系统的首选

对于大多数 Spring Boot 业务系统来说,JDBC 是最容易落地的持久化方式。

Spring AI 提供了JdbcChatMemoryRepository,可以把记忆存储到关系型数据库中。

依赖示例:

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId></dependency>

配置后可以这样使用:

@AutowiredJdbcChatMemoryRepositorychatMemoryRepository;@BeanChatMemorychatMemory(){returnMessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).maxMessages(10).build();}

JDBC 方式适合:

企业内部系统; 后台管理系统; 客服系统; 医疗报告系统; 需要持久化会话上下文的业务系统。

它的优势是:

容易接入; 方便运维; 便于备份; 便于和现有业务库集成; 适合中小规模生产系统。

如果你本来就是 Spring Boot + MySQL / PostgreSQL / Oracle 技术栈,那么 JDBC 记忆存储通常是最稳妥的选择。


八、Cassandra、MongoDB、Neo4j、CosmosDB:根据场景选择

除了 JDBC,Spring AI 还支持多种持久化实现。

1. CassandraChatMemoryRepository

适合高可用、高写入、可设置 TTL 的场景。

例如:

大规模聊天系统; 高并发客服系统; 需要长期保存但又要自动过期的消息; 需要利用时间序列特征进行治理和审计的场景。

Cassandra 的优势是扩展性强,但系统复杂度也更高。


2. MongoChatMemoryRepository

适合文档型存储场景。

例如:

消息结构比较灵活; 元数据较多; 系统本身已经使用 MongoDB; 需要 TTL 自动过期。

对于很多 AI 对话系统来说,MongoDB 的文档模型也比较自然。


3. Neo4jChatMemoryRepository

适合希望把对话、用户、工具调用、上下文关系以图结构组织的场景。

例如:

复杂 Agent 关系分析; 用户意图关系建模; 多轮会话路径分析; 知识图谱结合记忆管理。

但如果只是普通聊天记忆,不一定需要上 Neo4j。


4. CosmosDBChatMemoryRepository

适合 Azure 云环境下的全球分布式应用。

如果系统部署在 Azure,并且对全球分布、弹性扩展有要求,可以考虑这种方式。


九、在 ChatClient 中使用记忆

在 Spring AI 的生产开发中,更推荐通过ChatClient使用记忆。

Spring AI 提供了内置 Advisor 来管理记忆,其中推荐使用:

MessageChatMemoryAdvisor

示例:

ChatMemorychatMemory=MessageWindowChatMemory.builder().maxMessages(10).build();ChatClientchatClient=ChatClient.builder(chatModel).defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()).build();

调用时需要传入会话 ID:

StringconversationId="user-1001";Stringcontent=chatClient.prompt().user("我叫张三,请记住").advisors(a->a.param(ChatMemory.CONVERSATION_ID,conversationId)).call().content();

下一次调用继续使用同一个conversationId

Stringcontent=chatClient.prompt().user("我叫什么?").advisors(a->a.param(ChatMemory.CONVERSATION_ID,conversationId)).call().content();

这样模型就可以结合前面的消息进行回答。

这里最关键的是:

conversationId 决定了当前请求属于哪个会话。

没有 conversationId,就无法区分不同用户、不同会话的上下文。


十、conversationId 应该怎么设计?

在生产环境中,conversationId 的设计非常重要。

不要随便写成固定值:

StringconversationId="007";

这只适合官方示例或本地测试。

真实系统中可以根据业务设计:

用户 ID + 会话 ID; 患者 ID + 检查 ID; 客服工单 ID; 报告编辑任务 ID; Agent 任务执行 ID; 浏览器 session ID; 租户 ID + 用户 ID + 会话 ID。

例如医学影像报告质控系统中,可以设计为:

conversationId = tenantId + ":" + userId + ":" + reportId

或者:

conversationId = studyInstanceUid + ":" + qcTaskId

这样可以保证每个报告质控任务拥有独立上下文。

如果是普通聊天系统,可以设计为:

conversationId = userId + ":" + chatSessionId

设计原则是:

同一个业务上下文使用同一个 conversationId; 不同用户、不同任务、不同租户必须隔离。

十一、MessageChatMemoryAdvisor 与 PromptChatMemoryAdvisor 的区别

Spring AI 早期提供过PromptChatMemoryAdvisor

但现在更推荐使用MessageChatMemoryAdvisor

可以这样理解:

PromptChatMemoryAdvisor: 把历史对话拼进 System Prompt。 MessageChatMemoryAdvisor: 把历史对话作为结构化 Message 对象传给模型。

后者更符合现代 Chat Model 的消息结构。

因为现在很多模型本身就区分:

System Message User Message Assistant Message Tool Message

使用 Message 方式可以更清楚地保留角色信息,而不是把所有内容混成一段文本。

所以生产项目中建议优先使用:

MessageChatMemoryAdvisor.builder(chatMemory).build()

不建议继续依赖已经不推荐的旧方式。


十二、VectorStoreChatMemoryAdvisor:长期记忆的另一种思路

除了窗口记忆,Spring AI 还提供了VectorStoreChatMemoryAdvisor

它的思路和普通窗口记忆不一样。

窗口记忆关注最近几条消息。

向量记忆关注语义相关的历史内容。

例如用户很久以前说过:

“我正在开发一个医学影像报告质控系统。”

过了很多轮对话后,用户又问:

“这个功能应该怎么设计?”

单纯窗口记忆可能已经忘掉前面的项目背景。

而向量记忆可以通过语义检索,把历史上相关的内容召回,再放入当前上下文。

它适合:

长期助手; 复杂项目协作; 用户偏好记忆; 跨会话知识延续; 长期 Agent 任务。

但也要注意安全问题。

因为向量记忆通常会把用户历史输入重新注入 Prompt,如果历史内容中存在恶意提示词,就可能带来 Prompt Injection 风险。

所以,对于带工具调用能力的 Agent 系统,要谨慎使用长期记忆,并对召回内容进行隔离、转义和权限控制。


十三、记忆管理与 RAG 的区别

Chat Memory 和 RAG 都会给模型补充上下文,但它们不是一回事。

能力主要内容典型来源作用
Chat Memory当前会话上下文用户和 AI 的对话保持对话连续性
RAG外部知识内容文档、知识库、数据库提供事实依据
Chat History完整会话记录日志、数据库审计和追溯

例如在医学影像报告质控中:

Chat Memory: 用户刚才说“重点检查性别逻辑问题”。 RAG: 从知识库检索“男性患者不能出现子宫、卵巢”等质控规则。 Chat History: 完整记录用户上传了什么报告、AI 返回了什么结果、医生如何处理。

三者可以同时存在,但职责不同。

不要把所有内容都塞进 Memory。

更好的架构是:

Memory 管对话连续性; RAG 管业务知识; History 管审计留痕。

十四、生产级记忆管理架构

一个更接近生产环境的 AI 对话系统,可以这样设计:

前端会话 ↓ 后端生成 conversationId ↓ ChatClient 接收用户输入 ↓ MessageChatMemoryAdvisor 加载短期记忆 ↓ RAG 检索业务知识 ↓ Tool Calling 获取外部数据 ↓ ChatModel 生成回答 ↓ 结构化输出解析 ↓ 保存 Chat Memory ↓ 保存完整 Chat History ↓ 审计、统计、反馈闭环

其中,记忆管理只是整个 AI 工程链路的一部分。

真正生产级的系统,还需要:

身份认证; 租户隔离; 权限控制; 数据脱敏; 日志审计; 异常兜底; 敏感信息清理; Token 成本控制; 模型输出校验。

十五、在医学影像质控系统中的应用示例

假设我们正在开发一个医学影像报告质控助手。

医生在一个报告页面中连续与 AI 交互:

第一轮:请检查这份胸部 CT 报告是否有逻辑问题。 第二轮:重点关注肺结节和肋骨骨折。 第三轮:把质控意见整理成给医生看的建议。 第四轮:不要下诊断,只提示复核风险点。

如果没有记忆管理,第四轮时模型可能不知道前面正在讨论哪份报告,也不知道用户已经要求“重点关注肺结节和肋骨骨折”。

有了 Chat Memory 后,可以把当前报告质控任务的上下文连续传递给模型。

示例设计:

conversationId = reportId + ":" + qcTaskId

调用示例:

StringconversationId=reportId+":"+qcTaskId;Stringresult=chatClient.prompt().user(""" 请继续基于前面的报告质控上下文, 将结果整理成医生可阅读的复核建议。 """).advisors(a->a.param(ChatMemory.CONVERSATION_ID,conversationId)).call().content();

这样模型就可以围绕同一个报告任务持续交流。

但要注意,报告原文、患者信息、DICOM 图像元数据等敏感数据,不一定都适合长期进入 Memory。

更稳妥的做法是:

Memory 中保存必要上下文; 完整数据保存在业务数据库; 敏感字段进入模型前先脱敏; 最终结果进入审计表。

十六、记忆管理的常见问题

1. 记忆越多越好吗?

不是。

记忆越多,Token 成本越高,模型干扰越大。

生产环境中应该控制记忆规模。

例如:

普通客服:保留最近 10~20 条消息; 任务型 Agent:保留关键步骤和当前状态; 严肃业务:只保留必要上下文,不保留敏感冗余内容。

2. Memory 可以代替数据库吗?

不能。

Memory 是为了增强模型上下文,不是业务数据库。

例如:

订单状态; 患者检查信息; 报告审核状态; 用户权限; 业务配置。

这些内容应该存放在业务数据库中,而不是依赖模型记忆。


3. Memory 可以代替用户画像吗?

不建议。

用户画像通常是长期、结构化、可管理的数据。

例如:

用户偏好语言; 所属机构; 常用功能; 权限角色; 历史项目。

这些更适合放在数据库、配置中心或专门的画像服务中。

Memory 更适合处理当前对话上下文。


4. 分布式部署时还能用内存记忆吗?

不建议。

如果系统有多个实例,用户第一次请求落到 A 实例,第二次请求落到 B 实例,内存中的记忆就无法共享。

所以生产环境建议使用持久化 Repository,例如 JDBC、MongoDB、Cassandra 等。


5. Tool Calling 的中间消息会自动保存吗?

需要注意,工具调用过程中的某些中间消息不一定会被自动存入 Memory。

所以如果业务需要完整审计工具调用链路,应该单独记录:

工具名称; 工具参数; 工具返回; 调用时间; 调用是否成功; 异常信息; 最终模型输出。

不要完全依赖 Chat Memory 做审计。


十七、生产级最佳实践

1. 明确 Memory、History、RAG 的边界

不要把所有上下文都塞进 Memory。

建议分工:

Memory:短期对话上下文。 History:完整会话记录。 RAG:业务知识检索。 DB:权威业务数据。

2. 为每个业务任务设计 conversationId

conversationId 不只是技术参数,而是业务隔离边界。

建议包含:

租户; 用户; 业务对象; 会话任务。

例如:

tenantId:userId:sessionId tenantId:patientId:studyId tenantId:reportId:qcTaskId

3. 生产环境使用持久化存储

开发测试可以用 In-Memory。

生产环境建议使用:

JDBC; MongoDB; Cassandra; Neo4j; CosmosDB。

具体选哪个,取决于系统已有技术栈和业务规模。


4. 控制记忆窗口大小

不要无限制保存上下文给模型。

建议根据业务场景设置:

MessageWindowChatMemory.builder().maxMessages(10).build();

如果需要长期记忆,可以引入向量检索或结构化用户画像,而不是盲目扩大窗口。


5. 敏感信息要脱敏

在医疗、金融、教育等场景中,Memory 可能包含敏感数据。

建议对以下内容做处理:

姓名; 身份证号; 手机号; 患者 ID; 检查号; 机构信息; 地址; 病历敏感字段。

尤其是需要调用外部模型服务时,更要重视数据安全。


6. 记忆不等于事实

AI 记住了某个内容,不代表这个内容一定正确。

例如用户说:

“我是管理员。”

模型记住了这句话,并不代表系统应该相信用户真的有管理员权限。

权限、身份、状态等权威信息必须从业务系统查询。

记忆只能作为上下文,不应作为事实来源。


7. 保留审计链路

生产系统应该单独保存完整请求链路:

用户输入; conversationId; 记忆内容摘要; RAG 检索结果; 工具调用结果; 模型原始输出; 最终业务结果; 人工审核意见。

这样后续才能追溯问题、评估模型效果、优化 Prompt 和 Memory 策略。


十八、总结

Spring AI 的 Chat Memory 解决的是大模型无状态调用下的上下文连续性问题。

它通过ChatMemory管理会话记忆,通过ChatMemoryRepository负责底层存储,通过MessageChatMemoryAdvisorChatClient集成,让模型能够在多轮对话中保持上下文。

但在生产系统中,记忆管理不能简单理解为“保存聊天记录”。

更准确地说:

Chat Memory 是模型上下文管理机制; Chat History 是系统审计记录机制; RAG 是业务知识增强机制; 数据库才是权威业务数据来源。

只有把这几个边界分清楚,AI 应用才不会在上下文、事实、记忆和审计之间混乱。

对于生产级 Spring AI 应用来说,记忆管理的核心价值是:

让模型在有限上下文内保持连续交流, 让系统在可控成本下提供更自然的交互体验, 让业务在安全、可追溯、可维护的前提下使用 AI 能力。
http://www.jsqmd.com/news/946037/

相关文章:

  • OV摄像头SCCB协议实战:从I2C老司机到图像传感器配置的避坑指南
  • STM32虚拟串口踩坑实录:从CubeMX配置到PC端识别,一步步解决‘未知设备’问题
  • ESP8266 AP模式避坑指南:除了创建热点,这些softAPConfig和连接管理的细节你注意了吗?
  • Claude 4.8 深度实测:编程能力暴涨,真正拉开差距的却是这一点
  • 别再让EMC测试卡脖子!从PCB布局到外壳接地,一份给硬件工程师的电磁兼容自查清单
  • 苹果辅助功能开启引导式访问
  • 信号处理中的“幽灵”:常数1的傅里叶变换,那个2π到底是怎么冒出来的?
  • 提示词降英文AI率实战:从95%到10%的优化秘籍
  • LLM微调技术在Oracle到PostgreSQL数据库迁移中的应用
  • EduCoder平台金币机制与自动化策略:如何用多个账号‘可持续’获取实训参考答案
  • AMD Ryzen性能调校完全指南:SMU Debug Tool专业工具深度解析
  • 如何用Vosk API离线语音识别打破云端依赖的行业困境?
  • 告别通信故障:手把手调试施耐德LXM32伺服与西门子PLC的Profibus-DP网络
  • Abaqus工程师常用四工具包:cohesive单元自动插入、裂缝路径提取、混凝土骨料建模与CDP参数快速配置
  • 别再写重复的SQL了!MyBatis-Plus UpdateWrapper和LambdaUpdateWrapper实战对比(附避坑点)
  • R语言鸢尾花分析实战包:从数据探索到模型评估全流程代码+报告
  • 如何在5分钟内实现专业级直播背景替换:OBS背景移除插件终极指南
  • 避坑指南:用FDTD Solutions 8.0做薄膜仿真时,我踩过的那些‘坑’(反射率结果不对?网格设置误区?)
  • CFD驱动训练框架:湍流建模的高效优化方法
  • 别再只调参数了!Simulink模块的‘隐藏属性’这样用,效率翻倍
  • Python图像轮廓提取实战包:Jupyter笔记+测试图+可调脚本
  • 虚拟仿真实验教学平台选哪家靠谱?六维拆解帮你避坑
  • 从‘客户服务系统’看软件设计:如何用包图避免循环依赖这个坑?
  • Windows下SVN提交日志的‘门神’:手把手教你写Pre-commit Hook脚本(附防摸鱼检测)
  • 2026年新消息:南京民间纠纷律师咨询哪位好?关键维度解析 - 2026年企业资讯
  • 腾讯这两个AI模型开始收费了,企业用户该怎么应对?
  • 给无人机爱好者的地物识别指南:如何通过多光谱镜头一眼分辨庄稼、旱地和水塘?
  • 一键生成DApp:利用AI大模型基于ABI自动构建交互界面的尝试
  • 别再只画波形图了!用Python和MATLAB提取信号特征的保姆级对比教程
  • 告别手动转换:在CAPL中高效处理CAN FD和以太网SOME/IP的Hex数据块