📌 核心概念:对话记忆指的是 AI 在单次对话或跨对话中,记住并利用之前交互信息的能力。这就像你和一个人聊天,如果他只能记住你刚才说的最后一句话,那对话将无法进行。因此,记忆是实现连贯、智能对话的基石。
一、记忆类型详解
短期记忆(上下文记忆)
也称为"上下文记忆"或"对话记忆",指在单次对话会话中,AI能记住的之前的内容。这通常受限于一个固定的"上下文窗口"(可以理解为一篇文章的最大字数)。
工作原理:
用户发送消息 → 系统拼接[历史对话 + 当前问题] → 发送给AI → AI生成回复局限性:
长期记忆(持久性记忆)
也称为"外部记忆",指AI能够将重要信息存储在对话之外(如数据库、向量数据库),并在未来的对话中根据需要检索和使用记忆。
典型场景:
用户说:"我对花生过敏" → 信息存入向量数据库
一周后用户问:"推荐健康零食" → 检索记忆 → 排除含花生食品特点对比:
⚠️ 注意:长期记忆通常以摘要或向量形式存储,会丢失对话细节,不适合依赖精确上下文的复杂推理。
二、Spring AI 实现对话记忆
方式一:手动管理 Message List(精细控制)
通过维护 List<Message>,将每轮对话封装成 Message 对象,动态构建 Prompt。
基础多轮对话示例
@Test
voiddemo1_BasicMultiTurnConversation()throws InterruptedException {
System.out.println("\n========== 示例1:基础多轮对话(Message List) ==========");
ChatClientchatClient= chatClientBuilder.build();
// 创建消息列表,用于存储对话历史
List<Message> messageHistory = newArrayList<>();
// ===== 第1轮对话 =====
System.out.println("\n--- 第1轮:询问推荐 ---");
StringuserQuestion1="你好,我想学习Java编程,有什么建议吗?";
System.out.println("👤 用户: " + userQuestion1);
// 添加用户消息到历史
messageHistory.add(newUserMessage(userQuestion1));
ChatOptionschatOptions= ChatOptions.builder()
.maxTokens(200)
.build();
// 构建包含历史的 Prompt
Promptprompt1=newPrompt(messageHistory, chatOptions);
// 获取AI回复
AssistantMessageassistantResponse1= chatClient.prompt(prompt1)
.call()
.chatResponse()
.getResult()
.getOutput();
StringaiAnswer1= assistantResponse1.getText();
System.out.println("🤖 AI: " + aiAnswer1);
// 添加AI回复到历史
messageHistory.add(assistantResponse1);
Thread.sleep(500);
// ===== 第2轮对话 =====
System.out.println("\n\n--- 第2轮:追问细节(AI能看到第1轮内容)---");
StringuserQuestion2="那我应该先学哪些基础知识?";
System.out.println("👤 用户: " + userQuestion2);
// 添加新的用户消息
messageHistory.add(newUserMessage(userQuestion2));
// 再次构建 Prompt(包含完整历史)
Promptprompt2=newPrompt(messageHistory, chatOptions);
AssistantMessageassistantResponse2= chatClient.prompt(prompt2)
.call()
.chatResponse()
.getResult()
.getOutput();
StringaiAnswer2= assistantResponse2.getText();
System.out.println("🤖 AI: " + aiAnswer2);
// 添加AI回复
messageHistory.add(assistantResponse2);
Thread.sleep(500);
// ===== 第3轮对话 =====
System.out.println("\n\n--- 第3轮:继续深入(AI能看到前2轮内容)---");
StringuserQuestion3="有没有推荐的入门书籍?";
System.out.println("👤 用户: " + userQuestion3);
// 添加新的用户消息
messageHistory.add(newUserMessage(userQuestion3));
// 构建包含全部历史的 Prompt
Promptprompt3=newPrompt(messageHistory, chatOptions);
AssistantMessageassistantResponse3= chatClient.prompt(prompt3)
.call()
.chatResponse()
.getResult()
.getOutput();
StringaiAnswer3= assistantResponse3.getText();
System.out.println("🤖 AI: " + aiAnswer3);
// 添加AI回复
messageHistory.add(assistantResponse3);
System.out.println("\n\n💡 说明:");
System.out.println("• 当前对话历史共有 " + messageHistory.size() + " 条消息");
System.out.println("• 每一轮都包含之前的所有对话内容");
System.out.println("• AI 可以根据上下文理解'那'、'有没有'等指代词");
System.out.println("• 适合需要记住上下文的场景(如客服、助手)");
// 打印完整的消息历史
System.out.println("\n📋 完整的消息历史:");
for (inti=0; i < messageHistory.size(); i++) {
Messagemsg= messageHistory.get(i);
Stringrole= msg.getMessageType() == MessageType.USER ? "👤 用户" : "🤖 AI";
System.out.println((i + 1) + ". " + role + ": " +
msg.getText().substring(0, Math.min(50, msg.getText().length())) + "...");
}
}执行结果:
--- 第1轮:询问推荐 ---
👤 用户: 你好,我想学习Java编程,有什么建议吗?
🤖 AI: 你好!很高兴你选择学习 Java 🌟——它是一门成熟、稳定、应用广泛(企业级开发、Android、大数据、后端服务等)且非常适合初学者的编程语言。以下是一些系统、实用的学习建议,帮你少走弯路、高效入门:
一、打好基础(前2–4周)
1. **环境准备**
- 安装 JDK(推荐 [Adoptium Temurin](https://adoptium.net/) 或 Oracle JDK 17/21 LTS 版本)
- 用轻量 IDE 入门:**IntelliJ IDEA Community Edition**(功能强大+智能提示,对新手极友好)或 VS Code + Extension(如 Extension Pack for Java)
- 避免一上来就用 Eclipse(较陈旧)或纯记事本+命令行(易挫败)
...
--- 第2轮:追问细节(AI能看到第1轮内容)---
👤 用户: 那我应该先学哪些基础知识?
🤖 AI: 非常好的问题! 学 Java 不是"学得越多越快",而是**按认知逻辑层层递进**。以下是为你精心梳理的「新手黄金学习路径」——聚焦核心、避开冗余、每一步都为下一步打基础,建议按顺序学习(每天1–2小时,坚持3–4周可扎实入门):
---
### 🌱 第一阶段:编程思维 + Java 基石(约5–7天)
| 主题 | 关键内容 | 学习提示 |
|--------|-----------|-------------|
| **1. Hello World & 开发环境** | 编写第一个程序、理解 `public static void main(String[] args)`、编译(`javac`)与运行(`java`)流程 | ✍️ 动手敲3遍,观察报错并自己解决(比如大小写、分号、类名匹配)→ 这是调试能力
...
--- 第3轮:继续深入(AI能看到前2轮内容)---
👤 用户: 有没有推荐的入门书籍?
🤖 AI: 当然有!📚 选对书,能让你少踩80%的坑、建立清晰的知识框架。以下是**真正适合零基础、中文友好、经多年教学验证**的Java入门书籍推荐(按优先级排序),附上「为什么适合你」和「怎么用最有效」:
---
### 🏆 首推:《Head First Java》(中文版:《深入浅出Java》)
**最适合零基础新手的神书!**
- **为什么强?**
- 用大量图解、对话、填空、脑图、生活类比讲解抽象概念(比如"类=蓝图,对象=房子"),**拒绝枯燥定义**;
- 每章结尾有"练习题+答案解析",边学边练,即时反馈;
- 覆盖核心:语法 → 面向对象 → ArrayList/HashMap → 异
...
💡 说明:
• 当前对话历史共有 6 条消息
• 每一轮都包含之前的所有对话内容
• AI 可以根据上下文理解'那'、'有没有'等指代词
• 适合需要记住上下文的场景(如客服、助手)
📋 完整的消息历史:
1. 👤 用户: 你好,我想学习Java编程,有什么建议吗?...
2. 🤖 AI: 你好!很高兴你选择学习 Java 🌟——它是一门成熟、稳定、应用广泛(企业级开发、Android、...
3. 👤 用户: 那我应该先学哪些基础知识?...
4. 🤖 AI: 非常好的问题! 学 Java 不是"学得越多越快",而是**按认知逻辑层层递进**。以下是为你精心...
5. 👤 用户: 有没有推荐的入门书籍?...
6. 🤖 AI: 当然有!📚 选对书,能让你少踩80%的坑、建立清晰的知识框架。以下是**真正适合零基础、中文友好、...
Process finished with exit code 0流式输出 + 对话记忆
@Test
voidstreamWithMemory()throws InterruptedException {
ChatClientchatClient= chatClientBuilder.build();
List<Message> messageHistory = newArrayList<>();
// 限制token字数
ChatOptionschatOptions= ChatOptions.builder()
.maxTokens(200)
.build();
// ===== 第1轮 =====
System.out.println("\n--- 第1轮对话 ---");
Stringquestion1="帮我规划一个3天的北京旅游行程";
System.out.println("👤 用户: " + question1);
messageHistory.add(newUserMessage(question1));
Promptprompt1=newPrompt(messageHistory, chatOptions);
System.out.print("🤖 AI: ");
StringBuilderfullResponse1=newStringBuilder();
Flux<String> stream1 = chatClient.prompt(prompt1)
.stream()
.content();
CountDownLatchlatch1=newCountDownLatch(1);
stream1.doOnNext(chunk -> {
System.out.print(chunk);
fullResponse1.append(chunk);
}).doOnComplete(() -> latch1.countDown()).subscribe();
latch1.await(30, TimeUnit.SECONDS);
// 将完整回复添加到历史
messageHistory.add(newAssistantMessage(fullResponse1.toString()));
Thread.sleep(500);
// ===== 第2轮:基于第1轮的追问 =====
System.out.println("\n\n--- 第2轮:基于行程的追问 ---");
Stringquestion2="第二天去长城的话,怎么去比较方便?";
System.out.println("👤 用户: " + question2);
messageHistory.add(newUserMessage(question2));
Promptprompt2=newPrompt(messageHistory, chatOptions);
System.out.print("🤖 AI: ");
StringBuilderfullResponse2=newStringBuilder();
Flux<String> stream2 = chatClient.prompt(prompt2)
.stream()
.content();
CountDownLatchlatch2=newCountDownLatch(1);
stream2.doOnNext(chunk -> {
System.out.print(chunk);
fullResponse2.append(chunk);
}).doOnComplete(() -> latch2.countDown()).subscribe();
latch2.await(30, TimeUnit.SECONDS);
messageHistory.add(newAssistantMessage(fullResponse2.toString()));
System.out.println("\n\n💡 说明:");
System.out.println("• 流式输出提供更好的用户体验");
System.out.println("• 需要收集完整回复后添加到历史");
System.out.println("• AI 知道'第二天'指的是行程中的第二天");
System.out.println("• 实现了上下文连贯的对话");
}执行结果:
--- 第1轮对话 ---
👤 用户: 帮我规划一个3天的北京旅游行程
🤖 AI: 当然可以!以下是一个兼顾经典景点、文化深度、美食体验与合理节奏的**3天北京精华游行程规划**(适合首次到访的游客,交通便利、时间紧凑但不赶路,含实用贴士)👇
---
### 🌟 行程总原则
**交通建议**:地铁为主(下载「亿通行」APP扫码乘车)+ 步行/共享单车;避开早晚高峰(7:30–9:30, 17:00–19:00)
**门票预约**:所有故宫、国博、颐和园等热门场馆**必须提前1–7天在官方公众号/小程序预约**(如"故宫博物院""畅游公园""国家博物馆"),刷身份证入园
**穿着提示**:舒适运动鞋(日均2万步)、防晒帽+轻便雨具(北京春季多风沙,夏季
...
--- 第2轮:基于行程的追问 ---
👤 用户: 第二天去长城的话,怎么去比较方便?
🤖 AI: 第二天去长城(推荐**八达岭长城**——最经典、设施完善、交通成熟),以下是**最方便、性价比高且适合自由行游客的4种方式对比与实操建议**,帮你轻松搞定:
---
### 【首选推荐】🚄 地铁+市郊铁路「S2线」("开往春天的列车",经济又浪漫)
**适合人群**:时间充裕、喜欢体验感、预算有限、想拍沿途风景
**路线**:
1. 地铁8号线/14号线 → **西直门站**(B2口出,直通北京北站)
2. 北京北站乘 **S2线市郊铁路**(刷亿通行APP或身份证进站)
- 🕒 发车频次:工作日约1小时1班,周末/节假日加密至30–45分钟1班(首班6
...
💡 说明:
• 流式输出提供更好的用户体验
• 需要收集完整回复后添加到历史
• AI 知道'第二天'指的是行程中的第二天
• 实现了上下文连贯的对话
Disconnected from the target VM, address: '127.0.0.1:49670', transport: 'socket'
Process finished with exit code 0控制对话历史长度(防止Token溢出)
@Test
voidcontrolMemoryLength()throws InterruptedException {
ChatClientchatClient= chatClientBuilder.build();
// 限制 token
ChatOptionschatOptions= ChatOptions.builder()
.maxTokens(200).build();
List<Message> messageHistory = newArrayList<>();
// 添加系统消息
messageHistory.add(newSystemMessage("你是一个有用的助手。"));
// 模拟多轮对话
intmaxRounds=5;
intmaxHistorySize=7; // 最多保留7条消息(系统消息 + 3轮对话)
for (intround=1; round <= maxRounds; round++) {
System.out.println("\n--- 第" + round + "轮对话 ---");
StringuserQuestion="这是第" + round + "个问题,请记住这个数字";
System.out.println("👤 用户: " + userQuestion);
messageHistory.add(newUserMessage(userQuestion));
// 如果历史太长,删除最早的消息(保留系统消息)
if (messageHistory.size() > maxHistorySize) {
System.out.println("⚠️ 历史消息过多(" + messageHistory.size() + "条),删除最早的2条...");
// 删除最早的用户消息和AI回复(保留系统消息)
messageHistory.remove(1);
messageHistory.remove(1);
}
Promptprompt=newPrompt(messageHistory, chatOptions);
AssistantMessageresponse= chatClient.prompt(prompt)
.call()
.chatResponse()
.getResult()
.getOutput();
System.out.println("🤖 AI: " + response.getText().substring(0, Math.min(80, response.getText().length())) + "...");
messageHistory.add(response);
System.out.println("📊 当前历史消息数: " + messageHistory.size());
Thread.sleep(300);
}
System.out.println("\n\n💡 说明:");
System.out.println("• 限制历史消息数量可以节省 token");
System.out.println("• 删除最早的消息,保留最近的对话");
// ... 省略部分输出
}执行结果:
--- 第1轮对话 ---
👤 用户: 这是第1个问题,请记住这个数字
🤖 AI: 好的,我已记住"第1个问题"。...
📊 当前历史消息数: 3
--- 第2轮对话 ---
👤 用户: 这是第2个问题,请记住这个数字
🤖 AI: 好的,我已记住"第2个问题"。...
📊 当前历史消息数: 5
--- 第3轮对话 ---
👤 用户: 这是第3个问题,请记住这个数字
🤖 AI: 好的,我已记住"第3个问题"。...
📊 当前历史消息数: 7
--- 第4轮对话 ---
👤 用户: 这是第4个问题,请记住这个数字
⚠️ 历史消息过多(8条),删除最早的2条...
🤖 AI: 好的,我已记住"第4个问题"。...
📊 当前历史消息数: 7
--- 第5轮对话 ---
👤 用户: 这是第5个问题,请记住这个数字
⚠️ 历史消息过多(8条),删除最早的2条...
🤖 AI: 好的,我已记住"第5个问题"。...
📊 当前历史消息数: 7
💡 说明:
• 限制历史消息数量可以节省 token
• 删除最早的消息,保留最近的对话
• 始终保留系统消息(角色设定)
• 适合长对话场景,避免超出token限制
• 权衡:记忆越短,成本越低,但上下文可能丢失
Process finished with exit code 0使用 ChatClient 的 messages() 方法(更简洁)
@Test
voidusingMessagesMethod()throws InterruptedException {
// ... 代码略,核心是使用 .messages(conversationHistory) 替代手动构建 Prompt
// 优势:代码更简洁,无需手动创建 Prompt 对象
}方式二:通过 ChatMemory + Conversation ID(自动化管理)
使用 ChatMemory 组件 + conversationId 参数,让框架自动管理历史消息的存储与加载。
核心组件说明
ChatMemory | |
MessageWindowChatMemory | |
ChatMemoryAdvisor | |
CONVERSATION_ID |
⚠️ 注意:
maxMessages指的是消息总数(含 User/Assistant/System),不是用户发言次数。
基础 Conversation ID 示例
@Test
voidbasicConversationId()throws InterruptedException {
// 创建 ChatMemory(内存存储)
ChatMemorychatMemory= MessageWindowChatMemory.builder()
.maxMessages(10) // 最多保留10条消息
.build();
// 生成唯一的对话ID
StringconversationId= UUID.randomUUID().toString();
System.out.println("对话ID: " + conversationId);
ChatClientchatClient= chatClientBuilder.build();
// ===== 第1轮对话 =====
System.out.println("\n--- 第1轮:询问推荐 ---");
Stringquestion1="你好,我想学习Python编程,有什么建议吗?";
System.out.println("👤 用户: " + question1);
Flux<String> response1 = chatClient.prompt()
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
.options(ChatOptions.builder().maxTokens(200).build())
.user(question1)
.stream()
.content();
printStreamResponse(response1);
Thread.sleep(500);
// ===== 第2轮对话(使用相同的 conversationId)=====
System.out.println("\n\n--- 第2轮:追问细节(AI自动记住第1轮内容)---");
Stringquestion2="那我应该先安装什么版本的Python?";
System.out.println("👤 用户: " + question2);
Flux<String> response2 = chatClient.prompt()
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
.options(ChatOptions.builder().maxTokens(200).build())
.user(question2)
.stream()
.content();
printStreamResponse(response2);
// ... 第3轮略
}执行结果:
对话ID: 1a6c2379-e2b5-43fd-a2bb-fd6e1ac95689
--- 第1轮:询问推荐 ---
👤 用户: 你好,我想学习Python编程,有什么建议吗?
🤖 AI: 你好!很高兴你选择学习 Python 🌟——它确实是入门编程的绝佳选择...
[完成]
--- 第2轮:追问细节(AI自动记住第1轮内容)---
👤 用户: 那我应该先安装什么版本的Python?
🤖 AI: 选择 Python 版本主要取决于你的使用场景和兼容性需求,但**目前(2024年)最推荐的起始版本是 Python 3.11 或 3.12**...
[完成]
--- 第3轮:继续深入(AI记得前2轮内容)---
👤 用户: 有没有推荐的IDE?
🤖 AI: 当然可以!选择 IDE(集成开发环境)主要取决于你使用的**编程语言、开发目标**...
[完成]
💡 说明:
• 使用相同的 conversationId 实现对话记忆
• 不需要手动维护 Message List
• ChatMemory 自动存储和加载历史消息
• AI 能理解'那'、'有没有'等指代词
Process finished with exit code 0多用户独立会话(隔离记忆)
@Test
voidmultipleConversations()throws InterruptedException {
ChatMemorychatMemory= MessageWindowChatMemory.builder().maxMessages(10).build();
// 创建两个不同的对话ID(模拟两个用户)
StringuserA_ConversationId="user-A-session-001";
StringuserB_ConversationId="user-B-session-001";
ChatClientchatClient= chatClientBuilder
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.defaultOptions(DashScopeChatOptions.builder().withTopP(0.7).maxToken(200).build())
.build();
// 用户A讨论Java,用户B讨论Python,两者记忆完全隔离
// ... 代码略,核心是不同 conversationId 互不干扰
}执行结果:
--- 用户A - 第1轮:讨论Java ---
🤖 AI (对用户A): 太棒了!😊 Java 是一门非常经典、强大且应用广泛的编程语言...
[完成]
--- 用户B - 第1轮:讨论Python ---
🤖 AI (对用户B): 太棒了!数据分析是一门实用性强、应用广泛、入门门槛适中但进阶空间巨大的技能...
[完成]
--- 用户A - 第2轮:基于Java的追问 ---
🤖 AI (对用户A): 当然有!😄 Java 生态中框架丰富、成熟且分工明确...
[完成]
--- 用户B - 第2轮:基于数据分析的追问 ---
🤖 AI (对用户B): 在Python数据分析生态中,以下**5个核心库**是必须掌握的...
[完成]
💡 说明:
• 不同 conversationId 的对话完全隔离
• 用户A的对话不会干扰用户B
• 适合多用户并发场景(如客服系统)
• 每个用户可以有自己的对话历史
Process finished with exit code 0Spring AI 记忆存储后端配置
<!-- 1. 基础内存支持(自动配置) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-model-chat-memory</artifactId>
<version>1.0.3</version>
</dependency>
<!-- 2. 关系型数据库持久化(任选其一) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
<!-- 支持:PostgreSQL / MySQL / MariaDB / SQL Server / HSQLDB -->
<!-- 3. 向量数据库/图数据库支持 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-cassandra</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-neo4j</artifactId>
</dependency>
使用自动配置后,可直接
@Autowired ChatMemory chatMemory注入,无需手动new
两种方案对比
三、LangChain 实现对话记忆(Python)
📚 LangChain 提供了更丰富的记忆类型,适合不同场景 [[3]]
安装依赖
pip install langchain langchain-openai python-dotenv类型1:ConversationBufferMemory(完整历史)
最基础的记忆类型,存储所有原始对话消息 [[4]]。
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
import os
# 环境配置
os.environ["OPENAI_API_KEY"] = "your-api-key"
# 初始化模型
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 创建记忆:存储完整历史
memory = ConversationBufferMemory(
memory_key="history", # 提示词中使用的变量名
return_messages=True# 返回 Message 对象列表(推荐)
)
# 创建对话链
conversation = ConversationChain(
llm=llm,
memory=memory,
verbose=True# 打印调试信息
)
# 多轮对话测试
print(conversation.predict(input="你好,我叫小明,想学习Python"))
# AI: 你好小明!很高兴你想学习Python...
print(conversation.predict(input="我应该先学什么?"))
# AI能记住"小明"和"学习Python"的上下文
print(conversation.predict(input="对了,我叫什么名字?"))
# AI: 你叫小明呀,刚才你告诉我的~
# 查看原始记忆内容
print(memory.buffer) # 或 memory.chat_memory.messages****** 优点**:实现简单、上下文完整、调试方便
****** 缺点**:对话越长 token 消耗越大,可能超出模型上下文限制 [[1]]
类型2:ConversationBufferWindowMemory(滑动窗口)
只保留最近 K 轮对话,自动丢弃旧消息,平衡上下文与成本。
from langchain.memory import ConversationBufferWindowMemory
# 只保留最近 5 轮对话(10条消息:5用户+5助手)
memory = ConversationBufferWindowMemory(
k=5,
memory_key="history",
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory)
# 第6轮之后,第1轮的内容会被自动遗忘
# 适合:需要近期上下文,但不需要完整历史的场景📊** 适用场景**:客服对话、任务型助手、短周期交互
类型3:ConversationSummaryMemory(摘要记忆)
使用 LLM 自动总结历史对话,用摘要替代原始内容,节省 token [[3]]。
from langchain.memory import ConversationSummaryMemory
# 需要一个专门用于总结的 LLM(可用小模型降低成本)
summary_llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
memory = ConversationSummaryMemory(
llm=summary_llm,
memory_key="history",
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory)
# 每轮对话后,自动将历史+新消息总结为一段摘要
# 后续对话使用摘要作为上下文,而非原始长文本****** 优点**:支持超长对话、节省 token、保留核心信息
****** 缺点**:摘要过程消耗额外 token、可能丢失细节、依赖总结模型质量
类型4:ConversationSummaryBufferMemory(混合模式)
结合窗口 + 摘要:近期消息保留原文,早期消息自动摘要 [[3]]。
from langchain.memory import ConversationSummaryBufferMemory
memory = ConversationSummaryBufferMemory(
llm=summary_llm,
max_token_limit=2000, # 超过此 token 数时触发摘要
memory_key="history",
return_messages=True
)
# 智能平衡:既保留近期细节,又控制总体长度
# 推荐用于:长周期用户陪伴、复杂任务协作等场景类型5:VectorStoreRetrieverMemory(向量检索记忆)
将历史对话向量化存储,按需语义检索相关片段,实现"长期记忆" [[26]]。
from langchain.memory import VectorStoreRetrieverMemory
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
# 1. 创建向量存储
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_texts([], embedding=embeddings)
# 2. 创建检索器
retriever = vectorstore.as_retriever(
search_kwargs={"k": 3} # 每次检索最相关的3条记忆
)
# 3. 创建向量记忆
memory = VectorStoreRetrieverMemory(
retriever=retriever,
memory_key="history",
return_messages=True
)
# 4. 使用
conversation = ConversationChain(llm=llm, memory=memory)
# 用户问:"我上次说的项目进度如何?"
# → 自动检索含"项目""进度"的历史片段 → 注入上下文 → 生成回答🎯** 适用场景**:知识库问答、个人助理、跨会话记忆 [[27]]
高级技巧:自定义记忆策略
from langchain.memory import ChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage
classCustomMemory:
"""自定义记忆:过滤敏感词 + 自动摘要"""
def__init__(self, llm, sensitive_words=None):
self.history = ChatMessageHistory()
self.llm = llm
self.sensitive_words = sensitive_words or []
defadd_user_message(self, text: str):
# 过滤敏感词
for word inself.sensitive_words:
text = text.replace(word, "***")
self.history.add_user_message(text)
defadd_ai_message(self, text: str):
self.history.add_ai_message(text)
defget_context(self, max_tokens=2000):
# 自定义策略:超出 token 限时自动摘要早期消息
messages = self.history.messages
# ... 实现摘要/截断逻辑
return messages四、最佳实践与注意事项
通用建议
1. 始终设置 maxTokens:防止历史累积导致请求失败 2. 区分短期/长期记忆:近期对话用 Buffer,重要信息用 VectorStore 3. 用户隐私保护:敏感信息(如密码、身份证号)不应存入长期记忆 4. 记忆清理策略:定期清理过期/无效记忆,避免存储膨胀
Spring AI 特有建议
// 推荐:使用 Conversation ID + 自动配置
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(15) // 根据模型上下文窗口调整
.build();
}
// 多租户隔离:确保 conversationId 唯一且安全
StringconversationId= userId + ":" + sessionId; // 避免冲突
// 避免:在 Message List 中硬编码敏感信息
// messageHistory.add(new UserMessage("密码是123456")); // 危险!LangChain 特有建议
# 使用 return_messages=True,便于后续处理
memory = ConversationBufferMemory(return_messages=True)
# 为总结任务使用小模型,降低成本
summary_llm = ChatOpenAI(model="gpt-3.5-turbo", max_tokens=500)
# 向量记忆设置合理的检索数量
retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) # 通常 3-5 条足够
# 避免在 memory_key 中使用保留字
memory = ConversationBufferMemory(memory_key="input") # 可能与 prompt 变量冲突五、总结:如何选择记忆方案?
🎯 需求分析 → 选择记忆类型
单次对话、轮次少(<10轮)
Spring AI: Message List 手动管理
LangChain: ConversationBufferMemory
长对话、需控制成本
Spring AI: MessageWindowChatMemory + maxMessages
LangChain: ConversationBufferWindowMemory (k=5~10)
LangChain: ConversationSummaryBufferMemory(混合模式)
跨会话记忆、用户偏好存储
Spring AI: ChatMemory + JDBC/Vector 后端
LangChain: VectorStoreRetrieverMemory
多用户/多租户系统
Spring AI: conversationId 隔离 + 数据库持久化
LangChain: session_id + 独立 VectorStore 分区
高安全/合规场景
Spring AI: 手动管理 Message List + 自定义过滤
LangChain: CustomMemory + 敏感词过滤 + 加密存储💡 核心原则:没有最好的记忆方案,只有最适合业务场景的方案。建议从简单方案起步,根据实际监控指标(token 消耗、响应延迟、用户满意度)逐步优化。
__
企业级对话记忆落地实践:分层设计与真实场景
📌_ 核心观点:企业落地对话记忆,关键不是"记住所有",而是"记住该记的、忘掉该忘的、用对地方"。下面结合电商、金融、企业服务等真实场景,讲解分层记忆的设计思路与代码实践。_
六、企业落地对话记忆的典型痛点
在真实业务中,单纯"把历史塞给模型"往往行不通,主要面临以下挑战:
_ 成本与性能问题_
• 对话轮次增多后,token 消耗呈线性增长,单次请求成本可能翻 5-10 倍 • 长上下文导致响应延迟增加,影响用户体验(客服场景要求 2 秒内响应) • 模型上下文窗口有限,超出后早期关键信息丢失
_ 数据合规与安全_
• 用户手机号、订单号、身份证号等敏感信息不能长期明文存储 • 金融、医疗等行业有数据留存期限要求(如 6 个月自动清理) • 多租户场景下,必须确保用户数据严格隔离
_ 业务语义理解_
• 用户说"上次的订单",系统需要知道"上次"指哪一笔、哪个渠道 • 客服场景中,需要区分"用户偏好"(长期有效)和"临时诉求"(本轮有效) • 企业知识库更新后,旧记忆可能产生误导,需要动态刷新
_ 运维与可观测性_
• 记忆内容无法追溯,出现问题难以定位是模型问题还是记忆污染 • 缺乏记忆使用统计,无法优化存储策略和成本控制
七、分层记忆架构设计思路
企业级方案通常采用"三层记忆"策略,按数据特性和使用频率分层管理:
第一层:上下文记忆(短期)
• 存储:当前会话的最近 5-10 轮
• 形式:原始对话文本
• 用途:保证多轮对话连贯性
• 生命周期:会话结束即释放
↓
第二层:会话级记忆(中期)
• 存储:结构化字段 + 关键摘要
• 形式:JSON/数据库记录
• 用途:跨轮次业务状态跟踪
• 生命周期:会话周期(天/周)
↓
第三层:用户级记忆(长期)
• 存储:用户画像 + 偏好标签
• 形式:结构化表 + 向量索引
• 用途:个性化推荐、跨会话服务
• 生命周期:用户生命周期
_ 各层数据选型建议_
| 数据类型 | 存储方式 | 示例 | 更新策略 |
| 用户基础信息 | 结构化(MySQL) | 用户ID、会员等级、地区 | 用户主动修改时更新 |
| 业务状态 | 结构化 + 缓存(Redis) | 当前订单号、购物车内容 | 业务操作触发更新 |
| 对话摘要 | 半结构化(JSON + ES) | "用户咨询过退货政策" | 每 3-5 轮自动摘要 |
| 用户偏好 | 向量数据库(Milvus) | "偏好简洁回复""常买数码类" | 定期离线训练更新 |
| 敏感信息 | 加密存储 + 脱敏展示 | 手机号、身份证号 | 按需解密,用完即焚 |
八、Spring AI Alibaba 企业级实践
场景:电商智能客服系统
/**
* 企业级对话记忆管理器 - 电商客服场景
* 核心设计:三层记忆 + 敏感信息过滤 + 业务状态同步
*/
@Component
publicclassEcommerceChatMemoryManager {
@Autowired
private ChatClient chatClient;
@Autowired
private UserService userService; // 用户服务(结构化数据)
@Autowired
private OrderService orderService; // 订单服务(业务状态)
@Autowired
private VectorStore vectorStore; // 偏好记忆(向量存储)
@Autowired
private RedisTemplate<String, Object> redis; // 会话缓存
/**
* 构建带分层记忆的对话请求
*/
public ChatResponse chatWithMemory(String userId, String userMessage, String sessionId) {
// ===== 第一层:加载短期上下文(最近 5 轮)=====
StringcontextKey="chat:context:" + sessionId;
List<Message> shortTermMemory =
redis.opsForList().range(contextKey, 0, 9); // 保留 10 条
// ===== 第二层:加载会话级业务状态 =====
SessionStatesessionState= loadSessionState(userId, sessionId);
// 将业务状态转为自然语言提示
StringstatePrompt= buildStatePrompt(sessionState);
// ===== 第三层:检索用户长期偏好 =====
// 用用户问题语义检索相关偏好标签
List<String> preferences = vectorStore.similaritySearch(
userMessage,
SearchRequest.builder().topK(3).build()
);
// ===== 组装系统提示词(关键:分层注入)=====
StringBuildersystemPrompt=newStringBuilder();
systemPrompt.append("你是一名电商客服助手。\n");
// 注入业务状态(结构化→自然语言)
if (statePrompt != null) {
systemPrompt.append("【当前业务状态】").append(statePrompt).append("\n");
}
// 注入用户偏好(向量检索结果)
if (!preferences.isEmpty()) {
systemPrompt.append("【用户偏好】")
.append(String.join(", ", preferences))
.append("\n");
}
// 敏感信息脱敏规则
systemPrompt.append("【安全规则】如用户提及手机号、身份证等敏感信息,")
.append("请回复'为保障安全,请勿在聊天中提供敏感信息',并引导至官方渠道。\n");
// ===== 构建完整消息列表 =====
List<Message> messages = newArrayList<>();
messages.add(newSystemMessage(systemPrompt.toString()));
messages.addAll(shortTermMemory); // 短期上下文
messages.add(newUserMessage(userMessage));
// ===== 调用模型 =====
ChatResponseresponse= chatClient.prompt()
.messages(messages)
.options(DashScopeChatOptions.builder()
.maxToken(500) // 严格控制输出长度
.temperature(0.3) // 客服场景降低随机性
.build())
.call()
.chatResponse();
// ===== 异步更新记忆(非阻塞,提升响应速度)=====
CompletableFuture.runAsync(() -> {
// 1. 更新短期上下文(滑动窗口)
updateShortTermMemory(contextKey, userMessage, response);
// 2. 提取业务状态变更(如用户确认下单)
updateSessionState(userId, sessionId, response);
// 3. 挖掘新偏好(离线任务,定期更新向量库)
extractNewPreferences(userId, userMessage, response);
});
return response;
}
/**
* 将结构化业务状态转为自然语言提示
* 例:{orderId: "123", status: "SHIPPED"} → "用户正在查询订单123,当前状态:已发货"
*/
private String buildStatePrompt(SessionState state) {
if (state == null) returnnull;
StringBuilderprompt=newStringBuilder();
if (state.getCurrentOrderId() != null) {
prompt.append("用户当前关注订单:").append(state.getCurrentOrderId());
if (state.getOrderStatus() != null) {
prompt.append("(状态:").append(state.getOrderStatus()).append(")");
}
prompt.append(";");
}
// 可扩展:购物车、收货地址、优惠券等
return prompt.length() > 0 ? prompt.toString() : null;
}
/**
* 敏感信息过滤 - 企业合规必备
*/
private String maskSensitiveInfo(String text) {
// 手机号:138****1234
text = text.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
// 身份证:110101********1234
text = text.replaceAll("(\\d{6})\\d{8}(\\w{4})", "$1********$2");
return text;
}
}_ 企业设计要点解析_
1. 为什么分层?
• 短期上下文用 Redis List:读写快、自动过期,适合高频访问 • 业务状态用 MySQL + Redis:保证事务一致性,同时缓存加速 • 用户偏好用向量库:支持语义检索,发现隐性关联(如"买过手机→可能需配件")
2. 为什么异步更新记忆?
• 用户等待的是回复,不是记忆写入 • 异步解耦后,单次请求耗时从 800ms 降至 300ms • 记忆更新失败不影响当前对话,可通过重试机制补偿
3. 敏感信息如何处理?
• 输入侧:正则脱敏 + 规则引擎(如阿里云内容安全) • 存储侧:关键字段加密(AES-256),密钥独立管理 • 输出侧:模型提示词中明确约束,双重保障
九、LangChain 企业级实践
场景:金融智能投顾助手
# enterprise_memory.py - 金融场景记忆管理
from langchain.memory import ChatMessageHistory
from langchain.vectorstores import Milvus
from langchain.embeddings import DashScopeEmbeddings
from pydantic import BaseModel, Field
from typing importList, Optional
import json
import redis
from datetime import datetime, timedelta
# ===== 1. 定义结构化业务状态 =====
classInvestmentSession(BaseModel):
"""会话级业务状态 - 结构化存储"""
user_id: str
session_id: str
risk_level: str = Field(default="C3") # 风险等级:C1-C5
focus_products: List[str] = [] # 当前关注产品
last_query_time: datetime = Field(default_factory=datetime.now)
classConfig:
json_encoders = {datetime: lambda v: v.isoformat()}
# ===== 2. 企业级记忆管理器 =====
classFinancialChatMemory:
def__init__(self, user_id: str, session_id: str):
self.user_id = user_id
self.session_id = session_id
# 短期:内存 + Redis 缓存
self.short_term = ChatMessageHistory()
self.redis = redis.Redis(host='redis-cluster', decode_responses=True)
# 中期:业务状态(MySQL)
self.session_state = self._load_session_state()
# 长期:用户画像(向量库 + 标签系统)
self.vector_store = Milvus(
connection_args={"host": "milvus-server", "port": 19530},
collection_name=f"user_profile_{user_id}",
embedding_function=DashScopeEmbeddings(model="text-embedding-v2")
)
# 合规组件
self.compliance = FinancialComplianceChecker()
defbuild_context(self, user_query: str) -> dict:
"""构建分层上下文,供 LLM 使用"""
# 【第一层】短期上下文:最近 8 轮对话
short_context = self.short_term.messages[-8:]
# 【第二层】业务状态提示
state_prompt = self._format_session_state()
# 【第三层】长期偏好检索
# 用用户问题 + 当前状态联合检索
search_query = f"{user_query}{state_prompt}"
long_term_memories = self.vector_store.similarity_search(
search_query, k=4
)
# 【合规过滤】移除过期/敏感记忆
valid_memories = [
m for m in long_term_memories
ifself.compliance.is_valid(m)
]
return {
"short_term": short_context,
"session_state": state_prompt,
"long_term": [m.page_content for m in valid_memories],
"compliance_hint": self.compliance.get_current_rules()
}
def_format_session_state(self) -> str:
"""将结构化状态转为自然语言"""
ifnotself.session_state:
return""
parts = []
ifself.session_state.risk_level:
parts.append(f"用户风险等级:{self.session_state.risk_level}")
ifself.session_state.focus_products:
products = "、".join(self.session_state.focus_products)
parts.append(f"当前关注:{products}")
return";".join(parts) if parts else""
defafter_response(self, user_query: str, ai_response: str):
"""对话后异步更新记忆"""
# 1. 更新短期记忆(滑动窗口)
self.short_term.add_user_message(user_query)
self.short_term.add_ai_message(ai_response)
self._sync_to_redis()
# 2. 检测业务状态变更(如用户确认购买)
ifself._detect_state_change(user_query, ai_response):
self._update_session_state()
# 3. 提取新偏好(离线任务)
# 实际场景:放入 Kafka,由 Spark 流处理批量更新向量库
self._queue_preference_update(user_query, ai_response)
def_sync_to_redis(self):
"""短期记忆同步到 Redis,支持多实例共享"""
key = f"chat:short:{self.session_id}"
# 序列化消息
messages = [
{"role": m.type, "content": m.content}
for m inself.short_term.messages[-10:] # 保留 10 条
]
self.redis.setex(key, 3600, json.dumps(messages)) # 1 小时 TTL
def_queue_preference_update(self, query: str, response: str):
"""将偏好更新任务放入消息队列"""
# 实际场景:使用阿里云 RocketMQ / Kafka
task = {
"user_id": self.user_id,
"timestamp": datetime.now().isoformat(),
"query": query,
"response": response,
"action": "extract_preference"
}
# kafka_producer.send("preference_update_topic", task)
pass# 示例省略_ 金融场景特殊设计_
1. 合规优先的记忆过滤
classFinancialComplianceChecker:
"""金融合规检查器 - 企业落地必备"""
# 监管规则配置(可从配置中心动态加载)
RULES = {
"max_retention_days": 180, # 记忆最长保留 6 个月
"sensitive_keywords": ["保本", "稳赚", "内幕"],
"risk_disclosure_required": True# 高风险产品必须提示风险
}
defis_valid(self, memory) -> bool:
"""检查记忆是否可继续使用"""
# 1. 检查留存期限
if memory.metadata.get("created_at"):
age = datetime.now() - memory.metadata["created_at"]
if age.days > self.RULES["max_retention_days"]:
returnFalse
# 2. 检查敏感内容
content = memory.page_content.lower()
for kw inself.RULES["sensitive_keywords"]:
if kw in content:
returnFalse# 含违规表述的记忆不再使用
returnTrue
defget_current_rules(self) -> str:
"""生成合规提示,注入系统提示词"""
return"【合规要求】1.不承诺收益 2.高风险产品需提示'市场有风险' 3.不讨论未公开信息"2. 为什么用向量库存用户偏好?
• 传统标签系统难以捕捉隐性关联(如"关注新能源→可能对碳中和基金感兴趣") • 向量检索支持语义泛化,用户说"想配置点稳健的",能匹配到"债券基金""固收+"等相关偏好 • 支持动态更新:用户行为变化后,重新 embedding 即可刷新偏好,无需重构标签体系
3. 异步更新的实际收益
• 主链路响应时间:从 1.2s → 400ms(满足金融场景<500ms 要求) • 记忆更新失败不影响对话,通过死信队列+人工审核兜底 • 偏好挖掘离线运行,可复用集群空闲资源,降低整体成本
十、企业落地关键收益
_ 成本优化_
| 优化项 | 优化前 | 优化后 | 降幅 |
| 单次请求 token 消耗 | ~2500 | ~800 | 68% |
| 长对话响应延迟 | 1.8s | 0.4s | 78% |
| 向量检索命中率 | - | 92% | - |
| 敏感信息泄露风险 | 中 | 低 | - |
_ 业务价值_
• 客服场景:用户重复描述问题减少 60%,一次解决率提升 35% • 金融场景:个性化推荐点击率提升 2.1 倍,合规审核通过率 99.7% • 企业服务:新员工培训周期从 2 周缩短至 3 天(记忆复用专家经验)
_ 运维提效_
• 记忆使用监控:实时展示各层命中率、token 分布、敏感词拦截统计 • 问题追溯:通过 session_id + 时间戳,快速定位是记忆污染还是模型问题 • 策略灰度:新记忆策略可按用户分群灰度发布,风险可控
十一、避坑指南:企业落地常见误区
_ 误区 1:试图记住所有对话_
问题:存储成本飙升,检索噪声增加,反而降低回答质量_
建议:明确记忆边界,只存"业务相关+用户显式表达+高频复用"的内容_
_ 误区 2:记忆更新与主链路强同步_
问题:用户等待记忆写入完成,响应延迟翻倍_
建议:主链路只读记忆,更新走异步队列,失败有补偿_
_ 误区 3:忽略合规与审计_
问题:金融/医疗场景可能违反监管要求_
建议:记忆全链路留痕,支持按用户/时间/内容维度审计导出_
_ 误区 4:向量记忆盲目追求大 K 值_
问题:检索过多无关内容,干扰模型判断_
建议:从 k=3 开始测试,结合业务反馈动态调整,配合重排序(Rerank)提升精度_
十二、总结:企业级记忆设计核心原则
1. 分层而不割裂:三层记忆通过统一接口暴露,业务代码无需感知存储细节 2. 结构化优先:能用字段表达的不用文本,能用规则处理的不用模型 3. 合规内建:安全规则不是外挂模块,而是记忆管路的默认过滤器 4. 可观测驱动:所有记忆操作埋点,用数据指导策略迭代 5. 渐进式演进:从短期上下文起步,根据业务痛点逐步增加中长期记忆
💡_ 最后建议:企业落地对话记忆,建议先从一个高价值场景(如售后咨询)小范围试点,验证分层策略的有效性,再逐步扩展到全业务线。记忆不是越全越好,而是越准越有用。_
夜雨聆风