该错误表示请求 token 数超出模型上下文窗口上限,响应 HTTP 400 包含窗口容量和实际 token 数两个关键数字。溢出几百 token 属预算 bug,应优先检查 Prompt 增长来源而非直接截断。
不同提供商之间的措辞不同,版本之间也会有变化,但响应体有一个一致的结构:400 状态码、一个 error 对象、大约是 invalid_request_error 的 type,以及 context_length_exceeded 或类似 string_above_max_length 的 code。
{
"error": {
"message": "This model's maximum context length is 128000 tokens.
However, your messages resulted in 131204 tokens.
Please reduce the length of the messages.",
"type": "invalid_request_error",
"param": "messages",
"code": "context_length_exceeded"
}
}
第一个数字是窗口大小。第二个是你的请求在提供商进行 tokenize 后的测量值。两者相减就得到了溢出量——这里是 3,204 个 token,大约占窗口的 2.5%。这个大小决定了应该使用哪种修复方法,也是这条消息中最有用的信息。
几百个 token 的溢出是一个预算错误。某样东西稍微超过了你以为还在范围内的边界。不要重构任何东西;找出那个错误的估算值。
几千个 token 的溢出通常是多出来的一轮对话历史或一份额外检索到的文档。trim policy 能永久解决这个问题。
窗口大小的整数倍的溢出——420,000 token 进入 128,000 的窗口——是整个文档、整个日志文件,或一个反复追加相同内容的循环。修整无法挽救你;是设计出了问题。
有些提供商把计数放在单独的字段而不是正文里,有些只报告限制值。如果你的请求两个都没报告,下一节的 reconciliation script 会帮你算出这个数字。
溢出几乎总是出在人们忘记计算的组件上。这个列表中的所有内容都占用同一个窗口:
如果你的估算说是 90,000,而提供商说是 131,204,差距几乎可以确定是 tool schemas、images 或 chat template overhead,按可能性从高到低排列。这个差距有专门的页面:用两个不同的 tokeniser 测量同一个字符串。
不要猜测。分别测量每个组件一次,通常会发现其中一个占了请求的 80%。这个 harness 获取你已有的 token-counting 函数——本地 tokeniser、提供商 counting endpoint,或前一个响应的 usage.prompt_tokens 除以适当的系数——并报告细目分类。
import json
def breakdown(messages, tools, count):
"""count(text) -> int. Use the tokeniser that matches YOUR model."""
rows = []
for i, m in enumerate(messages):
text = m.get("content") or ""
if not isinstance(text, str): # multimodal parts
text = json.dumps(text)
rows.append((f"{i}:{m['role']}", count(text)))
if tools:
rows.append(("tool schemas", count(json.dumps(tools))))
total = sum(n for _, n in rows)
rows.sort(key=lambda r: -r[1])
for name, n in rows[:10]:
print(f"{n:>8} {100*n/total:5.1f}% {name}")
print(f"{total:>8} 100.0% TOTAL (excludes template overhead)")
用你发送的精确载荷运行它,而不是用重构后的版本。最常见的意外是一个单独的 tool result——API 响应、数据库转储、一页 HTML——被原样追加到对话中,并从此在每个请求中被重新发送。
限制对话历史。 保留系统消息、最近的 N 轮对话,删除中间部分。这可能是一半情况的原因,只需改动十行代码。删除中间而不是开头很重要:系统消息和原始任务通常包含约束条件,而四十轮中的第七轮则不是。当中间部分确实需要时,将其总结为一条消息而不是删除它。
在 tool results 进入对话前对其进行截断。 返回 40,000 token JSON 的工具应该只返回模型需要的字段。在追加的点将每个 tool result 限制在固定预算内,并将完整载荷放在模型可以通过 ID 请求的地方。
降低 top-k 或缩小 retrieval 中的 chunks。 十个 1,500 token 的 chunk 是 15,000 token 的上下文,而长上下文中间的材料被注意到的可靠性较低,所以这个修复通常在改善质量的同时也能解决问题。添加一个 reranker 并传递前三名而不是前十名。
精简工具列表。 Schemas 在每个请求中都要付费。如果注册了 20 个工具而任何单个任务需要 4 个,按请求选择相关子集。这也能提高 tool selection 准确性,原因相同。
最后才考虑使用更大的窗口。 这是不需要思考的修复方法,但它排在最后是有原因的:每个请求的成本更高,它不能修复导致这个问题的增长,而已经增长过的请求会继续增长。用它来争取时间,而不是 close the ticket。
还有第二种失败报告了相同的 code 但需要完全不同的修复。几个提供商要求 prompt_tokens + max_tokens 适合窗口内,因为输出必须在同一个缓冲区中生成。所以 130,000-token 的窗口中 126,000-token 的 prompt 和 max_tokens 设置为 8,192 会失败——尽管单独的 prompt 完全在范围内。
你可以从算术上识别它:如果报告的请求大小正好是你的 prompt 加上 max_tokens,就是这个问题。修复方法是计算 ceiling 而不是硬编码。
WINDOW = 128_000
SAFETY = 500 # chat template overhead you did not count
max_tokens = min(desired_output, WINDOW - prompt_tokens - SAFETY)
if max_tokens < 256:
raise ValueError("prompt leaves no room for a useful answer")
提出 ValueError 而不是限制到一个很小的数字是故意的。一个 max_tokens=40 成功的请求会返回被截断的答案和 finish_reason: "length",这比 400 更安静但更糟糕的失败。
持久的修复是在请求离开前在代码中强制执行的 budget。提前决定分配——系统 prompt 和 tools 获得固定 allowance,retrieval 获得固定 allowance,历史记录获得剩余的减去 output reservation——并让 assembly 函数将内容 trim 到该 budget,而不是希望它不会超过。一个无法超过窗口的请求不会返回这个错误。
然后记录每个响应的 usage.prompt_tokens,并在第 99 百分位超过窗口的 80% 时发出 alert。Alert 在错误发生前几天触发,这是计划内变更和生产事故之间的区别。关于预算方面的更多信息,请参阅在组件间分配上下文窗口。
如果你的请求跨越具有不同窗口的多个模型,ceiling 是按模型计算的,将它硬编码在调用者中会在你路由到其他地方时立即过时。Multigrid 在 catalogue 中公布每个模型的上下文窗口,以便在 assembly 时读取限制,而不是复制到没人更新的常量中。
对于进一步的操作,你可以考虑阻止这个人或报告滥用行为。