最近不少同学在群里吐槽:模型效果是挺好,但每天调接口像在烧钱,月底一看账单直接懵了。尤其是做 AI 应用、写自动化脚本、接客服机器人的朋友,对 token 的消耗速度非常敏感。很多时候,我们只关心“调用一次花多少钱”,却忽略了“一次调用到底吃了多少 token”“每天跑多少量级”“哪些环节在重复计费”。本文就围绕大模型 API 的 token 计费,手把手拆解成本构成、计算方式、监控手段和优化策略,帮你搞清楚“每天烧 token,我需要花多少钱”这件事。
文章适合这几类读者:正在接大模型 API 做应用的开发者、写脚本批量调用模型接口的工具党、需要给团队做成本评估的技术负责人。看完之后,你能自己估算业务场景下的每日 token 费用,能看懂官方账单里的关键字段,也能在出问题时快速定位“钱到底烧在哪了”。
1. 背景与核心概念
1.1 什么是 token
在自然语言处理领域,token 是模型处理文本的最小单元。可以简单理解为“模型认识的一个词、一个汉字、或一个子词片段”。
不同模型的分词方式不同,常见的经验规律是:
- 英文文本中,1 个 token 大约是 0.7 到 1 个单词。
- 中文文本中,1 个汉字大约是 1 到 2 个 token。
- 代码文本中,1 行代码可能被拆成 5 到 15 个 token。
- 标点、空格、换行、特殊符号也会占用 token。
为什么用 token 计费而不是按字数计费?因为模型本身并不直接处理“字”,而是把输入文本切分成 token 序列,再交给神经网络进行预测。也就是说,无论我们传多长的文本,模型实际消耗的计算资源与 token 数量强相关。按 token 计费,本质上是在按“模型计算量”付费。
1.2 为什么现在大家都觉得 token 贵
早期模型按次计费或按时计费,用户对资源消耗的感知不明显。现在主流大模型 API 普遍按“输入 token + 输出 token”分别计费,成本透明,却也让开发者感受到真正的用量压力。
日常场景中 token 消耗往往超出预期,常见原因包括:
- 每次请求都要携带系统提示词(system prompt),这部分 token 反复计费。
- 对话类应用把历史消息全部发给模型,上下文越长,单次调用越贵。
- 流式输出过程中,输出 token 数量可能成倍增加。
- 失败重试机制导致同一请求被计算多次。
- 模型输出超长,远超业务实际需要。
理解 token 计费逻辑,是所有成本控制的基础。
1.3 大模型 API 的常见计费维度
目前主流平台虽然具体规则不同,但核心计费维度可以归纳为三个:
| 计费维度 | 说明 | 典型场景 |
|---|---|---|
| 输入 token | 用户传入模型的全部文本,包括系统提示词、历史记录、用户问题、工具定义等 | 对话、翻译、摘要、信息抽取 |
| 输出 token | 模型生成的内容 | 文案生成、代码生成、回复消息 |
| 缓存 token | 命中缓存时,输入 token 的费用会大幅降低 | 相同前缀请求、固定系统提示词场景 |
有些平台还会区分“标准输入”和“缓存输入”,缓存命中的 token 费率往往是标准输入的十分之一甚至更低。这就解释了为什么同样调一个模型,有些团队每个月能省出一大笔开销。
1.4 为什么开发者需要掌握成本估算
从技术角度看,掌握 token 成本估算能帮助你:
- 在设计阶段就评估 AI 功能的可行性。
- 在开发中避免无意义的上下文膨胀。
- 在上线后快速定位异常消耗。
- 在做技术方案对比时,给出量化的成本依据。
很多项目不是死在模型能力不够,而是死在成本失控。提前建立“一条请求的完整成本 = 输入成本 + 输出成本 + 失败重试成本”这个思维模型,能避免很多后期麻烦。
2. 环境准备与版本说明
本文的成本计算和监控方案,依赖以下环境和工具。版本需要根据你的项目实际情况调整,下面以常见环境为例,重点演示配置思路。
- 操作系统:macOS / Linux / Windows 均可,本文代码使用 Python 3。
- Python 版本:3.8 或以上。
- 使用的 API:OpenAI 兼容格式的 Chat Completions 接口。其他大模型厂商的接口如果风格一致,也可以复用本文思路。
- 依赖库:openai、requests、python-dotenv。
- 辅助工具:飞书/钉钉/企业微信机器人(用于告警)、Redis(用于用量计数)。
安装依赖的参考命令:
pip install openai requests python-dotenv注意:不同平台的 SDK 版本和模型名称差异较大,本文示例代码以“模型名通过配置传入”的方式编写,方便你适配自己的环境。各模型的单价请务必以官方定价页面为准,下面是常见的示例价格,仅用于演示计算方法。
3. 核心概念拆解:如何计算一次调用的 token 费用
3.1 一次请求的完整构成
一次普通的 Chat Completions 请求,请求体通常长这样:
{ "model": "gpt-4o-mini", "messages": [ {"role": "system", "content": "你是一个客服助手,回答要简洁。"}, {"role": "user", "content": "帮我查一下订单状态"}, {"role": "assistant", "content": "好的,请问您的订单号是多少?"}, {"role": "user", "content": "订单号是 123456"} ], "max_tokens": 500 }这里面的所有 messages 数组都会计入输入 token,包括 system、user、assistant 的历史消息。也就是说,多轮对话中,之前所有轮次的文本都会进入本次计费。
3.2 输入价格与输出价格
以常见模型为例,假设:
- 输入 token 价格:0.15 美元 / 百万 token
- 输出 token 价格:0.60 美元 / 百万 token
- 缓存命中输入:0.015 美元 / 百万 token
单次调用的费用公式为:
费用 = (输入token数 × 输入单价) + (输出token数 × 输出单价) + (缓存token数 × 缓存单价)举例,一次调用输入 2000 token、输出 500 token,单价如上:
费用 = (2000 / 1000000 × 0.15) + (500 / 1000000 × 0.60) = 0.00030 + 0.00030 = 0.00060 美元约等于 0.0006 美元,单看一次确实很便宜。但如果每天调用 10 万次呢?
每日费用 = 0.0006 × 100000 = 60 美元一个月就是 1800 美元左右,折合人民币已经过万。这就是为什么“单次很便宜,总量吓死人”。
3.3 cached tokens 的计费区别
很多平台提供了“输入缓存”能力。当请求中的前缀内容与历史请求一致时,命中缓存的这部分输入不再按标准价格计费,而是按缓存价格计费。
例如,你的系统提示词固定为 1500 token,用户问题平均 300 token。每次都命中的话,1500 token 走缓存价,只有 300 token 走标准输入价。长期运行下来,节省的成本非常可观。
在代码中,可以通过 response.usage 字段查看缓存命中情况:
{ "prompt_tokens": 2000, "completion_tokens": 500, "total_tokens": 2500, "prompt_tokens_details": { "cached_tokens": 1500 } }如果发现自己请求的 cached_tokens 一直为 0,说明没有正确使用缓存能力,需要检查请求格式和平台文档。
3.4 max_tokens 对成本的影响
max_tokens 参数限制模型最大输出长度,但它不是“一定会用到这么多”。模型的输出 token 由生成内容决定,max_tokens 只是上限。
不过,如果模型开始“说废话”或重复内容,输出很容易接近上限。控制输出 token 数的办法包括:
- 在 system prompt 中明确“回答不超过 50 个字”。
- 设置合理的 max_tokens,比如普通客服回复 200,摘要生成 300。
- 使用结构化输出,让模型直接输出 JSON,避免多余解释。
4. 完整实战:写一个每日 token 成本估算器
下面用一个 Python 脚本,模拟多用户、多轮对话场景,统计每天的 token 消耗和预估成本。这个脚本可以用于上线前的压力评估,也可以结合日志统计真实用量。
4.1 项目结构
建议按下面的目录组织代码,便于后续扩展:
token-cost-estimator/ ├── config.py # 模型、价格、场景参数 ├── estimator.py # 核心成本计算逻辑 ├── mock_chat.py # 模拟一次调用,打印 token 明细 ├── daily_report.py # 统计每日汇总并输出报告 └── requirements.txt # 依赖列表4.2 配置模块
文件路径:config.py
# -*- coding: utf-8 -*- """ 价格配置 注意:价格和模型名只是示例,请以你实际使用的平台页面为准 """ MODEL_CONFIG = { "gpt-4o-mini": { "input_price_per_million": 0.15, # 美元/百万输入token "output_price_per_million": 0.60, # 美元/百万输出token "cache_price_per_million": 0.015, # 美元/百万缓存命中token }, "gpt-4o": { "input_price_per_million": 2.50, "output_price_per_million": 10.00, "cache_price_per_million": 1.25, }, } # 业务场景参数 SCENARIO = { "daily_requests": 100000, # 每日调用次数 "system_prompt_tokens": 1500, # 固定系统提示词 token "history_tokens": 1000, # 多轮历史消息 token "user_avg_tokens": 300, # 用户平均问题 token "output_avg_tokens": 400, # 模型平均输出 token "cache_hit_rate": 0.8, # 缓存命中率,0~1 }这里把价格和场景参数单独放在配置文件里,是为了方便对照平台账单调整,也方便多人协作时统一口径。
4.3 核心计算逻辑
文件路径:estimator.py
# -*- coding: utf-8 -*- """ 成本估算核心逻辑 """ def calc_request_cost(model_config, input_tokens, output_tokens, cache_hit_rate=0): """ 计算单次请求成本 参数: model_config: 模型价格配置 input_tokens: 标准输入 token 数(不含缓存命中部分) output_tokens: 输出 token 数 cache_hit_rate: 缓存命中比例,0 表示无缓存命中,0.8 表示80%的输入命中缓存 返回: dict: 包含各项费用的明细 """ input_price = model_config["input_price_per_million"] / 1_000_000 output_price = model_config["output_price_per_million"] / 1_000_000 cache_price = model_config["cache_price_per_million"] / 1_000_000 cached_tokens = int(input_tokens * cache_hit_rate) standard_tokens = input_tokens - cached_tokens standard_input_cost = standard_tokens * input_price cache_input_cost = cached_tokens * cache_price output_cost = output_tokens * output_price total_cost = standard_input_cost + cache_input_cost + output_cost return { "total_tokens": input_tokens + output_tokens, "standard_input_tokens": standard_tokens, "cached_tokens": cached_tokens, "output_tokens": output_tokens, "standard_input_cost": round(standard_input_cost, 6), "cache_input_cost": round(cache_input_cost, 6), "output_cost": round(output_cost, 6), "total_cost": round(total_cost, 6), } def calc_daily_cost(model_name, scenario): """ 根据每日调用量,估算一天的总成本 参数: model_name: 模型名称 scenario: 场景配置字典 返回: dict: 每日汇总成本 """ model_config = MODEL_CONFIG.get(model_name) if not model_config: raise ValueError(f"未找到模型 {model_name} 的配置,请先在 config.py 中添加") per_request_input = ( scenario["system_prompt_tokens"] + scenario["history_tokens"] + scenario["user_avg_tokens"] ) per_request_output = scenario["output_avg_tokens"] request_cost = calc_request_cost( model_config, per_request_input, per_request_output, scenario["cache_hit_rate"], ) daily_requests = scenario["daily_requests"] daily_cost = request_cost["total_cost"] * daily_requests return { "model": model_name, "per_request_detail": request_cost, "daily_requests": daily_requests, "daily_cost": round(daily_cost, 2), "monthly_cost_estimate": round(daily_cost * 30, 2), }4.4 模拟单次调用与每日报告
文件路径:mock_chat.py
# -*- coding: utf-8 -*- """ 模拟一次真实的 API 调用,并展示 usage 字段 需要将 OPENAI_API_KEY 配置在环境变量中 """ import os from openai import OpenAI client = OpenAI( api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"), ) response = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是一个客服助手,回答要简洁。"}, {"role": "user", "content": "我下单三天了还没发货,能帮我查一下吗?"}, ], max_tokens=200, ) usage = response.usage print("模型回复:", response.choices[0].message.content) print("=== usage 明细 ===") print("输入 token:", usage.prompt_tokens) print("输出 token:", usage.completion_tokens) print("总 token:", usage.total_tokens) # 缓存命中信息在不同平台可能字段不同,这里做兼容读取 try: cached = usage.prompt_tokens_details.cached_tokens print("缓存命中 token:", cached) except Exception: print("缓存命中 token: 当前接口未返回该字段,可忽略")文件路径:daily_report.py
# -*- coding: utf-8 -*- """ 每日成本报告入口 """ from config import MODEL_CONFIG, SCENARIO from estimator import calc_daily_cost def main(): for model_name in MODEL_CONFIG: report = calc_daily_cost(model_name, SCENARIO) print("=" * 50) print(f"模型: {report['model']}") print("单次请求明细:") detail = report["per_request_detail"] print(f" total_tokens: {detail['total_tokens']}") print(f" 标准输入: {detail['standard_input_tokens']}") print(f" 缓存命中: {detail['cached_tokens']}") print(f" 输出: {detail['output_tokens']}") print(f" 单次费用: ${detail['total_cost']}") print(f"每日调用次数: {report['daily_requests']}") print(f"每日成本(美元): ${report['daily_cost']}") print(f"每月预估成本(美元): ${report['monthly_cost_estimate']}") print("=" * 50) if __name__ == "__main__": main()4.5 运行与验证
运行报告脚本:
python daily_report.py预期输出大致如下:
================================================== 模型: gpt-4o-mini 单次请求明细: total_tokens: 3200 标准输入: 560 缓存命中: 2240 输出: 400 单次费用: 0.00059 每日调用次数: 100000 每日成本(美元): 59.19 每月预估成本(美元): 1775.7 ==================================================这里有一个很容易看错的地方:在开启缓存命中后,3200 token 的总量没有变,但 2240 个 token 走了缓存价,所以单次费用从原本的 0.001 美元级别降到了约 0.0006 美元。省下的钱是实打实的。
4.6 结果如何影响方案决策
假设你的业务单次利润只有 0.01 元人民币,而模型成本折算为 0.004 元人民币(约 0.0006 美元),那么利润空间还比较充裕。但如果一次调用吃掉 2000 输出 token,成本直接翻好几倍,可能瞬间把利润打没。
通过这个估算器,你可以在三分钟内回答几个关键问题:
- 预算是每天 100 美元,最多能支撑多少次调用?
- 模型输出从 400 token 降到 200 token,每月省多少钱?
- 缓存命中率从 80% 降到 50%,成本上升多少?
这些数据能帮助你在模型选型和功能设计阶段就做出判断,而不是等账单出来再后悔。
5. 真实场景下的成本监控与预警
估算器做的是事前评估,真实项目中还需要埋点监控实际用量。下面分享一套轻量级监控方案,不依赖复杂平台,用日志和脚本就能跑起来。
5.1 记录每次调用的 usage
无论使用官方 SDK 还是 requests 直连,响应体里通常会包含 usage 字段。建议在封装层统一把 usage 记录下来,落到本地日志或数据库。
日志字段建议最少包含:
- 时间戳
- 模型名称
- 业务场景编号
- 输入 token 数
- 输出 token 数
- 缓存命中 token 数
- 本次请求费用
- 是否重试过
参考日志格式:
2025-06-08 10:23:45|gpt-4o-mini|customer_service|input=1200|output=300|cache=900|cost=0.00039这样一条日志,既能看到单次请求的明细,也能为后续聚合统计提供原始数据。
5.2 用脚本按天汇总成本
文件路径:summarize_logs.py
# -*- coding: utf-8 -*- """ 从日志中汇总每日 token 消耗和费用 """ import datetime from collections import defaultdict def parse_line(line): """解析一行日志,返回字段字典""" parts = line.strip().split("|") if len(parts) < 5: return None return { "time": parts[0], "model": parts[1], "scene": parts[2], "input_tokens": int(parts[3].split("=")[1]), "output_tokens": int(parts[4].split("=")[1]), } def summarize(log_path): daily = defaultdict(lambda: {"requests": 0, "input_tokens": 0, "output_tokens": 0}) with open(log_path, "r", encoding="utf-8") as f: for line in f: data = parse_line(line) if not data: continue day = data["time"][:10] daily[day]["requests"] += 1 daily[day]["input_tokens"] += data["input_tokens"] daily[day]["output_tokens"] += data["output_tokens"] for day in sorted(daily): info = daily[day] print(f"{day}: 请求 {info['requests']} 次, 输入 {info['input_tokens']} token, " f"输出 {info['output_tokens']} token") if __name__ == "__main__": summarize("api_usage.log")5.3 设置每日成本阈值预警
在成本超过预期时第一时间通知,是避免“烧穿预算”的关键。常见的实现方式:
- 定时任务每 30 分钟统计一次当日累计成本。
- 超过阈值时,通过 Webhook 发送到企业微信/钉钉/飞书。
- 如果成本异常翻倍,自动暂停非核心任务。
简单告警示例:
# -*- coding: utf-8 -*- """ 每日成本阈值检查脚本 """ import requests DAILY_COST_LIMIT_USD = 50.0 WEBHOOK_URL = "https://your-webhook-url" def send_alert(message): payload = {"msgtype": "text", "text": {"content": message}} requests.post(WEBHOOK_URL, json=payload, timeout=5) def check_cost(cost_today): if cost_today > DAILY_COST_LIMIT_USD: send_alert(f"今日成本已达 ${cost_today:.2f}, 超过阈值 ${DAILY_COST_LIMIT_USD}") # 示例用法 check_cost(52.3)6. 常见问题与排查思路
6.1 我的 token 消耗量为什么比预想高很多?
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 单次请求输入 token 很多 | messages 数组里塞入了完整历史记录 | 只携带最近几轮对话,或做摘要 |
| 输出 token 远超预期 | system prompt 没约束长度,模型自由发挥 | 明确“50字以内”,调低 max_tokens |
| cached_tokens 一直为 0 | 请求未满足缓存条件 | 检查请求前缀是否严格一致,查看平台缓存规则 |
| 每日账单波动大 | 并发量突增或异常重试 | 排查线上流量,增加熔断机制 |
| 批量任务重复计费 | 失败重试时重新发送了完整上下文 | 重试前记录进度,避免整段重新请求 |
6.2 为什么上下文越长越贵?
在 Transformer 架构下,模型需要处理整个输入序列。上下文越长,每次请求的输入 token 越多,计算量越大,费用也越高。多轮对话如果不做历史裁剪,token 消耗会随着对话轮次线性增长,很容易失控。
一个 10 轮对话的机器人,假设每轮 500 token,最后一轮请求可能就要携带 5000+ token 的历史。如果在 10000 个并发会话上运行,成本会非常可观。
解决思路:
- 只保留最近 3 到 5 轮对话。
- 对历史消息做摘要压缩。
- 业务上需要的长期记忆,改用向量数据库存储。
6.3 为什么重试会导致费用翻倍?
很多开发者直接在异常后走“重新调用一次”的逻辑,但并没有清除掉上一次的上下文。如果一次失败调用实际已经在服务端计算了输出(比如超时但结果已生成),重试相当于两次计费。
更稳妥的做法:
- 使用幂等键,避免同一请求重复计费。
- 重试前确认上一次调用确实失败,而不是“响应慢”。
- 对非核心场景限制重试次数,例如最多 2 次。
6.4 有没有免费 token 额度可以用于测试?
大部分主流平台在注册时会赠送一定试用额度,或者提供免费模型档。用这些额度做功能验证和成本评估,是省钱的第一步。需要注意:
- 免费额度通常有有效期。
- 免费模型与收费模型的效果可能差异较大。
- 生产环境不要依赖免费额度。
7. 成本优化最佳实践
7.1 提示词压缩与固定前缀
系统提示词每个请求都在计费,所以要尽可能精简。下面是一个优化对比:
优化前:
你是一个专业的客服助手,你的名字叫小智。你来自我们公司的人工智能团队。你的任务是回答用户关于订单、物流、商品、退换货等问题。回答时要注意礼貌,同时要给出具体的信息。如果用户问的问题你不清楚,可以要求用户提供订单号。请用简洁的、平易近人的语气回复。这段大概 90 个汉字,可能消耗 120 到 150 token。优化后:
你是客服机器人。回复需简洁。不清楚时,请先询问订单号。大概是原文本的 1/4,每次请求省下约 90 token。按每天 10 万次请求计算,每月省下的费用非常可观。
优化提示词的核心原则:
- 用最短的句子表达必要条件。
- 删除冗余的“人情味”修饰语。
- 把高频约束(如“请用JSON输出”)合并成一条。
- 测试压缩后效果是否满足业务要求。
7.2 合理利用缓存命中
缓存命中需要请求前缀保持稳定。如果系统提示词固定、工具定义固定、历史记录起始部分固定,就可以触发缓存。
实际项目中的最佳实践:
- 不要把动态内容放在 system prompt 中。
- 动态参数(如用户ID)尽量放在最后一条 user 消息里。
- 保持 tool/function 定义的顺序和内容不变。
- 监控 usage 中 cached_tokens 的占比,常态化排查命中情况。
7.3 模型分级与降级策略
不是所有请求都需要最强模型。成本优化的关键思路是“按需分配”。
推荐分级策略:
| 请求类型 | 推荐模型档 | 原因 |
|---|---|---|
| 简单问答、正则抽取 | 小模型,价格最低档 | 效果够用 |
| 结构化抽取、意图识别 | 中等模型 | 准确率与成本平衡 |
| 代码生成、复杂推理 | 旗舰模型 | 需要强能力 |
| 批量离线任务 | 异步 + 低峰期 | 避开高峰,提升吞吐 |
降级策略示例:
- 先试小模型,如果一次返回质量不达标,再升级到大模型。
- 用户问答场景用快模型,深度分析场景用强模型。
- 高峰期自动切到低一档模型,保证服务不中断。
7.4 控制并发与流量
并发高不代表成本高,但并发高会放大异常情况的损失。在流量控制上,建议以下几点:
- 对每个用户的请求频率做限流,避免恶意请求打爆账单。
- 批量任务使用队列削峰,避免瞬时并发挤满。
- 设置单用户单日 token 上限,防止一个异常用户消耗大量额度。
7.5 定期进行成本复盘
建议每周或每月做一次成本复盘,围绕以下问题展开:
- 哪些场景消耗 token 最多?是否合理?
- 缓存命中率有没有下降?为什么?
- 是否存在输出过长、内容重复的情况?
- 有没有无效请求占用额度?
- 模型档位是否还有下调空间?
把成本复盘变成常规流程,而不是出了问题才看账单。
8. 总结与建议
通过本文的拆解,你应该已经掌握了大模型 API token 成本的核心计算模型:每次请求的费用由输入 token、输出 token、缓存命中 token 共同决定,日成本等于单次费用乘以调用次数,再叠加失败重试带来的额外损耗。
落到实际项目中,有几件事需要重点推进:
- 上线前用成本估算脚本模拟每日开销,不要等到账单出来才知道超支。
- 开发阶段统一封装 API 调用层,记录 usage 日志,为后续监控打好基础。
- 运行期间配置成本阈值告警,确保异常消耗能及时被发现。
- 定期优化提示词,压缩上下文,提升缓存命中率。
- 根据业务场景做模型分级,不让高成本模型处理无需高智能的任务。
如果你正打算做 AI 应用,或者已经在为大模型 API 账单发愁,强烈建议先把本文的估算脚本跑起来,用真实用量替换掉示例参数。清楚每一分钱烧在哪里,才能让 AI 功能在成本可控的前提下稳定跑起来。