1. 为什么你的 Claude Code 账单总比预期高
刚把 Claude Code 接进项目那几天,我盯着控制台里滚出来的一串数字发愣:input_tokens: 48690、cache_read_input_tokens: 2048、output_tokens: 590。明明只是让它改一个按钮的样式,怎么输入就快五万了?哪个数字才是我这次真正掏钱的?算成本时到底该拿哪个字段乘单价?
如果你也有同样的困惑,这篇就是写给你的。Claude Code 的 token 账单不是「一个数字」,而是由input_tokens、output_tokens、cache_creation_input_tokens、cache_read_input_tokens以及 thinking 这几条线共同构成的。它们单价不同、计费逻辑不同,混在一起看必然算不清。搞懂这五个字段,你就能回答三个最实际的问题:这次请求花了多少、钱花在哪、下次怎么省。
这篇面向刚接触 Claude Code 的开发者,交付一份可复制的 token 用量记录配置、一张账单字段对照表,并演示如何用 API 返回的usage字段逐项验证。读完你能建立一套「成本可观测」的日常习惯,而不是每次月底看账单才后知后觉。
先说结论,方便你带着框架往下读:input_tokens是你这次真正送进去的上下文总量,全额计费;output_tokens是模型吐出来的内容,单价通常是输入的数倍;cache_creation_input_tokens是「为未来省钱预付的写入成本」;cache_read_input_tokens是「已经赚到的便宜」,单价极低;thinking 是看不见但同样按输出价计费的部分。五个字段,五种角色。
2. 接入前的准备:拿到可观测的调用入口
要读懂账单,前提是你能拿到结构化的usage返回,而不是只靠控制台里一行行滚动的日志。Claude Code 本身会打印用量,但如果你想做长期记录、按会话归档、甚至写脚本统计,最稳的方式是走一个兼容 Anthropic 协议的 API 入口,自己发请求、自己收usage。
我这边日常用的是 TaoToken 的 API 入口,它兼容 Anthropic 的消息格式,返回体里带完整的usage字段,正好适合做账单拆解练习。官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个地址不加 UTM 参数,配置时直接用)。
准备工作分三步,都不复杂:
第一步,拿到 API Key。登录后进控制台,在 API Keys 页面创建一个新 Key。建议按用途分开建,比如「本地调试」「CI 脚本」各一个,方便后面按 Key 维度统计消耗。创建入口在 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。
第二步,确认你要用的模型 ID。不同模型的单价差别很大,账单拆解时必须知道自己在用哪个。模型列表和对话测试可以在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 里看,先跑通一次对话,确认 Key 和模型都对。
第三步,想清楚你要观测什么。如果只是偶尔看看,控制台日志够了;如果你想建立习惯,建议把每次请求的usage落盘成 JSONL,一天一个文件,后面用几行脚本就能算出当天各字段的累计值。这一步是「成本可观测」的核心,别跳过。
这里要提醒一句:不要把生产数据库的直连凭据、真实用户数据塞进调试请求里。做账单练习用脱敏的示例文本就够了,观测的是 token 结构,不是内容本身。
3. 可复制的配置:让每次请求都吐出 usage
这一节给你可以直接抄的配置。核心目标只有一个:每次调用都能拿到完整的usage对象,并且把关键字段记下来。
先看最小可用的请求配置。下面是一个settings.json风格的片段,用于把 Claude Code 指向兼容入口。路径按你本机的实际配置目录来,字段名保持一致:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的Key", "ANTHROPIC_MODEL": "claude-sonnet-4-5" } }三件套要记牢:Base URL 填https://taotoken.net/api,Key 填你刚创建的那串,Model ID 填模型列表里确认过的名字。三者缺一,请求要么 401,要么模型找不到。
如果你更习惯用 TOML 管理配置,等价写法是这样:
[anthropic] base_url = "https://taotoken.net/api" auth_token = "sk-你的Key" model = "claude-sonnet-4-5" [logging] usage_log = "./logs/usage.jsonl"接下来是重点:怎么把usage记下来。下面这段 Python 演示了发一次请求并把用量追加到 JSONL 文件,字段名和 API 返回保持一致,方便你后面直接对照:
import json, time, requests API = "https://taotoken.net/api/v1/messages" KEY = "sk-你的Key" def ask(prompt, model="claude-sonnet-4-5"): resp = requests.post( API, headers={ "x-api-key": KEY, "anthropic-version": "2023-06-01", "content-type": "application/json", }, json={ "model": model, "max_tokens": 1024, "messages": [{"role": "user", "content": prompt}], }, timeout=120, ) data = resp.json() usage = data.get("usage", {}) record = { "ts": time.strftime("%Y-%m-%dT%H:%M:%S"), "model": model, "input_tokens": usage.get("input_tokens", 0), "output_tokens": usage.get("output_tokens", 0), "cache_creation_input_tokens": usage.get("cache_creation_input_tokens", 0), "cache_read_input_tokens": usage.get("cache_read_input_tokens", 0), } with open("./logs/usage.jsonl", "a", encoding="utf-8") as f: f.write(json.dumps(record, ensure_ascii=False) + "\n") return record print(ask("用一句话说明什么是 token"))跑一次,你会在logs/usage.jsonl里看到一行结构化记录。这就是你账单观测的原始数据。字段对照表如下,建议存下来:
| 字段 | 含义 | 计费角色 | 单价量级 |
|---|---|---|---|
| input_tokens | 本次送入模型的上下文总量 | 全额计费 | 基准价 |
| output_tokens | 模型生成的内容 | 全额计费 | 约 5× 基准 |
| cache_creation_input_tokens | 写入缓存供后续复用 | 预付写入 | 约 1.25× 基准 |
| cache_read_input_tokens | 从缓存读取复用 | 已省下的部分 | 约 0.1× 基准 |
| thinking | 思考过程 token | 按输出价计费 | 同 output |
注意:
cache_read_input_tokens常常远大于input_tokens,这不是写错了,而是累计命中缓存的量。看到几十万别慌,那是之前省下来的。
配置里还有一个容易忽略的点:system prompt 和工具定义要保持稳定。如果你每次请求都往 system prompt 里塞时间戳、随机 ID,缓存前缀每次都变,cache_creation_input_tokens会一直涨而cache_read_input_tokens永远是 0,等于白付写入成本。固定前缀,是让缓存真正省钱的前提。
4. 验证请求:用 usage 字段逐项核对账单
配置好了,接下来验证。发一次真实请求,把返回的usage打印出来,逐项对照上一节的表。
先看一个典型返回:
{ "usage": { "input_tokens": 48690, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 2048, "output_tokens": 590 } }怎么读这组数字?input_tokens: 48690是这次真正送进去的上下文,包括对话历史、system prompt、工具定义、读入的文件内容、Git status 等自动加载项。全额计费,所以它是账单里最该盯的一项。一个原型生成请求,输入在 5k 到 15k 算正常;如果每次都 30k 以上,说明背景加载太多,得瘦身。
output_tokens: 590是模型这次吐出来的内容,单价通常是输入的 5 倍。一段完整代码实现可能 2k 到 5k,一个简单描述 200 到 500。如果 output 异常大而 input 很小,多半是 prompt 在引导模型长篇大论。
cache_read_input_tokens: 2048是从缓存读出来的部分,单价约 0.1 倍,非常便宜。它大于 0 且数值稳定,说明缓存机制在正常工作。如果每个新会话都从 0 开始,去查 system prompt 里有没有动态内容。
cache_creation_input_tokens: 0说明这次没有写入新缓存。如果你希望后续请求能命中缓存,第一轮应该看到它有值,第二轮它才会转化成便宜的cache_read。
验证方法很简单:连续发两次相同前缀的请求,观察第二次的cache_read_input_tokens是否上升、cache_creation_input_tokens是否下降。如果第二次缓存读取还是 0,说明前缀不稳定,回去检查配置。
再验证 thinking。在支持显式控制的模型上,你可以对比开启和关闭两种情况的output_tokens差异。开启 thinking 时,思考 token 会体现在输出侧计费里,虽然不出现在回答文本中。简单任务可以试低 effort,复杂代码生成建议保留,因为关掉后模型可能写出不执行的工具调用,重发一次反而更贵。
把每次请求的这组数字落盘后,你可以用几行脚本算当天累计:
import json from collections import defaultdict totals = defaultdict(int) with open("./logs/usage.jsonl", encoding="utf-8") as f: for line in f: r = json.loads(line) for k in ("input_tokens", "output_tokens", "cache_creation_input_tokens", "cache_read_input_tokens"): totals[k] += r.get(k, 0) for k, v in totals.items(): print(f"{k}: {v}")跑完你就有了一张按天汇总的账单底稿。坚持记一周,你会清楚自己的钱主要花在输入还是输出、缓存有没有生效。
5. 常见报错排查:401、proxy failed 与空 choices
做账单观测的路上,报错比数字更先到。这一节把几个高频问题对照真实报错说清楚。
401 Unauthorized。最常见的原因是 Key 没配对,或者 Base URL 和 Key 不属于同一环境。检查三件套:Base URL 是不是https://taotoken.net/api,Key 是不是从对应控制台创建的,Model ID 是不是模型列表里存在的。三者任一错位都会 401。另外注意别把 Key 写进会被提交到 Git 的文件里。
local proxy failed / connection refused。这类报错通常出在本地网络层,而不是 API 本身。先确认你的请求地址拼写正确,再确认本机没有残留的代理环境变量干扰。如果你在 CI 里跑,检查 runner 的出网策略。这类问题跟具体服务无关,属于本地链路排查。
返回体里 reading choices 为空 / choices 字段缺失。这多半是你把 Anthropic 格式的请求发到了 OpenAI 格式的端点,或者反过来。Anthropic 的返回是content数组加usage,不是choices。确认你调的是/v1/messages而不是/v1/chat/completions,字段结构对不上就会读不到内容。
OAuth 相关报错。如果你用的是需要 OAuth 的客户端,token 过期后会报鉴权失败。重新走一次授权流程即可。注意区分「API Key 鉴权」和「OAuth 鉴权」两套体系,别混用。
usage 字段为空。请求成功了但usage是空的,通常是流式响应没读完整,或者你读的是中间事件而不是最终消息。流式模式下,用量一般在最后一个事件里,确保你把流读到底再取usage。
排查顺序建议固定下来:先看状态码,401 查三件套,403 查权限;再看返回体结构,字段对不上查端点格式;最后看本地链路,连接类报错查网络配置。按这个顺序走,大部分问题五分钟内能定位。
6. 把成本观测变成日常习惯
读懂账单不是一次性任务,而是一个习惯。我的做法是每天收工前花两分钟看一眼当天的usage.jsonl汇总,重点看三个信号:input_tokens有没有异常膨胀、cache_read_input_tokens是不是稳定大于 0、output_tokens有没有失控。
如果输入持续偏大,就去精简 system prompt、减少一次性读入的大文件、只加载必要的技能包。如果缓存读取一直是 0,就去固定前缀、稳定工具定义顺序。如果输出偏大,就在 system prompt 里明确要求简洁输出,别用「请详细解释」这类引导长文本的词。
想长期做编码和 Agent 任务的话,可以了解下 Coding Plan,把用量和额度统一管理:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。需要随时验证模型行为、对比不同模型用量时,用模型对话页快速试:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。接入细节和字段说明查文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。Key 管理回到 API Keys 页:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。
最后分享一句我踩过坑才明白的话:token 账单不是天文数字游戏,你塞进什么就付什么钱,你重用什么就省什么钱。把input_tokens、output_tokens、cache_creation_input_tokens、cache_read_input_tokens和 thinking 这五个字段记熟,每次请求都落一份记录,一周之后你对成本的判断会比看任何账单都准。