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

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

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

例如:

text 复制代码
用户:我叫张三。
AI:好的,张三。

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

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

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

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

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

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

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

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

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

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

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


二、Chat Memory 和 Chat History 的区别

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

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

这两个概念很容易混淆。

1. Chat Memory 是给模型用的

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

例如:

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

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

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

Chat History 更像完整会话日志。

它关注的是:

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

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

3. 为什么不能混用?

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

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

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

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

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

简单说:

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

三、Spring AI 的 ChatMemory 抽象

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

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

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

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

典型使用方式是:

java 复制代码
ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .maxMessages(10)
        .build();

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

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

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

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

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

它的核心思想很简单:

text 复制代码
只保留最近 N 条消息。

例如:

java 复制代码
ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .maxMessages(10)
        .build();

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

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

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

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

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

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

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

但是它也有局限。

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

text 复制代码
"以后都用中文回答我。"
"我所在公司使用 Java 技术栈。"
"这个项目叫 wheat-qc。"

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

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


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

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

ChatMemoryRepository 负责把消息存到哪里。

这两个职责要分开理解。

text 复制代码
ChatMemory:记忆策略。
ChatMemoryRepository:记忆存储。

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


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

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

也就是:

java 复制代码
ChatMemoryRepository repository = new InMemoryChatMemoryRepository();

它的特点是:

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

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

text 复制代码
本地开发;
功能演示;
单元测试;
概念验证。

但不建议用于生产环境。

原因很简单:

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

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

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

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

依赖示例:

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>

配置后可以这样使用:

java 复制代码
@Autowired
JdbcChatMemoryRepository chatMemoryRepository;

@Bean
ChatMemory chatMemory() {
    return MessageWindowChatMemory.builder()
            .chatMemoryRepository(chatMemoryRepository)
            .maxMessages(10)
            .build();
}

JDBC 方式适合:

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

它的优势是:

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

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


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

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

1. CassandraChatMemoryRepository

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

例如:

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

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


2. MongoChatMemoryRepository

适合文档型存储场景。

例如:

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

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


3. Neo4jChatMemoryRepository

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

例如:

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

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


4. CosmosDBChatMemoryRepository

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

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


九、在 ChatClient 中使用记忆

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

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

text 复制代码
MessageChatMemoryAdvisor

示例:

java 复制代码
ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .maxMessages(10)
        .build();

ChatClient chatClient = ChatClient.builder(chatModel)
        .defaultAdvisors(
                MessageChatMemoryAdvisor.builder(chatMemory).build()
        )
        .build();

调用时需要传入会话 ID:

java 复制代码
String conversationId = "user-1001";

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

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

java 复制代码
String content = chatClient.prompt()
        .user("我叫什么?")
        .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
        .call()
        .content();

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

这里最关键的是:

text 复制代码
conversationId 决定了当前请求属于哪个会话。

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


十、conversationId 应该怎么设计?

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

不要随便写成固定值:

java 复制代码
String conversationId = "007";

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

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

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

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

text 复制代码
conversationId = tenantId + ":" + userId + ":" + reportId

或者:

text 复制代码
conversationId = studyInstanceUid + ":" + qcTaskId

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

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

text 复制代码
conversationId = userId + ":" + chatSessionId

设计原则是:

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

十一、MessageChatMemoryAdvisor 与 PromptChatMemoryAdvisor 的区别

Spring AI 早期提供过 PromptChatMemoryAdvisor

但现在更推荐使用 MessageChatMemoryAdvisor

可以这样理解:

text 复制代码
PromptChatMemoryAdvisor:
把历史对话拼进 System Prompt。

MessageChatMemoryAdvisor:
把历史对话作为结构化 Message 对象传给模型。

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

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

text 复制代码
System Message
User Message
Assistant Message
Tool Message

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

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

java 复制代码
MessageChatMemoryAdvisor.builder(chatMemory).build()

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


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

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

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

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

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

例如用户很久以前说过:

text 复制代码
"我正在开发一个医学影像报告质控系统。"

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

text 复制代码
"这个功能应该怎么设计?"

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

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

它适合:

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

但也要注意安全问题。

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

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


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

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

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

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

text 复制代码
Chat Memory:
用户刚才说"重点检查性别逻辑问题"。

RAG:
从知识库检索"男性患者不能出现子宫、卵巢"等质控规则。

Chat History:
完整记录用户上传了什么报告、AI 返回了什么结果、医生如何处理。

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

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

更好的架构是:

text 复制代码
Memory 管对话连续性;
RAG 管业务知识;
History 管审计留痕。

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

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

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

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

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

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

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

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

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

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

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

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

示例设计:

text 复制代码
conversationId = reportId + ":" + qcTaskId

调用示例:

java 复制代码
String conversationId = reportId + ":" + qcTaskId;

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

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

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

更稳妥的做法是:

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

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

1. 记忆越多越好吗?

不是。

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

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

例如:

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

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

不能。

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

例如:

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

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


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

不建议。

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

例如:

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

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

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


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

不建议。

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

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


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

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

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

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

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


十七、生产级最佳实践

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

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

建议分工:

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

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

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

建议包含:

text 复制代码
租户;
用户;
业务对象;
会话任务。

例如:

text 复制代码
tenantId:userId:sessionId
tenantId:patientId:studyId
tenantId:reportId:qcTaskId

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

开发测试可以用 In-Memory。

生产环境建议使用:

text 复制代码
JDBC;
MongoDB;
Cassandra;
Neo4j;
CosmosDB。

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


4. 控制记忆窗口大小

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

建议根据业务场景设置:

java 复制代码
MessageWindowChatMemory.builder()
        .maxMessages(10)
        .build();

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


5. 敏感信息要脱敏

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

建议对以下内容做处理:

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

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


6. 记忆不等于事实

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

例如用户说:

text 复制代码
"我是管理员。"

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

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

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


7. 保留审计链路

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

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

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


十八、总结

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

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

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

更准确地说:

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

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

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

text 复制代码
让模型在有限上下文内保持连续交流,
让系统在可控成本下提供更自然的交互体验,
让业务在安全、可追溯、可维护的前提下使用 AI 能力。
相关推荐
泡海椒11 分钟前
告别 iText 繁杂配置:jquick-pdf 极简 PDF 生成实战(零基础上手)
java·开发语言·pdf
光锥智能12 分钟前
手机存量时代,vivo能用Harness让用户换机吗?
人工智能
JaguarJack13 分钟前
PHP 8.6 只是个小版本,三十项弃用却已在为 PHP 9.0 铺路
后端·php·服务端
BingoGo14 分钟前
PHP 8.6 只是个小版本,三十项弃用却已在为 PHP 9.0 铺路
后端·php
步行cgn29 分钟前
Spring 注入 List 集合详解
windows·spring·list
比奥利奥还傲.2 小时前
哪吒监控面板实战:部署 Dashboard、接入 Agent、钉钉告警,再配置固定公网访问
网络·人工智能·分布式·1024程序员节
FII工业富联科技服务2 小时前
从人机共舞到工业精密操作:机器人如何突破动作控制与场景适应?
人工智能·机器人·机器翻译模型
serdes213 小时前
56G PAM4 SerDes RX 32 路时间交织 SAR ADC 顶层与采样前端
人工智能
Bs_MoneyMagnet3 小时前
基于springboot+vue的滑雪场票务与装备租赁系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·毕业设计·计算机毕业设计
AlienZHOU8 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试