3721 字
约 12 分钟
19
2.2 上下文窗口:Agent 的真正瓶颈
2026-10-25
无标签

2.2 上下文窗口:Agent 的真正瓶颈

一句话结论:上下文窗口不只是一条「装不下」的容量线,它还是一台只有一半的检索引擎——模型极擅长从上下文里"捞出"相关内容,却不会自动替你"数清楚、总结好"。这一半的缺失,是大量 Agent 行为诡异的根源。

读这篇之前

需要先读 2.1(Token 与上下文窗口的基本概念)和 1.3(上下文是能力天花板)。这一篇比前一篇更深一层:不只讲"装多少",还讲"装进去的东西怎么被使用"。


一、先纠正一个常见的理解偏差

大多数人把上下文窗口理解成一个"容器":装得下就没事,装不下就截断。

这个理解只对了一半。

真正决定 Agent 表现的,除了装多少,还有装进去之后模型怎么用它。而模型的注意力机制有一个非常特殊的行为特性:

上下文学习更像"检索",而不是"推理"。[原文 4668]

意思是:模型擅长从已有内容里查找相关信息——你问什么,它能从成千上万 Token 里捞出相关片段,效果相当于把 RAG 内置进了每一次前向传播。

但模型不擅长主动归纳总结。上下文里的东西,永远不会被自动"数一遍、建个索引、就地总结成结论"。

原文给了一个极精准的比喻:

上下文窗口是一台只有一半的检索引擎。 检索那一半非常强,但缺少了"提炼层"。[原文 4668]


二、这"一半缺失"会造成什么

理解了这个特性,很多 Agent 的诡异行为立刻有了解释。

案例:为什么 Agent 会忘记"已经打了三次"

设一个场景:Agent 打电话处理业务,系统提示词里明确要求「每个商家最多打 3 次」。

但实际运行中,Agent 打了 3 次之后经常数不清,又打了第 4 次,甚至陷入循环。[原文 4668]

为什么?不是因为模型不识字,也不是因为它不遵守指令。根源是:

「已经打了几次」这个知识没有自动提炼出来,而是分散在原始通话记录的向量表示里。

模型每次需要判断"还能不能再打",都得从那些原始记录里现算一遍。而这个计算的代价随内容量 N 上涨——记录越多,越算不准。

修复方法极其简单却极有效:在每次工具调用结果里直接标明累计次数。

本次是第 3 次呼叫该商家(上限 3 次)

加上这一行之后,模型立即发现已达限制,错误率大幅降低。[原文 4668]

从这个案例提炼出的原则

把分散在上下文各处的隐式状态,提炼为可以直接读取的显式信息。

这是一条能反复复用的设计原则。它解释了为什么高水平的 Agent 系统里,总能看到类似这些设计:

设计 解决什么问题
系统状态栏 Agent 不感知执行环境、用户时间、工作状态
任务清单外显 Agent 忘记做到哪一步了
每轮标注已用次数 / 剩余额度 Agent 数不清、超限
进度摘要 长任务中模型丢失全局

它们的共同点是:把"需要算"的变成"直接读"的。


三、为什么长上下文并不等于更强

由上面这层理解,可以推出一条反直觉但极其重要的结论:

上下文越长,不一定越好;超过某个点,反而更差。

原因有三条,逐条说清:

原因一:关键信息被稀释

窗口里塞了 20 万 Token,其中真正相关的可能只有 2 千。注意力要在这 20 万里捞出那 2 千——虽然它擅长检索,但信噪比下降会让准确率下降。

原因二:"现算"的代价随内容量上涨

回到那个"打了几次"的例子:上下文越长,需要现算的原始记录越多,模型越容易算错。上下文里每增加一份原始记录,都在增加"需要提炼"的负担。

原因三:污染风险

上下文里任何一段内容都可能影响模型判断。工具返回的网页正文、文件里的注释、甚至日志里的一行报错,都可能夹带误导性文字(第 14 章「提示注入」)。

上下文越多,攻击面越大。


四、于是,上下文怎么装就成了核心工程问题

综合下来,上下文工程要回答的不是"能塞多少",而是这五个问题:

问题 手段 详见
该装什么 优先级分层:系统规则 > 任务目标 > 相关证据 > 历史 2.3、5.1
装不下怎么办 压缩、摘要、丢弃低价值内容 5.2
怎么让它"能被提炼" 状态栏、显式计数、结构化摘要 本篇第二节
怎么省钱省时 KV Cache 复用、前缀稳定 5.3
怎么防污染 隔离不可信输入、标注来源 5.4、14.2

一个实用的分层模板

把上下文按"重要性"和"稳定性"两个维度分层,是实践中最好用的结构:

┌─────────────────────────────────────┐
│ ① 系统规则(最稳定,几乎不变)        │  ← 放最前,可被缓存
├─────────────────────────────────────┤
│ ② 任务目标与约束(一次任务内不变)    │
├─────────────────────────────────────┤
│ ③ 显式状态(每轮更新:进度/次数/时间)│  ← 提炼层,关键
├─────────────────────────────────────┤
│ ④ 相关证据(工具结果、检索片段)      │  ← 最易膨胀,需管理
├─────────────────────────────────────┤
│ ⑤ 历史对话(最易稀释,可压缩或丢弃)  │
└─────────────────────────────────────┘

这个分层有两个好处:稳定性高的放前面(有利于缓存复用,见 5.3);膨胀风险大的放后面(好截断,且不破坏前缀)。


五、给 Agent 装一只"状态栏"

原文提到一个很实用的技术:系统状态栏(status bar)。

它要解决的问题很具体:Agent 常常出现状态遗忘、目标偏离,根源在于缺乏对环境当前状态的感知和任务进展的跟踪。状态栏的做法是在上下文中嵌入结构化元信息,为 Agent 补上自我感知机制。[原文 4668]

写起来很简单,就是每轮在消息里附一段结构化文本:

【当前状态】
- 任务:处理账单争议
- 进度:第 3/10 个商家
- 本轮动作:已呼叫「商家 A」,累计 3 次(上限 3)
- 剩余预算:7 次调用
- 当前时间:2026-10-25 14:30

为什么这么有效? 因为它把"需要模型从原始记录里现算"的东西,变成了"模型一眼能读到"的东西——正好补上了那"缺失的一半提炼层"。

判断标准:如果你的 Agent 需要"数数"(第几次、还剩几个、做了多少),就应该外显;如果需要"记得"某个不容易从原始记录推出的结论,也应该外显。



六、上下文里到底装了哪些东西

要管理上下文,先得知道它由什么构成。一次 Agent 调用里,进入窗口的内容通常有六类:[原文 4568]

类别 具体内容 特点 管理策略
系统提示 身份、行为规则、约束 最稳定、优先级最高 放最前,尽量不变(利于缓存)
工具定义 每个工具的名字、描述、参数 schema 数量与体积容易失控 按需加载(第 9 章 Skills、第 4.5 工具爆炸)
任务目标 用户请求、当前子目标 一次任务内基本不变 明确、简短、可复述
工具结果 读到的文件、API 返回、检索片段 最容易膨胀 截断、摘要、只留关键字段
历史对话 之前轮次的问答 长任务中迅速堆积 压缩、折叠、只留结论
环境状态 时间、目录、运行状态、进度 模型无法自行感知 必须显式外显(状态栏)

这张表最有价值的地方在第 4、5、6 行:膨胀最快的三类,恰恰是最不该无限增长的。而第 6 行是最容易被忽略的——很多团队把注意力全放在"塞知识"上,却忘了告诉 Agent「现在是几点、你在哪个目录、任务做到第几步」。

一个量化视角

假设一个 Coding Agent 每轮平均消耗:

  • 系统提示 + 工具定义:约 3K Token(固定)
  • 本次读入的文件:约 8K Token
  • 历史对话:第 1 轮 1K,第 10 轮可能 15K

那么到第 10 轮,光"历史"就占了总预算的一大半,而它对当下决策的价值远低于"当前文件内容"。

这就是为什么压缩的对象总是历史对话,而不是系统规则。


七、一个反直觉的自检清单

把这一篇的结论落成五个问题,用来体检你自己的 Agent:

# 问题 如果答案是"没有"
1 我有没有把当前进度/次数/时间显式写进上下文? 模型会数不清、忘记做到哪
2 我的系统提示是否足够稳定(不随轮次变化)? 前缀缓存失效,成本翻倍
3 工具结果有没有截断或摘要策略? 几轮之后就爆窗口
4 历史对话有没有压缩或折叠机制? 长任务必然崩
5 我在上下文里区分了可信与不可信来源吗? 易被注入攻击

这五个问题里,第 1 条最便宜也最有效,第 5 条最容易被忽略但后果最严重。


八、动手:给你的 Agent 做一次上下文体检

理论讲完,最重要的是一次实际检查。

五步体检法

步 动作 看什么
1 打印每轮实际发送的完整 messages 总量与各段占比
2 统计各类内容的 Token 占比 谁最占地方
3 找出"每轮重复出现"的内容 哪些可以缓存或引用
4 找出"从不被引用"的内容 哪些是白占地方
5 检查状态信息是否外显 模型能不能"读到"进度

一个典型体检结果

总输入 12,400 Token
├─ 系统提示        1,500 (12%)
├─ 工具定义        2,000 (16%)
├─ 历史对话        5,800 (47%)   ← 最多
├─ 本轮文件内容    2,600 (21%)
└─ 环境状态            0 ( 0%)   ← 缺失!

这张表立刻给出两个结论:

发现 行动
历史占 47% 该压缩(5.2)
环境状态 0% 该加状态栏(2.2 第五节)

第 3 步和第 4 步最容易被跳过,但它们的收益最大。

一个常见发现:工具定义从未变化却重复发送

10 个工具 2,000 Token,每次调用都发一遍。如果前缀稳定,这部分应该被缓存(5.3)。

如果没命中缓存,通常是这两个原因:

原因 检查
工具顺序不固定 用字典遍历导致顺序随机
系统提示里有动态内容 时间、用户名

第一个很隐蔽:很多语言的字典遍历顺序不稳定,导致每次拼出的前缀都不同。


九、上下文腐化的实测信号

5.2 讲了"腐化"这个概念,但怎么发现它?

信号 说明
重复纠结已解决的问题 说明它找不到之前的结论
反复调同一个工具 说明结果没被正确利用
前后自相矛盾 说明注意力被分散
越到后面越差 典型的腐化曲线

最简单的检测:对比"第 2 轮的回答质量"与"第 8 轮的回答质量"(同样难度的问题)。

如果明显下降,就是腐化——该压缩了。


十、常见误区

误区一:窗口越大越好,干脆全塞进去。
不行。信噪比下降、现算代价上升、污染风险上升,三个问题会同时恶化。

误区二:模型会自己总结上下文。
不会。它擅长检索,不擅长提炼。"一共几条""进行到哪步"这类结论,每次都要现算,而且容易算错。

误区三:只要提示词写清楚"最多 3 次",模型就会遵守。
指令清楚是必要条件,但不充分。把"当前是第几次"也告诉它,遵守率才会显著改善。

误区四:长上下文能力是新模型的卖点,所以旧问题消失了。
窗口变大解决的是"装不下",不解决"算不准"和"信噪比"。窗口越大,后两个问题反而更突出。

误区五:上下文工程就是压缩。
压缩只是其中一环。完整的上下文工程包括**选择(装什么)、组织(怎么排)、提炼(状态外显)、维护(压缩与丢弃)**四件事。


十一、下一篇

现在我们知道了模型的"容器"有多大、也知道了它的"检索强、提炼弱"。

下一个问题更棘手:既然模型只是在预测下一个 Token,那它为什么会一本正经地说错话?幻觉到底从哪来,有没有可能彻底消除?

下一篇专门讲幻觉。

2.2 上下文窗口:Agent 的真正瓶颈
https://clxhxhhr.top/posts/4873/
作者
clxstart
发布于
2026-10-25
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。