第13章:Token经济学:每个字都有价格¶
生活类比
手机流量套餐很好理解:你不是“爱怎么用就怎么用”,而是在固定额度里分配给视频、聊天、下载、导航。Claude Code 里的 token 也是预算,不只是技术指标,更是产品成本。
这一章要回答的问题
Claude Code 怎么知道自己会不会“超套餐”?它又是怎么把静态规则、动态上下文、thinking、tool search 这些都装进有限预算里的?
如果不理解 token 经济学,你会把很多现象看成随机:为什么有时自动压缩?为什么 prompt 里要放边界标记?为什么任务预算要跨 compact 继续算?其实这些都和“钱”和“窗口”有关。
13.1 先算账:Claude Code 并不等到 API 报错才知道自己快满了¶
在 services/compact/autoCompact.ts 里,calculateTokenWarningState(...) 会根据:
- 当前 tokenUsage
- 模型上下文窗口
- 是否开启 auto compact
算出一组关键状态:
percentLeftisAboveWarningThresholdisAboveErrorThresholdisAboveAutoCompactThresholdisAtBlockingLimit
flowchart TD
A["当前 tokenUsage"] --> B["calculateTokenWarningState"]
C["模型上下文窗口"] --> B
D["auto compact 是否开启"] --> B
B --> E["warning"]
B --> F["error"]
B --> G["auto compact"]
B --> H["blocking limit"]
style B fill:#fff8e1,stroke:#f9a825,color:#000
style H fill:#ffebee,stroke:#c62828,color:#000
这说明 Claude Code 的策略不是:
- “先猛跑”
- “撞墙以后再说”
而是:
- 提前估算
- 提前预警
- 真到危险线时再阻断
为什么 blocking limit 和 auto compact threshold 要分开¶
这两个阈值不是一回事:
auto compact threshold:系统还想自己救一下blocking limit:再往前走就危险,必须停
换成生活类比:
- 油表见底灯亮了,不代表车马上趴窝
- 但真到完全没油,系统就该阻止你继续赌运气
query.ts 会在打 API 前先做 blocking 检查¶
如果当前上下文已经到了 blocking limit,query.ts 会直接返回 PROMPT_TOO_LONG_ERROR_MESSAGE,而不是发一笔大概率失败的请求出去。
这不是“小优化”,而是真钱优化。
源码证据
OpenClaudeCode/src/services/compact/autoCompact.ts:93-145:warning / error / auto compact / blocking 四档判断OpenClaudeCode/src/query.ts:628-646:真正打模型前的 blocking limit 预检
13.2 为什么 prompt caching 能省钱:因为 prompt 被分成了静态和动态两半¶
第 9 章讲过 SYSTEM_PROMPT_DYNAMIC_BOUNDARY,这里我们从“钱”的角度再看一次。
constants/prompts.ts 里专门定义了一个边界标记:
- 前面是静态、跨组织可缓存内容
- 后面是用户和会话相关动态内容
而在最终返回 system prompt 数组时,源码明确做了:
- 静态 section
- 边界 marker
- 动态 section
flowchart LR
A["静态前缀<br/>系统规则 工具说明 风格规范"] --> B["DYNAMIC_BOUNDARY"]
B --> C["动态尾部<br/>MCP 指令 工作目录 会话现场"]
C --> D["同一请求体中发往模型"]
style A fill:#e8f5e9,stroke:#2e7d32,color:#000
style B fill:#fff3e0,stroke:#ef6c00,color:#000
style C fill:#e3f2fd,stroke:#1565c0,color:#000
这背后真正节省的是哪部分钱¶
多轮任务里最稳定的,往往不是用户消息,而是这些大块内容:
- 系统规则
- 工具说明
- 输出风格
- 安全要求
如果每一轮都把这些内容当成“全新输入”重新计算,会很浪费。
所以 Claude Code 不只是“写 prompt”,而是把 prompt 做成缓存友好结构。
为啥注释反复强调“不要随便移动边界”¶
因为一旦边界位置改了,相关缓存逻辑和 API 端的分割也得跟着改。
这就像数据库 schema 的分区键,不是你想换位置就换位置。
换句话说,这个字符串常量不是文案细节,而是成本架构的一部分。
源码证据
OpenClaudeCode/src/constants/prompts.ts:105-115:动静态边界常量定义OpenClaudeCode/src/constants/prompts.ts:560-576:边界在最终 system prompt 里的插入位置
13.3 Task Budget:不只是“最多几轮”,而是“这次任务允许花多少 token”¶
很多人会把 maxTurns 和预算混为一谈,其实不是。
maxTurns管的是循环次数taskBudget管的是 token 预算
在 services/api/claude.ts 里,Claude Code 会把任务预算编码成:
type: 'tokens'totalremaining
并在需要时把 TASK_BUDGETS_BETA_HEADER 加进请求。
flowchart TD
A["用户或系统给定任务预算"] --> B["configureTaskBudgetParams()"]
B --> C["output_config.task_budget"]
B --> D["追加 TASK_BUDGETS_BETA_HEADER"]
C --> E["模型侧感知预算上限"]
D --> E
style B fill:#fff8e1,stroke:#f9a825,color:#000
style E fill:#e8f5e9,stroke:#2e7d32,color:#000
更高级的一点:预算会跨 compact 继续扣¶
query.ts 里有一段特别漂亮的注释:
- 如果 compact 发生了
- 服务器只看得到压缩后的摘要
- 它就会低估之前已经花掉的上下文
所以 Claude Code 自己维护了一个 taskBudgetRemaining:
- compact 前,服务器自己能看全量历史,先不用额外处理
- compact 后,客户端会把“被摘要掉的那部分上下文成本”继续从 remaining 里扣掉
这就像公司报销系统:你把明细装订成摘要,不代表财务可以假装前面那几页没花钱。
为什么这很重要¶
如果没有这层补偿,compact 会带来一个错觉:
- 上下文变短了
- 看起来预算又富裕了
但实际上,那只是“账单被折叠了”,不是“钱回来了”。
源码证据
OpenClaudeCode/src/services/api/claude.ts:473-500:task_budget的 API 参数编码OpenClaudeCode/src/query.ts:282-291:taskBudgetRemaining的设计目的OpenClaudeCode/src/query.ts:504-515:compact 前上下文成本的扣减OpenClaudeCode/src/query.ts:699-705:把 total / remaining 带回模型调用OpenClaudeCode/src/query.ts:1135-1145:恢复路径下继续扣减 budget
13.4 成本不只是 token 数量,还受请求形态影响¶
到这里你可能会问:
既然都按 token 算,为什么还要讲 thinking、advisor、tool search、betas?
因为这些开关会改变一次请求长什么样,而请求形态又会影响 token 的花法。
Thinking 会占输出预算¶
在 claude.ts 里,如果 thinking 开启,系统会根据模型能力决定:
- 支持 adaptive 的模型:走
type: 'adaptive' - 不支持 adaptive 的模型:走
budget_tokens
而且 thinking budget 还会被限制在 maxOutputTokens - 1 之内。
这说明“thinking 不是白送的”,它是输出预算的一部分。
Tool search 会带 beta header,还会动态筛工具¶
如果工具搜索开启,系统会:
- 判断哪些工具是 deferred
- 根据历史里有没有
tool_reference决定是否把工具声明真正带上 - 再追加对应的 tool-search beta header
这既影响提示词长度,也影响请求元数据。
Advisor 和其他 server-side tool 也会改变请求形态¶
当 advisor 启用时,会追加 advisor beta header,还可能选择一个专门的 advisor model。
这意味着“同样一句用户输入”,实际请求体并不总是同一形状。
mindmap
root((请求成本形态))
基础成本
输入 tokens
输出 tokens
thinking
adaptive
budget_tokens
工具系统
tool schema 长度
tool search defer loading
beta headers
advisor
task budget
tool search
这就是“Token 经济学”比“Token 计数”更重要的原因¶
Token 计数 是问:
- 这次用了多少 token?
Token 经济学 是问:
- 这些 token 为什么会花在这里?
- 哪部分是结构性开销?
- 哪部分可以缓存?
- 哪部分是为了质量提升而主动付出的成本?
Claude Code 的源码显然站在第二种视角上。
源码证据
OpenClaudeCode/src/services/api/claude.ts:1064-1181:betas、advisor、tool search 的请求形态调整OpenClaudeCode/src/services/api/claude.ts:1591-1628:thinking 的 adaptive / budget 选择
🔭 深水区(架构师选读)
第 13 章最值得带走的,不是“token 很贵”这句废话,而是 Claude Code 对成本的态度:
- 它在客户端提前估算
- 在 prompt 层做缓存友好切分
- 在循环层做 blocking / compact / budget 护栏
- 在 API 层显式编码 task budget 和 betas
- 在请求形态层承认 thinking、advisor、tool search 都会改变成本结构
这说明它把成本当成架构问题,而不是财务报表上的事后统计。
本章小结
一句话:Claude Code 的 Token 经济学不是“事后看看花了多少”,而是从 warning 阈值、prompt caching、task budget、thinking 配置到 beta header,整条链路都在主动管理成本和上下文窗口。
关键源码索引
| 证据层 | 文件 | 本章关注点 |
|---|---|---|
| 补全层 | OpenClaudeCode/src/services/compact/autoCompact.ts:93-145 |
token warning / error / blocking 计算 |
| 补全层 | OpenClaudeCode/src/query.ts:628-646 |
正式请求前的 blocking limit 检查 |
| 补全层 | OpenClaudeCode/src/constants/prompts.ts:105-115 |
prompt 动静态边界 |
| 补全层 | OpenClaudeCode/src/constants/prompts.ts:560-576 |
静态前缀与动态尾部拼接 |
| 补全层 | OpenClaudeCode/src/services/api/claude.ts:473-500 |
task_budget API 参数 |
| 补全层 | OpenClaudeCode/src/query.ts:282-291 |
compact 后继续追踪 remaining budget |
| 补全层 | OpenClaudeCode/src/query.ts:504-515 |
compact 时预算扣减 |
| 补全层 | OpenClaudeCode/src/services/api/claude.ts:1064-1181 |
advisor / tool search / beta headers |
| 补全层 | OpenClaudeCode/src/services/api/claude.ts:1591-1628 |
thinking 的预算选择 |
逆向提醒
- ✅ 可信度高:blocking、task budget、dynamic boundary、thinking 配置都能直接在源码里定位
- ⚠️ 要避免简化:
maxTurns不是 token budget,compact 也不等于“成本归零” - ❌ 不要误读:prompt caching 节省的不是“所有输入”,而是边界前那一大段稳定前缀