本章目标:建立 Agent 记忆的完整分类学;讲透短期滑窗、向量检索、摘要压缩、 RAG、工作记忆的实现与取舍;最后讨论遗忘与隐私------记忆不是越多越好。
1. 为什么 Agent 需要记忆
没有记忆的 Agent 有三宗罪:
- 每次对话都像陌生人:用户上次说「我喜欢简洁回复」,这次它又长篇大论;
- 跨会话无法持续:上个月定的偏好,这个月全忘;
- 上下文窗口装不下:聊了 50 轮,不可能把所有历史都塞进 prompt。
记忆的本质是把「已发生的交互」转化为「可复用的上下文」, 从而在有限的上下文窗口内,让 Agent 拥有「更长的时间线」和「更丰富的背景知识」。
类比:模型是大脑,上下文窗口是工作台,记忆系统是书架和便签。 工作台放不下所有书,所以要把读过的内容提炼成笔记,按需取用。
2. 记忆的分类学
2.1 按时间尺度(认知科学的三级记忆模型)
| 类型 | 时间尺度 | 容量 | Agent 中的实现 |
|---|---|---|---|
| 感觉记忆 | <1 秒 | 巨大 | (一般不做,模型输入本身即时) |
| 短期记忆(工作记忆) | 秒--分钟 | 小(≈上下文窗口) | 对话缓冲 + 上下文窗口 |
| 长期记忆 | 天--年 | 大 | 向量库 / 数据库 / 文件 |
2.2 按内容类型(认知科学的记忆分类)
| 类型 | 是什么 | Agent 中的例子 |
|---|---|---|
| 情景记忆(Episodic) | 具体发生过的事 | 「昨天用户问过耳机物流」 |
| 语义记忆(Semantic) | 抽象的事实与知识 | 「用户生日是 1998-03-12」 |
| 程序记忆(Procedural) | 怎么做(技能) | 「遇到订单问题时先查订单工具」 |
2.3 按存取方式(工程分类,最实用)
| 类型 | 存取方式 | 实现 |
|---|---|---|
| 对话缓冲 | 追加式,滑窗淘汰 | ConversationBuffer |
| 关键事实 | 显式写入,按需读取 | saveFact / 向量库 |
| 向量记忆 | 语义检索 | VectorMemory + embedding |
| 摘要记忆 | 压缩历史 | 模型总结 |
| 外部数据库 | 结构化查询 | SQL / KV / 图数据库 |
3. 短期记忆:对话缓冲与上下文窗口
3.1 滑动窗口(最简单、最常用)
保留最近 N 条消息,最旧的丢弃。系统消息永远保留。
ts
// mini-agent: src/memory/conversation.ts
const buf = new ConversationBuffer({ maxMessages: 20 });
buf.add(userMsg); // 追加
buf.add(assistantMsg);
const msgs = buf.messages(); // 自动裁剪到 20 条(system 不占配额)
取舍:
- 优点:实现极简、token 可预测、无模型调用成本;
- 缺点:丢掉的信息永远丢。如果被丢掉的是关键约束,Agent 会「失忆」。
3.2 上下文窗口管理(第 02/03/05 章的工程化)
滑窗按「条数」控制,但真正的约束是 token。所以要按 token 预算裁剪:
ts
// mini-agent: src/context/window.ts
const window = new ContextWindow({ maxTokens: 32_000 });
const { messages, dropped, droppedTokens } = window.trim(allMessages);
裁剪策略(从简单到复杂):
| 策略 | 描述 | 适用 |
|---|---|---|
| 丢弃最旧 | 从旧到新丢弃 | 绝大多数场景 |
| 保留关键帧 | 标记重要消息不丢 | 需要保关键约束 |
| 摘要旧消息 | 把被丢弃的压缩成摘要 | 长会话、多轮任务 |
| 重要度加权 | 按重要度评分保留 | 长期运行 Agent |
「预算怎么分、消息怎么排、怎么省 token(缓存 / 动态工具子集)」是 第 05 章上下文工程的完整主题。本章只负责「记忆」这一端: 存什么、怎么取、怎么压缩。两章合起来才构成完整的上下文治理。
3.3 摘要压缩(Summarization)
当会话超过预算,把旧消息交给模型总结成摘要,用摘要代替原文:
ini
[摘要(第一小时)]
用户是产品经理,项目是 AI 客服;已决定用 TypeScript 实现;拒绝过用 Python。
--- 以下是最近消息 ---
user: 那 API 用哪家?
assistant: 建议 OpenAI 兼容接口...
摘要的设计要点:
- 结构化摘要:固定模板(用户是谁 / 目标 / 已决定 / 待办 / 偏好), 比自由文本摘要更利于信息恢复;
- 关键信息优先:重要约束、用户偏好、未完成任务放在摘要开头;
- 滚动摘要:每轮增量更新摘要,而不是每次全量重写;
- 防丢失:摘要本身也会被摘要------用「分层摘要」(每 N 条消息一层)。
mini-agent 的
MemoryManager预留了summarize钩子(注释处),生产实现 就是在这里接入模型的摘要调用。
4. 长期记忆:向量检索
4.1 直觉
「记住事实」不能像短记忆那样全量保留(存不下,也取不出)。 长期记忆的核心是把记忆变成可检索的索引,用的时候按相关性取回。
流程:
makefile
写入: 文本 → 嵌入(embedding 向量) → 存进向量库
读取: 查询 → 嵌入 → 余弦相似度 → 取回 top-k 相关文本 → 拼进上下文
4.2 嵌入(Embedding)是什么
嵌入是把文本映射为固定维度向量,使得语义相近的文本向量也相近。 如「生日是哪天」与「我的生日是 1998-03-12」的距离,远小于与「今天天气很好」的距离。
- 真实场景 :用专门的 embedding 模型(如 OpenAI
text-embedding-3-small、 BGE、E5),维度数百到数千; - mini-agent 的教学实现:用字符 n-gram 哈希把文本映射成 256 维向量 (src/memory/vector.ts)。对中文有效、确定性高、零依赖,但语义远不如真实模型。 「向量记忆的骨架」与真实系统完全一致,替换 embedding 模型即可升级。
4.3 相似度与检索
- 余弦相似度 :最常用。向量夹角越小越相似,值域 -1, 1。定义: cos(A,B)=∥A∥∥B∥A⋅B=∑iAi2 ⋅∑iBi2 ∑iAiBi 它只关心方向 、不关心模长,所以对「词频差异大但语义相近」的文本鲁棒 (同一件事说得长些或短些,方向不变)。
- Top-k 检索:返回得分最高的 k 条(如 3 条)。
- 阈值过滤:得分低于阈值的丢弃,避免把不相关的记忆强塞进上下文。
- 混合检索(生产级) :向量检索(语义)+ 关键词检索(BM25,精确匹配)融合, 兼顾语义与字面。常用 RRF(Reciprocal Rank Fusion,倒数排名融合) 合并两个结果集: RRF(d)=∑r∈Rdk+r1( Rd 是文档 d 在两个结果集中的排名集合, k 通常取 60) 它不依赖打分绝对值可比,只依赖排名,简单且稳健。这是 RAG 系统的标准做法。
工程深入 :本章讲的是「记忆/检索的骨架」。到生产级,你还需要知道------ 向量索引算法(IVF / HNSW / PQ)怎么选、向量数据库怎么选型、混合检索的 融合细节、重排序(cross-encoder)怎么做、完整 RAG 管线怎么调优。 这些在第 21 章附录 H「向量检索与 RAG 工程深入」。mini-agent 的
VectorMemory(n-gram 哈希 + 余弦)是这条骨架的教学版。
4.4 记忆的元数据与过期
长期记忆条目应带元数据:
jsonc
{
"text": "用户生日是 1998-03-12",
"metadata": {
"source": "user-stated",
"time": "2026-08-01",
"importance": 0.9,
"agent": "customer-support",
"ttl": "2027-08-01"
}
}
- importance(重要度):写入时评估,检索时加权,高重要度的记忆优先保留;
- ttl / 过期:过时的记忆(如「用户正在升级」)应被清理,否则会污染上下文;
- 溯源(source):记录记忆来源,便于审计与纠错。
4.5 知识图谱记忆:从「向量」到「关系」
向量记忆擅长「语义相似」,但不擅长多跳推理 与精确关系 : 「用户 A 是谁的上级」无法用一条向量回答。知识图谱记忆(KG Memory) 把记忆建模为实体 + 关系 + 属性的三元组:
scss
(用户A) --上级--> (用户B)
(用户B) --部门--> (财务部)
(用户B) --偏好--> (喜欢简洁回复)
- 写入:记忆线程把对话中的实体与关系抽取为三元组(模型调用,或规则提取);
- 存储 :图数据库(Neo4j)或「关系表」(SQL);mini-agent 的
VectorMemory可扩展一个relations数组; - 读取:沿边做多跳遍历------「B 的上级的部门是什么?」→ A → B → 财务部;
- 与向量的配合 :向量做语义召回,图做精确推理。先向量召回候选实体, 再在图里沿关系补全上下文------这是 Mem0 等产品「图记忆」的实际用法。
取舍:
- 优点:多跳推理、精确关系、可解释(路径可审计);
- 缺点:写入复杂(抽取质量依赖模型)、维护成本高、小型任务收益不显著;
- 经验:先向量后图。当评测(第 10 章)显示「关系类问题检索不到」 且这类问题占比高时,再引入图谱。
5. 把记忆织进 Agent:记忆管理器
一个完整的记忆系统要统一管理短期与长期。mini-agent 的 MemoryManager 是教学版的统一门面:
ts
const memory = new MemoryManager();
// 短期:每轮自动
memory.remember({ role: 'user', content: '...' });
// 长期:显式沉淀(通常由 remember 工具或策略触发)
memory.saveFact('用户生日是 1998-03-12', { importance: 0.9 });
// 读取:查询时语义检索,拼成上下文
const ctx = memory.recallAsContext('生日'); // 返回 "[相关记忆]\n1. ..."
5.1 何时把信息写入长期记忆?
这是记忆系统最关键的决策。两个极端都错:
- 什么都不写:Agent 没有记忆;
- 什么都写:向量库全是噪声,检索质量崩坏。
工程上常用的「提取-筛选」策略:
- 策略触发 :设定明确的写入时机------
- 用户显式要求记住(「记住:...」);
- 用户陈述了稳定的偏好/事实(「我习惯用中文」);
- 任务完成后的重要结论;
- 反思产出的经验教训。
- 模型筛选:用一次模型调用对对话做「记忆提取」------ 只把高价值信息存入长期记忆(生产常用,如 MemGPT 的做法)。
- 重要度评分:存入时打分,检索与淘汰时按分加权。
5.2 写入时模型的角色
生产级记忆系统通常有一个记忆线程(memory thread): 一个独立的模型循环,把「当前对话」提炼成「结构化记忆」写入记忆库。 它与主对话解耦,不占主循环的上下文窗口。
5.3 记忆的完整读写架构
把前面所有组件拼起来,一个生产级记忆系统的写路径 与读路径长这样:
scss
写路径(写入时机 = 策略触发 + 模型提取)
对话消息 ──► 记忆线程(独立模型循环)
├─ 提取事实/偏好/事件 ← 模型筛选
├─ 打重要度 / 打标签 / 记溯源 ← 元数据
├─ 去重合并(与已有记忆比对) ← 一致性
└─ 写入:短期缓冲 + 长期向量库 + 结构化数据库
读路径(读取时机 = 每轮模型调用前)
当前问题 ──► 查询构建
├─ 短期:最近 N 条对话 ← 滑窗
├─ 长期:向量 top-k(语义检索) ← 按相关度
├─ 关键事实:结构化查询(按实体) ← 精确命中
└─ 汇总 → 拼进上下文(缓存友好位置,第 05 章 §4)
两个关键设计点:
- 写路径慢、读路径快。写入要模型提取(贵、可异步),读取要快(每次调用 都在读)。生产实现通常把「记忆线程」做成异步任务,不阻塞主对话循环。
- 多路读取、按需拼装 。短期(时间近)、长期(语义相关)、关键事实(实体精确) 是三个互补的读取维度。mini-agent 的
recallAsContext是「长期向量」这一路 的教学实现;完整生产系统是三路都要。
5.4 生产级记忆系统的三个参照
2024--2026 年,主流产品把「记忆」做成了可感知的能力。研究它们的取舍, 比背任何框架 API 都更有价值:
| 产品 | 记忆形态 | 关键设计 | 值得学什么 |
|---|---|---|---|
| ChatGPT Memory | 用户显式/隐式声明的事实 + 对话记忆 | 用户可查看/编辑/删除每条记忆;可关闭 | 「记忆的可见性与用户控制」是产品标配 |
| Claude Memory | 跨会话的项目记忆 + 会话内「记住」 | 用户可见、可删除;记忆是结构化条目 | 记忆要「人可读、人可控」,不是黑盒 |
| Mem0 | 开源记忆层(可接入任意 Agent) | 提取→去重→存储→检索 四步流水线;支持图记忆 | 「记忆即服务」的架构;与框架解耦 |
共同规律 :这三家都把「记忆」做成了用户可见、可控、可删的独立层, 而不是藏在 Agent 循环里的隐式状态------这正是第 04 章反复强调的: 记忆的透明度 = 信任的工程化。
6. RAG:把外部知识接进来
RAG(Retrieval-Augmented Generation,检索增强生成)是第 01 章提到的 「Agent 的工具箱里的一件工具」。它的完整管线:
scss
文档 → 切分(chunk) → 嵌入 → 存入向量库
┌──────────────────────┐
查询 → 嵌入 → 检索 top-k → 拼进 prompt → 模型基于证据回答
└──────────────────────┘
6.1 RAG 与 Agent 记忆的关系
记忆与 RAG 的技术栈几乎相同(嵌入 + 向量库 + top-k), 区别在于内容来源与写入方:
| Agent 记忆 | RAG | |
|---|---|---|
| 内容来源 | 与用户的交互历史、Agent 自身经验 | 外部文档、知识库、数据库 |
| 写入方 | Agent 运行时动态写入 | 离线/异步批量灌入 |
| 更新频率 | 实时 | 低频 |
| 典型载体 | 事实、偏好、事件 | 手册、论文、内部知识 |
生产系统通常共用同一个向量库:一个 collection 存「交互记忆」, 一个 collection 存「知识文档」。
6.2 RAG 的工程细节(决定效果的关键)
-
切分(chunking):文档切成块,块太小丢上下文,太大检索不准。 常用 300--800 token + 重叠(overlap 50--100 token)。切分策略直接影响召回质量:
策略 做法 适合 固定窗口 按固定长度切 + 固定重叠 通用文本、新闻 结构切分 按标题/段落/代码块切 手册、论文、Markdown、代码 语义切分 按嵌入相似度拐点断句 长对话、观点密集文本 递归切分 先按大块切,超限再递归细分 混合格式文档 经验:优先用结构切分(信息边界天然清晰);重叠长度取块长的 15--25%, 避免「一个完整的要点被从中间切断」。
-
元数据过滤:按来源、时间、类型过滤,先粗筛再排序。
-
重排序(re-rank):top-k 粗检后用 rerank 模型精排,显著提升精度。 「粗检 top-50 → rerank 取 top-5」通常比「直接取 top-5」效果好得多, 因为向量检索的 top-k 排序在小 k 时不稳定。
-
引用溯源:让模型回答时带上来源,提高可信度与可审计性。 至少在 system 提示中声明「仅依据检索内容回答,无证据时明确说不知道」, 从机制上压制幻觉。
6.3 RAG 的失败模式与对策
| 失败模式 | 症状 | 对策 |
|---|---|---|
| 检索不相关 | 回答了但答非所问 | 更好的 chunk/重排/混合检索 |
| 检索相关但缺失 | 答案遗漏关键点 | 提高 k、多路召回 |
| 幻觉 | 答案看着对但编造 | 约束「没有证据就说不知道」 |
| 上下文超载 | 塞太多无关片段 | 更严格的阈值与重排 |
6.4 Agentic RAG:把检索决策交给 Agent
传统 RAG 是「一次性检索 → 拼进 prompt → 回答」的固定管线,检索策略(查什么、查几次)由代码写死。 Agentic RAG 则把检索本身变成 Agent 循环中的自主决策:
markdown
用户提问 ──► Agent 判断「需要什么信息」──► 调用 search 工具
▲ │
│ ▼
│◄──── 信息不足 / 需要补充 ── 观察检索结果
│
└── 信息足够 ──► 组织回答
Agentic RAG 的几种关键形态:
- 查询改写(Query Rewriting):Agent 根据对话上下文把用户问题改写为更利于检索的查询 (「他们上个月说的是哪个项目?」→ 检索「项目名 + 上个月」),可多次改写、多次检索;
- 多跳检索(Multi-hop Retrieval):先检索到线索(如「该公司 CEO 是张三」), 再以线索为新的查询继续检索(「张三的公开演讲」),把多轮检索结果拼接成证据链;
- 迭代检索(Iterative Retrieval):第一次检索结果不足以回答时, Agent 自主决定「还要补充什么」再调一次工具,而不是一次定生死;
- 充分性判断(Sufficiency):Agent 判断「当前证据够不够回答」, 够了就停止检索,避免无谓的 token 消耗。
工程要点:
- Agentic RAG 把「查一次还是查多次」从代码策略升级为模型决策,代价是多轮模型往返的成本;
- 要给检索工具设计「可迭代」的返回:返回内容 + 元数据(来源、时间、置信度), 让 Agent 能判断「这条信息可靠吗、还需要什么」;
- 与普通 RAG 的选择:问题简单、知识固定 → 普通 RAG(便宜、快、稳定); 问题开放、需要多步推理、证据要拼装 → Agentic RAG(灵活、准,但贵);
- 防跑偏:给「检索次数」设上限(如最多 3 次),超限强制回答或承认不知道。
6.5 RAG 的效果评测:把「感觉好」变成「可量化」
RAG 效果不能靠「看起来答对了」判断。一套可落地的评测指标:
| 指标 | 度量什么 | 怎么测 |
|---|---|---|
| 召回率(Recall@k) | 该检索到的相关文档检到了多少 | 标注「问题→应命中文档集」,看 top-k 命中比例 |
| 答案正确率 | 基于检索的最终回答对不对 | 黄金集 + 结果断言(第 10 章) |
| 引用准确率 | 回答引用的来源是否真实支持结论 | 人工/LLM 检查「每个引用是否真能支撑那句话」 |
| 检索次数 | 效率(Agentic RAG 里多调了几次) | 统计 search 工具调用数 |
| 无证据拒答率 | 检索不到时是否诚实说「不知道」 | 构造「知识库里没有答案」的用例 |
两个最常见的评测误区:
- 只测答案、不测检索。答案对了但引用了错误文档(幻觉的隐蔽形态), 下次换个问法就露馅。引用准确率必须单独测;
- 用「与黄金文档的相似度」代替「与答案正确性的关系」。检索分高 ≠ 答案对;评测最终要落在「端到端答案正确率」,检索指标只做中间诊断。
工程建议:把 RAG 评测纳入第 10 章的评测平台------「检索指标 + 答案指标」 一起追踪,而不是只看其中一个。向量库升级、chunk 策略调整、rerank 引入, 都用这套指标验证,而不是拍脑袋。
7. 记忆的系统架构:MemGPT 与分层记忆
MemGPT(2023)提出了一个极具影响力的 Agent 记忆架构: 把「操作系统分页」的思想搬到 Agent------主上下文窗口是「内存」, 外部存储是「磁盘」,Agent 自己决定何时「换入/换出」信息。
scss
主上下文(内存,有限)
├── system 指令
├── 工作记忆(当前任务状态)
└── 最近交互
外部记忆(磁盘,无限)
├── 长期向量库
├── 摘要档案
└── 工具结果缓存
MemGPT 的关键机制:Agent 通过「函数调用」管理自己的记忆 ------ core_memory_append、archival_memory_search、conversation_search 等工具。 这让「何时检索、何时写入」成为模型的可学习决策,而不是代码写死的策略。
工程启示 :记忆管理本身可以被 Agent「自主化」。 memory 工具化(如 mini-agent 的 remember 工具、recallAsContext) 就是这条路的起点。
7.1 记忆去重与一致性:记忆系统的隐性成本
记忆写得越勤,「脏数据」越多。三个最常见的脏数据问题与对策:
| 问题 | 症状 | 对策 |
|---|---|---|
| 重复记忆 | 同一事实写了几十条(「生日是 3/12」「生日 1998-03-12」) | 写入前去重:按语义/实体比对已有记忆,合并或拒绝;Mem0 流水线里的「去重」步骤 |
| 矛盾记忆 | 「用户在北京」与「用户在上海」同时存在 | 带时间戳 + 版本;新事实优先;矛盾时显式标记待确认 |
| 过期记忆 | 「正在升级」三周前写的,早已失效 | TTL 过期 + 主动重评估(§8.1) |
为什么重要 :脏记忆不只是浪费存储,它会让 Agent 给出错误回答且难以排查 ------「用户明明改了地址,Agent 还按旧地址回答」比「没有记忆」更糟。 一致性是记忆系统的质量底线,建议把它纳入评测(第 10 章的记忆质量评测)。
8. 遗忘与隐私:记忆不是越多越好
8.1 遗忘(Forgetting)
遗忘是记忆系统必不可少的部分,而不是缺陷:
- 防止污染:过时/错误的记忆会让 Agent 给出错误回答,比没有记忆更糟;
- 控制成本:向量库无限膨胀,检索变慢、噪声变多;
- 三类遗忘策略 :
- 被动淘汰:TTL 过期 / 容量上限 / LRU 淘汰;
- 主动清理:定期重评估记忆,删除低重要度/过期条目;
- 记忆修正:发现记忆错误时,显式删除并替换。
8.2 隐私与合规
记忆系统天然携带敏感信息:
- 最小化:只存完成任务所需的信息,不存无关隐私;
- 用户控制:提供「查看 / 删除 / 导出」我的记忆的接口(GDPR 的核心要求);
- 加密与隔离:记忆库加密存储,按用户/租户隔离;
- 不写入敏感令牌:绝不在记忆里存 API Key、密码、支付信息;
- 审计:记录每次记忆写入与读取,便于合规审计与事后追溯。
工程红线:任何记忆系统,都必须先回答「用户如何删除自己的数据」。 没有删除能力的记忆系统,在生产环境是合规事故。
9. 对照 mini-agent
| 概念 | 代码 | 说明 |
|---|---|---|
| 短期滑窗 | src/memory/conversation.ts |
FIFO 裁剪,system 不占配额 |
| 长期向量 | src/memory/vector.ts |
n-gram 哈希嵌入 + 余弦检索 |
| 记忆管理器 | src/memory/manager.ts |
短期+长期统一门面 |
| 上下文窗口 | src/context/window.ts |
token 预算裁剪 |
| remember 工具 | src/tools/builtin.ts |
把事实写入长期记忆 |
| recall 注入 | src/agent.ts / 示例 |
查询时语义检索拼上下文 |
| token 估算 | src/util/tokens.ts |
预算管理 |
动手验证 :node examples/chat.ts 第二、三、四轮演示了 「记住生日 → 检索命中 → 跨轮引用」的完整记忆链路。
10. 本节要点
- 记忆 = 把「已发生的交互」转化为「可复用的上下文」;
- 分类维度:时间尺度(短/长)、内容类型(情景/语义/程序)、存取方式;
- 短期记忆 = 对话缓冲 + 上下文窗口,滑窗最简、摘要更强;
- 长期记忆 = 嵌入 + 向量库 + top-k,检索按语义而非字面;
- 记忆条目要带重要度 / 过期 / 溯源元数据;
- 何时写入比怎么存储更难------用「策略触发 + 模型提取 + 重要度打分」;
- 读快写慢:写路径走「记忆线程」异步提炼,读路径多路(短期/向量/结构化)按需拼装;
- 记忆要透明可控:生产级记忆(ChatGPT/Claude/Mem0)都是用户可见、可删的独立层;
- 去重与一致性是质量底线------重复/矛盾/过期的脏记忆比没有记忆更糟;
- RAG 与 Agent 记忆共用技术栈,区别在内容来源与写入方;
- 遗忘与删除是记忆系统的一等公民,尤其是隐私合规;
- 让记忆「工具化」,Agent 就能自主管理自己的记忆(MemGPT 思路);
- Agentic RAG 让检索成为 Agent 的自主决策------查询改写 / 多跳检索 / 迭代检索 / 充分性判断;但它更贵,要设检索次数上限防止跑偏;
- 知识图谱记忆补上向量记忆的短板(多跳推理 / 精确关系),先向量后图, 用评测驱动引入;
- RAG 要评测:召回率 + 答案正确率 + 引用准确率 + 无证据拒答率, 别用「感觉好」代替「可量化」。
下一章,从「存什么」走向「怎么装配」------上下文工程。