接入教程

Claude API 上下文窗口:什么在吃掉它、真实额度怎么读、以及怎么让对话活过它

Kenji Watanabe

Claude API 的上下文窗口,是单次请求能携带的输入总量——系统提示、工具定义、之前的每一轮对话、每一条工具返回,以及你附上的任何文档。它不是记忆,不是按会话计的,也不等于输出上限。人们归咎于「上下文」的那些困惑行为,几乎全部来自把这三件事混为一谈。

本文讲清楚:当前的额度是多少、怎么用程序读出来而不是写死、真正吃掉窗口的是什么,以及让一段对话活过窗口的两种机制——compaction 与 context editing。

当前的数字,以及为什么不该写死

当前这批 Claude 模型的上下文窗口都是 100 万 token:Claude Fable 5.1 与 Fable 5、Claude Opus 5、Opus 4.8、Opus 4.7 与 Opus 4.6,以及 Claude Sonnet 5 与 Sonnet 4.6。Claude Haiku 4.5 是例外,为 20 万。

输出是另一条独立的上限。当前的大模型支持最多 128K 输出 token,但 SDK 在这个量级上要求走流式——因为一个要生成这么多内容的非流式请求,会在跑完之前先撞上 HTTP 超时。

与其把这些值钉在代码里,不如从 Models API 读。取回一个模型会返回 max_input_tokens——也就是上下文窗口——以及作为输出上限的 max_tokens,还有一个 capabilities 对象。有一个细节值得在你去找它之前就知道:没有 context_window 这个字段。上下文窗口就是 max_input_tokens,去取那个更符合直觉的名字,只会取到空。

真正吃掉窗口的是什么

发给 Claude 的请求是无状态的。服务端不存在一段对话;每一轮你都要把完整历史再发一遍。这一个事实就解释了大部分意料之外的上下文膨胀。

按渲染顺序,窗口被这些东西占用:

  1. 工具定义。 每个工具的名称、描述和 JSON schema,每次请求都带。工具面越大,每次调用的固定税越高。
  2. 系统提示。
  3. 消息历史——你重发的每一轮用户与助手消息。
  4. 工具返回结果。 在 agent 循环里,这一项通常是主导项。几次文件读取或搜索结果的体量,就可能超过其余所有部分之和。
  5. 附带的文档与图片。

在同一个模型上把 thinking 块回传时,它们同样占空间——而你应该回传,它们是这一轮的组成部分。

由此得到的实际结论是:在 agent 循环里,上下文并不随对话的表面长度线性增长,它随你的工具返回了多少内容增长——那才是第一个该埋点的数字。

用 API 数 token,不要用本地分词器

用计数端点 client.messages.count_tokens,并且传入与你即将发送的完全相同的模型、messages、system 和 tools。只有这个测量结果与真正被计费和被强制执行的口径一致。

不要用 tiktoken 或任何其他第三方分词器。它是为另一个模型家族做的,与 Claude 的分词不一致。

这件事现在比以前更重要,因为分词器变了。Claude Opus 4.7 引入了新分词器,Opus 4.8 与 Fable 5 系列共用。从 Opus 4.6、Sonnet、Haiku 或更老的模型迁到这些模型上,同样一段文本可能消耗大约 1 倍到 1.35 倍的 token。如果你的应用跑在某个阈值附近——一个预算、一个分块大小、一个截断保护——换模型之后要用计数端点重新标定基线,别假设旧数字还成立。

上下文窗口与 prompt caching 的相互作用

缓存不会放大窗口。被缓存的 token 依然占据它们在请求中的位置;变的只是读取它们的成本。

真正要紧的相互作用是:缓存是前缀匹配。内容按 tools、system、messages 的顺序渲染,前缀里任何一个字节发生变化,都会让它之后的一切失效。由此得到一条设计规则,而它恰好也是良好的上下文卫生:

  • 稳定内容放前面——冻结的系统提示、确定性排序的工具列表、请求之间不变的参考材料。
  • 易变内容放后面——时间戳、请求 ID、用户当前的问题。

一个以非确定性顺序拼出来的工具列表,或者一个把当前时间写进去的系统提示,会在每一次请求上悄悄地把缓存打掉。用 usage.cache_read_input_tokens 验证:如果在共享同一前缀的多次请求里它始终为零,说明那个前缀里有东西在动。

另外还存在一个最小可缓存前缀,随模型不同大约在 512 到 4096 token 之间。低于它什么都不会被缓存,而且不会有任何报错告诉你。

对于对话中途到达的操作者指令——切换模式、注入状态——把一条 role: "system" 的消息追加进 messages 数组,可以保住已缓存的前缀,而改动顶层 system 字段则会让它失效。这一能力在 Claude Opus 5、Opus 4.8 以及 Fable 与 Mythos 的 5 / 5.1 系列上受支持;Claude Sonnet 5 不支持。

当对话长过窗口

存在两种截然不同的机制,而它们经常被混淆,因为两者都通过 context_management 配置。

Compaction 是「摘要」。 这是一个 beta 功能——请求头 compact-2026-01-12——可用于 Fable 5 与 5.1、Opus 5、Opus 4.8、4.7 与 4.6,以及 Sonnet 5 与 4.6。当请求接近触发阈值(默认 15 万 token)时,API 会在服务端把更早的上下文摘要化,并在响应里返回一个 compaction 块。

这里的失败方式既具体又无声:你必须把完整的 response.content 追加回消息历史,而不是只追加抽取出来的文本。 那个 compaction 块正是 API 在下一次请求中用来替换被摘要历史的东西。把第一个文本块取出来、只追加那个字符串——一个非常常见的写法——会丢掉 compaction 状态,于是对话悄无声息地退回到「每次都带全量」。

Context editing 是「清除」。 在 beta 请求头 context-management-2025-06-27 下,你传 context_management.edits 并带一个策略:clear_tool_uses_20250919 移除旧的工具返回(可选连工具入参一起清),clear_thinking_20251015 移除 thinking 块。什么都不会被摘要,内容就是没了。

按「你需要保住什么」来选。如果更早的对话里承载着模型仍须遵守的决定,摘要能留住它们,清除会丢掉。如果你上下文里的大头是已经被处理过的陈旧工具输出,清除更便宜也更可预测。在一个要读很多文件的 agent 循环里,清除工具返回通常是正确的第一步,而它正对着上面识别出的那个主导项。

不要把标识符搞混:compact_20260112 属于 compaction,不该出现在为 context editing 准备的 edits 数组里。

顺带说预算——它不是上下文限制

有三条上限很容易被混为一谈:

  • 上下文窗口——单次请求能携带多少输入。
  • max_tokens——对响应强制执行的上限。模型并不知道它的存在,所以撞上它会把输出从句子中间截断。
  • 任务预算(task budget)——给 agent 循环的一个建议性 token 上限,让模型自己把握节奏、干净地收尾,而不是被硬切。它设在 output_config 里,需要 beta 请求头,total 最小值为 20000。

在运维上真正要紧的区别是:max_tokens 是模型会一头撞上的墙;任务预算是模型可以据以规划的信息。如果你的 agent 输出被从句中砍断,那就调高 max_tokens;如果它跑得又长又收尾很差,任务预算才是对的工具。

任务预算统计的范围比人们以为的窄:它统计的是模型本轮生成的内容,加上它本轮读到的工具返回——不包括你每次请求都要重发的完整历史。

实操建议

  • 从 Models API 读额度并缓存到你自己的配置里,别把字面量撒得代码里到处都是。
  • 在 agent 循环里,第一件要埋点的事是工具返回的体量。窗口就是从那里没的。
  • 任何一次换模型之后都重新标定 token 基线,跨越 Opus 4.7 分词器边界时尤其如此。
  • 请求按「稳定在前、易变在后」组织,并在生产环境里查 cache_read_input_tokens,而不是假设缓存生效了。
  • 在 compaction 与 context editing 之间做有意识的选择;用 compaction 就把整个 content 数组追加回去。
  • max_tokens 给够——非流式约 16000,流式约 64000——并且任何可能跑长的请求都走流式。

常见问题

Claude API 的上下文窗口是多大?

当前这批模型——Fable 5.1 与 5、Opus 5、4.8、4.7 与 4.6、Sonnet 5 与 4.6——都是 100 万 token。Claude Haiku 4.5 是 20 万。建议运行时从 Models API 读,不要写死。

上下文窗口和 max_tokens 是一回事吗?

不是。上下文窗口限制输入,max_tokens 限制响应。两者独立,而且响应上限小得多——当前大模型最高 128K,且在这个量级上必须走流式。

为什么 Models API 不返回 context_window 字段?

因为这个字段叫 max_input_tokens。响应里还带表示输出上限的 max_tokens 和一个 capabilities 对象。去找 context_window 什么也找不到。

发请求之前怎么数 token?

client.messages.count_tokens,并传入你准备发送的同一套模型、system、tools 和 messages。tiktoken 这类第三方分词器是为另一个模型家族做的,对不上。

prompt caching 能扩大上下文窗口吗?

不能。被缓存的内容仍然占用窗口,缓存只降低读取成本。由于缓存是前缀匹配,把稳定内容放前面、易变内容放后面,并用 usage.cache_read_input_tokens 确认。

对话超过窗口之后会怎样?

除非你启用了那两种机制之一,否则不会自动发生任何事。Compaction 在服务端把更早的上下文摘要化;context editing 清除旧的工具返回或 thinking 块。两者都没有时,输入放不下的那一刻请求就会失败。

一句话版本

上下文窗口是单次请求的输入总量,当前主力模型为 100 万 token,而它在 Models API 里叫 max_input_tokens,不叫 context_window。因为请求无状态,你重发的一切都算数,而在 agent 循环里工具返回是主导项。用计数端点测量而不是本地分词器,跨过 Opus 4.7 分词器边界后重新标定。缓存不给你更多空间,只给更便宜的读取,而且前提是你的前缀真的稳定。当对话长过窗口,在「摘要」的 compaction 与「清除」的 context editing 之间做选择——如果用 compaction,把整个 content 数组追加回去,否则你会悄无声息地丢掉那个让它生效的状态。