Token 管理与成本控制完全指南:别让 AI 账单悄悄长成事故
做 AI 产品时,成本失控往往不是一夜之间发生的,而是被长上下文、重复调用、无效输出和缺少观测一点点堆出来的。本文系统讲清 token 预算、计费估算、上下文裁剪、缓存复用、模型分层与成本告警。
Token 管理与成本控制完全指南:别让 AI 账单悄悄长成事故
AI 产品早期有一种很容易让人上头的错觉:
- 一个请求也就几分钱
- 用户还没那么多
- 先把体验做出来,成本以后再说
这句话在 demo 阶段没问题。
一旦功能开始被真实用户持续使用,成本会以一种很不戏剧化、但很致命的方式上涨:
- prompt 越写越长
- 上下文一轮轮累加
- 同一个用户问题反复重试
- 输出明明只用 100 字,却让模型生成 800 字
- 分类、总结、改写、抽取分别调了四次模型
- 没人看 token 用量,也没人知道钱到底花在哪
最后到月底,财务给你发一张账单,大家才第一次认真打开监控后台。
这时候往往已经不是“优化一下就好”的程度,而是:
AI 成本开始反噬产品模型。
所以 Token 管理和成本控制,不是上线后的收尾工作,而是 AI 工程的核心部分。
这篇文章就围绕这个主题展开:
- token 成本为什么总比团队预估高
- 怎样建立单位请求、单位用户、单位功能的成本视角
- 如何通过上下文裁剪、缓存复用、模型分层和输出约束降低成本
- 怎样做预算、告警和成本归因
- 什么时候该追求更低成本,什么时候不该为了省钱把体验做坏
如果你已经开始做 AI 功能,这篇越早看越值钱。
一、先认清现实:AI 成本失控,大多不是因为模型贵,而是因为调用方式浪费
很多团队第一次看到账单,第一反应是:
- 这个模型太贵了
- 换便宜模型吧
模型单价当然重要,但多数成本问题,真正的源头是:
- 不必要的上下文太长
- 重复调用太多
- 输出太啰嗦
- 没有做结果复用
- 没有分场景选模型
1.1 一个很典型的浪费例子
假设用户想要一个 3 行摘要,但你的系统做了这些事:
- 把最近 20 条历史对话都带上
- 再附上大段系统说明
- 输出要求没限制,模型生成 700 token
- 结果格式不稳定,又重试一次
最后为了一个很简单的功能,成本可能放大到原本的 5~10 倍。
1.2 成本问题的本质
成本控制最核心的不是“省钱”本身,而是:
让每一份 token 支出都对应到真实业务价值。
如果某个功能很贵,但能显著提高转化或续费,它未必有问题。
真正有问题的是:
- 花了很多 token
- 用户没有明显感知
- 业务结果没有改善
- 团队还不知道钱花去了哪里
二、先建立单位成本视角:不要只看月账单
月账单是结果,不是分析入口。
更有用的做法,是把成本拆到多个粒度:
2.1 四层成本视角
| 粒度 | 要回答的问题 |
|---|---|
| 单次调用 | 一个请求多少钱 |
| 单个功能 | 总结、问答、抽取分别多少钱 |
| 单用户 | 一个活跃用户平均消耗多少 |
| 单业务结果 | 每完成一次转化或一次成功任务,成本是多少 |
2.2 为什么这很重要
因为只有这样你才能判断:
- 哪个功能最烧钱
- 哪类用户最贵
- 是输入成本高,还是输出成本高
- 某次版本更新是否让成本突然上涨
2.3 一个简单的成本估算函数
type Usage = {
promptTokens: number
completionTokens: number
}
type Pricing = {
inputPer1k: number
outputPer1k: number
}
export function estimateCost(usage: Usage, pricing: Pricing) {
return (
(usage.promptTokens / 1000) * pricing.inputPer1k +
(usage.completionTokens / 1000) * pricing.outputPer1k
)
}
这个函数不复杂,但非常值得统一收口到调用层。因为后面所有的预算、归因、告警,都是从这里长出来的。
三、你真正要盯的,不只是 token 总量,而是 token 结构
一个请求贵,可能贵在完全不同的地方。
3.1 常见的四种成本结构
A. 输入太长
最常见原因:
- 把整段上下文原样传给模型
- 系统提示写得又长又重复
- 历史会话不做裁剪
B. 输出太长
很多场景其实只要:
- 分类标签
- 3 条摘要
- 一段简短回复
结果系统没有约束,模型就会“热情发挥”。
C. 同一请求被多次调用
例如:
- 失败重试
- 流式中断后重新发起
- 前端重复提交
- 后端多个模块分别调模型做相似任务
D. 同一结果没有复用
像:
- 热门内容摘要
- 公共问答结果
- 标准化标签提取
这些结果如果每次都重算,成本会平白增加。
3.2 一个更有意义的观测表
| 指标 | 含义 |
|---|---|
| 平均输入 token | prompt 是否膨胀 |
| 平均输出 token | 输出是否失控 |
| 单次请求重试率 | 稳定性是否拖累成本 |
| 缓存命中率 | 是否复用已有结果 |
| 长上下文请求占比 | 是否需要上下文治理 |
这组指标通常比“本月花了多少钱”更能指导优化动作。
四、最值钱的降本手段,通常不是换模型,而是少传、少调、少生成
4.1 上下文裁剪:第一优先级
绝大多数 AI 成本优化,最先该做的是上下文治理。
常见做法
- 只保留最近 N 轮对话
- 对历史对话做摘要压缩
- 把系统提示拆成固定模板和场景变量
- 删除与当前任务无关的信息
4.2 一个简单的消息裁剪示例
type Message = { role: 'system' | 'user' | 'assistant'; content: string }
export function trimMessages(messages: Message[], maxChars = 8000) {
const reversed = [...messages].reverse()
const kept: Message[] = []
let total = 0
for (const msg of reversed) {
total += msg.content.length
if (total > maxChars) break
kept.push(msg)
}
return kept.reverse()
}
这当然不是严格 token 级裁剪,但能说明一个核心原则:
上下文不是越多越好,而是越相关越好。
4.3 输出约束:第二优先级
很多团队认真管输入,却不管输出。结果模型生成了大量业务并不需要的文字。
更好的写法是明确限制:
- 最多 3 条要点
- 每条不超过 40 字
- 只输出 JSON
- 不要解释推理过程
输出越受控,越省钱,也越稳定。
五、模型分层:把“贵模型”用在最值钱的地方
这是非常实用的工程策略。
5.1 不同任务,配不同模型
例如:
| 任务 | 推荐策略 |
|---|---|
| 分类、抽取 | 用轻量模型 |
| 标题生成、短摘要 | 默认模型 |
| 高价值复杂问答 | 强模型 |
| fallback | 备用便宜模型或规则兜底 |
5.2 为什么这比统一换便宜模型更稳
因为用户真正感知的,不是“你整体用了哪个模型”,而是:
- 关键任务质量够不够
- 响应快不快
- 错误多不多
一刀切换便宜模型,可能省了账单,却把转化和留存打掉。
5.3 一个路由示例
export function chooseModel(task: 'classify' | 'summary' | 'qa') {
switch (task) {
case 'classify':
return 'small-fast-model'
case 'summary':
return 'balanced-model'
case 'qa':
return 'strong-reasoning-model'
default:
return 'balanced-model'
}
}
真正成熟的系统,做的是任务路由,不是“全站统一一个模型名”。
六、缓存复用:不是所有请求都值得重新生成
很多 AI 场景有天然复用价值,但团队经常忽略。
6.1 适合缓存的 AI 结果
- 热门文章摘要
- 常见 FAQ 问答
- 商品卖点提炼
- 标签生成结果
- 内容审核初筛结果
6.2 不适合直接缓存的场景
- 强个性化对话
- 高时效信息
- 依赖实时上下文的回答
6.3 一个简单的缓存思路
import crypto from 'crypto'
export function makePromptCacheKey(task: string, input: string, version: string) {
const hash = crypto
.createHash('sha256')
.update(`${task}:${version}:${input}`)
.digest('hex')
return `llm:${task}:${version}:${hash}`
}
这里的关键点是把:
- 任务类型
- prompt 版本
- 输入内容
都纳入 key。否则 prompt 改了,旧结果还会继续污染新逻辑。
七、限制重试和重复调用:很多钱不是花在“回答”,而是花在“重新回答”
7.1 常见重复调用来源
- 前端按钮重复点击
- SSE 中断后直接重放整次请求
- 后端超时阈值太短,导致误判失败再重试
- 一个页面多个模块对同一文本各调一次模型
7.2 两个实用手段
A. 请求去重
同一用户、同一输入、短时间内相同请求,可以复用结果或合并中间态。
B. 幂等 key
对关键调用增加幂等标识,避免链路重复提交。
export function makeIdempotencyKey(userId: string, action: string, payload: string) {
return `${userId}:${action}:${payload}`
}
这不是 AI 特有问题,但在 AI 场景里,因为调用成本更高,所以收益特别明显。
八、预算与告警:不要等月底才知道超支
8.1 至少做三层预算
| 层级 | 作用 |
|---|---|
| 日预算 | 及时发现异常放量 |
| 功能预算 | 看哪个模块在失控 |
| 用户 / 租户预算 | 控制大客户或异常行为 |
8.2 一个实用告警方式
- 当日成本达到预算的 70%:提醒
- 达到 90%:通知 owner
- 达到 100%:触发限流、切模型或关闭非核心功能
8.3 为什么预算不是财务动作,而是系统保护动作
因为当成本失控时,背后往往对应:
- 异常重试
- 用户滥用
- prompt 膨胀
- 某次发布带来了额外调用
预算报警,很多时候就是异常检测。
九、怎样做成本归因:不然你永远不知道该先优化哪
很多团队知道“本月花了很多”,但不知道:
- 花在哪个产品功能上
- 哪个模型贡献最多
- 哪个租户最贵
- 是输入贵还是输出贵
9.1 建议最少记录这些维度
featureNametaskTypemodelpromptVersiontenantId/userSegmentpromptTokenscompletionTokensestimatedCost
9.2 一个记录示例
logger.info('llm.usage', {
featureName: 'ticket_summary',
taskType: 'summary',
model: 'balanced-model',
promptVersion: 'v3',
tenantId: 'tenant_42',
promptTokens: 820,
completionTokens: 140,
estimatedCost: 0.0042,
})
有了这组信息后,你才能真正回答:
- 该优化哪一条 prompt
- 哪个功能需要缓存
- 哪个租户需要配额
- 哪个模型是否该降级替换
十、什么时候不该一味省 token
这点很重要。
不是所有降本都值得做。
10.1 不值得过度压缩的情况
- 高价值用户的关键问答
- 结果质量直接影响转化
- 复杂推理场景本来就需要更多上下文
- 过度缩短输出会让结果不可用
10.2 一个成熟团队会这样看成本
不是只看“每月花多少钱”,而是看:
这笔成本和获得的业务价值是否匹配。
如果一个 AI 功能:
- 提升了留存
- 节省了人工审核
- 降低了客服成本
- 提高了付费转化
那它贵一点也可能是划算的。
真正糟糕的是:
- 成本高
- 用户感知弱
- 指标无提升
- 团队还说不清原因
十一、一套可执行的 AI 成本治理顺序
第 1 步:先打 usage 日志
没有 usage,后面所有优化都是猜。
第 2 步:看输入、输出和重试结构
先确认钱到底花在哪。
第 3 步:先做上下文裁剪和输出约束
这是最便宜、收益最高的优化。
第 4 步:再做模型分层和缓存复用
把高成本调用放到最有价值的地方。
第 5 步:最后补预算和自动保护
让成本异常能自动被发现,必要时自动限流或降级。
十二、给团队的 Token 管理检查清单
观测层
- 是否记录了输入、输出 token 和估算成本
- 是否能按功能、模型、租户做归因
- 是否有日级和周级趋势图
优化层
- 是否裁剪历史上下文
- 是否限制输出长度和结构
- 是否避免无意义重试
- 是否对高复用结果做缓存
- 是否按任务类型做模型分层
保护层
- 是否有预算告警
- 是否有超预算时的降级策略
- 是否有限流或配额控制
- 是否能快速定位异常成本上涨的版本或功能
总结
把 Token 管理与成本控制讲透,可以收敛成 5 句话:
- 多数 AI 成本问题,根源不是模型贵,而是调用方式浪费。
- 先建立单次、单功能、单用户的成本视角,再谈优化。
- 最值钱的降本手段通常是裁剪上下文、约束输出、减少重复调用。
- 模型分层和缓存复用,是让成本和体验同时可控的关键。
- 预算、告警、归因必须上线前就准备,不要等账单教育团队。
如果你只记住一句话,我希望是这一句:
成本控制不是把 AI 变便宜,而是让每一个 token 都花在真正值钱的地方。
否则产品上线得越顺,月底最先变得不顺的通常不是接口,而是——
财务同学的表情。