第 6 章 上下文压缩与精选:装不下怎么办
把最值得的东西装进有限的口袋
上一章说上下文是一笔要管好的账。这一章要回答一个更直接的问题:装不下了怎么办?
上下文窗口是有限的——几万到几十万 token,看着不小,但真实世界很大:一个大型代码库动辄几十万行代码、一个 PDF 可能几 MB、一个网页塞满了导航和广告。全塞进去,要么爆掉,要么"注意力"被噪音稀释。
这一章讲四个模式,核心思路都是同一个:别把原始数据塞给模型,把"提炼后最值得的东西"塞进去。
- Context Window Auto-Compaction(上下文自动压缩):溢出时自动压缩、自动重试,用户无感知。
- Curated Code/File Context Window(精选代码/文件窗口):只载入最相关的模块,而不是整个仓库。
- Semantic Context Filtering(语义过滤):把原始数据转成"语义抽象"再给模型。
- Progressive Disclosure(渐进披露):先给元数据,需要时再加载内容。
模式一:上下文自动压缩(Context Window Auto-Compaction)
问题
上下文溢出是 Agent 可靠性的"无声杀手"。对话历史累积到超过模型的窗口时:
- 请求直接失败(
context_length_exceeded之类的错误)。 - 运维要手动截断对话记录,宝贵的上下文被丢掉。
- 检测溢出、压缩重试的过程又容易出错。
方案
让系统在溢出时自动压缩、自动重试,全程对用户透明。核心机制:
- 溢出检测:捕获 "上下文超长" 类的 API 错误。
- 自动压缩重试:检测到溢出后,压缩会话记录,然后自动重发请求。
- 保留 token 下限:压缩后,确保至少留出一段可用空间(默认约 20k token),防止刚压缩完立刻又溢出。
- 压缩后验证:压缩完估算 token 数,确认确实比压缩前少,防止压缩无效。
- 按模型适配:不同模型对对话格式要求不同(比如 Anthropic 严格要求的 turn 顺序、Gemini 不同的记录要求),压缩逻辑要按模型调整。
async def 压缩会话(会话文件, 配置):
# 1. 加载会话,配置保留token
# 2. 检测到溢出 → 触发压缩
# 3. 压缩到保留下限之内
# 4. 验证压缩后token数 < 压缩前
# 5. 重发请求证据
- 来自 Clawdbot、Pi Coding Agent、OpenAI Codex 等生产实现,状态是
validated-in-production(生产验证过)。 - 压缩是几乎所有长会话 Agent 的标配能力。
怎么用
- 任何可能长时间运行的 Agent 会话,都应该配上自动压缩。
- 压缩指令要明确"什么必须留、什么可以丢"(见第 5 章的配套动作)。
- 保留 token 下限按你的模型和任务调——太小容易反复溢出,太大等于没压。
取舍
- 好处:会话永不因溢出而中断;用户无感知;保留关键信息。
- 代价:压缩本身花 token 和延迟;压得不好会丢信息。
- 注意:压缩是"保底",不是"不精选的借口"——最好在源头就少塞垃圾(见下面几个模式)。
模式二:精选代码/文件窗口(Curated Code/File Context Window)
问题
把整个代码仓库都丢进上下文,会出大问题:
- 无关的代码会带偏模型——塞满了 test 工具和文档,模型在处理 UI 组件时就"失去连贯性"。
- 大上下文让 token 用量暴涨,拖慢推理,也让多轮训练变慢。
方案
给主 Agent 维护一个最小、高信号的上下文——让上下文保持"无菌"(sterile)。
核心动作:用一个小 Agent 来找"该看哪些文件"
派一个轻量的 SearchSubagent(可以是小模型、向量检索,或普通的 grep/ripgrep),负责回答"哪几个文件定义了 UserModel"这类问题。它返回排序后的文件片段,只有排名最前的 3 个片段(每个 ≤150 token)被注入主 Agent 的上下文。
典型的工作循环:
- 主 Agent:"我要重构
UserService。" - SearchSubagent:"找到了
user_service.py、models/user.py、utils/auth.py。" - 上下文注入:只有这三个文件(或它们的摘要)进入主 Agent 的窗口。
配套技巧:渐进披露——先取文件摘要,真正需要时再加载全文(详见模式四)。
证据
- 多个生产实践验证(
validated-in-production),来源包括开源 Agent RL 的分享、Prime Intellect、Thorsten Ball 等一线实践者。
怎么用
- 编码类 Agent 几乎都该用:它决定了"模型到底在看什么"。
- 文件选择的标准要明确(函数/类名、相关模块),别让搜索 Agent 也"自由发挥"。
- 和下一章的上下文最小化配合:选出来的文件要干净,别带污染。
取舍
- 好处:推理更快、更准;token 大幅减少;模型不被无关代码带偏。
- 代价:需要额外的检索逻辑;如果搜索漏了关键文件,Agent 会在错误的信息上工作。
模式三:语义过滤(Semantic Context Filtering)
问题
原始数据对模型来说太啰嗦、太吵了。举个惊人的数据:网页内容里 40%-80% 是导航、页脚、广告这类样板内容(boilerplate),在语义处理前就该被过滤掉。
这个问题到处都是:
- 网页抓取:完整的 HTML DOM 塞满了脚本、样式、追踪 iframe。
- API 响应:JSON 里全是嵌套的元数据、内部字段、调试信息。
- 文档处理:页眉、页脚、导航、样板文字。
- 代码分析:注释、空白、样板代码。
塞给模型的结果就是:token 爆炸、信噪比极差、推理变慢、噪音把推理带偏甚至产生幻觉。
方案
核心原则一句话:别把原始数据发给 LLM,发给它语义抽象(semantic abstraction)。
把原始数据里"有语义的、可交互的、相关的"元素提取出来,过滤掉噪音,给模型一份干净的表示。生产系统里到处是这个思路的验证:
- 浏览器自动化工具用可访问性树(accessibility tree)而不是原始 DOM。
- RAG 框架(LangChain、LlamaIndex)做语义分块。
- 代码分析工具(Aider)用 AST 生成 repo-map,而不是贴源码。
证据
- 网页样板检测的研究(Kohlschütter 等人,SIGIR 2010)给出了 40%-80% 的数据。
- 浏览器、RAG、代码分析三大领域都验证了"语义抽象"的路线。
怎么用
- 凡是"要把外部数据给模型看"的地方,先问一句:这是原始数据,还是提炼后的抽象?
- 网页 → 用可访问性树或正文提取;API → 只保留业务字段;文档 → 去样板;代码 → 用 AST 摘要。
- 过滤要在进入上下文之前做,而不是让模型自己"忽略噪音"——模型做不到。
取舍
- 好处:token 大减、推理更准、幻觉更少。
- 代价:需要写提取/转换逻辑;过度过滤可能丢掉边缘信息。
模式四:渐进披露(Progressive Disclosure for Large Files)
问题
大文件(PDF、DOCX、图片)如果被"朴素地"整个塞进上下文,会瞬间撑爆窗口。一个 5-10MB 的 PDF,真正有用的文字/表格可能只有 10-20KB,但整个文件往往被一股脑塞进去,浪费 token、拖垮性能。
方案
渐进披露:先只加载文件的元数据,然后提供工具,需要时再按需加载内容。
第一步:永远把元数据放进提示词,而不是全文:
文件:
- id: f_a1
名称: my_image.png
大小: 500,000 字节
类型: 图片第二步:提供按需加载的工具,让 Agent 在真正需要时,用 读取(f_a1, 页码/区域/摘要模式) 去取具体内容。
这样,Agent 先"看到"有哪些文件、大概多大,然后精准地只加载它需要的部分——可能是第 3 页、可能是某个表格、可能是某个摘要。
证据
- 来自 Will Larson(lethain.com)的工程实践总结。
- 和"懒加载"(lazy loading)思想一脉相承,在数据库、前端、分布式系统里都是成熟模式。
怎么用
- 任何 Agent 可能接触大文件(PDF、图片、大日志、大 CSV)的场景都适用。
- 元数据要够用:名称、大小、类型、结构概览。
- 加载工具要支持多种模式:按页、按区域、按摘要、按关键字。
取舍
- 好处:大文件不再是上下文的负担;Agent 按需取用,精准高效。
- 代价:需要设计加载工具和元数据格式;Agent 可能"不知道该加载哪部分",需要好的元数据引导。
四个模式怎么选
| 场景 | 推荐模式 |
|---|---|
| 会话太长,溢出报错 | 自动压缩(保底) |
| 编码 Agent,仓库太大 | 精选代码窗口 |
| 网页/API/文档噪音太多 | 语义过滤 |
| 会接触 PDF/图片/大文件 | 渐进披露 |
四个模式可以组合成一条流水线:源头用语义过滤和精选窗口"少塞垃圾",中间用渐进披露"按需取用",兜底用自动压缩"装不下就提炼"。这就是完整的上下文管理链路。
实践清单
- 长会话 Agent 配自动压缩,保留 token 下限约 20k
- 压缩指令明确"什么必须留、什么可以丢"
- 编码 Agent 用搜索子 Agent 精选文件,只注入 top-3 片段
- 外部数据进上下文前,先转成语义抽象(可访问性树/AST/字段提取)
- 大文件只给元数据,提供按需加载工具
- 检查:模型看到的到底是原始数据,还是提炼后的东西?
本章小结
- 自动压缩:溢出时自动提炼重试,会话永不中断。
- 精选窗口:用小 Agent 找"该看哪些文件",只注入高信号内容。
- 语义过滤:给模型语义抽象,而不是原始数据。
- 渐进披露:先给元数据,需要时再加载内容。
- 组合成流水线:源头少塞、按需取用、兜底提炼。
下一章,我们讲上下文管理的另一个角度——最小化:不仅要"装得下",还要"不装脏东西"。