上下文窗口 | 上下文与 RAG | 做产品
上下文窗口
上下文窗口是模型在一次请求里能看到的全部内容的上限。它是按 Token 算的,而且——要装的东西比大多数人以为的多。
你会遇到的现象
-
聊到第三十轮,它开始忘记开头定好的规矩
-
把整份文档丢给它之后,回答反而变含糊了
-
某些长输入会莫名报错,短的就没事
台面上都有什么
很多人以为窗口装的只是「对话」。实际上它要同时装下五样东西——那张图上的五块。
系统提示:产品的设定和规则,每一轮都要重发。 历史对话:之前所有的来回,包括工具调用的结果。这块会持续增长。 检索到的资料:RAG 塞进来的文档片段。这块往往是最大的一块。 用户这句话:以及这次附带的材料。 留给输出的额度:这一块最常被忘。
最后那条要展开说。**输出也占窗口。**如果一个模型标称 128K 上下文,而你把输入塞到了 127K,它就几乎没有空间写回答了。所以做预算时,必须先给输出留出足够额度,剩下的才是输入能用的。
另一个常见误解:**「200K 上下文」指的是 20 万 Token,不是 20 万字。**中文能塞进去的字数通常比这个数字多一些,代码则往往比估计的少——要准数只能实测。
装不下会怎样
两种结局,取决于平台和你的实现方式。
**一种是请求直接报错。**这其实是好事——你至少知道出问题了,可以处理。
**另一种更麻烦:最早的历史被静默丢弃。**很多框架和产品会自动裁掉最前面的对话,好让请求能发出去。
问题在于,最前面的内容往往正是最重要的——开头定好的规矩、任务的原始描述、用户一开始给的背景。丢掉之后,模型表现就开始漂移,而且没有任何提示。你只会觉得「它今天有点笨」,查不出原因。
所以有一条实践纪律:**搞清楚你用的框架在溢出时到底做了什么。**是报错,还是裁剪?裁的是哪一头?有没有日志?这个问题的答案,决定了你以后能不能查得清问题。
窗口大就够用了吗
现在动辄百万级窗口,很多人以为「把所有东西都塞进去」就解决问题了。**不是。**三个原因。
**一、塞得下不等于看得清。**内容一长,中间那段会被明显忽略——下一节专门讲这件事。塞一百页进去,它可能只真正用上了开头和结尾。
**二、贵。**整个上下文每一轮都要重新发送、重新计费。一段 50K Token 的资料,聊十轮就是 50 万 Token 的输入量。长窗口的账单增长是很吓人的,尤其在 Agent 这种多轮循环的场景里。
**三、慢。**输入越长,首字延迟越明显。用户能感觉到。
所以正确的思路不是「窗口够大就全塞」,而是只塞真正需要的那几段——这正是 RAG 存在的理由。检索的意义不只是找到资料,更是只把相关的那几段放进来。
给窗口做预算
把窗口当成一份要分配的预算,而不是一个想填多满就多满的桶。
一个可以直接用的分配思路:先扣掉输出额度(按你允许的最长回答估,再留点余量);再扣掉系统提示(固定值,算一次就行);剩下的在「历史对话」和「检索资料」之间分。
然后定两条规则:检索资料给一个硬上限(比如最多 5 段、每段最多 500 Token),历史对话超过阈值就压缩(压缩那一节讲怎么做)。
最后,把「实际用了多少 Token」打点记录下来。这个数据非常有用:你能看到哪个功能最费、增长趋势如何、什么时候快要撞上限。多数团队是等到线上开始报错才发现这件事,那时候已经晚了。
把窗口当成一份预算来分,而不是一个桶
128K 总窗口
系统提示
检索到的资料
历史对话
留给输出
固定,算一次 给一个硬上限 超过阈值就压缩 最先扣掉
分配顺序 ① 先扣输出额度——按你允许的最长回答估,再留点余量。这一块最常被忘,忘了就没空间写回答。 ② 再扣系统提示,固定值,算一次就行。 ③ 剩下的在历史对话和检索资料之间分:检索最多 5 段、每段 500 Token;历史超阈值就压缩。
把「这次实际用了多少 Token」打点记下来——多数团队等到线上开始报错才发现,那时候已经晚了。
「窗口够大就全塞」之所以不成立,是因为三件事同时发生:**塞得下不等于看得清,整段每轮都要重新计费,输入越长首字延迟越明显。**检索的意义不只是找到资料,更是只把相关的那几段放进来。
上下文中段丢失 塞得下不等于看得清,位置决定了它有多重视。
对话压缩 历史超出预算时怎么处理,以及会丢掉什么。
上下文预算 该给什么、不该给什么,比给得多重要。
