AI Agent 的记忆系统:从 ChatMemory 到持久化记忆的 Java 实战

为什么 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;

// 只保留最近 20 条消息
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(20);

这就像你只记住最近跟人说的 20 句话,再早的就忘了。简单粗暴,但在很多场景下够用。

优点:实现简单,上下文不会超长导致 token 爆炸。

缺点:早期的重要信息会被丢掉。比如用户在第 3 条消息里说了”我叫张三”,到第 25 条消息时这条信息就被挤出去了。

更聪明的方式:Token 窗口

1
2
// 按 token 数量控制,更精确
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();

// 每个用户用独立的 memory
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemoryProvider(memoryId ->
MessageWindowChatMemory.withMaxMessages(30))
.build();

// 使用:不同 sessionId 之间记忆互不干扰
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) // 超过 50 条开始摘要
.chatModel(model) // 用于生成摘要的模型
.build();

这种方式特别适合客服场景——用户可能反复询问同一个问题,摘要能保留核心信息,同时释放 token 空间。

但要注意:摘要压缩会丢失细节。如果用户在第 10 条消息里给了一个精确的订单号,摘要可能不会保留。所以摘要策略要根据业务场景调整。

第三层:长期记忆(向量存储)

短期记忆的问题在于:会话结束就没了。你和 Agent 聊了一个小时,第二天再来,它完全不记得你是谁。

长期记忆要解决的是跨会话的信息持久化。最常用的方式是:

  1. 把重要信息(用户偏好、历史对话摘要)转成向量
  2. 存入向量数据库
  3. 新会话开始时,检索相关的历史信息注入上下文

这就是 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;

// 使用 Redis 向量数据库作为持久化存储
ChatMemoryStore store = RedisChatMemoryStore.builder()
.host("localhost")
.port(6379)
.indexName("agent_memory")
.build();

// ChatMemoryProvider:每个 sessionId 自动创建/恢复记忆
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", "帮我推荐一个适合我技术栈的框架");
// 回复会参考:你喜欢用 IntelliJ,正在做 Spring Boot 项目

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 官方文档