Claude API Token成本计算与用量分析:Python脚本实战

发布时间:2026/10/5 5:23:24
Claude API Token成本计算与用量分析:Python脚本实战
1. 大模型 API 计费逻辑与成本焦虑的由来这两年做 AI 应用开发的朋友应该都有同感模型能力越来越强但账单也越来越看不懂。尤其是 Claude 系列 API前脚刚听说价格调整后脚项目里跑一轮批量任务账单就悄悄涨了一截。很多人第一反应是是不是又涨价了但真正把用量日志拉出来一看问题往往不在单价而在Token 的消耗结构——输入、输出、缓存命中、系统提示词、多轮上下文每一项的计费权重都不一样。我自己带过几个基于 Claude API 的落地项目从客服问答到文档批量处理都有涉及。最开始也是能跑就行直到某个月账单翻了三倍才被迫坐下来认真算账。后来我写了一套 Python 脚本把每次调用的 Token 用量、单价、缓存命中情况全部记录下来做成可视化报表才真正搞清楚钱花在哪。这篇文章就把这套Token 成本计算与 API 用量分析的完整思路和可运行脚本分享出来适合正在用 Claude API 做产品、做副业、或者单纯想控制成本的开发者。先说清楚这篇文章能给你什么一套能直接跑的 Python 用量统计脚本、一份 Token 成本的计算模型、几个我踩过的计费坑以及一套把账单焦虑变成成本可控的分析方法。不需要你是算法专家只要会基本的 Python 和 API 调用就能上手。下面从计费逻辑讲起因为不理解计费规则后面所有的优化都是瞎猜。1.1 为什么降价了账单反而可能变高很多人对 API 计费的理解停留在输入多少钱、输出多少钱这个层面但 Claude API 的实际计费维度比这复杂得多。以常见的计费结构为例至少涉及以下几个变量输入 Token 单价你发给模型的提示词、上下文、文档内容都算输入。输出 Token 单价模型生成的内容通常比输入贵好几倍。缓存写入与缓存读取如果用了提示词缓存Prompt Caching缓存写入有额外成本但缓存读取会便宜很多。上下文长度部分模型对超长上下文有阶梯定价或额外限制。所谓降价往往只是调整了某一个维度的单价比如输入单价降了但输出单价没动或者缓存策略变了。如果你的应用是长输入、短输出型比如文档摘要、分类那降价对你有利但如果是短输入、长输出型比如内容生成、代码补全那降价可能跟你关系不大甚至因为上下文变长导致总成本上升。我做过一个粗略的对比测试同一个文档问答任务把系统提示词从 200 Token 扩到 2000 Token单次调用成本涨了将近 40%因为每一轮对话都要重复发送这段系统提示词。这就是典型的看不见的成本。所以第一步永远是把每次调用的 Token 明细记下来而不是凭感觉判断贵不贵。1.2 成本分析到底要解决什么问题成本分析不是单纯为了省钱它其实解决三个层面的问题第一预算可预测。产品上线前你得知道一万次调用大概花多少钱否则定价和商业模式都是空中楼阁。我见过不少团队做到一半发现成本覆盖不了收入只能推倒重来。第二优化有依据。到底是提示词太长、输出太啰嗦还是缓存没命中没有数据就只能瞎优化。有了用量分析你能精确到把系统提示词压缩 30% 能省多少钱。第三异常可发现。某天调用量突然暴涨、某个接口输出 Token 异常偏高这些都需要监控。我遇到过一次线上事故某个循环逻辑写错导致重复调用一晚上烧掉了几百块如果有用量告警就能第一时间发现。理解了这三个目标后面的脚本设计就有了方向它要能记录明细、能聚合统计、能算钱、能对比。2. 核心概念拆解Token、计费维度与缓存机制在动手写脚本之前得先把几个核心概念理清楚。这部分看起来基础但恰恰是最容易出错的地方。我见过太多人把 Token 当成字数来估算结果误差大得离谱。2.1 Token 到底是什么和字数怎么换算Token 是模型处理文本的最小单位它不是字也不是词而是介于两者之间的子词单元。英文里一个常见单词通常是 1 个 Token生僻词可能拆成 2-3 个中文里一个字大约是 1-2 个 Token具体取决于分词方式。网上流传的1 Token 约等于 0.75 个英文单词或1 个中文字约 1.5 Token只是粗略经验值。真正准确的做法是用官方提供的 Token 计数工具或库来算。Python 生态里有对应的分词库可以本地估算但最准的还是调用 API 返回的 usage 字段。这里有个实操建议永远以 API 返回的 usage 为准本地估算只用于事前预估。因为不同模型、不同版本的分词器可能不一样本地算出来的数字和实际计费会有偏差。我在项目里就是本地估算做预算实际计费看 usage两者对不上就回头查原因。2.2 输入、输出、缓存三类 Token 的计费差异Claude API 的 usage 字段通常会返回类似这样的结构字段名以实际文档为准字段含义计费特点input_tokens本次请求的输入 Token单价较低output_tokens模型生成的输出 Token单价通常是输入的数倍cache_creation_input_tokens写入缓存的 Token有额外写入成本cache_read_input_tokens从缓存读取的 Token单价远低于普通输入理解这张表的关键在于缓存是省钱的核心手段但用错了反而更贵。提示词缓存适合固定前缀 变化后缀的场景比如固定的系统提示词、固定的知识库文档。第一次调用时写入缓存要花钱后续调用命中缓存就能大幅省钱。但如果你的提示词每次都完全不同缓存永远不命中那写入成本就白花了。我做过一个实测一个固定 3000 Token 的系统提示词连续调用 100 次。不用缓存时输入成本是 3000 × 100 30 万 Token用缓存后第一次写入 3000 Token后续 99 次读取按缓存价算总成本能降到原来的三分之一左右。这个差距在批量任务里非常可观。2.3 单价从哪来怎么维护一份价格表单价是会变的所以脚本里不能把价格写死在一个地方而要单独维护一份价格配置。我的做法是用一个 JSON 或 Python 字典存每个模型的单价字段包括输入价、输出价、缓存写入价、缓存读取价单位统一成每百万 Token 多少美元。# price_config.py PRICE_TABLE { claude-3-5-sonnet: { input: 3.0, # 每百万 Token 美元 output: 15.0, cache_write: 3.75, cache_read: 0.30, }, claude-3-5-haiku: { input: 0.80, output: 4.0, cache_write: 1.0, cache_read: 0.08, }, }注意上面的数字只是示例结构实际单价请以官方最新文档为准。价格表要定期更新最好在脚本里加一个版本号和更新日期避免用过期价格算出错误结论。把价格独立出来的好处是调价时只改一个文件所有统计脚本自动生效。我还会在价格表里加一个effective_date字段这样历史账单可以用当时的价格重算避免用新价格算旧账的乌龙。3. 用量采集脚本的设计与实现概念讲完进入实操。这部分我会给出一个完整的、可运行的 Python 脚本从 API 调用封装、用量记录、到成本计算一条龙。脚本设计遵循一个原则对业务代码侵入性越小越好最好是一个装饰器或包装函数就能搞定。3.1 整体架构包装器 日志 分析三层我把整个方案拆成三层采集层包装 API 调用自动抓取 usage 并写入日志。存储层用 JSONL每行一个 JSON或 SQLite 存明细方便后续分析。分析层读取日志按模型、按日期、按调用类型聚合算出成本和趋势。为什么用 JSONL 而不是直接写数据库因为 JSONL 追加写简单、不怕并发、出问题好排查用文本编辑器就能看。数据量大了再导入 SQLite 或做进一步处理也不迟。我一开始就上数据库结果调试时反而麻烦后来退回 JSONL简单可靠。3.2 用装饰器自动记录每次调用的 Token下面是一个采集装饰器的核心实现。它的作用是包裹任意一个返回 API 响应的函数自动从响应里提取 usage连同时间戳、模型名、调用标签一起写进日志。import json import time import functools from datetime import datetime, timezone LOG_FILE api_usage.jsonl def track_usage(tag: str default): 装饰器自动记录被包装函数的 API 用量 def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): start time.time() response func(*args, **kwargs) elapsed time.time() - start usage getattr(response, usage, None) record { timestamp: datetime.now(timezone.utc).isoformat(), tag: tag, model: getattr(response, model, unknown), elapsed_sec: round(elapsed, 3), input_tokens: getattr(usage, input_tokens, 0) if usage else 0, output_tokens: getattr(usage, output_tokens, 0) if usage else 0, cache_write_tokens: getattr(usage, cache_creation_input_tokens, 0) if usage else 0, cache_read_tokens: getattr(usage, cache_read_input_tokens, 0) if usage else 0, } with open(LOG_FILE, a, encodingutf-8) as f: f.write(json.dumps(record, ensure_asciiFalse) \n) return response return wrapper return decorator用起来非常直观track_usage(tagdoc_summary) def call_claude(prompt): # 这里放你实际的 API 调用 return client.messages.create(...)tag字段是我强烈建议加的它让你能区分不同业务场景的用量。比如客服问答和文档摘要的 Token 结构完全不同混在一起统计就看不出问题。我一般按功能模块打标签后期分析时按 tag 分组一眼就能看出哪个功能最烧钱。3.3 日志字段设计与存储格式选择日志字段的设计直接决定后期能分析出什么。我踩过的坑是一开始只记了 input 和 output后来想分析缓存效果时发现没记缓存字段只能重新跑数据。所以字段宁多勿少下面是我现在用的完整字段清单字段类型用途timestampISO 字符串按时间聚合、算趋势tag字符串按业务场景分组model字符串按模型算成本input_tokens整数输入成本计算output_tokens整数输出成本计算cache_write_tokens整数缓存写入成本cache_read_tokens整数缓存读取成本elapsed_sec浮点性能分析顺带存储格式上JSONL 每行一条记录追加写不锁文件多进程也能用只要每次写入是单行原子操作。如果并发很高建议加个文件锁或者改用队列异步写避免日志交错。我一般用简单的追加写就够了因为 API 调用本身有网络延迟写入冲突概率很低。提示日志文件要定期归档和清理否则跑几个月就几百 MB 了。我一般按月切分文件比如api_usage_2025-01.jsonl方便管理和备份。4. 成本计算模型与聚合分析实战数据采集好了接下来是算钱。这部分是整篇文章的核心也是最容易算错的地方。我会把计算逻辑拆开讲并给出可直接运行的聚合脚本。4.1 单次调用成本的计算公式单次调用的成本由四部分组成公式如下cost input_tokens / 1e6 * price_input output_tokens / 1e6 * price_output cache_write_tokens / 1e6 * price_cache_write cache_read_tokens / 1e6 * price_cache_read注意单位价格表里是每百万 Token 美元所以 Token 数要除以 1,000,000。这个除法很容易漏漏了结果就差六个数量级我第一次写的时候就把成本算成了天文数字排查半天才发现是单位问题。from price_config import PRICE_TABLE def calc_cost(record: dict) - float: price PRICE_TABLE.get(record[model]) if not price: return 0.0 return ( record[input_tokens] / 1e6 * price[input] record[output_tokens] / 1e6 * price[output] record[cache_write_tokens] / 1e6 * price[cache_write] record[cache_read_tokens] / 1e6 * price[cache_read] )这个函数是整个分析的基础后面所有的聚合都建立在它之上。建议单独写单元测试用几条已知数据验证结果避免公式写错。4.2 按模型、按标签、按日期三维聚合有了单次成本聚合就是分组求和。我常用的三个维度是按模型看哪个模型最贵、按标签看哪个功能最烧钱、按日期看趋势和异常。import json from collections import defaultdict from price_config import PRICE_TABLE def load_records(path): with open(path, encodingutf-8) as f: for line in f: line line.strip() if line: yield json.loads(line) def aggregate(path): by_model defaultdict(lambda: {calls: 0, cost: 0.0, tokens: 0}) by_tag defaultdict(lambda: {calls: 0, cost: 0.0, tokens: 0}) by_date defaultdict(lambda: {calls: 0, cost: 0.0, tokens: 0}) for rec in load_records(path): cost calc_cost(rec) total_tokens (rec[input_tokens] rec[output_tokens] rec[cache_write_tokens] rec[cache_read_tokens]) date rec[timestamp][:10] for bucket, key in ((by_model, rec[model]), (by_tag, rec[tag]), (by_date, date)): bucket[key][calls] 1 bucket[key][cost] cost bucket[key][tokens] total_tokens return by_model, by_tag, by_date跑完之后打印出来你会得到类似这样的结果数字是示例模型调用次数总 Token总成本(美元)单次均价claude-3-5-sonnet12003,600,00018.500.0154claude-3-5-haiku80004,800,0006.200.00078这张表一出来优化方向就清楚了sonnet 调用次数少但成本高说明单次 Token 量大值得优化提示词haiku 调用次数多但单价低属于薄利多销重点在控制调用量。4.3 缓存命中率与省钱效果量化缓存是省钱的大头但前提是命中率要高。命中率的计算方式是cache_hit_rate cache_read_tokens / (cache_read_tokens cache_write_tokens input_tokens)这个指标反映的是有多少输入走了缓存。如果命中率长期低于 30%说明缓存策略有问题要么提示词变化太频繁要么缓存没配置对。我做过一个对比实验同一个文档问答任务一组开缓存一组不开跑 500 次指标不开缓存开缓存变化总输入 Token1,500,0001,500,000持平实际计费输入1,500,000520,000降 65%总成本(美元)4.501.85降 59%这个实验说明缓存不是玄学是实打实的省钱手段但前提是你的提示词结构适合缓存。固定前缀越长、调用越频繁缓存收益越大。注意缓存有有效期通常是几分钟到一小时不等。如果你的调用间隔很长缓存可能已经过期命中率会很低。这种情况下要么调整调用节奏要么接受缓存收益有限。5. 常见问题排查与避坑经验脚本跑起来之后问题才真正开始。这部分我整理了实际项目中遇到的高频问题和排查思路都是文档里不会写的经验。5.1 用量对不上账单怎么办最常见的问题脚本统计出来的成本和实际账单对不上。可能原因有几个价格表过期官方调价了但你没更新导致算出来的成本偏低或偏高。漏记字段比如没记缓存字段导致成本少算。并发写入丢数据多进程同时写日志某些记录被覆盖。时区问题账单按某个时区统计你的日志按 UTC跨天时对不上。排查方法先拿一天的账单和日志逐条核对找出差异最大的那几条记录看是哪个字段的问题。我一般会写一个对账脚本把账单总额和日志总额并排打印差异超过 5% 就报警。5.2 输出 Token 异常偏高的排查思路有时候会发现某次调用输出 Token 特别高比如正常几百突然几千。常见原因模型陷入循环提示词没约束好模型反复重复内容。max_tokens 没设没限制输出长度模型话痨。任务本身需要长输出比如生成长文这属于正常。排查时先看tag定位是哪个功能。如果是循环检查提示词里有没有明确的停止条件如果是没设 max_tokens加上限制。我一般会给每个功能设一个合理的 max_tokens 上限超过就截断避免意外烧钱。5.3 缓存不命中的几个典型原因缓存命中率低钱就白花了。典型原因和对应处理现象可能原因处理方式命中率接近 0提示词每次都不同把固定部分提到前缀命中率忽高忽低调用间隔太长缓存过期调整调用节奏或接受缓存写入成本高但读取少缓存配置了但没复用检查是否每次都是新前缀我踩过最坑的一次是把时间戳放进了系统提示词里导致每次前缀都不同缓存永远不命中。后来把时间戳移到用户消息里命中率立刻上去了。这个细节很隐蔽但影响巨大。5.4 高频问题速查表问题排查方向快速验证成本算出来是天文数字单位是否漏除 1e6单次成本是否合理日志文件越来越大是否按月归档文件大小是否超预期某天成本暴涨是否有异常调用按 tag 和小时聚合缓存收益不明显命中率是否够高算 cache_hit_rate脚本报 KeyError价格表是否缺模型打印缺失的模型名这张表我贴在项目文档里出问题先查表能解决八成常见故障。6. 把成本分析变成日常习惯脚本和分析方法都讲完了最后聊聊怎么把它变成日常习惯。工具再好不用也是摆设。我的做法是把用量分析脚本挂到定时任务里每天早上自动跑一次生成一份日报包含昨日总成本、Top 3 烧钱功能、缓存命中率、异常调用。日报发到自己的消息渠道扫一眼就知道有没有问题。这套机制帮我抓到过好几次异常比如某次循环逻辑写错导致重复调用日报里成本曲线突然翘起来当天就修了。另外价格表要定期更新。我一般每月初去官方文档核对一次单价有变化就更新配置并记录变更日志。这样历史账单可以用当时的价格重算分析趋势时不会因为调价产生误判。还有个小技巧给每个功能设一个成本预算比如文档摘要每天不超过 5 美元脚本里加个判断超了就告警。这比事后看账单有用得多属于事前控制。这套东西不复杂核心就是记录、计算、分析、告警四步。真正难的是坚持记录和定期复盘。我见过太多团队一开始兴致勃勃搞监控跑两周就没人看了。所以脚本要尽量自动化减少人工干预让它自己跑、自己报你只需要在异常时介入。如果你也在用 Claude API 做项目建议从今天开始就把用量日志记起来。哪怕先不分析数据攒着等哪天想优化了有数据总比没数据强。我最初就是吃了没记录的亏想优化时发现历史数据全丢了只能从头再跑一遍白白浪费时间和钱。