
1. 从一条账单说起为什么每个调用大模型的人都该会算 Token 账上个月帮一个朋友看他项目的 API 账单他做的是一个文档摘要工具日活不算高但一个月下来 API 费用比预期多了将近三倍。我让他把调用日志导出来跑了一遍 Token 统计问题立刻现形他在拼接提示词的时候把一整份原始文档无脑塞进了 system 提示里每次请求平均输入 Token 超过 8000而真正有用的内容可能只有 1500。剩下的 6500 个 Token全是重复计费的陪跑选手。这件事特别典型。现在用 Claude API、DeepSeek API、智谱 API 或者各类兼容接口做应用的人越来越多但真正把 Token 成本算明白的人并不多。很多人对 Token 的认知还停留在大概一个字等于一个 Token这种模糊印象上结果就是预算失控、成本黑箱、优化无门。这篇内容就是来解决这个问题的。我会从 Token 的本质讲起把 Claude API 的计费逻辑拆开然后给你一份可以直接跑的 Python 脚本用来统计你的 API 用量、估算成本、找出浪费点。不管你是刚接触 API 调用的新手还是已经在跑生产环境的开发者这套方法都能直接用。核心关键词就三个Claude API、Token、成本计算外加一份能落地的Python 脚本。先说清楚适用人群如果你正在用任何一家大模型的 API 做产品、做副业、做自动化脚本或者单纯想搞清楚自己每个月钱花在哪了这篇都值得看完。如果你只是偶尔在网页版聊天框里问问题那暂时用不上但了解一下 Token 计费逻辑也没坏处——毕竟免费额度也是按 Token 算的。我自己的习惯是任何接入 API 的项目第一件事不是写业务逻辑而是先把 Token 计数和成本监控搭起来。这就像开车先看油表不然你永远不知道自己是省油还是漏油。2. Token 到底是什么把计费单位讲成人话2.1 Token 不是字也不是词是切碎后的碎片很多人第一次听到 Token 会以为是令牌或者密钥其实在计费语境里Token 是模型处理文本的最小单位。你可以把它理解成把一句话用剪刀剪成一小块一小块每一块就是一个 Token。英文里一个 Token 大约对应 4 个字符或者说 0.75 个单词。比如 understanding 这个词可能会被切成 understand ing 两个 Token。中文里一个汉字通常对应 1 到 2 个 Token具体取决于分词器的实现。标点、空格、换行也都会占用 Token。这里有个特别容易踩的坑中文的 Token 消耗普遍比英文高。同样一段意思用中文写出来Token 数往往是用英文的 1.5 到 2 倍。如果你的应用面向中文用户成本估算时一定要按中文的密度来算别拿英文的经验值套。我做过一个对比测试同样一句请帮我总结这段文字的核心观点中文版本大约 15 个 Token英文版本 Please summarize the key points of this text 大约 10 个 Token。单看一句差距不大但放大到每天几万次调用差距就是真金白银。2.2 输入 Token 和输出 Token 是两笔账这是成本计算里最关键的认知输入和输出分开计费而且价格不一样。输入 Token 是你发给模型的内容包括 system 提示、历史对话、用户问题、附带的文档等等。输出 Token 是模型生成的内容。绝大多数 API 的定价里输出 Token 的单价都比输入贵通常是 3 到 5 倍。为什么输出更贵因为生成过程需要模型逐个 Token 地思考和吐字计算量比读取输入大得多。读取输入可以并行处理生成输出只能串行。这个成本差异直接决定了优化策略能减少输出长度的地方优先减少输出。举个例子如果你让模型详细解释一下它可能吐 800 个 Token如果你说用三句话说明可能只要 150 个 Token。同样的信息量成本差好几倍。这不是让模型偷懒而是让输出更精准。2.3 上下文窗口和计费的关系上下文窗口context window指的是模型一次能处理的最大 Token 数比如 200K、100K 这些数字。很多人误以为窗口大就能随便塞但窗口大小和计费是两回事你塞进去的每一个 Token 都要付钱不管窗口有多大。窗口大只意味着能装下不意味着免费装。我见过有人把整本 PDF 塞进去做问答一次请求输入 5 万 Token问的还是第一章讲了什么这种只需要局部信息的问题。这就是典型的浪费——你完全可以用检索的方式先定位到相关段落只把那一小段发给模型。所以记住一句话上下文窗口是能力上限不是免费额度。用多少付多少这是 API 计费的基本盘。3. Claude API 的计费逻辑拆解价格、模型、缓存3.1 不同模型档位的价格差异Claude 系列目前主要有几个档位从强到弱大致是 Opus、Sonnet、Haiku 这个梯度。价格也是从高到低排列Opus 最贵Haiku 最便宜。具体数字各家会调整我这里不写死具体金额因为价格会变写死了反而误导。你要做的是去官方定价页确认当前单价然后填进我后面给的脚本里。但价格梯度背后的逻辑值得说清楚强模型贵在推理能力弱模型便宜在响应速度。如果你的任务是把这段文字分类成正面/负面用最便宜的 Haiku 就够了没必要上 Opus。反过来如果任务是复杂的代码重构或者长链条推理用便宜模型可能反复出错重试几次的成本反而更高。我自己的选型原则是先用便宜模型试效果不达标再往上换。很多任务其实 Haiku 就能干得不错尤其是格式转换、简单摘要、关键词提取这类。真正需要强模型的场景比大多数人想象的要少。3.2 提示缓存能省大钱但容易被忽略Claude API 有一个很实用的机制叫提示缓存prompt caching。原理是如果你有一段固定的长提示比如系统指令、知识库背景、few-shot 示例反复用在多次请求里可以把这段缓存起来后续命中缓存的部分按更低的单价计费。这个机制对什么场景最有用固定 system 提示 变化的用户输入。比如你做一个客服机器人system 提示里写了一大段产品知识和回复规范每次用户提问都要带上这段。如果不缓存这段每次都按全价算缓存之后只有第一次全价后面都打折。缓存有写入成本和读取成本通常写入比正常输入略贵读取比正常输入便宜很多。所以判断标准是这段固定内容会被复用多少次。如果只用一两次缓存反而亏如果用几十上百次缓存能省一大笔。我实测过一个场景system 提示约 3000 Token每天调用 2000 次。不缓存的话光 system 部分每天就是 600 万输入 Token。开启缓存后这部分成本降到了原来的一个零头。这个优化不需要改业务逻辑只是加个参数的事性价比极高。3.3 计费里的隐藏项重试、流式中断、失败请求有几个容易被忽略的成本点我踩过坑这里提醒一下。重试成本网络抖动、超时、限流都会导致请求失败如果你的代码无脑重试失败的请求可能也计了费取决于失败发生在哪个阶段。我遇到过 connection reset 的情况请求发出去了但没收到完整响应这种有时候会计入输入 Token。流式中断用流式输出时如果中途断开已经生成的部分 Token 照样计费。所以流式场景下客户端要做好断线处理别让用户反复触发。失败请求参数错误、超长上下文被拒这类通常不计费但如果是模型已经开始生成才失败那部分可能会计费。所以日志里要区分请求失败和生成中断。这些细节单看金额不大但在高并发场景下会累积成可观的数字。我的做法是在脚本里把成功、失败、重试分开统计这样月底对账时能看清楚钱到底花在哪。4. 手把手写一个 Token 成本统计脚本4.1 环境准备与依赖安装先说环境。你需要 Python 3.8 以上我实测 3.10 和 3.11 都没问题。依赖主要两个一个是调用 API 的官方 SDK一个是 Token 计数的库。pip install anthropic tiktoken如果你用的是兼容接口比如通过第三方网关调用SDK 可能换成 openai 或者其他但 Token 计数的思路是一样的。tiktoken 是 OpenAI 开源的计数库对英文和通用场景够用Claude 官方也有自己的计数接口精度更高但需要联网调用。我的建议是日常估算用 tiktoken精确对账用官方计数接口。如果你在 Windows 上遇到pnpm 无法识别或者脚本闪退这类问题那是环境变量没配好跟本文主题无关但顺手提一句Python 脚本闪退通常是异常没捕获加个 try-except 打印堆栈就能看到原因。4.2 核心计数函数怎么写先写一个通用的计数函数。这里用 tiktoken 的 cl100k_base 编码它对中英文的切分和主流模型比较接近。import tiktoken def count_tokens(text: str, model: str cl100k_base) - int: 统计文本的 Token 数量 try: enc tiktoken.get_encoding(model) except Exception: enc tiktoken.get_encoding(cl100k_base) return len(enc.encode(text))这个函数很简单但有几个细节要注意。第一get_encoding每次调用都有开销生产环境应该把 encoder 缓存起来别在循环里反复创建。第二如果文本里有大量特殊符号或者代码计数会有偏差这是正常的估算够用。改进版把 encoder 缓存import functools functools.lru_cache(maxsize8) def get_encoder(model: str cl100k_base): return tiktoken.get_encoding(model) def count_tokens(text: str, model: str cl100k_base) - int: enc get_encoder(model) return len(enc.encode(text))这样第一次调用之后后续都是缓存命中速度快很多。我在一个批量处理 10 万条文本的脚本里用这个优化整体耗时从几分钟降到了几十秒。4.3 成本计算函数与价格配置接下来是成本计算。价格我做成配置字典方便你按当前实际单价修改。# 价格单位美元 / 百万 Token请以官方最新定价为准 PRICING { claude-opus: {input: 15.0, output: 75.0, cache_write: 18.75, cache_read: 1.5}, claude-sonnet: {input: 3.0, output: 15.0, cache_write: 3.75, cache_read: 0.3}, claude-haiku: {input: 0.25, output: 1.25, cache_write: 0.3, cache_read: 0.03}, } def calc_cost(model_key: str, input_tokens: int, output_tokens: int, cache_write: int 0, cache_read: int 0) - float: 计算单次调用的成本美元 p PRICING.get(model_key) if not p: raise ValueError(f未知模型: {model_key}) cost ( input_tokens / 1_000_000 * p[input] output_tokens / 1_000_000 * p[output] cache_write / 1_000_000 * p[cache_write] cache_read / 1_000_000 * p[cache_read] ) return cost这里的价格数字只是示例结构你一定要去官方定价页核对当前单价再填。我特意把缓存写入和缓存读取单独列出来因为很多人的成本模型里漏了这两项导致估算和实际账单对不上。4.4 把调用日志变成成本报表光有计算函数不够你需要把每次调用的数据记下来最后汇总。我一般用一个简单的列表或者写进 CSV。import csv from datetime import datetime class UsageTracker: def __init__(self): self.records [] def log(self, model_key, input_tokens, output_tokens, cache_write0, cache_read0, tag): cost calc_cost(model_key, input_tokens, output_tokens, cache_write, cache_read) self.records.append({ time: datetime.now().isoformat(), model: model_key, input: input_tokens, output: output_tokens, cache_write: cache_write, cache_read: cache_read, cost_usd: round(cost, 6), tag: tag, }) return cost def summary(self): total sum(r[cost_usd] for r in self.records) by_model {} for r in self.records: by_model.setdefault(r[model], 0) by_model[r[model]] r[cost_usd] return {total_usd: round(total, 4), by_model: by_model, calls: len(self.records)} def export_csv(self, pathusage.csv): if not self.records: return with open(path, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesself.records[0].keys()) writer.writeheader() writer.writerows(self.records)这个 tracker 的tag字段特别有用。你可以给不同类型的调用打标签比如 summary、chat、extract月底一看就知道哪类任务最烧钱。我有个项目就是靠这个发现摘要任务占了 70% 成本后来针对性优化了提示词直接砍掉一半。5. 实战一次真实调用的成本拆解5.1 构造一个典型请求假设我们做一个文档问答system 提示 500 Token用户问题 50 Token附带的文档片段 2000 Token模型输出 300 Token。用 Sonnet 档位。先算输入500 50 2000 2550 Token。输出 300 Token。按我上面配置的示例价格Sonnet 输入 3 美元/百万输出 15 美元/百万输入成本2550 / 1,000,000 × 3 0.00765 美元输出成本300 / 1,000,000 × 15 0.0045 美元单次合计约 0.01215 美元看起来很少对吧但如果你每天调用 5000 次一个月就是 0.01215 × 5000 × 30 1822.5 美元。这就是为什么必须算清楚——单次几分钱规模化就是几千刀。5.2 开启缓存后的对比现在假设 system 那 500 Token 开启了缓存复用 1000 次。第一次写入按 cache_write 计费后续 999 次按 cache_read 计费。首次写入500 / 1,000,000 × 3.75 0.001875 美元后续每次读取500 / 1,000,000 × 0.3 0.00015 美元对比不缓存的每次 500 / 1,000,000 × 3 0.0015 美元。单看一次缓存读取便宜了 90%。1000 次下来不缓存是 1.5 美元缓存是 0.001875 999 × 0.00015 ≈ 0.1518 美元。省了将近 90%。这个账算下来只要你的固定提示复用超过几次缓存就是稳赚。判断临界点很简单缓存写入溢价 ÷ (正常单价 - 缓存读取单价) 需要复用的次数。用上面的数字(3.75 - 3) ÷ (3 - 0.3) ≈ 0.28也就是复用 1 次以上就回本。当然实际还要考虑缓存有效期但大方向就是这样。5.3 用脚本跑一遍完整流程把上面的东西串起来写一个完整的示例tracker UsageTracker() # 模拟一次带缓存的调用 input_tokens 2550 output_tokens 300 cache_write 500 # 首次写入 cache_read 0 cost tracker.log(claude-sonnet, input_tokens, output_tokens, cache_writecache_write, cache_readcache_read, tagdoc_qa) print(f本次成本: ${cost:.6f}) # 模拟后续 999 次命中缓存 for i in range(999): tracker.log(claude-sonnet, input_tokens - 500, output_tokens, cache_write0, cache_read500, tagdoc_qa) print(tracker.summary()) tracker.export_csv(doc_qa_usage.csv)跑完你会看到总成本和按模型的汇总。这个脚本可以直接改成对接真实 API 的版本把每次响应的 usage 字段读出来填进去就行。Claude API 的响应里通常带有 input_tokens、output_tokens 这些字段直接取用即可。6. 常见问题与排查技巧实录6.1 估算和账单对不上怎么办这是最高频的问题。原因通常有几个一是价格配置过期了官方调价你没更新二是漏算了缓存项三是重试和失败请求没统计进去四是 Token 计数库和官方分词器有偏差。排查顺序我建议这样先核对价格再检查日志里有没有失败重试然后用官方计数接口抽样验证几个请求的 Token 数。如果偏差在 5% 以内属于正常超过 10%一定有系统性问题。6.2 中文场景的 Token 估算偏差中文的 Token 切分比英文复杂不同库的结果可能差 10% 到 20%。我的经验是用 tiktoken 估算时中文按 1 个汉字约 1.5 个 Token 来粗算比按 1:1 更接近实际。如果你要精确就用官方提供的计数接口虽然多一次网络调用但对账时值得。6.3 流式输出的成本统计流式输出时你拿到的是一段段增量文本。统计输出 Token 有两种做法一是把增量拼起来最后统一计数二是累加每个 chunk 的计数。前者更准后者更快。我一般用前者因为流式场景通常对实时性要求没那么极端。6.4 常见问题速查表问题现象可能原因排查方法账单比估算高很多价格配置过期核对官方最新定价账单比估算高很多漏算缓存写入检查日志是否有 cache_write账单比估算高很多重试请求重复计费统计失败和重试次数中文 Token 数偏差大分词器差异用官方计数接口抽样验证流式成本统计不准chunk 计数遗漏拼接完整输出后统一计数缓存没生效提示内容每次都变确保固定部分完全一致6.5 几个我踩过的坑第一个坑把时间戳写进了 system 提示。结果每次请求的 system 都不一样缓存永远命中不了。后来把时间戳挪到用户消息里缓存立刻生效。第二个坑用 f-string 拼接提示词时混入了随机 ID。这种隐蔽的变化会让缓存失效而且很难发现。建议固定提示部分用常量别做任何动态拼接。第三个坑重试逻辑没有上限。有一次接口不稳定代码疯狂重试一晚上烧掉了几十美元。后来加了最大重试次数和退避策略问题解决。第四个坑忽略了输出长度控制。早期提示词里写请详细说明模型动不动输出上千 Token。改成用不超过 200 字说明之后输出成本直接降了六成信息量其实没少多少。7. 把成本监控变成日常习惯7.1 给项目加一个成本看板脚本跑通之后我建议把它做成一个定时任务每天汇总一次输出到 CSV 或者推送到你的监控系统。这样你不用等到月底才发现异常当天就能看到趋势。我用的是一个简单的 cron 任务每天早上跑一次把前一天的用量汇总发到自己的邮箱。看板上重点看三个指标总成本、单次平均成本、按 tag 的成本分布。总成本看趋势单次平均看效率tag 分布看结构。如果某天单次平均成本突然上升多半是某类请求的输入变长了顺着 tag 就能定位。7.2 优化优先级怎么排成本优化不是盲目砍要按投入产出比排序。我的优先级是开启提示缓存——改动最小收益最大优先做。精简 system 提示——去掉冗余说明能省不少固定成本。控制输出长度——在提示词里明确字数或格式要求。按任务选模型——简单任务换便宜模型。引入检索——长文档场景先检索再喂给模型别整篇塞。前三条基本不需要改架构改改提示词和参数就行性价比最高。后两条需要一些工程投入但长期收益也大。7.3 一个真实项目的优化记录我手上有个文档处理项目优化前每月 API 成本大约 400 美元。做了三件事开启 system 缓存、把输出限制从详细改成分点简述、把分类任务从 Sonnet 换成 Haiku。一个月后成本降到 130 美元左右功能没受影响用户反馈反而说回复更简洁了。这个案例说明一个道理大部分成本浪费不是模型太贵而是用法太粗。同样的任务精细化的提示词和合理的模型选择能省下大半的钱。最后分享一个小技巧每次改完提示词别急着上线先用脚本跑一批历史数据对比优化前后的 Token 消耗和输出质量。我一般会准备 20 条左右的测试样本改一次跑一次确认质量没降、成本确实降了再推到生产。这个习惯帮我避免了好几次省了钱但效果变差的翻车。