Agent 记忆压缩通常有哪些方法?
上下文窗口(Context Window)再大也是有限资源,而且费用和延迟随 token 线性甚至超线性上涨。 记忆压缩要解决的是同一个矛盾:保留足够的信息让模型不犯傻,同时把 token 压到预算之内。压缩不是简单截断,而是有损但可控的信息提炼。
1. 结论:五类方法,按"信息损失"排序
| 方法 | 压缩比 | 信息损失 | 主要风险 | 典型场景 |
|---|---|---|---|---|
| 滑动窗口 / 截断 | 高 | 大(直接丢) | 忘掉早期约束 | 闲聊、短任务 |
| 增量摘要(Summary) | 5~20 倍 | 中(丢细节保结论) | 摘要漂移、越摘越虚 | 长会话 |
| 结构化抽取(Slots) | 10~50 倍 | 小(只留有字段的) | 字段设计不全就漏 | 表单、客服、偏好 |
| 检索替代全量(RAG) | 视 top-k | 可控(按需取) | 召回不到就等于没有 | 知识型长文档 |
| 工具输出压缩 | 10~100 倍 | 中(丢原始细节) | 需要回溯时找不到 | 日志、大 JSON |
工程上通常组合使用:结构化抽取优先,摘要兜底,检索补充,原始数据落盘留后路。
2. 机制:一条压缩管线
┌──────────── 预算 B = 8000 tokens ────────────┐
新消息 ──▶ ① 分类 ──▶ 关键事实? ──是──▶ ② 结构化槽位(永久保留,极小)
│ │
│ 否
▼
③ 写入滚动缓冲 ──▶ 超阈值? ──是──▶ ④ 摘要最旧一段(保留决策与未完成项)
│
▼
⑤ 溢出内容落盘,回填摘要 + 引用 id
│
▼
⑥ 按预算装配:系统提示 > 槽位 > 摘要 > 最近 N 轮 > 检索片段
装配顺序体现优先级:系统约束永远不能被压缩掉,最近几轮永远保留原文(模型对近处信息最敏感),中间部分才允许有损。
3. 结构化抽取:性价比最高的那一类
不要存"用户说他想在北京找一份后端工作,最好不加班",而是抽成字段:
SLOTS = """
从对话中抽取用户信息,只输出 JSON,没有提及的字段填 null:
{"city": null, "target_role": null, "avoid": [], "deadline": null}
"""
def distill(turns: str, llm) -> dict:
data = json.loads(llm(SLOTS + turns))
return {k: v for k, v in data.items() if v not in (None, "", [])}
# 输入:我在北京,想找后端,最好别加班,月底前要定下来
# 输出:{"city": "北京", "target_role": "后端", "avoid": ["加班"], "deadline": "月底"}
一条槽位十几个 token,却承载了原本两百 token 对话的核心信息,且天然支持去重与更新——新值直接覆盖旧值即可。
4. 增量摘要:注意"摘要漂移"
摘要最怕链式叠加:摘要的摘要的摘要,几轮之后变成一句无信息量的"用户在讨论工作"。
def compact(messages, keep_recent=6, max_tokens=8000, llm=None):
if count_tokens(messages) <= max_tokens:
return messages
recent = messages[-keep_recent:] # 最近若干轮永不压缩
old = messages[:-keep_recent]
# 关键:摘要基于"旧摘要 + 新增原文",且强制保留四类要素
prev = load_summary()
new = llm(f"""把下面内容并入已有摘要,务必保留:
1) 用户的硬性约束与偏好;2) 已做出的决定及理由;
3) 未完成的待办;4) 已知的失败尝试(避免重复踩坑)。
已有摘要:{prev}
新增内容:{render(old)}
输出不超过 400 字。""")
save_summary(new) # 原文落盘,按 id 可回溯
archive(old)
return [{"role": "system", "content": f"[历史摘要]\n{new}"}] + recent
两个细节决定成败:一是摘要必须结构化提示(明确要求保留哪几类要素),二是原文必须归档,否则一旦摘要丢了关键约束,就再也找不回来。
5. 常见追问
问:什么时候触发压缩? 按 token 使用率触发(如超过预算 70%),不要每轮都压。压缩本身是一次模型调用,频繁压缩既贵又容易丢信息。
问:压缩会不会破坏前缀缓存(Prompt Cache)? 会,而且这是容易被忽略的成本问题。前缀一变,缓存全失效。做法是把稳定内容(系统提示、槽位)放最前面,只在尾部追加;压缩时尽量只改尾部摘要块。
问:怎么发现压缩丢了关键信息? 两个手段:一是在评测集里放"长会话后仍能答对早期约束"的用例;二是在摘要提示里要求输出"本轮丢弃了什么",抽样人工核对。线上还可以监控"用户重复陈述同一约束"的比例,升高说明压缩过度。
问:摘要用小模型行吗? 可以,摘要属于"信息搬运"而非"推理",小模型足够,成本能降一个数量级。但要用同一套要素模板,并定期与人工摘要对比抽检。
问:多模态内容怎么压? 图片、音频不要反复重传,转成文字描述或摘要后存起来,原始文件用 URL 引用。多模态 token 通常比文本贵得多。
小结
记忆压缩的核心是分层:硬约束与槽位无损保留,中间历史摘要化,大块原始数据落盘 + 引用,最近若干轮保原文。判断压缩做得好不好,不看压了多少 token,而看"长会话后模型是否还记得早期约束、是否重复踩同一个坑"。