实际 AI 项目里,Token 成本往往不是模型选型那一刻决定的,而是在调用量上去之后悄悄失控的。近期有消息称,某大型科技公司开始收紧员工使用 AI 的内部预算,甚至出现单个账号在 28 天内消耗掉 2.8 万美元 Token 的情况。这里的具体数字和内部统计口径无法核实,但它足够说明一个趋势:Token 已经不只是“模型输入输出的计量单位”,而是变成了一项需要纳入工程治理、预算管控、监控告警和成本核算的资源。
这篇文章围绕一个核心问题展开:当 AI 调用变成团队基础设施,如何避免 Token 账单在月底超出预期。内容会覆盖 Token 成本的计算方式、失控场景、预算配额设计、用量跟踪、告警搭建、账单调查路径,以及一套可以直接落到内部的检查清单。适用对象是正在接入大模型 API 的开发团队、负责成本治理的后端工程师,以及准备把 AI 功能从个人 Demo 推向正式生产的技术负责人。
1. 先理解 Token 成本的计算逻辑:它为什么能在 28 天内冲到几万美元
要治理成本,首先得知道成本是怎么产生的。Token 不是简单的“字数统计”,它与字符数、语言类型、分词器实现、上下文长度、输出长度都有关系。把 Token 的计量逻辑和计费逻辑拆开,后面的成本治理才有依据。
1.1 Token 到底是什么
Token 是模型处理文本时的最小语义单位。英文里一个单词可能被拆成一个或几个 Token,中文里一个汉字通常对应 1 到 2 个 Token。不同模型使用不同分词器,所以“同样一句话到底消耗多少 Token”在不同模型之间会有差异。
常见的直观理解:
- 英文场景:1 Token 约等于 0.7 到 1 个英文单词。
- 中文场景:1 个汉字约等于 1 到 2 个 Token。
- 代码场景:空格、缩进、符号都可能独立成 Token,所以代码的“Token 密度”比普通文本高。
- 特殊字符、Markdown 标记、JSON 结构都会增加 Token 数量。
这意味着,同一段业务提示词,在不同供应商、不同模型版本之间,实际消耗的 Token 数量不一样。做成本估算时,不能只看字符数,要看模型返回的 usage 字段,以及服务商计费口径。
1.2 成本公式:单价、输入输出和上下文放大三要素
API 计费通常分为:
- 输入 Token:发送给模型的提示词、历史对话、工具返回结果、系统提示词。
- 输出 Token:模型生成的回复内容。
- 缓存 Token:部分服务商提供提示词缓存,命中缓存时输入价格更低。
单次请求成本的基本公式是:
单次请求成本 = 输入 Token 数 × 输入单价 + 输出 Token 数 × 输出单价单价看起来很小,但乘数效应非常明显。假设某个模型的输入单价是 1 元/百万 Token,输出单价是 3 元/百万 Token,一次请求输入 5000 Token、输出 500 Token,单次成本约 0.0065 元。如果一天有 10 万次请求,单日成本约 650 元,一个月就是 2 万元左右。这还是在模型单价不高、上下文较短的前提下。
如果换成高参数模型,输入输出单价更高,上下文又长,成本会成倍放大。比如一次请求携带 100K Token 上下文,即使输出只有 100 Token,输入费用也占绝对主导。对话场景如果每轮都把完整历史发给模型,成本会随着对话轮数近似线性上涨。
1.3 用一个最小示例计算单次请求的 Token 消耗
下面是一段简化计算逻辑,用于估算单次调用成本。真实项目中应读取模型返回的 usage 字段,而不是按字符估。
def estimate_cost(input_tokens: int, output_tokens: int, input_price_per_million: float, output_price_per_million: float) -> float: input_cost = input_tokens / 1_000_000 * input_price_per_million output_cost = output_tokens / 1_000_000 * output_price_per_million return input_cost + output_cost # 示例参数:某模型输入 10 元/百万,输出 30 元/百万 single_cost = estimate_cost( input_tokens=8000, output_tokens=1000, input_price_per_million=10, output_price_per_million=30, ) print(f"单次请求成本: {single_cost:.4f} 元")输出结果约为 0.11 元一次。如果这个接口被调用 10 万次,总成本约 1.1 万元。值得注意的是,这里的输入 Token 只是 8000,实际对话或 Agent 场景经常把上下文撑到几万甚至几十万。
注意:以上单价和计算仅为示例。落地前必须到模型服务商的计费页面确认最新价格、缓存价格和最低计费粒度,不要凭第三方文章或旧文档估算。
1.4 Token 成本失控往往出现在“隐性乘数”上
从成本公式可以看出,要让账单失控,不一定要把单价谈崩,只需要让以下三个乘数变大:
- 请求次数:同一个功能被高频调用。
- 输入上下文:每次请求都携带大量历史信息。
- 输出长度:模型回复比预期长很多。
28 天消耗 2.8 万美元,换算下来日均约 1000 美元。如果是单人开发者在 IDE 里高频补全代码、反复调试、让 Agent 自动执行多轮任务,完全可能达到这个量级。成本失控不是单一原因,而是请求次数、上下文长度、模型价格三个因素叠加的结果。
2. AI 项目里 Token 成本失控的五种典型场景
成本治理不是去修改模型 API 的价格,而是减少不必要的 Token 消耗。下面五种场景在团队项目中非常常见,每一条都对应具体的治理手段。
2.1 每次请求都携带超长历史上下文
聊天机器人类产品最常见的做法是把完整会话历史拼进下一次请求。对话超过 20 轮后,历史文本可能已经超过 1 万 Token,其中大部分是重复内容。更严重的是,系统提示词里如果塞入了长篇文档、固定模板、完整商品信息,这部分输入 Token 会在每次请求中反复消耗。
治理方向:
- 只保留最近 N 轮对话,更早内容做摘要。
- 系统提示词里的大段静态内容改为按需注入。
- 使用服务商提供的提示词缓存能力,降低重复输入的单价。
2.2 失败重试和 Agent 循环把成本倍数放大
普通 API 调用失败后重试一次,成本大约是原来的两倍。Agent 场景更危险,一个任务可能需要模型多次调用工具、读取结果、重新规划。如果某个工具持续报错,Agent 会反复进入“调用工具—看到错误—再次调用”的循环,一次任务可能消耗 20 到 50 次模型调用。
治理方向:
- 给 Agent 设置最大执行步数和总 Token 预算。
- 对工具调用设置超时和失败熔断。
- 当连续失败次数达到阈值时,直接结束任务并返回错误。
2.3 一个任务调用多个模型,缺少路由
很多团队在项目里同时接入了多个模型:一个负责普通问答,一个负责代码生成,一个负责长文档分析。如果没有做模型路由,所有请求都走最高配模型,成本会明显高于实际需要。
治理方向:
- 简单任务走低价低延迟模型。
- 复杂推理、长文本分析走高配模型。
- 可以在网关层根据任务类型、输入长度、用户等级动态选择模型。
2.4 代码补全和 IDE 插件在循环消费
代码补全场景下,IDE 插件会在用户输入停顿后自动触发请求。一次补全可能只有几十到几百 Token,但用户在一天内会触发几百次。如果团队里几十人都开了 AI 编程工具,且没有按团队配置限额,账单会快速上升。
治理方向:
- 为 AI 编程工具设置团队统一限额。
- 关闭不必要的自动触发功能。
- 区分个人使用和项目 Debug 使用场景。
2.5 测试与调试环境没有和线上隔离
开发人员在本地联调和测试环境里反复调用模型接口,是很正常的事。但如果没有把测试环境的模型降配、限流、加标记,这些请求会和生产环境共用同一个 API Key,导致账单里出现大量无法解释的“测试费用”。
治理方向:
- 不同环境使用不同 API Key。
- 测试环境默认使用低价模型。
- 所有请求都写入 request_id,便于定位环境来源。
下面的表格可以用于团队内部快速自查:
| 失控场景 | 成本放大方式 | 治理手段 |
|---|---|---|
| 超长历史上下文 | 输入 Token 随轮数线性增长 | 截断、摘要、缓存 |
| 失败重试与 Agent 循环 | 单任务调用次数成倍增加 | 限制步数、超时、熔断 |
| 缺少模型路由 | 简单任务也消耗高配模型 | 按任务维度做模型选择 |
| 代码补全自动触发 | 请求次数大但单次很小 | 团队限额、关闭自动触发 |
| 测试与线上共用 Key | 测试流量污染生产账单 | 环境拆分、独立配额 |
3. 预算和配额怎么落到工程上
只靠“提醒大家省着点用”无法治理成本。需要把预算约束变为代码逻辑和系统能力,从模型、项目、用户三个维度做限制。
3.1 从模型、项目和用户三个维度分配额度
可以借鉴公有云的 Quota 设计思路。每次模型调用前,先检查当前项目或用户在本周期内已经消耗的 Token 和预估费用,超过阈值直接拒绝请求,或者降级到低价模型。
代码层面可以封装一个预算检查函数:
import time class TokenBudget: def __init__(self, max_cost: float, window_seconds: int = 86400): self.max_cost = max_cost self.window_seconds = window_seconds self.cost_records = [] def try_consume(self, estimated_cost: float) -> bool: now = time.time() cutoff = now - self.window_seconds self.cost_records = [r for r in self.cost_records if r[0] > cutoff] total_cost = sum(r[1] for r in self.cost_records) if total_cost + estimated_cost > self.max_cost: return False self.cost_records.append((now, estimated_cost)) return True budget = TokenBudget(max_cost=100.0) if not budget.try_consume(estimated_cost=0.05): # 返回限流提示,或降级到低价模型 print("budget exceeded")这是最简示例,生产环境建议把记录存到 Redis 或数据库,避免单机内存状态在服务重启后丢失。
3.2 用低价模型承担简单任务,用高配模型处理高难度请求
模型路由是成本治理里收益最高、改动最少的方案。具体实现可以是纯函数,也可以做成网关中间件。核心逻辑是根据输入特征返回一个模型名称。
def route_model(task_type: str, input_length: int) -> str: # 简单分类任务走低价模型 if task_type in ("classify", "keyword", "extract"): return "fast-model" # 长文本分析走长上下文模型 if input_length > 20000: return "long-context-model" # 复杂推理和代码生成走高配模型 return "high-quality-model"路由策略要避免把所有请求都落到同一类模型上。建议在路由表里配置阈值,并定期看各模型的调用占比和成本占比。
3.3 对提示词做静态裁剪和摘要压缩
提示词越长,输入 Token 越高。很多系统提示词里包含了不会变化的产品介绍、政策条款、格式说明,这些内容可以:
- 移到服务端模板中,按需拼接。
- 利用提示词缓存,减少重复计费。
- 定期审查提示词,移除与当前任务无关的历史规则。
对于会话历史,可以做“滑动窗口 + 摘要”:
保留最近 10 轮完整对话 更早内容压缩为一段 200 Token 以内的摘要 把摘要放在系统消息中,把最近对话放在用户消息中这种做法的好处是既保留关键信息,又限制输入 Token 上限。
3.4 给 Agent 循环加预算上限和最大步数
Agent 场景最容易出现成本失控。设计任务执行器时,至少要加两个限制:
- 最大步数:例如最多执行 10 次工具调用。
- 最大 Token 预算:例如一次任务最多消耗 50 万 Token。
class AgentExecutor: def __init__(self, max_steps: int, max_total_tokens: int): self.max_steps = max_steps self.max_total_tokens = max_total_tokens self.used_tokens = 0 self.step = 0 def run(self, task): while self.step < self.max_steps: result = self.step_once(task) self.used_tokens += result.usage_tokens if self.used_tokens > self.max_total_tokens: self.abort("token budget exceeded") return if result.finished: return result self.step += 1 self.abort("max steps reached")这里的核心不是阻止 Agent 完成任务,而是让成本消耗存在上限。即使出现异常循环,也不会带来天价账单。
3.5 缓存与语义缓存:减少重复计算
如果多个用户经常问同一个问题,或者同一份文档需要反复分析,可以考虑输出缓存或语义缓存。
- 精确缓存:请求的 prompt 完全一致,直接返回历史结果。
- 语义缓存:对用户输入做向量化,相似度高时返回已有结果。
缓存能明显降低 Token 消耗,但要注意时效性和业务正确性。涉及实时数据、价格、库存等动态信息的请求,不应直接命中长期缓存。
注意:缓存方案适合“高重复、低动态”的常见问题场景。业务数据频繁变化的接口,不要为了省成本牺牲数据准确性。
4. 用量跟踪与告警:不能等账单出来才后悔
预算控制不能只靠请求前的拦截,还要做使用量分析和异常告警。没有观测,就无法回答“钱花到哪里了”。
4.1 在网关层统一记录 Token 用量
每个模型请求都应在网关层记录结构化日志,而不是让各个业务模块各自为战。推荐记录以下字段:
{ "request_id": "588cea3a-9d2d-4f95-a2be-6c9c1f0e2b11", "user_id": "user_1001", "project": "customer-service", "model": "high-quality-model", "prompt_tokens": 8500, "completion_tokens": 1200, "total_tokens": 9700, "estimated_cost": 0.095, "timestamp": "2025-01-20T10:30:00+08:00" }这些日志既可以写入 ClickHouse 或 Elasticsearch,用于查询明细;也可以每天离线汇总成报表,用于团队成本分摊。
4.2 用量报表怎么设计
成本报表至少需要支持四个维度:
| 维度 | 用途 | 示例 |
|---|---|---|
| 模型 | 看哪个模型最贵 | 高配模型占 70% 成本 |
| 项目 | 看哪个业务线消耗最多 | 智能客服占 40% Token |
| 用户/账号 | 看是否存在单人消耗异常 | 某账号 1 天消耗 100 万 Token |
| 时间 | 看趋势是否正常 | 工作日白天消耗高,凌晨突然飙升 |
报表可以做成每日任务,在前一天结束后生成,邮件或企业微信推送给相关负责人。成本异常不需要做到实时,但每日看到已经足够定位大部分问题。
4.3 设置三级告警
建议设置三级成本告警:
- 黄色:日消耗达到预算的 50%,提醒关注。
- 橙色:日消耗达到预算的 80%,提示接近上限。
- 红色:日消耗达到预算的 100%,立即暂停非核心调用。
告警不只要看总额,还要看突增。例如某项目前 7 天平均日消耗 5 万 Token,今天突然变成 100 万 Token,即使离预算上限还很远,也应该触发突增告警。
def check_alert(daily_cost: float, budget: float, spike_threshold: float = 3.0): ratio = daily_cost / budget if ratio >= 1.0: return "red" if ratio >= 0.8: return "orange" if ratio >= 0.5: return "yellow" if average_daily_cost and daily_cost > average_daily_cost * spike_threshold: return "yellow" return "ok"4.4 核账:模型返回的 usage 与账单口径可能不一致
模型响应里的 usage 字段是服务商统计的 Token 数,但账单里还可能包含:
- 部分服务商的最低计费粒度。
- 缓存命中时的特殊价格。
- 失败请求是否计费。
- 请求上下文被服务端自动补充的 Token。
因此,自建用量统计建议以“本地计算预估成本”作为监控口径,以“服务商账单”作为最终财务口径。两者出现偏差时,优先核对服务商文档和账单明细。
5. Token 成本排查:从账单倒推消费链路
成本问题出现时,最忌讳的是“凭感觉猜”。排查应该沿着“账单—明细—请求—代码链路”逐层下钻。
5.1 先看趋势再看明细
第一步确认异常范围:总账单从哪一天开始涨,涨的是模型价格,还是调用量。
检查方式:
- 拉取最近 30 天每日 Token 消耗趋势。
- 按模型、项目、用户分组,对比各组占比变化。
- 定位到具体模型和具体账号后,再翻请求明细日志。
如果趋势图显示某一天开始线性上升,大概率是某个业务功能或某个定时任务上线了。如果是某几天出现尖峰,大概率是测试脚本、批量任务或异常重试导致。
5.2 定位具体 Key 和请求
在日志系统里按 request_id、user_id、project、model 过滤。重点确认:
- 请求是否来自预期环境。
- 请求的输入上下文为什么这么长。
- 输出结果是否异常长。
- 是否存在短时间内高频调用同一接口。
通过明细日志可以快速还原消费链路。
5.3 检查上下文是否被重复携带
看到某个请求的 prompt_tokens 异常大时,优先检查:
- 是否把全部历史对话都拼接进了下一次请求。
- 是否每次请求都重新注入大量静态文本。
- 是否在循环里反复调用同一个“构建 prompt”的函数。
常见做法是在日志里额外记录 prompt 的字符数和构成的几个主要部分,后续定位会快很多。
5.4 检查重试与 Agent 循环
如果请求数量远高于任务数量,说明存在重试循环。建议记录:
- 同一 task_id 产生了多少次模型调用。
- 工具调用失败后是否被重新规划。
- Agent 的总步数和总 Token 消耗。
把 task_id 与 request_id 关联起来,就能看到单次任务的成本被放大了多少倍。
下面的表格整理了从账单倒推成本问题的排查链路:
| 问题现象 | 可能原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 某日成本突增 | 批量任务、定时脚本上线 | 按日期查请求量变化 | 限制批量任务并发和频率 |
| 某账号消耗过高 | 测试 Key 泄露或 IDE 插件循环 | 查该账号请求明细 | 重置 Key、配置团队限额 |
| 单请求输入 Token 过大 | 历史对话全量携带 | 查 prompt_tokens 构成 | 滑动窗口 + 摘要 |
| 任务数不多但调用次数很多 | Agent 异常循环 | 按 task_id 聚合 | 限制最大步数和失败熔断 |
| 账单与本地统计不一致 | 计费口径不同 | 核对服务商账单明细 | 统一按服务商口径核账 |
注意:不要混淆“认证 Token”和“模型 Token”。文章里所有成本讨论都是指模型计费用 Token;JWT Token、登录态 Token 属于认证体系,不在同一链路,排查时不要把两类错误日志混在一起。
6. 生产环境 Token 成本管理清单与扩展方向
成本管理不是一次性上线,而是持续治理。这里给出一份可以直接用于团队内部评审的清单。
6.1 上线前检查清单
一个 AI 功能上线前,至少核对以下项:
- 是否申请了独立的 API Key,并设置了预算上限。
- 是否区分了生产环境和测试环境的模型路由。
- 是否记录了 model、prompt_tokens、completion_tokens、estimated_cost。
- 是否设置了请求超时、失败重试阈值和熔断策略。
- 是否给 Agent 场景配置了最大步数和总 Token 预算。
- 是否有每日成本报表和异常告警接收人。
- 是否确认了服务商的计费口径,包括缓存、最低计费粒度和失败请求是否收费。
- 是否有回滚方案,例如关停某个功能或切换低价模型。
这套清单既适用于自研网关,也适用于团队直接接入第三方 AI API 的场景。
6.2 学习环境、开发环境和生产环境的成本策略差异
学习环境的关键是快速跑通,可以使用免费额度或低价模型,优先保证体验。生产环境则必须把成本、限流、审计和告警作为一等公民。
| 环境 | 模型选择 | 日志要求 | 配额要求 |
|---|---|---|---|
| 本机学习 | 最低配模型或免费额度 | 可以不开 | 不需要 |
| 测试环境 | 低价模型 | 记录调试信息 | 每日总额限制 |
| 生产环境 | 按任务路由,高配模型受限 | 全量结构化日志 | 项目/用户/模型三级配额 |
6.3 下一步可以做的扩展方向
Token 成本治理可以继续向平台化方向演进:
- 模型网关:统一接入多家大模型 API,承担路由、限流、缓存、计费统计。
- 语义缓存:对高频问题做向量化缓存,减少重复模型调用。
- 成本分摊:按项目、部门、用户维度生成成本报表,支持内部 FinOps。
- 自动降级:当预算接近上限时,自动把模型切换为低价版本,或缩短上下文。
对于一开始还没有统一网关的团队,建议先做两件事:把模型调用统一封装到一个 Client 里,把用量日志统一写到一个数据源。这两步做到后,模型路由、缓存、告警都能在网关层自然扩展。
给团队的实际建议是:不要把成本治理放在账单出来之后。每个月月底被账单提醒一次,不如从第一天就把 request_id、usage、estimated_cost 记录下来。AI 应用的成本和计算资源一样,只有可观测、可限制、可追溯,才能安全地放进生产环境长期运行。