Agent 开发进阶(六):上下文不是越多越好,如何实现智能压缩
本文是「从零构建 Coding Agent」系列的第六篇,适合想让 Agent 处理长对话、长任务的开发者。
先问一个问题
你有没有遇到过这种情况:
- 让 Agent 读一个大文件,上下文直接爆了
- 跑一条长命令,输出塞满了整个对话
- 多轮任务推进后,Agent 突然"失忆"了
这背后的原因很简单:上下文窗口是有限的,而任务产生的文本是无限的。
语言模型的「上下文膨胀」问题
当你的 Agent 越来越强大时,它会产生越来越多的中间结果:
- 读一个大文件,会塞进很多文本
- 跑一条长命令,会得到大段输出
- 多轮任务推进后,旧结果会越来越多
如果没有压缩机制,很快就会出现这些问题:
- 注意力被淹没:模型注意力被旧结果淹没,分不清主次
- 成本爆炸:API 请求越来越重,越来越贵
- 任务中断:最终直接撞上上下文上限,任务被迫中断
这就是上下文压缩要解决的核心问题:怎样在不丢掉主线连续性的前提下,把活跃上下文重新腾出空间。
上下文压缩的核心设计:三层策略
用一个图来表示上下文压缩的工作原理:
tool output
|
+-- 太大 -----------------> 保存到磁盘 + 留预览
|
v
messages
|
+-- 太旧 -----------------> 替换成占位提示
|
v
if whole context still too large:
|
v
compact history -> summary
关键点只有三个:
- 大结果先落盘:太大不直接塞进上下文,写磁盘只留预览
- 旧结果先缩短:替换成简短占位,不一直原样保留
- 整体过长再摘要:生成一份保持连续性的摘要
几个必须搞懂的概念
上下文窗口
模型这一轮真正能一起看到的输入容量,不是无限的。
活跃上下文
当前这几轮继续工作时,最值得模型马上看到的那一部分,不是历史上出现过的所有内容。
压缩
不是 ZIP 压缩文件,而是:
用更短的表示方式,保留继续工作真正需要的信息。
最小实现
第一步:大工具结果先写磁盘
PERSIST_THRESHOLD = 5000
def persist_large_output(tool_use_id: str, output: str) -> str:
if len(output) <= PERSIST_THRESHOLD:
return output
stored_path = save_to_disk(tool_use_id, output)
preview = output[:2000]
return (
"<persisted-output>\n"
f"Full output saved to: {stored_path}\n"
f"Preview:\n{preview}\n"
"</persisted-output>"
)
def save_to_disk(tool_use_id: str, output: str) -> str:
output_dir = Path(".task_outputs/tool-results")
output_dir.mkdir(parents=True, exist_ok=True)
stored_path = output_dir / f"{tool_use_id}.txt"
stored_path.write_text(output)
return str(stored_path)
这一步的关键思想是:
让模型知道“发生了什么”,但不强迫它一直背着整份原始大输出。
第二步:旧工具结果做微压缩
def collect_tool_results(messages: list) -> list:
results = []
for msg in messages:
if isinstance(msg.get("content"), list):
for block in msg["content"]:
if isinstance(block, dict) and block.get("type") == "tool_result":
results.append(block)
return results
def micro_compact(messages: list) -> list:
tool_results = collect_tool_results(messages)
for result in tool_results[:-3]:
result["content"] = "[Earlier tool result omitted for brevity]"
return messages
这一步不是为了优雅,而是为了防止上下文被旧结果持续霸占。
第三步:整体历史过长时,做一次完整压缩
def compact_history(messages: list) -> list:
summary = summarize_conversation(messages)
return [{
"role": "user",
"content": (
"This conversation was compacted for continuity.\n\n"
+ summary
),
}]
def summarize_conversation(messages: list) -> str:
summary_parts = [
"Context was compacted due to length.",
"Key information to preserve:",
"- Current task objective",
"- Completed key actions",
"- Modified/viewed files",
"- Key decisions and constraints",
"- Next steps to take"
]
return "\n".join(summary_parts)
这里最重要的不是摘要格式多么复杂,而是要保住这几类信息:
- 当前目标是什么
- 已经做了什么
- 改过哪些文件
- 还有什么没完成
- 哪些决定不能丢
第四步:在主循环里接入压缩
def agent_loop(state):
while True:
state["messages"] = micro_compact(state["messages"])
if estimate_context_size(state["messages"]) > CONTEXT_LIMIT:
state["messages"] = compact_history(state["messages"])
state["has_compacted"] = True
response = call_model(...)
...
新手最容易犯的 5 个错
1. 以为压缩等于删除
# ❌ 错误
messages = messages[-10:] # 直接砍掉前面的,断了连续性
# ✅ 正确
messages = compact_history(messages) # 生成摘要,保留关键信息
不是,压缩是把"不必常驻活跃上下文"的内容换一种表示。
2. 只在撞到上限后才临时乱补
更好的做法是从一开始就有三层思路:
- 大结果先落盘
- 旧结果先缩短
- 整体过长再摘要
3. 摘要只写成一句空话
# ❌ 错误
summary = "用户和AI聊了很久"
# ✅ 正确
summary = """
Context was compacted due to length.
Key information preserved:
- Current task: Review code for PR #123
- Completed: Read 5 files, identified 3 security issues
- Modified files: src/auth.py, src/utils.py
- Next: Generate review report
"""
如果摘要没有保住文件、决定、下一步,它对继续工作没有帮助。
4. 把压缩和 memory 混成一类
- 压缩解决的是:当前会话太长了怎么办
- memory解决的是:哪些信息跨会话仍然值得保留
5. 一上来就给初学者讲过多产品化层级
先讲清最小正确模型,比堆很多层名词更重要。
压缩后,真正要保住什么
压缩不是"把历史缩短"这么简单。
真正重要的是:让模型还能继续接着干活。
所以一份合格的压缩结果,至少要保住下面这些东西:
| 必须保留的信息 | 说明 |
|---|---|
| 当前任务目标 | 用户想让 Agent 做什么 |
| 已完成的关键动作 | 做到哪一步了 |
| 已修改的文件 | 改了什么,防止重复 |
| 关键决定与约束 | 哪些约束不能违反 |
| 下一步应该做什么 | 继续推进的方向 |
为什么这很重要
因为 Agent 之所以能「持续干活」,不是因为它记忆力好。
而是因为系统持续维护着活跃上下文的预算,把真正重要的信息留在窗口里。
就像人一样:工作记忆是有限的,聪明人会把不重要的信息暂时记在纸上,只把关键的留在脑子里。
主循环的新责任
从这一章开始,主循环不再只是:
- 收消息
- 调模型
- 跑工具
它还多了一个很关键的责任:管理活跃上下文的预算
也就是说,agent loop 现在同时维护两件事:
任务推进 <--> 上下文预算
这也和后面的机制紧密联动:
- memory 决定什么信息值得长期保存
- prompt pipeline 决定哪些块应该重新注入
- error recovery 处理压缩不足时的恢复分支
下一章预告
有了上下文压缩,你的 Agent 可以在更长的会话中稳定工作。下一章我们将探讨如何让 Agent 具备自我纠错能力,在执行失败时能够自动恢复和重试。
一句话总结:上下文压缩的核心,不是尽量少字,而是让模型在更短的活跃上下文里,仍然保住继续工作的连续性。
如果觉得有帮助,欢迎关注,我会持续更新「从零构建 Coding Agent」系列文章。