30 秒快速回答
上下文腐烂(Context Rot)指的是:模型的输入越长,它在同一个任务上的准确率就越低——哪怕答案就在上下文里,哪怕窗口远没装满。
这个现象由向量数据库公司 Chroma 在 2025 年 7 月的技术报告《Context Rot: How Increasing Input Tokens Impacts LLM Performance》正式命名并量化。他们测了 18 款主流模型(含 GPT-4.1、Claude 4、Gemini 2.5、Qwen3),约 19.4 万次调用,结论是没有一款模型幸免。
核心价值一句话:上下文窗口大小说的是”能装多少”,上下文腐烂说的是”装进去之后还用得动多少”——这是两个完全不同的数字,而后者永远不会出现在产品参数页上。
一、先分清四个容易混的概念
| 概念 | 本质 | 典型表现 | 该怎么治 |
|---|---|---|---|
| 上下文溢出(Overflow) | 超过窗口上限 | 报错或截断,非常明显 | 换大窗口、切片 |
| 中间信息丢失(Lost in the Middle) | 位置问题:中段召回差 | 把答案挪到开头/结尾就恢复 | 重排提示词 |
| 上下文腐烂(Context Rot) | 长度问题:token 越多越不准 | 静默变差,不报错、不截断 | 必须”剪”,重排无效 |
| 幻觉(Hallucination) | 输出没有依据 | 编造事实 | 校验、RAG、结构化输出 |
最常踩的坑是把上下文腐烂当成”中间信息丢失”来治:重排一遍提示词,看起来清爽了,但一个顺序完全正确、内容依旧臃肿的 prompt 照样会烂。位置和体积是两条独立的失效轴,只能靠削减来解决体积问题。
二、证据:三个实验,一个结论
Chroma 的实验设计很克制:固定任务难度,只改输入长度,这样准确率的变化只能归因于长度本身。
实验 1:语义化大海捞针
经典 NIAH 是逐字匹配,”针”和问题用的是同一批词,前沿模型基本满分,于是长上下文看起来”已解决”。Chroma 做了三处加难:把针换成需要语义匹配的版本、加入看起来合理的干扰项、降低问题与答案的相似度。结果准确率随长度明显下滑,相似度越低掉得越陡。
实验 2:LongMemEval 对话记忆
同一个答案给模型看两遍:一份是约 300 token 的聚焦提示,一份是约 113,000 token 的完整历史(里面完整包含那 300 token)。每个模型家族都是聚焦版更高,而且不少模型是以”拒答”而不是”答错”的形式失败。信息在不在,不是唯一变量。
实验 3:重复词复制
这个任务完全不需要推理,只要求原样复述一串重复词加一个不同的词。长度上去之后,所有模型开始错位、随机替换,个别模型直接拒绝任务。连”复制”都做不好,说明问题不在理解能力,而在注意力被摊薄。
另外两个反直觉的发现值得记住:
- 干扰项比长度更毒。一个”看起来像答案”的段落就能拉低准确率,四个会进一步叠加,而且杀伤力不均匀。
- 连贯的文档反而更差。18 款模型上一致出现:在乱序的 haystack 里找针,比在逻辑连贯的文章里更容易。连贯文本给了模型更多”貌似合理”的错误线索。
三、为什么会烂:三个机制
1. 注意力预算被摊薄。 Softmax 权重要分给每一个 token,多一个 token 就多一个竞争者。Anthropic 的说法很形象:注意力是一份有限预算,每个 token 都在花它。
2. 干扰项累积。 真正致命的是”相似但不对”的内容,而 Agent 的工具返回、搜索结果里恰恰遍地都是。
3. 上下文惯性(Context Inertia)。 这是 2026 年 10 月 Drew Breunig 在 AGNTCon 演讲《The State of Context Engineering》里新增的失败模式:Agent 在长步骤里会形成一个”工作假设”,之后倾向于继续细化它而不是推翻它——即使新证据已经要求转向。典型症状是 50 步计划走到第 40 步,还在挖同一个坑。
顺带记住他归纳的四种老失败模式,排错时很好用:中毒(幻觉进了上下文被反复引用)、分心(历史太长,模型跟着自己的旧行为走)、混淆(无关的工具和文档影响判断)、冲突(新旧信息互相打架)。
他给的操作建议非常朴素:上下文到 20 万 token 就该清。”Delete your context. I’m not joking.”
四、五种修复(按杠杆大小排序)
| 手法 | 一句话做法 | 能解决什么 |
|---|---|---|
| 少而准(augmentation) | 检索 top-k 相关片段,而不是贴整篇文档 | 直接降体积,杠杆最大 |
| 按需装载工具(tool loadout) | 只暴露这一步需要的工具与技能 | 治”混淆”,30 个以上工具描述开始重叠 |
| 剪枝 + 卸载(pruning/offloading) | 旧工具结果只留一行结论,原文落盘 | 治”分心”,顺带省钱 |
| 子代理隔离(quarantine) | 子 Agent 用干净窗口探索,只回传摘要 | 治”冲突”,父窗口保持小 |
| 定期清空(strategic forgetting) | 写一条笔记 → 清空上下文 → 保留环境 | 治”惯性”,最反直觉但最有效 |
一个可直接抄的上下文装配器:
BUDGET = 60_000 # 可用预算,不是窗口上限
def assemble(system, retriever, tools, history, task):
ctx = [system]
# 1) 工具按需装载:只给这一步要用的
loadout = [t["name"] for t in tools.loadout(task)]
ctx.append({"role": "system", "content": f"可用工具: {loadout}"})
# 2) 检索少而准:拿片段,不拿整篇
ctx.append({"role": "system", "content": retriever.top_k(task, k=5)})
# 3) 剪枝:旧工具结果只留结论,原文已落盘
for turn in history:
if turn.kind == "tool_result":
ctx.append({"role": "user", "content": f"[落盘 {turn.path}] {turn.one_line}"})
else:
ctx.append(turn.raw)
# 4) 超预算就从最旧的非关键轮次开始压
while tokens(ctx) > BUDGET:
ctx = compact_oldest(ctx, keep=ctx[:3])
return ctx
一个容易忽略的细节:prompt caching 对腐烂没用。缓存改变的是你为这些 token 付多少钱,并不改变它们占不占窗口。
关于定期清空,有两个真实参考:Anthropic 的 Memento harness 让 Agent 每隔约一个半小时写一条简短笔记,然后清空上下文,环境(文件、仓库状态)原样保留;微软研究院的 Fork-and-Flush 是同类思路。
五、怎么确认是”腐烂”而不是”提示词写坏了”
写个二十行的消融脚本,同一任务跑三种上下文:
def ablation(task, build_ctx, model):
variants = {
"full_history": build_ctx(task, mode="full"),
"curated": build_ctx(task, mode="curated"),
"minimal": build_ctx(task, mode="minimal"),
}
for name, ctx in variants.items():
acc = run_eval(model, task, ctx, n=30)
print(f"{name:14s} tokens={tokens(ctx):>7d} acc={acc:.1%}")
判读方法:
curated明显高于full_history→ 你在付”腐烂税”,去剪枝;- 三者都低 → 不是腐烂,是提示词、工具或检索本身的问题;
minimal反而最低 → 上下文削过头了,缺关键前提。”少”是方向,不是教条,稀疏同样会失败。
六、总结
- 窗口大小不等于可用长度:200K 窗口的模型,可能 50K 就开始掉准。
- 腐烂是静默的:不报错、不截断,模型还很自信,所以只能靠消融测出来。
- 治它靠”剪、换、隔、卸、清”五个动作,重排位置没用。
- 记一个数字:约 20 万 token 是值得主动清空上下文的警告线。
- 下一阶段的难点是策略性遗忘——决定丢什么、什么时候丢、按什么标准丢。
延伸阅读
- Chroma《Context Rot: How Increasing Input Tokens Impacts LLM Performance》(2025-07)
- Drew Breunig《The State of Context Engineering》(2026-10-24,AGNTCon 演讲整理稿)
- Anthropic《Effective context engineering for AI agents》
- 本站延伸:《什么是上下文工程?》《什么是 Agent 上下文压缩?》《什么是 Agent 记忆修订?》