为什么 Agent 需要记忆?
你有没有遇到过这样的场景:你和一个 AI 聊了半天,中间提到了你的名字、你的偏好、你的项目背景,结果下一轮对话它全忘了。你不得不把所有上下文重新说一遍——这种体验就像跟一个患有严重健忘症的人聊天。
这就是没有记忆系统的 AI Agent 的真实状态。
大语言模型本身是无状态的(stateless)。每次调用,它只看到你这次传给它的文本,之前的对话、你的身份、你的偏好,统统不存在。所谓”记忆”,其实是应用层帮它”记”住的。
所以问题来了:怎么设计一套记忆系统,让 Agent 真正”记住”该记住的东西?
今天我们就来聊聊 AI Agent 的记忆架构,从原理到实战,用 Java + LangChain4j 把它落地。
记忆的三层架构:借鉴人类大脑
要理解 Agent 记忆,最好的方式是借鉴人类大脑的工作方式。认知科学把人类记忆分为三层:
1 2 3 4 5 6 7 8 9 10 11 12 13
| ┌─────────────────────────────────────────────────┐ │ 长期记忆(Long-term) │ │ 持久存储:你的知识、经验、身份信息 │ │ 类比:硬盘 │ ├─────────────────────────────────────────────────┤ │ 工作记忆(Working) │ │ 当前任务的上下文:正在处理的对话、中间结果 │ │ 类比:RAM │ ├─────────────────────────────────────────────────┤ │ 感官记忆(Sensory) │ │ 最新输入:刚刚收到的消息、工具调用结果 │ │ 类比:CPU 寄存器 │ └─────────────────────────────────────────────────┘
|
Agent 的记忆系统也遵循类似的分层设计:
| 记忆类型 |
存储内容 |
生命周期 |
实现方式 |
| 短期记忆 |
当前对话的上下文 |
单次会话 |
ChatMemory(内存) |
| 工作记忆 |
当前任务的中间状态 |
任务期间 |
上下文窗口 + 摘要 |
| 长期记忆 |
用户偏好、历史对话 |
持久化 |
向量数据库 + Key-Value Store |
接下来我们逐层拆解。
第一层:短期记忆(ChatMemory)
短期记忆是最基础的,本质上就是维护一个消息列表,每次对话时把历史消息一起发给模型。
最简单的实现:滑动窗口
最粗暴的方式就是保留最近 N 条消息:
1 2 3 4 5
| import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory;
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(20);
|
这就像你只记住最近跟人说的 20 句话,再早的就忘了。简单粗暴,但在很多场景下够用。
优点:实现简单,上下文不会超长导致 token 爆炸。
缺点:早期的重要信息会被丢掉。比如用户在第 3 条消息里说了”我叫张三”,到第 25 条消息时这条信息就被挤出去了。
更聪明的方式:Token 窗口
1 2
| ChatMemory chatMemory = TokenWindowChatMemory.withMaxTokens(4000);
|
按 token 数量控制比按消息条数更合理,因为一条消息可能只有一句话,也可能是一大段代码。
实战:用 LangChain4j 搭建对话 Agent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.service.AiServices; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.MemoryRetrieval; import dev.langchain4j.service.SystemMessage;
public interface Assistant { @SystemMessage("你是一个有记忆的助手,能记住用户之前说过的话。") String chat(@MemoryId String sessionId, @MemoryRetrieval String userMessage); }
ChatLanguageModel model = OpenAiChatModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("gpt-4o") .build();
Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(30)) .build();
String reply1 = assistant.chat("user-001", "我叫张三,我是后端开发"); String reply2 = assistant.chat("user-001", "我叫什么名字?");
String reply3 = assistant.chat("user-002", "我叫什么名字?");
|
注意 @MemoryId 的作用——它实现了多用户记忆隔离。每个 sessionId 对应一个独立的 ChatMemory 实例,张三的记忆不会泄漏到李四的对话中。
第二层:工作记忆(摘要与压缩)
当你和 Agent 进行很长的对话(比如 100 轮以上),即使使用 Token 窗口,也面临一个两难:
- 窗口太小 → 丢失早期重要信息
- 窗口太大 → token 费用飙升,甚至超出模型上下文限制
工作记忆的解决方案是:摘要压缩。
核心思路是:当消息列表快满时,用 LLM 把早期的对话压缩成一段摘要,然后用摘要替代原始消息。
1 2 3 4 5 6 7 8
| ┌────────────────────────────────────────┐ │ 原始对话(50条消息,10000 tokens) │ │ ↓ LLM 摘要压缩 │ │ 摘要(1条消息,500 tokens) │ │ + 最近 20 条原始消息(3000 tokens) │ │ ↓ 合计 │ │ 总计 3500 tokens(节省 65%) │ └────────────────────────────────────────┘
|
LangChain4j 中的摘要记忆
1 2 3 4 5 6 7
| import dev.langchain4j.memory.chat.ChatMemory; import dev.langchain4j.memory.chat.SummarizingChatMemory;
ChatMemory memory = SummarizingChatMemory.builder() .maxMessages(50) .chatModel(model) .build();
|
这种方式特别适合客服场景——用户可能反复询问同一个问题,摘要能保留核心信息,同时释放 token 空间。
但要注意:摘要压缩会丢失细节。如果用户在第 10 条消息里给了一个精确的订单号,摘要可能不会保留。所以摘要策略要根据业务场景调整。
第三层:长期记忆(向量存储)
短期记忆的问题在于:会话结束就没了。你和 Agent 聊了一个小时,第二天再来,它完全不记得你是谁。
长期记忆要解决的是跨会话的信息持久化。最常用的方式是:
- 把重要信息(用户偏好、历史对话摘要)转成向量
- 存入向量数据库
- 新会话开始时,检索相关的历史信息注入上下文
这就是 Memory + RAG 的组合。
架构图
1 2 3 4 5 6 7 8 9
| 用户消息 ──→ 重要信息提取 ──→ Embedding ──→ 向量数据库(持久化) │ 新会话开始 ──→ 用户消息 Embedding ──→ 向量检索 ←┘ │ 相关历史信息 │ 注入 System Prompt │ LLM 生成回复
|
LangChain4j + Redis 向量存储实战
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.store.memory.chat.ChatMemoryStore; import dev.langchain4j.store.memory.chat.RedisChatMemoryStore;
ChatMemoryStore store = RedisChatMemoryStore.builder() .host("localhost") .port(6379) .indexName("agent_memory") .build();
ChatMemoryProvider memoryProvider = sessionId -> MessageWindowChatMemory.builder() .id(sessionId) .maxMessages(30) .chatMemoryStore(store) .build();
Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemoryProvider(memoryProvider) .build();
assistant.chat("zhang3", "我喜欢用 IntelliJ IDEA 写代码"); assistant.chat("zhang3", "我最近在做一个 Spring Boot 项目");
String reply = assistant.chat("zhang3", "帮我推荐一个适合我技术栈的框架");
|
RedisChatMemoryStore 会自动把消息序列化存到 Redis。关键是 chatMemoryProvider——它根据 sessionId 从 Redis 加载历史消息,实现了跨会话记忆恢复。
记忆系统设计的几个实战建议
1. 不是所有信息都值得记住
把每条消息都存入长期记忆是浪费。你可以在消息经过时做一个重要性判断:
1 2 3 4 5 6 7 8 9 10 11 12
| public class MemoryFilter { private final ChatLanguageModel model; public boolean shouldRemember(String sessionId, String userMessage) { String response = model.chat( "判断以下用户消息是否包含值得长期记住的信息(如身份、偏好、重要决策)。" + "只回复 YES 或 NO。\n\n消息:" + userMessage ); return response.trim().toUpperCase().startsWith("YES"); } }
|
用户说”帮我算一下 1+1”——不值得记。用户说”我们公司用的是 Java 17”——值得记。
2. 多层记忆优先级
实际生产中,建议按优先级使用记忆:
1 2 3 4 5 6 7 8 9
| System Prompt(固定指令) ↓ 补充 长期记忆检索(向量数据库) ↓ 补充 工作记忆摘要(压缩的历史) ↓ 补充 短期记忆(最近 N 条消息) ↓ 当前用户输入
|
越靠上的信息优先级越高,LLM 会更”重视”它们。
3. 隐私与安全
长期记忆涉及用户隐私数据。要做好:
- 数据脱敏:PII(个人身份信息)存储前做加密
- 过期清理:设置 TTL,非活跃用户记忆自动清除
- 删除支持:提供”忘记我”的 API,支持 GDPR 合规
总结:一张图看懂 Agent 记忆
1 2 3 4 5 6 7 8 9 10 11
| AI Agent 记忆系统 │ ┌───────────────┼───────────────┐ │ │ │ 短期记忆 工作记忆 长期记忆 (ChatMemory) (摘要压缩) (向量存储) │ │ │ 滑动窗口/Token窗口 LLM生成摘要 Embedding + DB 单次会话有效 释放token空间 跨会话持久化 │ │ │ 实现简单 平衡精度与成本 需要基础设施
|
记忆系统是 AI Agent 从”玩具”走向”产品”的关键一步。没有记忆的 Agent 就像一个每次见面都不认识你的陌生人——再聪明也没用。
从今天开始,给你的 Agent 装上记忆吧。
本文代码基于 LangChain4j 0.36+ 版本,完整示例可参考 LangChain4j 官方文档。