「我的 Agent 昨天答过的问题,今天又忘了。」这句话里的「忘了」,其实是两个完全不同的东西被当成了一件事。模型本身不记任何东西,每次调用都是无状态的:你所谓「记住」,是系统在每次请求前把该给的内容重新塞进上下文窗口。所以 Agent 记忆从来不靠模型,靠的是外面那套「存下来、找回来、塞进去」的工程。

判断放开头:先分清忘了的是「这次对话里说过的」还是「上次会话留下的」,两者答案完全不同,修法也完全不同。

短期缓冲就是上下文窗口

短期记忆的全部,就是上下文窗口。系统提示词、到目前为止的对话、贴进来的文档、工具跑出来的结果、正在生成的回答,全都挤在同一个窗口里竞争空间。窗口满了,最旧的内容让位给最新的,会话一结束什么都不剩。它的本质是「租」来的工作区,每次调用都要把历史重新发一遍,输入按次计费,而不是存一次用到底。

短期侧的优化只有三种:滑动窗口(只留最近 N 轮)、摘要压缩(把旧轮次压成一段摘要)、结构化草稿(把关键实体抽成字段钉在 prompt 里)。三者都在决定「窗口里装什么」,管不住「掉出窗口之后去哪」。

长期存储在外面

长期记忆活在模型外面,最常见是向量库,放的是应该活过这次会话的东西:用户偏好、既往事件、领域知识、流程步骤。它跟 RAG 一个形状:写入时把事实抽出来做向量,查询时按相似度召回最相关的几条,塞回窗口。存是任务的一半,找回是另一半,塞进窗口还要考虑窗口有限。

长期记忆最容易做错的是写入那一步——不是把什么都存进去,而是存进去之前先筛:只保留有长期价值的,过时的、被推翻的、可以合并的,要么删要么合并。一个没维护的存储,比没有存储更糟,因为它会把过期的事实源源不断地召回出来污染答案。

记忆该分几层

落到实现上,生产系统很少只用一个库,而是按用途分几层:profile 存用户稳定属性(名字、角色、时区、偏好单位),几乎每次都全量进上下文;会话摘要存更早轮次的浓缩版,定时刷新;episodic 存「上次会话发生了什么」这类事件记录,供「这事我们聊过」的召回;semantic 存按语义索引的领域文档,就是经典 RAG 库;procedural 存学到的做法(好用的工具序列、能跑通的代码片段),常以指令形式进系统提示。分层的好处是每层各管各的写入和读取策略,而不是把所有东西一股脑倒进一个向量库,指望一次检索全找回来。

两个最常见的误区

第一个误区是把长上下文当记忆。窗口越大,越容易让人以为「容量解决了记忆」。但容量不等于组织——把什么都塞进一个大窗口,会召回互相矛盾的历史、让旧行为压过新判断、用无关内容挤掉关键内容。窗口大了,如果没做筛选,记忆不是更可靠,而是更不可靠。

第二个误区是把向量库当万能记忆。向量检索是「模糊语义匹配」,只能召回语义相近的内容,不保证事实准确。用户的姓名、生日、过敏史、明确的禁忌这些强事实、零容错数据,用向量检索必然有召回错误的风险。这类数据该走结构化存储,用精确键值查,而不是靠相似度猜。语义相似不等于事实正确,这是长期记忆落地里翻车最多的一处。

一个可复现的做法

最小可用的长期记忆,把「强事实」和「语义记忆」分开放:强事实进结构化存储精确查,语义记忆进向量库模糊召回。下面这段代码只做结构化那一路,演示怎么把零容错事实存下来、精确读出来:

# memory.py —— 强事实走结构化存储,精确读,不靠相似度猜
import json
from pathlib import Path

def load_facts(path: str) -> dict:
    p = Path(path)
    return json.loads(p.read_text()) if p.exists() else {}

def save_fact(path: str, key: str, value: str) -> None:
    facts = load_facts(path)
    facts[key] = value
    Path(path).write_text(json.dumps(facts, ensure_ascii=False))

def recall(path: str, key: str):
    return load_facts(path).get(key)

用的时候,把用户说的关键约束抽出来 save_fact 存进 facts.json,下次会话按 key 精确 recall,命中率是 100%,不靠向量猜。语义类的、需要模糊匹配的内容,再单独走向量库,两条路别混。

怎么判断哪层坏了

一个很实用的诊断:如果系统忘了「这次对话早先说过」的东西,坏在短期层,修法是重述约束或重开一个干净的会话;如果忘了「上次会话确立」的东西,那长期记忆从一开始就没建,再怎么换措辞也补不回来。两个问题从外面看一模一样,但共享的解法是零。先定位是哪一层,再动手,别对着「忘了」这个词瞎修。