
1. 从一次账单异常说起为什么我要自己算 Token 成本上个月帮一个做知识库问答的朋友排查问题他跟我说“这个月 API 账单比上个月翻了一倍多但我感觉用户量没怎么涨啊。”我让他把最近两周的调用日志导出来跑了一遍统计发现一个很典型的情况他的系统里有一段“对话历史拼接”逻辑每次请求都会把之前所有轮次的对话原封不动带上而且没有做长度截断。结果就是一个用户聊到第 15 轮的时候单次请求的输入 Token 已经涨到了 8000 多而输出只有短短一两百 Token。账单里绝大部分成本其实都花在了那些被反复重发的历史对话上。这件事让我意识到很多开发者对 API 成本的理解还停留在“用多少付多少”的模糊阶段真正落到 Token 这个计量单位上心里是没有数的。Claude API 的计费方式是按输入 Token 和输出 Token 分别计价输入和输出的单价还不一样缓存命中和未命中的价格也有差异。如果你不去主动计算和分析很容易出现“感觉没怎么用账单却很高”的情况。这篇内容就是把我自己这套 Token 成本计算与 API 用量分析的实战方法完整拆出来。核心目标有三个第一搞清楚 Claude API 的计费逻辑和 Token 计算方式第二写一个能直接跑的 Python 脚本把调用日志里的 Token 用量算清楚第三基于统计结果做成本优化把该省的钱省下来。适合正在用 Claude API 做产品、做副业项目或者单纯想搞清楚自己钱花在哪儿的开发者。哪怕你刚接触 API 调用跟着步骤走也能跑通。2. Claude API 计费逻辑拆解钱到底花在哪几个地方2.1 输入 Token 与输出 Token 的定价差异Claude API 的计费模型并不复杂但有几个关键点容易被忽略。最基础的一条输入 Token 和输出 Token 是分开计价的而且输出 Token 通常比输入 Token 贵。以目前主流的模型档位为例输出单价往往是输入单价的数倍。这意味着什么呢意味着如果你的应用是“长输入、短输出”型比如文档摘要、分类打标成本相对可控但如果是“短输入、长输出”型比如内容生成、代码补全那输出 Token 就是成本大头。我拿一个实际场景算过假设某次请求输入 2000 Token输出 500 Token。如果输入单价是每百万 Token 3 美元输出单价是每百万 Token 15 美元那么这次请求的成本是输入成本2000 / 1,000,000 × 3 0.006 美元输出成本500 / 1,000,000 × 15 0.0075 美元合计0.0135 美元看起来不多但如果你的应用每天有 10 万次这样的请求一天就是 1350 美元。所以单价的小数点后面几位放大到规模上就是真金白银。2.2 缓存机制对成本的影响Claude API 有一个对成本影响很大的机制提示缓存Prompt Caching。简单说如果你有一段很长的系统提示词或者固定的上下文在多次请求中反复使用可以把它标记为可缓存内容。缓存写入的时候会有一个略高于普通输入的单价但后续命中缓存的部分输入单价会大幅降低。这个机制的设计意图很明确鼓励开发者把那些“不变的长内容”缓存起来而不是每次请求都重新传一遍。我见过不少项目系统提示词写了三四千字每次请求都完整带上从来不缓存等于每次都在为同一段文字重复付费。把这段内容改成缓存模式之后输入成本能降下来一大截。注意缓存有有效期一般是几分钟到几十分钟不等。如果你的请求间隔很长缓存可能已经失效这时候命中不了反而要重新写入。所以缓存适合“短时间内高频重复使用同一段上下文”的场景。2.3 不同模型档位的价格梯度Claude 系列有多个模型档位从轻量快速型到高能力型价格差距可以到十几倍。很多开发者习惯性地所有任务都用最强模型结果就是简单任务也在付高价。我的做法是按任务复杂度分层分类、抽取、格式转换这类任务用轻量模型复杂推理、长文生成、代码理解用高能力模型。光这一条就能把整体成本压下来不少。下面这张表是我自己整理的一个对照参考帮助你在选型时快速判断任务类型推荐档位理由文本分类、意图识别轻量快速型任务简单不需要深度推理信息抽取、结构化输出轻量或中档对指令遵循要求高但推理深度要求低长文摘要、内容生成中档或高能力型需要语言质量和连贯性复杂代码理解、多步推理高能力型需要强推理能力轻量模型容易出错高频简单问答轻量快速型成本敏感响应速度优先这张表不是绝对的但作为一个起步参考能帮你避免“杀鸡用牛刀”的浪费。3. Token 到底怎么数计算原理与常见误区3.1 Token 不是字数也不是单词数很多人第一次接触 Token 这个概念会下意识地把它等同于“字数”或者“单词数”。实际上Token 是模型处理文本的最小单位它的切分规则和语言、标点、空格都有关系。英文里一个常见单词可能是 1 个 Token也可能被拆成 2 到 3 个中文里一个字通常是 1 到 2 个 Token具体取决于分词方式。举个直观的例子英文句子 “I am learning Claude API” 大概会被切成 5 到 6 个 Token而同样意思的中文“我正在学习 Claude API”Token 数量可能接近甚至更多。这就导致一个现象同样长度的内容中文的 Token 消耗往往比英文高。如果你的应用面向中文用户做成本预估时不能直接套用英文的经验值。3.2 用官方 Tokenizer 做精确计算靠肉眼估算 Token 数量是不靠谱的尤其是涉及代码、特殊符号、多语言混排的时候。正确做法是用官方提供的 Tokenizer 工具做精确计算。Anthropic 提供了对应的计数接口和库你可以在发送请求之前先算一遍做到心里有数。在 Python 里最直接的方式是调用官方的计数接口。下面这段代码演示了如何计算一段文本的 Token 数量import anthropic client anthropic.Anthropic(api_key你的API密钥) def count_tokens(text, modelclaude-3-5-sonnet-20241022): response client.messages.count_tokens( modelmodel, messages[{role: user, content: text}] ) return response.input_tokens sample_text 请帮我总结这段内容的核心观点并用三点列出。 print(fToken 数量: {count_tokens(sample_text)})这个接口的好处是准确缺点是每次计数都要发一次请求有网络开销。如果你要批量统计大量历史日志更高效的做法是用本地的 Tokenizer 库做离线计算。虽然本地计算和官方接口可能有极小的偏差但对于成本分析来说完全够用。3.3 容易被忽略的 Token 消耗点在实际项目里有几个地方的 Token 消耗特别容易被低估系统提示词很多人写系统提示词的时候很随意一写就是上千字而且每次请求都带上。这部分是纯输入成本积少成多。对话历史多轮对话场景下如果不做截断或摘要历史会越来越长输入 Token 线性增长。工具定义和函数描述如果你用了工具调用Tool Use每个工具的名称、描述、参数 schema 都会计入输入 Token。工具多了这部分开销不小。格式化的输出要求如果你在提示词里要求模型输出 JSON、Markdown 表格等结构化内容模型为了满足格式要求输出 Token 往往会比自由文本更多。我自己的经验是做一次完整的 Token 审计把每个请求的输入构成拆开看经常能发现一两个“隐形大户”优化掉之后成本立竿见影地下降。4. 动手写一个可运行的用量分析脚本4.1 脚本整体设计思路这个脚本的目标很明确读取 API 调用日志统计每天的请求数、输入 Token、输出 Token、缓存命中情况并计算出对应的成本。设计上我遵循几个原则第一日志格式要兼容常见情况。不同项目的日志格式不一样有的记 JSON有的记纯文本。我在脚本里做了一个简单的解析层你可以根据自己的日志格式调整。第二成本计算参数可配置。模型单价会变缓存价格和普通价格不同这些我都做成配置项改起来方便。第三输出要直观。除了打印汇总还会按天聚合方便你看趋势。4.2 日志解析与数据清洗假设你的日志是每行一个 JSON 对象包含时间戳、模型名、输入 Token 数、输出 Token 数、缓存读取 Token 数、缓存写入 Token 数这几个字段。解析代码如下import json from datetime import datetime from collections import defaultdict def parse_log(file_path): records [] with open(file_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue try: obj json.loads(line) records.append({ timestamp: obj.get(timestamp), model: obj.get(model, unknown), input_tokens: obj.get(input_tokens, 0), output_tokens: obj.get(output_tokens, 0), cache_read_tokens: obj.get(cache_read_tokens, 0), cache_write_tokens: obj.get(cache_write_tokens, 0), }) except json.JSONDecodeError: continue return records这里有个细节日志里缺失的字段要补默认值否则后面统计的时候会报错。另外如果日志里有异常记录比如请求失败但记了 Token最好单独标记出来不要混进正常统计。4.3 成本计算核心逻辑成本计算的关键是把不同类别的 Token 乘以对应的单价。我定义了一个价格配置字典按模型区分PRICING { claude-3-5-sonnet-20241022: { input: 3.0, output: 15.0, cache_write: 3.75, cache_read: 0.30, }, claude-3-5-haiku-20241022: { input: 0.80, output: 4.0, cache_write: 1.0, cache_read: 0.08, }, } def calc_cost(record): model record[model] price PRICING.get(model) if not price: return 0.0 cost ( record[input_tokens] / 1_000_000 * price[input] record[output_tokens] / 1_000_000 * price[output] record[cache_write_tokens] / 1_000_000 * price[cache_write] record[cache_read_tokens] / 1_000_000 * price[cache_read] ) return cost提示上面的单价是示例值实际使用时请以官方最新价格为准。价格会调整脚本里的配置要跟着更新。4.4 按天聚合与结果输出统计部分我按天做聚合同时保留按模型维度的拆分这样你能看到“哪个模型花钱最多”def aggregate(records): daily defaultdict(lambda: { requests: 0, input_tokens: 0, output_tokens: 0, cost: 0.0, }) for r in records: day r[timestamp][:10] if r[timestamp] else unknown daily[day][requests] 1 daily[day][input_tokens] r[input_tokens] daily[day][output_tokens] r[output_tokens] daily[day][cost] calc_cost(r) return daily def print_report(daily): print(f{日期:12}{请求数:10}{输入Token:14}{输出Token:14}{成本(美元):12}) for day in sorted(daily.keys()): d daily[day] print(f{day:12}{d[requests]:10}{d[input_tokens]:14}{d[output_tokens]:14}{d[cost]:12.4f})跑出来的效果大概是这样日期请求数输入Token输出Token成本(美元)2025-01-1015202,340,000380,00012.842025-01-1116802,890,000420,00015.632025-01-1214202,100,000350,00011.37有了这张表哪天用量异常、哪个模型成本高一眼就能看出来。5. 从数据到决策用量分析与成本优化实战5.1 识别成本大头输入还是输出拿到统计结果之后第一件事是看输入和输出的比例。如果输入 Token 远大于输出 Token说明你的成本主要花在“喂给模型的内容”上优化方向是精简提示词、做对话截断、启用缓存。如果输出 Token 占比很高那就要考虑是不是输出要求太啰嗦或者模型档位选高了。我帮朋友分析的那次输入输出比大概是 8:1明显是输入侧的问题。后来把对话历史做了滑动窗口截断只保留最近 5 轮输入 Token 直接降了六成。5.2 缓存命中率分析如果你启用了提示缓存一定要单独统计缓存命中率。命中率高说明缓存策略有效命中率低可能是缓存内容太短、有效期设置不合理或者请求间隔太长导致缓存失效。我一般会算一个指标缓存读取 Token 占总输入 Token 的比例。这个比例越高说明省下的钱越多。5.3 模型分层使用的效果验证把简单任务切到轻量模型之后要回头验证效果有没有下降。我的做法是抽一批样本用两个模型分别跑人工对比输出质量。如果轻量模型在简单任务上表现够用那就放心切。实测下来分类和抽取类任务轻量模型和高端模型的差距很小但成本能差好几倍。5.4 一个完整的优化前后对比还是拿朋友那个项目举例。优化前每天约 1500 次请求平均每次输入 1800 Token输出 200 Token全部用高端模型日成本约 13 美元。优化措施包括对话历史截断到 5 轮、系统提示词启用缓存、分类任务切到轻量模型。优化后平均输入降到 700 Token缓存命中率约 40%日成本降到约 4.5 美元。功能上没有明显退化用户反馈也正常。6. 常见问题与排查技巧实录6.1 Token 统计对不上官方账单怎么办这是最常见的问题。可能的原因有几个一是本地 Tokenizer 和官方计数有偏差尤其是特殊字符和代码二是日志记录不完整有些请求没记上三是缓存 Token 的计费方式和你理解的不一样。排查顺序建议是先用官方计数接口抽样验证几条记录确认偏差范围再检查日志是否有遗漏最后核对缓存部分的计费规则。6.2 请求报错但 Token 已经消耗了有些错误是在模型已经开始生成之后才发生的比如连接中断、超时。这种情况下已经生成的 Token 可能会计费。我的做法是在日志里区分“成功请求”和“异常请求”异常请求单独统计看看占比高不高。如果异常请求的 Token 消耗占比超过 5%就要排查网络稳定性或者超时设置。6.3 缓存不生效的几种情况缓存不生效通常有这几个原因缓存内容太短没达到最小缓存长度要求请求间隔超过了缓存有效期缓存内容每次都有细微变化导致无法命中。解决办法是尽量让缓存内容保持稳定把变化的部分放到缓存之外。6.4 常见问题速查表问题现象可能原因排查方向账单比预估高很多对话历史未截断、未启用缓存检查输入 Token 构成缓存命中率低缓存内容不稳定、有效期短检查缓存内容和请求间隔轻量模型输出质量差任务复杂度超出模型能力换回高端模型或优化提示词Token 统计与账单偏差大本地计数偏差、日志遗漏用官方接口抽样验证异常请求 Token 消耗高网络不稳定、超时设置短检查网络和超时配置6.5 几个我踩过的坑第一个坑是用字符数估算 Token。早期我图省事按“一个汉字约等于 1.5 Token”来估结果实际偏差能到 30% 以上成本预估完全不准。后来老老实实用 Tokenizer 算才把误差控制住。第二个坑是缓存内容里带了时间戳。我一开始把“当前时间”写进了缓存内容结果每次请求缓存都无法命中白白付了缓存写入的钱。后来把时间戳挪到缓存之外命中率立刻上来了。第三个坑是忽略了工具定义的 Token 开销。有一次接入了十几个工具每个工具的描述都写得很详细结果光工具定义就占了一千多 Token。后来精简了工具描述只保留必要信息输入成本降了不少。7. 把脚本用起来日常监控与持续优化脚本写完之后我建议把它挂到日常流程里。最简单的做法是每天定时跑一次把结果输出到一个文件或者发到自己的通知渠道。这样一旦某天成本异常你能第一时间发现而不是等到月底看账单才反应过来。如果你用的是云服务也可以把统计结果推到监控面板上做一个成本趋势图。我自己的习惯是每周看一次趋势每月做一次完整的成本审计把优化措施的效果量化出来。成本优化不是一锤子买卖模型价格会变业务量会变使用模式也会变定期回顾才能持续把钱花在刀刃上。另外脚本里的价格配置记得定期更新。API 价格调整的时候如果不更新配置算出来的成本就是错的基于错误数据做的决策也会跑偏。我一般会在官方价格页做个书签每隔一段时间核对一次。最后分享一个我常用的技巧在脚本里加一个“单次请求平均成本”的指标。这个数字的变化比总成本更敏感能更早地反映出使用模式的变化。比如总成本没涨但单次平均成本涨了说明请求结构在变化可能某个功能模块的调用方式出了问题。这个指标帮我提前发现过好几次潜在的成本异常。