1516 字
约 5 分钟
22
记忆系统与上下文压缩
2026-10-13
无标签
记忆系统与上下文压缩:每一次请求,都带上该带的历史
系列:ToyAgent 智能体知识库 05/26
一句话核心:模型本身没有记忆。所谓记忆,是把历史重新放进上下文窗口——加上超限后的压缩策略。
为什么需要
先破除一个普遍误解:模型没有数据库、没有存储、不会"记住"你。它表现得有记忆,唯一原因是历史消息被重新放进了这次请求的上下文里。Step01 的 MVP 每轮只发一条 user 消息,所以模型"失忆"——不是缺陷,是它本来就什么都不知道。
一旦要支持多轮对话,就必须自己管理"每次请求带什么历史"。而上下文窗口是稀缺资源:虽然 128K 起步,但 Token 昂贵,且长上下文存在"中间遗忘"(lost in the middle)——关键信息埋在窗口中央时模型反而容易忽略。历史无限增长不可持续,必须压缩。
核心机制
记忆的全部秘密就是四行代码的核心思路——请求前把历史拼进消息列表:
public String chat(String input) {
history.add(Message.user(input));
// 历史超限 → 压缩:最旧两轮摘成一句话
if (history.size() > MAX_ROUNDS * 2) {
compress();
}
List<Message> context = new ArrayList<>();
context.add(Message.system(SYSTEM_PROMPT));
if (!summary.isEmpty()) {
context.add(Message.system("【历史摘要】" + summary));
}
context.addAll(history); // ← 记忆的全部秘密
String answer = model.chat(context);
history.add(Message.assistant(answer));
return answer;
}
两板斧(可并用)
| 策略 | 做法 | 代价 |
|---|---|---|
| 滑动窗口 | 只保留最近 N 轮 | 丢失早期信息 |
| 摘要压缩 | 旧对话摘成一条 system 消息 | 摘要本身有损 |
| 混合(本场景) | 滑窗 + 摘要并用 | 工程复杂度略高 |
混合策略的直觉:近处的细节用原文保真,远处的轮廓用摘要保命。它对应用户感知最合理——人也是记得"刚才说了什么",只模糊记得"很久之前聊过什么"。
graph TD
A["新用户输入"] --> B["追加进历史列表"]
B --> C{"历史超过上限?"}
C -->|否| E["组装上下文"]
C -->|是| D["旧轮次摘成一句 system 摘要"]
D --> E
E --> F["system 提示词 + 摘要 + 最近滑窗历史"]
F --> G["发给模型"]
G --> H["回答写回历史"]
图示:滑窗 + 摘要的混合记忆策略——请求前拼历史,超限时旧轮压缩成摘要,近处原文保真、远处轮廓保命。
三种记忆的分层
- 短期记忆:即对话历史,生命周期 = 一次会话;
- 长期记忆:跨会话持久化(向量库 / 数据库),使用时检索注入——技术上就是 RAG(见 07 场景);
- 工作记忆:ReAct 循环中当前任务的中间状态(Thought / Observation)。
举例:压缩前后的上下文对比
用户:我叫小傅哥,在学智能体开发。
助手:你好小傅哥!……
用户:ReAct 是什么?
助手:ReAct 是推理与行动交替的模式……
用户:我叫什么名字?在学什么?
(历史超过上限,触发压缩)
--- 压缩前 context ---
system: 系统提示词
user: 我叫小傅哥,在学智能体开发。
assistant: 你好小傅哥!……
user: ReAct 是什么?
assistant: ReAct 是……
user: 我叫什么名字?在学什么?
--- 压缩后 context ---
system: 系统提示词
system: 【历史摘要】用户自称小傅哥,正在学习智能体开发;已介绍过 ReAct 概念。
user: 我叫什么名字?在学什么?
→ 模型答:"你是小傅哥,正在学智能体开发。"
点【重置】清空历史后再问同一句,模型就真的忘了——这就是"记忆 = 上下文"最直接的实验证明。
生产视角(LangChain4j 自带这套机制):
// 滑动窗口 + 摘要,框架内置
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(20);
// 或 TokenWindowChatMemory.withMaxTokens(4096, tokenizer)
上下文工程
"往上下文里放什么、放多少、什么时候清掉"正在成为一门显学——Context Engineering。与提示词工程的分工:提示词工程决定模型怎么答,上下文工程决定模型看见什么。看见什么,决定它知道什么。生产事故里相当一部分"模型变笨"其实不是模型问题,而是上下文里塞了错误信息、或关键信息被压缩/裁剪掉了。
面试考点
- "模型是怎么记住上一轮对话的?"——它没记住。是应用层把历史消息重新放进本次请求,模型每次都在无状态地重新阅读全部上下文。
- "长期记忆和 RAG 的关系?"——长期记忆的"使用时检索注入"在技术上就是 RAG:把用户画像/历史结论向量化存库,检索 Top-K 拼进上下文。
- "滑窗和摘要怎么选?"——看信息时效性:强时效闲聊用滑窗足够;有需要长期保真的关键事实(用户偏好、已完成决策)用摘要或结构化存储。
生产化路径
- 用框架内置记忆组件(LangChain4j
ChatMemory),不要手写消息列表——但必须清楚它底层就是"拼历史 + 裁剪"。 - 压缩触发用 Token 计量而非消息条数:一条贴代码的 user 消息和一句寒暄差几十倍 Token,按条数裁剪不可控。
- 压缩要保关键事实:用户 ID、明确决策、未完成任务应在压缩时原样保留或结构化落库,不能指望摘要无损。
- 区分会话级与用户级:会话历史随会话销毁;用户级长期记忆(偏好、背景)另建存储,检索注入——本项目 learn 模块的三层记忆就是这一思路的落地。
评论
0 条
还没有评论,先写一条吧。