窗口越做越大,从 4K 到 128K 再到百万级,但工程结论反直觉:窗口大了,更要省着用。 这篇文章讲为什么,以及怎么花这笔预算。

三个物理事实

注意力不均匀。 lost in the middle 现象在长上下文里普遍存在:开头与结尾的内容被"看见"的程度高于中段。塞满 200K 的仓库代码再问一个中段函数的问题,命中率不如精准检索 20 个片段。

成本随长度线性甚至更差。 输入 token 计费之外,超长上下文的预填充(prefill)延迟明显增长,缓存命中才是长会话经济的救命稻草——前缀稳定(system + 工具定义 + 早期对话)才能吃上缓存,前缀一变,全量重算。

位置本身就是信号。 越晚进入上下文的信息,对即将生成的 token 影响越强。重要的约束放后面重申一次,不是啰嗦,是利用位置权重。

预算分配:一个起点模板

假设总预算 100 份,我会这样起步(按任务调):

区块 预算 说明
System + 角色约束 5% 稳定不变,吃缓存
工具定义(名+schema+说明) 10% 工具描述质量直接决定调用准确率
检索材料(RAG/文档) 30% 有引用价值才进来
对话/任务历史 35% 滚动窗口,旧轮次摘要化
当前任务与输入 15% 永远完整保留
余量 5% 给输出留呼吸空间

原则:每样东西进入上下文都要付"租金",付不起租金的挤出去。

什么时候不用长上下文

百万窗口在手,三种场景我依然选 RAG/摘要:

  1. 重复访问同一批材料:每次全量塞入是按次付费;检索只取相关片段,缓存友好
  2. 材料会动态变化:全量上下文 = 每次变更全量失效;检索可以增量更新索引
  3. 需要精确引用定位:RAG 的片段天然带出处,全量上下文里模型引用页码的可靠性并不高

反过来,一次性深读几个文件再重构的场景,长上下文直接塞比 RAG 打散更合适——按访问模式选工具,不按窗口大小炫技。

实操检查清单

  • system 与工具定义是否稳定(缓存命中)
  • 是否存在"每次请求都重塞一遍的大块静态材料"——该进检索或缓存
  • 对话历史有没有滚动摘要机制
  • 关键约束是否在输入末尾重申
  • 有没有观测:每个区块实际占多少 token(不测不知道,测了吓一跳)

上下文工程的第一性原理一句话:上下文是稀缺资源,工程就是预算管理。 下一篇讲预算里最贵的那块——记忆——怎么压缩。