记忆不等于上下文
很多人把"记忆"和"上下文"当成一回事,于是记忆系统的第一版通常是:把所有历史消息存起来,下次全塞进上下文。
这在两个月内一定出问题——上下文越塞越长、成本线性上升、模型对中间部分的注意力下降(见《上下文是有限预算》)。而且它混淆了两种性质完全不同的东西。
1. 三个层次,别混为一谈
| 层次 | 载体 | 生命周期 | 写入时机 |
|---|---|---|---|
| 工作记忆 | 模型上下文窗口 | 单次请求 | 直接送入 |
| 会话记忆 | checkpointer / 会话缓存 | 单次会话 | 每轮结束 |
| 长期记忆 | 事实库 / 知识图谱 | 跨会话 | 异步抽取 |
三层解决的是三个不同的问题:工作记忆管"这次调用看到什么",会话记忆管"这个会话里进行到哪了",长期记忆管"关于这个用户,系统知道什么"。
它们不能互相替代。把长期记忆全塞进上下文,是用最贵的方式做了一件最不该做的事;只做会话记忆,用户每次回来都像第一次见面。
2. 长期记忆和 RAG 是两条独立的路
这是最容易混淆的一组概念:
RAG 取的是外部静态语料。 文档、手册、知识库——由人写入,不随 Agent 行为变化,对所有用户大体相同。
记忆存的是动态经验。 由 Agent 自己写入,来自与用户的实际交互,按用户隔离,会随着交互增长。
一个做客服的 Agent:产品手册走 RAG(所有人一样),"这位用户上次反馈过什么问题、偏好邮件还是电话、上次的问题解决了没有"走记忆(因人而异)。
生产系统通常两条路都要有,并且在上下文组装阶段汇合。把它们塞进同一个库、用同一套检索,是常见但错误的设计——因为两者的写入频率、隔离要求、过期策略完全不同。
3. 写什么,比怎么存更重要
长期记忆最常见的失败是什么都存。存了一堆之后,检索时噪声淹没信号,反而比没有记忆更糟。
值得存的只有三类:
事实。 关于用户或任务的稳定信息(称呼、时区、使用的产品型号、明确表达过的约束)。
偏好。 反复出现的行为倾向("回复尽量简短""不要主动发邮件")。偏好要有多次证据才写入,一次的行为不算偏好。
决策与结果。 之前做过什么决定、结果如何。这决定了下次遇到同类情况时不用重复试错。
不该存的:过程细节(工具调用的原始输出)、一次性的临时状态、以及任何能从系统里实时查到的东西(能查就不要存——存下来就会过期,过期的记忆比没有记忆更有害)。
4. 写入要异步,抽取要有结构
记忆写入放在请求的关键路径上会拖慢响应。标准做法是对话轮次结束后异步抽取:
一轮对话结束
→ 抽取:这段对话里有哪些值得记住的事实/偏好/决策?
→ 去重与合并:和已有的记忆冲突吗?是更新还是新增?
→ 打分:重要性与置信度
→ 写入存储
抽取这一步要用结构化输出来做(见《结构化输出与自愈》),输出的是若干条带类型和来源的记忆条目,而不是一段自由文本。自由文本的记忆在检索时无法按类型过滤,也无法做冲突检测。
异步方案有一个必须处理的边界:写后读一致性。用户刚说"以后都用中文回答",如果这条记忆还在队列里没落库,下一轮对话读不到它,用户会认为"它根本没记住"。三种处理,按可靠性排:
- 会话内直写:当前会话产出的记忆,先在内存里挂一份,下一轮拼接时优先读它,落库后移除
- 写屏障:轮次结束到下一次请求之间等抽取完成(通常几百毫秒),代价是首字延迟
- 显式确认:写入成功后给用户一个轻提示("记住了"),没写成功就不提示
队列失败也要有退路:抽取失败不能静默丢弃,至少进重试队列并记录失败率。记忆是"越用越值钱"的资产,但一条错的记忆比没有更糟——所以重试要有上限,超过就丢弃并告警。
4.1 冲突与过期
同一件事用户可能说过两次不同的话。处理规则要提前定:
后说覆盖先说(对状态类事实,比如"我现在用的是 X 型号")。
保留多条并标记时间(对观点类,比如预算、偏好,可能只是变了主意,也可能是场景不同)。
过期机制。 记忆要有重要性和时间戳,长期不被检索到的条目逐渐衰减淘汰。没有淘汰机制的记忆库,半年后会变成一堆自相矛盾的垃圾。
5. 检索:什么时候把记忆取回来
不要默认全量注入。 每条记忆都占上下文预算,全量注入等于又把第一版错误做了一遍。
做法和其他检索一样:按当前查询做语义检索,只取相关的几条。检索策略上常见的三种:
- 相似度 + 时间衰减 —— 最近的记忆优先,适合对话型 Agent
- 相似度 × 重要性 —— 高价值记忆即使语义稍远也能浮上来
- 混合检索 —— 记忆里常有人名、订单号这类精确词,单纯向量检索容易漏,可以复用《混合检索与 RRF 融合》里的那套做法
6. 两条安全纪律
记忆按用户(和租户)严格隔离。 这不是优化项,是底线。一条跨用户泄露的记忆,比一次错误的回答严重得多。隔离要做在存储层(分区或独立集合),而不是检索后过滤。
写入记忆是特权操作。 间接提示注入的目标之一就是往记忆里塞假信息——因为它持久、且影响后续所有决策。所以:
- 记忆写入要有校验,不能是模型随手写什么就存什么
- 高风险场景里,记忆写入要走审批(见《护栏要做在基础设施层》里的 Rule of Two)
- 用户要能看到、能改、能删自己的记忆。这既是合规要求,也是最有效的纠错手段
7. 用现成的还是自己写
开源侧已经有成熟的记忆层(Mem0、Zep、Letta 等),它们把抽取、去重、重要性打分、检索都做好了。自己从零写一套,通常要投入数周,而且容易在去重和衰减这两个环节做得不完整。
务实的选择是:用现成方案提供 API 层,但把存储换成自己的库(pgvector 或已有的向量库)。这样既省掉了最麻烦的部分,又保住了数据不出自己的边界。
8. 一个判断标准
问三个问题:
- 用户第二次来的时候,系统记得上次聊过什么吗?
- 用户能不能看到系统记住了什么,并且删掉其中一条?
- 三个月前的记忆,现在还在污染检索结果吗?
第一个答不了,说明记忆没做;第二个答不了,说明安全和合规有缺口;第三个答不了,说明缺少过期机制。三个都答得了,这套记忆才算完整。