第 4 章 · 记忆系统
上下文工程(第 3 章)解决"一次会话内"的问题,记忆系统解决"跨会话"的问题:用户上周说的话、Agent 昨天犯的错、三个月积累的领域知识——这些必须活在窗口之外,按需召回。
4.1 记忆分类学
按内容性质分四类,每一类的读写模式完全不同:
| 类型 | 存什么 | 例子 | 写入方式 |
|---|---|---|---|
| 工作记忆 | 当前任务的活跃状态 | 笔记、计划、最近几轮 | 循环内自动维护 |
| 情景记忆(episodic) | 发生过的具体事件 | "用户 3 月 2 日抱怨过响应慢" | 会话结束或事件发生时提取 |
| 语义记忆(semantic) | 提炼出的事实与偏好 | "用户偏好简洁回答""部署目标是 k8s" | 从情景中归纳(可多次修正) |
| 程序性记忆(procedural) | 怎么做事的知识 | 技能、SOP、代码模板 | 显式编写或 PR 审核 |
两种最容易犯的分类错误:
- 把语义记忆当情景记忆存:存了一万条"某天用户说了某话"的原始记录,召回时全靠运气。正确做法是两层:情景层存事件(可追溯),语义层存归纳(可检索)——归纳有更新的机会,事件只有追加;
- 把程序性记忆当语义记忆存:把"怎么部署"写成一条条事实散落在记忆库里。程序性知识是流程,应该是结构化的技能文档(可版本化、可测试、可 PR 审),不是记忆检索的对象。
4.2 三种实现模式
文件式(file-based)
记忆 = 一个有结构的目录树。目录即分类,文件即条目,_meta.json 之类的索引文件记录描述与统计。
memory/
├── _index.md # 顶层目录索引(供渐进披露)
├── 用户偏好/
│ ├── _meta.json # 子目录元数据:描述、更新时间、条目数
│ └── 输出风格.md
├── 项目-x/
│ ├── _meta.json
│ ├── 架构决策.md
│ └── 已知问题.md
└── 归档/
Agent 通过文件工具(列目录 → 读索引 → 读具体文件)访问记忆,天然支持渐进披露(第 3 章):上下文里只放 _index.md,需要哪个分支再深入。
优点:结构清晰、可人审可 git 管理、零额外基础设施。缺点:检索靠目录导航(Agent 要多几次工具调用),规模大了之后依赖索引质量。这是多数产品记忆系统的最佳起点——Claude Code 的 CLAUDE.md/Memory 机制、大量个人助手的记忆实现都属此类。
向量式(embedding-based)
记忆条目 → embedding 向量 → 向量库;查询时把当前上下文 embed 成查询向量,召回最近邻。
def remember(text: str, meta: dict):
vec = embed(text)
store.upsert(vector=vec, payload={**meta, "text": text, "ts": now()})
def recall(query: str, k=5, min_score=0.75):
hits = store.search(embed(query), k=k)
return [h for h in hits if h.score >= min_score] # 相似度低宁可不召回
优点:语义召回("部署的事"能召回"上线流程"),规模无压力。缺点与纪律:
- 召回质量是玄学高发区:相似 ≠ 相关。必须设相似度阈值,宁可漏召回也不要塞进不相关记忆污染上下文;
- 写入门禁:Agent 自动写入的记忆必须去重与冲突处理("用户喜欢详细回答"和"用户喜欢简洁回答"并存是灾难),写入前先检索相似条目做 merge;
- 来源可追溯:每条记忆记着来源会话,用户要求"删除关于我的记忆"时要能删干净(合规硬要求)。
结构化式(structured / graph)
把记忆建成实体与关系(用户 —偏好→ 简洁;项目-x —使用→ k8s),支持多跳查询("用户在用过框架 X 的那个项目里踩过什么坑")。实现可以是图数据库,也可以是关系表 + 查询工具。
优点:精确、可推理。缺点:抽取(从对话里抽实体关系)与 schema 维护成本高。适用:实体关系本身是核心资产的场景(CRM 类助手、长期项目协作者)。
务实结论:文件式打底 + 向量检索增强是当前的最佳实践组合;结构化式只在实体关系密集的场景引入。
4.3 生命周期:写入、召回、遗忘、巩固
记忆系统是完整的数据生命周期管理,四个环节都要设计:
写入(write)。三个过滤器:
- 值得记吗——一次性上下文("帮我看下这个文件")不记;持久事实("我们生产环境是 k8s")记;
- 属于哪一层——按 4.1 分类;
- 与已有记忆冲突吗——先检索再写入,冲突时更新而非追加(并保留更新时间)。
召回(recall)。两条路径并行:
- 主动召回:每次会话开始,用当前任务做查询拉取 Top-K 相关记忆注入上下文(注意 3.1 的预算,注入 500-1500 token 为宜);
- 被动召回:给 Agent 一个
search_memory工具,它在需要时自己查。两条腿缺一不可——主动召回解决"该想起没想起",被动召回解决"临时需要"。
遗忘(forgetting)。没有遗忘的记忆库会单调膨胀,检索质量随规模下降,旧记忆还会与新风控冲突。工程手段:
- 时间衰减:每条记忆带权重
w = base * exp(-λ * days_since_last_access),召回排序时叠加权重; - 归档:低权重超过阈值的移入"归档"分区(不再自动召回,但可显式检索)——删除是危险操作,归档是安全操作;
- 显式失效:用户说"我搬去上海了"时应触发对旧地址记忆的失效标记,而不是静默并存两条矛盾记忆。
巩固(consolidation)。类比人类睡眠记忆巩固:后台任务周期性把情景层的高频/高价值内容归纳升级为语义层(三条"用户在周会前都要求提前看材料"→ 一条"用户习惯周会前预审材料"),并合并重复条目。巩固是让记忆库从"流水账"进化为"知识库"的关键环节,也是用户感知"这个助手越来越懂我"的直接来源。
4.4 记忆的失败模式
| 失败模式 | 现象 | 根因与防御 |
|---|---|---|
| 记忆污染 | 错误记忆反复出现,越纠正越多 | 写入无冲突检测;防御:写入前查重 + 归纳式更新 |
| 记忆幻觉 | Agent 声称记得从未发生的事 | 召回结果与本次生成混淆;防御:注入时标注"以下是历史记忆" |
| 过度召回 | 每次回复都牵扯八竿子打不着的旧事 | 无阈值/无权重排序;防御:min_score + 时间衰减 |
| 隐私事故 | 用户 A 的记忆出现在用户 B 的上下文 | 共享存储无租户隔离;防御:记忆按用户硬隔离,工具层强制过滤 |
| 记忆僵化 | 环境变了但 Agent 用旧认知行事 | 无遗忘/巩固;防御:时间衰减 + 用户可查看可删除记忆 |
记忆幻觉值得特别强调:记忆是注入上下文的"别人的话",模型可能把它当成自己此刻的认知。注入时的包装格式很重要:
[以下是与当前任务相关的历史记忆,供参考,可能过时:
- (2026-08-12, 来源:会话#a1b2) 用户的生产环境是 k8s
]
标注时间与来源,模型才能正确处理"记忆与现实冲突"的情况(以现实/用户最新陈述为准)。
4.5 评测你的记忆系统
记忆系统没有评测就没有迭代方向。三层指标:
- 召回指标:构造"记忆问答对"(写入记忆 X 后,N 轮对话外提相关问题),测命中率与误召回率;
- 端到端指标:带记忆 vs 不带记忆的对话质量对比(同任务双跑);长期一致性(跨会话约束遵守率);
- 健康指标:记忆库规模增长曲线、冲突检测触发率、衰减归档量——异常增长通常意味着写入过滤器失效。
实现作业
给第 2-3 章的 Agent 加一个文件式 + 向量混合的记忆系统:
- 会话结束时用 LLM 抽取"值得跨会话记住的事实"(写入过滤器),语义层落文件、情景层落向量库;
- 新会话开始时主动召回 Top-5 注入上下文(带时间来源标注),并提供
search_memory工具; - 实现时间衰减排序与"更新代替追加"的冲突处理;
- 写 10 个"隔轮问答对"验证召回命中率,并故意制造一条冲突记忆验证更新逻辑。
深入材料
- MemGPT / Letta 论文:arXiv 2310.08560(把 OS 虚存思想引入 LLM 记忆的鼻祖)
- Generative Agents(arXiv 2304.03442,斯坦福小镇:记忆流 + 反思 + 计划的完整实现,巩固思想的来源)
- Anthropic Claude Memory 功能说明(产品级文件式记忆的取舍)
读者留言
COMMENTS 暂无还没有留言,来说第一句?