一次问答花多少 token:给自己的 AI 应用装计量
从"账单到了才知道超"到每条请求都有出处。计量表设计、成本归因和一次真实的浪费排查。
我的应用某个月的账单比预期高出一倍,而我完全说不出是哪类请求造成的。那之后做的第一件事就是加计量——不是省钱,是让成本变成可归因的东西。
一、把 usage 原样存下来
所有 OpenAI 兼容接口都返回 usage 字段。别只记一个"用了多少",把结构化明细原样存:
| 字段 | 为什么要单独存 |
|---|---|
| prompt_tokens | 反映输入侧浪费,是提示词和检索条数的直接体现 |
| completion_tokens | max_tokens 设多大都不影响实际计费,但要防失控 |
| reasoning/cot tokens | 推理模型的思维链通常按输出计费,且量可能远大于正文 |
| cache_hit_tokens | 命中前缀缓存的部分,多数接口有折扣 |
| model | 不同模型单价差一个数量级,没有这个字段其余都白记 |
再补上业务侧的维度:调用来源、场景标识、耗时、HTTP 状态、是否重试。这样当被问"为什么上周三的费用高"时,答案是查出来的,不是猜出来的。
二、表设计:够用就行
CREATE TABLE IF NOT EXISTS llm_usage (
id bigserial PRIMARY KEY,
ts timestamptz NOT NULL DEFAULT now(),
scene text NOT NULL, -- 业务场景,不是模型名
model text NOT NULL,
prompt_tokens int NOT NULL,
comp_tokens int NOT NULL,
cache_tokens int NOT NULL DEFAULT 0,
reasoning_tokens int NOT NULL DEFAULT 0,
latency_ms int,
http_status int,
attempt int NOT NULL DEFAULT 1 -- 第几次重试
);
CREATE INDEX IF NOT EXISTS llm_usage_ts_scene_idx ON llm_usage (ts, scene);
三个后来证明关键的决定:
scene 和 model 分开。费用归因的第一问是"哪个场景花的",第二问才是"哪个模型花的"。只记 model 的话,你只能回答"某个模型贵",回答不了"我为什么需要它"。
记 attempt。重试是隐性成本大户。这个字段是后来加的,因为最初有一次超时重试风暴,账单涨了但日志里请求数没变——同一条日志里塞了两次成功一次失败,我却按行计数。
别记 prompt 原文。只记长度。原文含用户数据,为省成本做监控反而制造合规风险。真要排查内容问题,另设脱敏开关。
三、单价写进配置,不要硬编码
INSERT INTO llm_price(model, in_per_1k, out_per_1k) VALUES
('qwen2.5-7b-instruct', 0.0005, 0.0010),
('some-hosted-model', 0.0060, 0.0240)
ON CONFLICT (model) DO UPDATE SET in_per_1k = EXCLUDED.in_per_1k;
上面是我自己填的数字,仅用于让账能算通。单价一定以服务商当期公布页面为准,别照抄任何人的表,包括我这份。价格变动、汇率、阶梯计价这些都会让算出来的费用和账单对不上,我的处理是在表里再存一列生效日期,每月对一次账。
四、一次真实的浪费排查
有了表之后,第一个结论有点意外:"文档摘要"这个场景占了将近一半的费用,但它的调用次数只占 12%。
拆开看是提示词的问题。那个场景我给每次请求都塞了整份原文(平均 6 千 token 输入),但输出只有 200 字。摘要任务的输出成本几乎可以忽略,输入才是全部成本。
改法不是换便宜模型,而是改流程:先按段落抽取要点(每段输入小),再对要点做二次汇总。第二次改完输入总量降到约 2.4 千 token,费用跟着下来,摘要质量还略好一点,因为模型不用在长材料里自己判断哪些能省。
第二个结论:同一份系统提示被重复计费。加前缀缓存后,那部分命中量占输入的三成多。这块单价折扣各服务商差得很多,不能凭印象估。
五、预算护栏要真拦得住
只在超支时发告警是没用的——收到时钱已经花了。我的做法:
- 应用启动时从环境变量读预算值,读不到就退出,绝不静默用默认值。这条很关键,一旦用了默认值,预算形同虚设。
- 按天聚合,到 80% 开始告警,到 100% 直接拒绝非核心场景,核心场景降级到便宜模型并标记。
- 降级必须留痕。我在响应里带一个字段标明本次是否被降级,否则用户拿到质量下降的结果,而排查时找不到原因。
六、现在我会固定的三个数
- 每场景的平均输入 token 数,用来发现"输入被塞爆"。
- 输出触顶比例(completion_tokens 撞到 max_tokens 的次数占比)——这个比例高说明输出被截断了,是质量问题而不只是成本问题。我曾有 4% 的截断率没察觉。
- 重试率与重试成本占比,用来判断超时是真网络抖动还是我的客户端配置不合理。
成本监控最大的价值,是它逼着你去看真实的输入输出结构,而不是停留在"感觉挺费"。这三个数同时也在暴露质量隐患,一举两得。
内容标识:本文正文与脚本为本人编写;文中统计数字来自我自己应用的日志,样本仅一个月,不代表一般情况。
本文出处:http://honeysss.cn/post/api-call-cost-and-metering.html 转载请注明出处。