3.1 记忆:从"什么都不记"到"记住关键事实"
我遇到了什么问题
第 2 章末尾说到,LLM 是"无状态"的。我当时的解法是:保存 10 轮对话历史,每次调用时带上。
这个解法能用,但很快遇到了三个问题:
问题 1:对话历史 ≠ 记忆
用户说:"我叫张三,以后叫我小张就行。" LLM 回复:"好的,小张。"
五轮对话后,对话历史里"我叫张三"这条消息已经被截断了。LLM 又开始叫"用户"。
对话历史是"流水账"——它记录了对话的过程,但没有"记住"关键信息。我需要的是记忆,不是历史。
问题 2:上下文从哪来?
用户说:"帮我看看这个项目的架构。"
LLM 需要知道"这个项目"是什么——目录结构、模块划分、技术栈、命名规范。这些信息不在对话历史里,也不在系统提示词里。
LLM 不知道它不知道什么。 你得主动把信息喂给它。
问题 3:上下文越长,效果越差
对话历史 10 轮,大概 4K Token。加上系统提示词 2K,加上用户输入 1K,一次调用 7K Token。
看起来不多,但:
- Token 是要钱的,按次计费
- 上下文越长,响应越慢
- 上下文越长,LLM 越容易"走神"——它会忘记重点,开始胡说
这三个问题,本质上是一个问题:怎么让 Agent 在有限的上下文窗口里,既记住关键信息,又不被无关信息干扰?
这个问题,我花了三个阶段才想清楚。
阶段一:没有记忆——LLM 只是一个节点

最开始,我的产品里只有两种 Agent 节点:LLM 节点和意图识别节点。
LLM 节点很简单——接收输入,调用大模型,返回输出。每次调用都是独立的,没有"会话"的概念。就像你跟一个人说话,说完他就忘了,下次见面得从头开始。
这个阶段不需要记忆。节点是一次性的工具,用完就扔。
阶段二:内存记忆——够用就好
后来我做了一些简单的 Agent,比如代码生成 Agent、浏览器操作 Agent、FFmpeg 视频处理 Agent。这些 Agent 有一个共同特点:对话轮数少,任务明确。
用户说"帮我生成一段排序代码",Agent 问一句"用什么语言",用户说"C#",Agent 输出代码。三到五轮结束。
对于这种场景,我做了最简单的内存记忆:
csharp
// Agent.Memory —— 最简实现
internal sealed class InMemoryStore : IMemoryStoreProvider
{
private sealed class SessionData
{
public string SessionId { get; init; } = "";
public List<ChatMessage> Messages { get; } = new(); // 对话历史
public List<string> Facts { get; } = new(); // 记忆事实
}
}就两个列表:
Messages:对话流水账,按时间顺序存Facts:关键事实,比如"用户偏好 C#"、"项目用 .NET 8"
为什么把这两个分开存?因为对话历史会被截断,但关键事实不能丢。
用户说"叫我小张",这句话会被提取到 Facts 里。就算对话历史被压缩到只剩最近 5 轮,"用户叫小张"这个事实还在。
用户退出软件,记忆清空,下次重新开始。
这个设计看起来很简陋,但对于桌面端软件来说完全够用。Agent 是工具,用户用完就走,不需要跨会话的"长期记忆"。就像你打开记事本,用完关掉,下次打开是一个新页面。
这个阶段,内存记忆解决了"会话内不丢信息"的问题,够用了。
阶段三:需要更聪明的记忆——门户 Agent 的出现
后来产品功能越来越多,我需要做一个统一的门户 Agent——用户所有功能的起点。
这个 Agent 不再是"用完就走"的工具。用户每天打开,跟它对话,让它帮忙处理各种任务。它应该越用越懂你。
这时候,内存记忆的三个问题暴露了:
- 不持久:退出就清空,下次又得从头教
- 不分类:所有事实平铺在一个列表里,没有结构
- 不会压缩:对话越来越长,Token 消耗越来越大
我开始调研业界怎么做的。
Hermes 的做法让我眼前一亮。
Hermes 的记忆系统有一个核心概念:记忆提供者(MemoryProvider)。它不是一个简单的存储,而是一个有生命周期的管理器:
initialize() → 启动时连接后端
system_prompt_block() → 静态文本注入系统提示词
prefetch(query) → 每轮对话前,根据用户输入"召回"相关记忆
sync_turn(user, asst) → 每轮对话后,异步写入新记忆
on_session_end() → 会话结束时,提取关键事实
on_pre_compress() → 压缩前,抢救即将被丢弃的信息这个设计的关键洞察是:记忆不是被动存储,是主动召回。
Hermes 不是把所有记忆都塞给 LLM,而是在每轮对话前,根据用户当前说的话,主动检索最相关的记忆。用户说"帮我看看数据库",系统就召回"项目用 SQLite"这条记忆,而不是把所有记忆都倒出来。
字节 OpenViking 的做法给了我另一个启发:三层结构。

OpenViking 把每条记忆分成三层:
| 层级 | 内容 | Token 预算 | 用途 |
|---|---|---|---|
| L0 | 一行摘要 | ~100 | 快速判断"这条记忆跟当前话题有没有关系" |
| L1 | 结构化摘要 | ~500 | 提供关键信息,不需要展开细节 |
| L2 | 完整内容 | 不限 | 需要深入了解时才加载 |
这个设计非常聪明。就像你看一本书:先看目录(L0),觉得有用再翻到那一章看摘要(L1),确实需要才读全文(L2)。
我把这两个思路结合起来,做了 OpenVikingStore。
关键认知
记忆的演进不是一步到位的,而是被产品需求逼出来的:
- 节点时代 → 不需要记忆
- 简单 Agent 时代 → 内存记忆够用
- 门户 Agent 时代 → 需要主动召回 + 三层结构
记忆的复杂度,应该匹配产品的复杂度。 不要一上来就搞向量数据库、知识图谱——先问清楚:你的 Agent 需要"记住"什么?