本文从「Agent 为什么需要记忆」出发,讲清上下文记忆的底层原理、三层记忆结构、溢出处理策略、主流框架落地方式,以及工程上的权衡与常见坑。读完能理解:Agent 的"记忆"本质上不是模型记住了东西,而是一段编排逻辑决定"把什么塞进有限的上下文窗口"。
1. 一句话核心
大模型(LLM)是无状态的,它唯一能感知到的就是每次请求时喂给它的全部文本------即"上下文"。所谓 Agent 记忆,就是一段代码在每轮对话时,决定哪些历史进入这个容量有限的上下文窗口、进入多少、以什么形式进入。
2. 为什么需要"记忆"------无状态性
每次推理,LLM 都是从零开始读入输入,它不会自动记住上一轮说了什么,也不存在跨请求的内部状态。它能"看到"的只有:
- 系统提示(system prompt)
- 历史消息列表
- 当前用户输入
- 工具调用与结果(function/tool call)
这些文本的总 token 数受到上下文窗口上限约束(例如 8k / 32k / 128k)。因此:
放进去的,模型就"记得";被压缩、丢弃或没检索回来的,模型就"忘记"。
理解了这一点,后面所有记忆方案都只是在回答一个问题:大量历史,往哪放、怎么取舍、什么时候取回。
3. 记忆的三个层次
主流系统通常把记忆拆成三层:
| 层次 | 载体 | 特点 | 类比 |
|---|---|---|---|
| 工作记忆(短期) | 上下文窗口内的原始对话 + 工具结果 | 精确、完整,但最贵、最有限 | 人脑的"当下注意" |
| 长期记忆 | 向量库 / 数据库 / 文件等外部存储 | 近乎无限,靠检索召回,可能漏或错 | 人脑的"知识沉淀" |
| 反思 / 元记忆 | 总结、教训、偏好、目标等提炼信息 | 信息密度高,以摘要或结构化字段保存 | 人脑的"经验与自我认知" |
- 工作记忆:Agent 的"当下"全靠它,是最真实的状态。
- 长期记忆 :历史超出窗口后持久化到外部,需要时检索相关片段塞回窗口------这就是 RAG 在记忆场景的应用。
- 反思记忆:不是原始对话,而是 Agent 对过往的总结与偏好("用户不喜欢表格""上次这个方案跑挂了")。MemGPT 的 core memory、Reflexion 的反思缓存都属于此类。
4. 上下文溢出后的三种处理策略
对话越长,历史迟早超过窗口上限。此时有三类策略,通常组合使用:
4.1 滑动窗口(Sliding Window)
暴力截断,只保留最近 N 条,最旧的直接丢掉。
- 优点:实现最简单。
- 缺点:早期上下文被彻底遗忘。
4.2 摘要压缩(Summarization)
把较老的一段对话用 LLM 压缩成一小段摘要,用"摘要"替代"原文"继续留在窗口。
- 优点:保大意、省 token。
- 缺点:细节丢失,且摘要需要额外一次 LLM 调用。
4.3 长期存储 + 检索(Store & Retrieve)
把完整历史写入向量库,每轮根据当前问题做相似度检索,只取回最相关的几条。
- 优点:容积极大、扩展性最好,是目前主流方案。
- 缺点:依赖检索质量,可能召错或召漏。
循环总结:新信息进来 → 窗口不够 → 挤出去的历史或被丢弃、或被压成摘要、或被存档 → 需要时再检索回流。
5. 一次完整对话的记忆读写流程
markdown
用户输入
│
├─ 1) 组装候选记忆:当前输入 + 常驻 prompt(人设/指令)
│ + 短期原始对话(滑动窗口内)+ 检索回的长期相关片段 + 反思摘要
│
├─ 2) 拼成完整 prompt,调用 LLM
│
├─ 3) LLM 输出 + 可能发起工具调用,工具结果再回流传给 LLM
│
└─ 4) 收尾:本轮内容落入短期记忆
│ 超窗 → 触发滑动窗口丢弃 / 摘要压缩 / 写入长期存储(向量化)
└ 可选 → 提炼反思/偏好,更新元记忆
关键点:记忆的读(检索组装)发生在调用 LLM 之前,写(落库/压缩/丢弃)发生在调用之后。
6. 主流框架 / 项目落地对照
| 项目 / 框架 | 记忆实现方式 |
|---|---|
| LangChain / LlamaIndex | 提供可插拔 Memory 组件:ConversationBufferMemory(全量)、ConversationBufferWindowMemory(滑窗)、ConversationSummaryMemory(摘要)、VectorStoreRetrieverMemory(向量检索) |
| MemGPT / Letta | 把 LLM 比作操作系统:主上下文=内存,外部存储=磁盘,实现"换页 / 缺页调入",并显式写入反思类 core memory |
| AutoGPT / BabyAGI | 早期做法:把目标与中间结果写文件 / 向量库,下次读回,是显式外存化雏形 |
| 现代对话 / 多 Agent 产品 | 通常是「滑动窗口 + 摘要 + 向量检索」三者混合,人设/system prompt 常驻窗口最前 |
7. 记忆的分级(按作用范围)
除了按"时限"分层,工程上还常按作用范围分级,避免"一个向量库装所有东西":
- 会话级记忆:仅本次会话有效,随会话结束清空------即短期记忆。
- 用户级记忆(profile):跨会话持久的用户画像、偏好、习惯("记住我的时区""我是开发者")。
- 项目级 / 知识级记忆:某个项目、主题的长期知识库,供多会话复用。
- 全局 / 组织级:团队规范、产品文档等公共知识。
分层的好处:不同范围用不同存储与生命周期,检索时也按范围隔离,减少噪声。
8. 一个最小实现示例
以下是一个极简的「滑动窗口 + 摘要 + 向量检索」记忆循环骨架(TypeScript 示例,便于理解原理):
typescript
interface Turn {
user: string;
content: string;
}
interface Prompt {
system: string;
recent: Turn[];
retrieved: string[];
user: string;
}
interface Model {
generate(prompt: Prompt): Promise<string>;
summarize(text: string): Promise<string>;
}
interface VectorStore {
add(content: string): Promise<void>;
search(query: string, topK?: number): Promise<string[]>;
}
class MinimalAgent {
private shortTerm: Turn[] = []; // 近期原始对话(滑动窗口)
private summary = ''; // 早期对话的摘要
private readonly maxTurns: number;
constructor(
private readonly model: Model,
private readonly vectorStore: VectorStore,
maxTurns = 8,
) {
this.maxTurns = maxTurns;
}
async remember(turn: Turn): Promise<void> { // 写:新内容落入短期记忆
this.shortTerm.push(turn);
if (this.shortTerm.length > this.maxTurns) {
const expired = this.shortTerm.shift()!; // 滑动窗口:丢最旧
this.summary = await this.model.summarize( // 摘要压缩
`${this.summary} ${expired.content}`,
);
await this.vectorStore.add(expired.content); // 长期存储:向量化存档
}
}
async buildPrompt(userInput: string): Promise<Prompt> { // 读:组装候选记忆
const retrieved = await this.vectorStore.search(userInput, 5);
return {
system: `你是助手。历史摘要:${this.summary}`,
recent: this.shortTerm,
retrieved,
user: userInput,
};
}
async run(userInput: string): Promise<string> {
const prompt = await this.buildPrompt(userInput);
const answer = await this.model.generate(prompt);
await this.remember({ user: userInput, content: answer });
return answer;
}
}
对应关系:short_term(shortTerm)= 工作记忆,summary = 反思/摘要记忆,vector_store(vectorStore)= 长期记忆。三者以不同形式一起进入 prompt。
9. 关键权衡与常见坑
9.1 三个核心权衡
- 成本 vs 记忆:塞越多记越多,但注意力代价随长度(近似平方)增长,token 成本也线性上升,必须剪裁。
- 保真 vs 紧凑:原文最真但最贵,摘要最省但失真,检索最灵活但可能召错,工程上要按场景配比。
- 位置敏感 :模型对窗口开头和结尾记得最牢,中间容易被忽略("Lost in the Middle" 现象),因此常把关键指令/人设放最前、最新对话放最后。
9.2 常见坑
- 上下文污染:检索回的无关片段会干扰模型,甚至劫持指令(prompt injection),需要过滤与去重。
- 检索噪声:embedding 相似度不等于"有用",召回的片段需重排(rerank)或加阈值。
- 摘要滚雪球:摘要逐轮叠加会累积误差,越到后面越失真,可定期"重建摘要"。
- 记忆漂移/冲突:长期记忆里新旧信息矛盾时(如用户改了偏好),需要覆盖或冲突消解策略。
- 重复记忆:同一信息多次写入造成冗余,需去重。
- 遗忘策略缺失:只加不减的长期记忆会越来越大、越来越慢,需要定期清理或过期机制。
10. 总结
- LLM 无状态,上下文窗口=它全部可感知的输入,记忆=对窗口内容的编排。
- 记忆分工作 / 长期 / 反思三层,分别对应"当下 / 存档检索 / 提炼经验"。
- 窗口溢出后的三大策略:滑动窗口(丢弃)、摘要压缩(压原文)、长期存储+检索(外存回流),实际工程混合使用。
- 主流框架(LangChain、MemGPT/Letta、AutoGPT)只是把上述策略做成了不同粒度的组件。
- 落地时要兼顾 成本、保真、位置敏感,并防住污染、噪声、冲突、滚雪球等坑。
一句话收束:一个好的 Agent 记忆系统,本质是在"记住该记的"和"放得下、取得回"之间做动态平衡。