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

我们平时使用 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 获取消息;
控制哪些消息应该被保留;
控制哪些消息应该被移除;
为下一次模型调用提供上下文。

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

典型使用方式是:

ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .maxMessages(10)
        .build();

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

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

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

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

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

它的核心思想很简单:

只保留最近 N 条消息。

例如:

ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .maxMessages(10)
        .build();

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

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

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

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

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

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

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

但是它也有局限。

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

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

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

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


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

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

ChatMemoryRepository 负责把消息存到哪里。

这两个职责要分开理解。

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

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


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

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

也就是:

ChatMemoryRepository repository = new InMemoryChatMemoryRepository();

它的特点是:

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

所以,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>

配置后可以这样使用:

@Autowired
JdbcChatMemoryRepository chatMemoryRepository;

@Bean
ChatMemory chatMemory() {
    return MessageWindowChatMemory.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

示例:

ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .maxMessages(10)
        .build();

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

调用时需要传入会话 ID:

String conversationId = "user-1001";

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

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

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

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

这里最关键的是:

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

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


十、conversationId 应该怎么设计?

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

不要随便写成固定值:

String conversationId = "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

调用示例:

String conversationId = reportId + ":" + qcTaskId;

String result = 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 能力。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐