做产品 PMaker
空格的键盘
这张台面上摆的所有东西,加起来不能超过窗口上限 系统提示 固定,每轮都发 历史对话 越聊越长 检索到的资料 RAG 塞进来的 这句话 留给输出 常被忘 全部按 Token 算,不按字数 装不下的时候会发生什么 最早的历史被丢掉 丢的常常是开头定好的规矩 而且不会有任何提示 或者请求直接报错 取决于平台和你的实现 报错反而是好事,至少你知道了

窗口是一次请求能装下的全部内容——包括你没算进去的那几样。装不下时它会安静地丢东西。

上下文窗口

上下文窗口是模型在一次请求里能看到的全部内容的上限。它是按 Token 算的,而且——要装的东西比大多数人以为的多。

你会遇到的现象
  • 聊到第三十轮,它开始忘记开头定好的规矩
  • 把整份文档丢给它之后,回答反而变含糊了
  • 某些长输入会莫名报错,短的就没事

台面上都有什么

很多人以为窗口装的只是「对话」。实际上它要同时装下五样东西——那张图上的五块。

系统提示:产品的设定和规则,每一轮都要重发。
历史对话:之前所有的来回,包括工具调用的结果。这块会持续增长。
检索到的资料:RAG 塞进来的文档片段。这块往往是最大的一块。
用户这句话:以及这次附带的材料。
留给输出的额度:这一块最常被忘。

最后那条要展开说。输出也占窗口。如果一个模型标称 128K 上下文,而你把输入塞到了 127K,它就几乎没有空间写回答了。所以做预算时,必须先给输出留出足够额度,剩下的才是输入能用的。

另一个常见误解:「200K 上下文」指的是 20 万 Token,不是 20 万字。中文能塞进去的字数通常比这个数字多一些,代码则往往比估计的少——要准数只能实测。

装不下会怎样

两种结局,取决于平台和你的实现方式。

一种是请求直接报错。这其实是好事——你至少知道出问题了,可以处理。

另一种更麻烦:最早的历史被静默丢弃。很多框架和产品会自动裁掉最前面的对话,好让请求能发出去。

问题在于,最前面的内容往往正是最重要的——开头定好的规矩、任务的原始描述、用户一开始给的背景。丢掉之后,模型表现就开始漂移,而且没有任何提示。你只会觉得「它今天有点笨」,查不出原因。

所以有一条实践纪律:搞清楚你用的框架在溢出时到底做了什么。是报错,还是裁剪?裁的是哪一头?有没有日志?这个问题的答案,决定了你以后能不能查得清问题。

窗口大就够用了吗

现在动辄百万级窗口,很多人以为「把所有东西都塞进去」就解决问题了。不是。三个原因。

一、塞得下不等于看得清。内容一长,中间那段会被明显忽略——下一节专门讲这件事。塞一百页进去,它可能只真正用上了开头和结尾。

二、贵。整个上下文每一轮都要重新发送、重新计费。一段 50K Token 的资料,聊十轮就是 50 万 Token 的输入量。长窗口的账单增长是很吓人的,尤其在 Agent 这种多轮循环的场景里。

三、慢。输入越长,首字延迟越明显。用户能感觉到。

所以正确的思路不是「窗口够大就全塞」,而是只塞真正需要的那几段——这正是 RAG 存在的理由。检索的意义不只是找到资料,更是只把相关的那几段放进来。

给窗口做预算

把窗口当成一份要分配的预算,而不是一个想填多满就多满的桶。

一个可以直接用的分配思路:先扣掉输出额度(按你允许的最长回答估,再留点余量);再扣掉系统提示(固定值,算一次就行);剩下的在「历史对话」和「检索资料」之间分。

然后定两条规则:检索资料给一个硬上限(比如最多 5 段、每段最多 500 Token),历史对话超过阈值就压缩(压缩那一节讲怎么做)。

最后,把「实际用了多少 Token」打点记录下来。这个数据非常有用:你能看到哪个功能最费、增长趋势如何、什么时候快要撞上限。多数团队是等到线上开始报错才发现这件事,那时候已经晚了。

把窗口当成一份预算来分,而不是一个桶 128K 总窗口 系统提示 检索到的资料 历史对话 留给输出 固定,算一次 给一个硬上限 超过阈值就压缩 最先扣掉 分配顺序 ① 先扣输出额度——按你允许的最长回答估,再留点余量。这一块最常被忘,忘了就没空间写回答。 ② 再扣系统提示,固定值,算一次就行。 ③ 剩下的在历史对话和检索资料之间分:检索最多 5 段、每段 500 Token;历史超阈值就压缩。 把「这次实际用了多少 Token」打点记下来——多数团队等到线上开始报错才发现,那时候已经晚了。
「窗口够大就全塞」之所以不成立,是因为三件事同时发生:塞得下不等于看得清,整段每轮都要重新计费,输入越长首字延迟越明显。检索的意义不只是找到资料,更是只把相关的那几段放进来。