核心结论 :Agent 上下文越长越难管,根源是三个增长源------多轮对话历史、工具返回的大量结果、模型自己的思考链 。塞满了会怎样?要么报错超限,要么「注意力涣散」(长上下文里关键信息被稀释,模型开始漏看、走神)。解决它靠的不是一招,而是四类策略的组合 :① 丢弃 (滑动窗口,只留最近 N 轮)→ ② 压缩 (把历史摘要成一段话)→ ③ 卸载 (offloading,全文存外部、模型只看预览)→ ④ 检索 (用 ID / 向量按需把相关记忆拉回来)。「最近上下文怎么传」的答案是:系统提示常驻 + 最近 N 轮原样保留 + 更早的压缩成摘要 + 关键事实存外部可检索------这就是「短期窗口 + 长期记忆」的分层架构。
目录
-
- 一句话结论
-
- 上下文为什么会「太长」:三个增长源
-
- 四类压缩策略总览:丢弃 / 压缩 / 卸载 / 检索
-
- 策略一:滑动窗口------最近上下文怎么传
-
- 策略二:摘要压缩------把历史「折叠」成一段话
-
- 策略三:卸载 offloading------模型只看预览
-
- 策略四:检索召回------按需把记忆拉回来
-
- 组合打法:短期窗口 + 长期记忆的分层架构
-
- 完整可运行代码:一个上下文压缩管理器
-
- 对照表:症状 → 原因 → 解法
-
- 判断收尾
1. 一句话结论
先建立一个直觉 🎭:Agent 的上下文像「一张书桌」 ------你能同时摊开的资料有限,摊太多就找不到东西、还会把水杯撞倒(关键信息被淹没)。聪明的做法不是「换一张无限大的桌子」(上下文窗口再大也会满),而是只把正在用的几份放桌上,其余的归档到抽屉(外部存储),需要时再取出来。
所以记住这套框架:
- 三个增长源:对话历史、工具结果、思考链------它们是上下文膨胀的三大元凶。
- 四类策略:丢弃(滑动窗口)、压缩(摘要)、卸载(offloading)、检索(召回)。
- 最近上下文怎么传:系统提示常驻 + 最近 N 轮原样 + 更早的摘要 + 关键事实外部存。
一句话:上下文管理不是「删内容」,而是「分层」------最近的留在桌上,久远的归档到抽屉,需要时按编号取。
2. 上下文为什么会「太长」:三个增长源
先搞清楚是什么把上下文撑爆的。Agent 跑一个长任务,上下文只在三处膨胀:
| 增长源 | 典型表现 | 特点 |
|---|---|---|
| 多轮对话历史 | 用户和 Agent 来回几十轮 | 越滚越大,且大部分轮次已无关 |
| 工具返回结果 | 读了 500 行文件、查了 20 条搜索结果 | 单次就很大,是「尖峰」 |
| 模型思考链 | 每步推理几千 token | 累积快,但对后续价值低 |
- 对话历史是「慢增长」:一轮一轮往上爬。
- 工具结果是「尖峰」:一次大返回就把上下文顶上去。
- 思考链是「高消耗低价值」:占地方,但对后续决策帮助有限。
用类比 🎭:这就像「健身后堆在玄关的东西」 ------购物袋(工具结果)一次就占一大块,换下的衣服(对话)一件件累积,汗湿的毛巾(思考链)又占地方又没用。你要做的不是「买个大玄关」,而是分类处理。
💡 我在Agent规则管理:让 Memory 做索引、让 Skill 存规则------上下文上限实测与分工方案里实测过上下文上限的实际表现,本文更聚焦「膨胀了怎么压」的具体策略。
3. 四类压缩策略总览:丢弃 / 压缩 / 卸载 / 检索
针对三个增长源,业界沉淀出四类策略,从「简单粗暴」到「精细可控」:
| 策略 | 做什么 | 优点 | 缺点 |
|---|---|---|---|
| 丢弃(滑动窗口) | 只保留最近 N 轮,更早的删掉 | 极简单、成本低 | 丢长期信息 |
| 压缩(摘要) | 把历史总结成一段摘要 | 保留语义、省空间 | 有信息损失、要额外 LLM 调用 |
| 卸载(offloading) | 全文存外部,上下文只放短预览 | 止住尖峰、可回溯 | 要管理外部存储 |
| 检索(召回) | 按需把相关记忆拉回上下文 | 精准、可无限扩展 | 依赖检索质量 |
- 丢弃是基础款:先保证不爆,再谈质量。
- 压缩是性价比款:一段摘要替掉十轮对话。
- 卸载是对付「工具尖峰」的利器。
- 检索是长期记忆的入口:把「无限记忆」变成可能。
💡 记住一个判断顺序:先用滑动窗口兜底 → 再用摘要压缩 → 大结果用 offloading → 长期事实用检索。四招组合,不是四选一。
4. 策略一:滑动窗口------最近上下文怎么传
「最近上下文怎么传」这个问题,最直接的答案就是滑动窗口(sliding window)。
- 原理:只保留最近 N 轮对话(或最近 K 个时间步),更早的直接丢弃。
- 实现:维护一个固定长度的消息队列,超过 N 就弹出最老的。
- 优点:实现极简,成本几乎为零,适合短期任务(客服对话、单轮调试)。
- 缺陷 :丢失所有长期信息------用户问「昨天你提到的那个方案」,如果被滑出窗口,模型就答不上来。
滚动窗口的典型实现,就是「传最近 N 轮」:
python
def keep_recent(messages, max_turns=10):
"""只保留最近 N 轮消息(滑动窗口)"""
return messages[-max_turns:]
⚠️ 纯滑动窗口是不够的------它解决了「不爆」,但解决了「记不住早期信息」。所以真实系统通常滑动窗口 + 摘要一起用(第 8 节讲)。
参考资料:
5. 策略二:摘要压缩------把历史「折叠」成一段话
滑动窗口会丢信息,摘要(summarization) 就是来补这个洞的:
- 原理:把较早的历史(或整段上下文)交给 LLM,压缩成一段「到目前为止发生了什么」的摘要。
- 做法:保留最近 N 轮原样 + 把更早的压成一段摘要放在最前面。
- 优点 :保留语义,十轮对话能压成两三句,信息密度高。
- 代价:有信息损失(细节会丢),且摘要本身要一次 LLM 调用。
业界把这类做法叫 compaction(压缩) ------LangChain 的说法很形象:offloading 止住小尖峰,summarization 把整体上升的曲线「重置」下来。
用类比 🎭:摘要像「会议纪要」------两个小时的会压成一页要点,你不需要逐字回忆,只要记住「决定了什么、下一步谁做什么」。
参考资料:
6. 策略三:卸载 offloading------模型只看预览
摘要解决「历史太多」,卸载(offloading) 解决「单次结果太大」:
-
原理 :把大块内容(长文件、长搜索结果、完整命令输出)存到外部 (文件 / 数据库),上下文里只放一个短预览 + 引用。
-
效果:模型看到的是「已读取 config.py(128 行,摘要:端口是 8080)」而不是 128 行全文。
-
好处 :瞬间止住上下文尖峰;需要全文时再按引用取回。
-
代价:要管理外部存储,且模型要「知道去哪取」。
-
参考 ARC(Addressable Recall Compaction)的做法:把工具观察存进append-only、ID 可寻址 的日志,压缩时把旧观察替换成紧凑引用,Agent 之后可以用这些 ID 请求原内容,而不用重新执行工具。
用类比 🎭:offloading 像「把大衣存到衣帽间」------你手上只拿一张号码牌(引用),需要穿时凭号码去取,而不是一直抱着大衣逛商场。
参考资料:
- Addressable Recall Compaction for Long Context-Window Control -- arXiv ⭐值得阅读
- Compaction -- Microsoft Learn
7. 策略四:检索召回------按需把记忆拉回来
前三种策略都在「省」,检索(retrieval) 在「按需取回」,这是长期记忆的关键:
- 原理:把重要信息存进外部记忆库(向量库 / 结构化存储 / ID 日志),需要时按相关性检索,把 top-k 拉回上下文。
- 两种召回方式 :
- 向量检索:按语义相似度找(问「上次说的部署方案」→ 拉回相关片段)。
- ID 可寻址召回:按明确 ID 取(ARC 的做法,比相似度更精准、无歧义)。
- 优势:记忆容量「无限」------上下文只放当前需要的那部分,剩下都在库里。
SimpleMem 的做法更进一步,用三段式处理长期记忆:① 语义结构化压缩 (把原始对话提炼成独立记忆单元)→ ② 递归整合 (把相关单元组织成更高层抽象)→ ③ 自适应检索(按查询动态调整召回范围)。
用类比 🎭:检索像「图书馆查书」------书架(外部存储)能装无数本书,你不需要把它们全搬到桌上,只要按索引(向量/ID)借出你要的那几本。
参考资料:
8. 组合打法:短期窗口 + 长期记忆的分层架构
四类策略不是四选一,而是分层组合。这直接回答了「最近上下文怎么传 + 怎么保持记忆」:
短期层(最近上下文怎么传):
- 系统提示常驻:永远放在最前面,不被裁剪。
- 最近 N 轮原样保留:滑动窗口,保证「刚才聊了什么」不丢。
- 关键状态摘要常驻:当前任务目标、已完成的步骤,压成一段放在窗口前。
长期层(怎么保持记忆):
- 工具大结果 → offloading:全文存外部,上下文只留预览 + 引用。
- 久远对话 → 摘要 compaction:压成纪要,替换掉原始轮次。
- 关键事实 → 检索库:存进向量库 / ID 日志,按需召回。
一个可用的「分段上下文」结构长这样:
| 位置 | 内容 | 策略 |
|---|---|---|
| 最前 | 系统提示 + 角色 | 常驻,永不裁剪 |
| 次前 | 任务目标 + 进展摘要 | compaction,定期更新 |
| 中间 | 检索回来的相关记忆 | retrieval,按需注入 |
| 最新 | 最近 N 轮对话 | sliding window,原样保留 |
💡 一句话记住这套分层:最前永远在(系统提示)+ 目标压成段(摘要)+ 旧事存抽屉(外部)+ 最近原样带(窗口)+ 需要再召回(检索)。
9. 完整可运行代码:一个上下文压缩管理器
下面是一段复制粘贴就能跑的 Python 代码,实现一个「四策略合体」的上下文管理器:滑动窗口保留最近 N 轮、超长内容 offloading 到外部存储、把更早的对话摘要成一段、并支持按 ID 召回。运行无需第三方库。
python
# 上下文压缩管理器:滑动窗口 + offloading + 摘要 + 召回
class ContextManager:
def __init__(self, max_turns=5, max_content_len=100):
self.system_prompt = "你是一个助手。" # 系统提示:常驻不裁
self.recent = [] # 最近 N 轮(滑动窗口)
self.max_turns = max_turns
self.max_content_len = max_content_len
self.summary = "" # 历史摘要
self.archive = {} # 外部存储(offloading)
self._id = 0 # 归档 ID 计数器
def offload(self, content):
"""卸载:超长内容存外部,返回引用预览"""
if len(content) <= self.max_content_len:
return content # 不长,直接保留
self._id += 1
ref = f"<archive#{self._id}>" # 生成可寻址引用
self.archive[self._id] = content # 全文存外部
return f"{ref}(预览:{content[:30]}...,共 {len(content)} 字)"
def add(self, role, content):
"""加一条消息:先 offloading,再滑窗,超窗则摘要"""
content = self.offload(content) # 大内容先卸载
self.recent.append({"role": role, "content": content})
if len(self.recent) > self.max_turns: # 超过窗口
oldest = self.recent.pop(0) # 弹出最老的
# 摘要:把弹出的内容压进 summary(真实场景用 LLM 生成)
self.summary += f"[{oldest['role']}] {oldest['content'][:20]};"
def recall(self, ref_id):
"""召回:按 ID 从外部存储取回全文"""
return self.archive.get(ref_id, "未找到")
def build_context(self):
"""组装最终传给模型的分段上下文"""
return [self.system_prompt] + \
([f"历史摘要:{self.summary}"] if self.summary else []) + \
[f"{m['role']}: {m['content']}" for m in self.recent]
if __name__ == "__main__":
cm = ContextManager(max_turns=3, max_content_len=50)
# 加 5 条消息(其中一条是超长工具结果)
cm.add("user", "帮我检查配置文件")
cm.add("tool", "port = 8080\n" * 30) # 超长结果 → 卸载
cm.add("assistant", "已读取配置,端口是 8080")
cm.add("user", "改成 9090")
cm.add("assistant", "已修改为 9090")
for line in cm.build_context(): # 打印组装后的上下文
print(line)
print("召回 #1 长度:", len(cm.recall(1))) # 按 ID 取回全文
怎么读这段代码 :offload() 是第 6 节「卸载」------超过 50 字的内容存进 archive,上下文只留 <archive#1> 引用 + 预览,止住尖峰;add() 里「超过 max_turns 就弹出最老的并压进 summary」是滑动窗口 + 摘要的组合(第 4、5 节);build_context() 组装出的正是第 8 节的分段结构:系统提示 → 历史摘要 → 最近 N 轮;recall() 是第 7 节「按 ID 召回」。把「用规则压摘要」换成「调 LLM 生成摘要」,就是一套能上生产的上下文管理器。
10. 对照表:症状 → 原因 → 解法
| 症状 | 原因 | 解法 |
|---|---|---|
| 请求报错「超出上下文上限」 | 历史 + 工具结果总长超窗口 | 滑动窗口兜底 + 摘要压缩 |
| 长对话里模型「忘了」早期信息 | 滑动窗口把早期轮次滑出去了 | 早期内容摘要成纪要常驻 |
| 读一个大文件后上下文爆满 | 单次工具结果太大(尖峰) | offloading:全文存外部,只留预览 |
| 关键事实隔几轮就「想不起来」 | 没有长期记忆层 | 关键事实存检索库,按需召回 |
| 模型在长上下文里注意力涣散 | 无关信息太多稀释了关键信息 | 精简上下文,只留当前需要的 |
| 摘要后细节丢了、答不准 | 摘要压缩有信息损失 | 细节用 ID 归档、按需召回原文 |
11. 判断收尾
什么时候该上这套分层方案 :你的 Agent 要跑长任务、多轮对话、或频繁读大文件,上下文必然会膨胀------这时「滑动窗口 + 摘要 + offloading + 检索」的组合几乎是刚需,否则迟早撞上上限或注意力涣散。什么时候可以简化 :任务很短(几轮就结束)、或上下文远没到上限------只用一个滑动窗口就够,别为没发生的问题过度设计。判断标准只有一条:你的 Agent 会不会长期跑、且信息量持续增长------会,就上分层;不会,一个滑动窗口足矣。
本专栏《AI-Agent 工具》从 Agent 记忆、上下文管理、工具调用一路讲到架构选型与评测,每篇配可直接运行的代码,持续更新。想在 Agent 实战里少踩坑,可在专栏页订阅。
参考资料(综合):
- Context Engineering with Deep Agents: How To Remember Without Keeping Everything -- LangChain ⭐值得阅读
- Addressable Recall Compaction for Long Context-Window Control -- arXiv ⭐值得阅读
- SimpleMem: Efficient Lifelong Memory for LLM Agents -- arXiv ⭐值得阅读
- Compaction -- Microsoft Learn
- 我们来说说 Agent 记忆压缩通常有哪些方法? -- CSDN