AI 成本可观测性怎么做?让账单、用量与业务线对上账的四步法
价格数据均标注来源与快照日期;文中金额与日志均为演示数据。数据截至 2026-08-24,正式核算前请以官方定价页复核。
一、总额对得上,却没人说得清钱花在哪
AI 应用上线之后,团队通常很快会遇到第二道坎。第一道坎是「花了多少钱」——控制台导出、财务对账,这步不难。第二道坎是「钱花在哪个功能、哪条业务线、哪个模型上」——这步往往卡住,因为账单里只有 token 记录,没有业务语义。
四类断层比较常见。
一是计量语言与业务语言不通。 账单按每百万 token 计价,业务侧说的是「一次对话」「一份工单」「一个客户」。中间没有换算关系,于是所有人只能盯着总额。
二是输入与输出不对称。 以官方定价页为准,Claude Sonnet 5 输入 2 美元/百万 token、输出 10 美元/百万 token(来源:Anthropic 官方定价页,2026-08-24 起为标准价),相差 5 倍。但在检索增强、长文档摘要这类场景里,输入量可能是输出量的十倍以上,「单价低的一端」反而吃掉大半成本。
三是一次业务操作未必对应一次模型调用。 重试、结构化校验、兜底逻辑会让一次点击触发多次请求,账单上是一堆 token 记录,业务上只有一个按钮。
四是价格本身在动。 DeepSeek 自 2026-08-17 起执行峰谷定价,V4-Pro 高峰输出价每百万 token 27 元、闲时 13.5 元(来源:DeepSeek 官方定价页,2026-08-17 新规):档位一变,静态预算表立刻失真。
所以成本可观测性不是「做一次报表」,而是维护一条持续可跑的链路。下面四步,按顺序做,每一步都有可交付物。
二、第一步:定字段——把业务语义写进调用日志
先说数据来源。通常两条路都要走:官方控制台导出用于对总账(口径权威、可直接对扣款,但只有天级汇总、缺业务维度);自建调用日志用于拆结构(单次调用级、可打标签,但需要埋点)。
平台导出的总量和自建日志的汇总对不上时,先查自建日志有没有漏埋——异步任务、定时脚本、本地调试流量是常见的漏埋来源,不要急着怀疑平台账单。
字段一次定死,跨月对比才成立。下面这套是够用且不过度设计的字段集合:
| 字段 | 类型 | 说明 | 必填 |
|---|---|---|---|
| call_id | string | 单次调用唯一 ID | 必填 |
| ts | datetime | 调用时间戳(建议统一 UTC 存储) | 必填 |
| model | string | 模型标识,如 claude-sonnet-5 | 必填 |
| input_tokens | int | 输入 token 数 | 必填 |
| output_tokens | int | 输出 token 数 | 必填 |
| cached_tokens | int | 缓存命中的输入 token 数,无则 0 | 选填 |
| biz_line | string | 业务线标签 | 必填 |
| scene | string | 功能场景,如「工单摘要」「智能问答」 | 建议 |
| env | string | 环境,prod / staging / dev | 建议 |
| operator | string | 触发方,用户 ID / 定时任务 / 服务名 | 选填 |
其中 biz_line 与 scene 是全部下钻能力的来源,必须在调用处透传,不能事后猜。工程上的做法是把这两个字段放进统一调用封装,业务方只传业务语境,token 数由封装层从响应里读。
三、第二步:定单价——把价格表变成数据字典
价格单独抽成一张表,每条记录强制带 as_of 字段。这是整条链路里容易被忽视、也容易出问题的一环:一张没有日期的价格表,会让一次悄然的调价变成整月预算失真。
| 模型 | 输入价 | 输出价 | 缓存命中 | as_of(数据日期与来源) |
|---|---|---|---|---|
| Claude Sonnet 5 | $2.00 | $10.00 | $0.20 | 2026-08-24,Anthropic 官方定价页(确认转正为标准价) |
| Claude Opus 5 | $5.00 | $25.00 | 以官方页为准 | 2026-08-24,Anthropic 官方定价页快照 |
| Claude Haiku 4.5 | $1.00 | $5.00 | 以官方页为准 | 2026-08-24,Anthropic 官方定价页快照 |
| DeepSeek V4-Pro(高峰) | — | ¥27.00 | — | 2026-08-17,DeepSeek 官方定价页新规 |
| DeepSeek V4-Pro(闲时) | — | ¥13.50 | — | 2026-08-17,DeepSeek 官方定价页新规 |
单位统一为「每百万 token」。两点纪律:一是币种先统一再汇总,否则月度合计无意义;二是价格表更新走一次评审,避免生产环境出现两个版本的单价。
四、第三步:批量结算——按业务线拆出一张成本表
有了字段和单价,结算就是一次可重复的数据加工。下面这段代码演示:读取用量日志、关联价格表、按业务线汇总、输出对账表。Python 3.10+ 可直接运行。
# settle_cost.py —— 用量日志 × 价格表 → 业务线成本报表
import pandas as pd
# 1) 价格数据字典(单位:每百万 token;as_of 为必填字段)
PRICE_TABLE = pd.DataFrame([
{
"model": "claude-sonnet-5", "input_per_m": 2.00, "output_per_m": 10.00, "cache_hit_per_m": 0.20, "as_of": "2026-08-24"},
{
"model": "claude-opus-5", "input_per_m": 5.00, "output_per_m": 25.00, "cache_hit_per_m": 0.00, "as_of": "2026-08-24"},
{
"model": "claude-haiku-4.5", "input_per_m": 1.00, "output_per_m": 5.00, "cache_hit_per_m": 0.00, "as_of": "2026-08-24"},
])
# 2) 用量日志字段集:ts / model / input_tokens / output_tokens / cached_tokens / biz_line
usage = pd.DataFrame([
{
"ts": "2026-08-24T09:10:00Z", "model": "claude-sonnet-5", "input_tokens": 20000,
"output_tokens": 800, "cached_tokens": 16000, "biz_line": "合同摘要"},
{
"ts": "2026-08-24T10:22:00Z", "model": "claude-sonnet-5", "input_tokens": 2000,
"output_tokens": 500, "cached_tokens": 0, "biz_line": "智能问答"},
{
"ts": "2026-08-24T15:40:00Z", "model": "claude-haiku-4.5", "input_tokens": 5000,
"output_tokens": 1200, "cached_tokens": 0, "biz_line": "工单分类"},
])
def settle(usage: pd.DataFrame, price: pd.DataFrame = PRICE_TABLE) -> pd.DataFrame:
"""关联价格表并逐条计价;缺单价的行会以 NaN 暴露,便于及时补齐。"""
df = usage.merge(price, on="model", how="left")
billed_input = (df["input_tokens"] - df["cached_tokens"]).clip(lower=0)
hit_price = df["cache_hit_per_m"].where(df["cache_hit_per_m"] > 0, df["input_per_m"])
df["cost_usd"] = (
billed_input / 1_000_000 * df["input_per_m"]
+ df["cached_tokens"].fillna(0) / 1_000_000 * hit_price
+ df["output_tokens"] / 1_000_000 * df["output_per_m"]
).round(6)
return df
def by_biz_line(df: pd.DataFrame) -> pd.DataFrame:
"""按业务线汇总:成本、调用次数、平均单次成本,供月度复盘使用。"""
return (df.groupby("biz_line")
.agg(cost_usd=("cost_usd", "sum"),
calls=("cost_usd", "size"),
avg_cost=("cost_usd", "mean"))
.sort_values("cost_usd", ascending=False)
.round(6))
if __name__ == "__main__":
detail = settle(usage)
print(detail[["ts", "model", "biz_line", "cost_usd"]].to_string(index=False))
print("\n按业务线汇总:")
print(by_biz_line(detail).to_string())
assert detail["cost_usd"].notna().all(), "存在缺单价记录,请先补齐价格字典"
这段代码有三个刻意的设计:缺单价不静默填零(用 NaN 暴露,避免错误悄悄累积);缓存命中单独计价;汇总维度就是业务线,输出可直接用于复盘会。
五、第四步:对账与预警——让偏差在月初就暴露
结算表出来之后,做三方对账:
- 平台控制台总额(对扣款口径);
- 自建日志汇总(对拆分口径);
- 结算表金额(对决策口径)。
三者偏差超过预设阈值(例如 5%),先排查漏埋与重复上报,再检查价格字典是否过期。这一步的价值在于:偏差在月初暴露,而不是年底审计时暴露。
预警规则建议按「业务线 × 模型」两维设置,而不是只设总额阈值。总额超支是结果,业务线异常才是原因——某个场景的输入 token 突然翻倍,往往意味着检索召回失控或提示词被塞了冗余上下文。
六、把成本压下去:三个优先动作
- 缓存前缀:把稳定的系统提示与知识前缀做成可缓存内容,命中率是重点跟踪指标;多轮对话场景下,同样 10,000 token 固定前缀,无缓存每轮约 0.023 美元,开启缓存后约 0.005 美元(按 Sonnet 5 单价测算,来源:Anthropic 官方定价页,2026-08-24)。
- 错峰调度:可延迟的离线任务排到闲时段。DeepSeek 场景闲时输出价为高峰的一半(来源:DeepSeek 官方定价页,2026-08-17 新规),这类调度基本不需要改造应用代码。
- 上下文瘦身:检索场景做召回裁剪与去重。单次输入 20,000、输出 800 的案例里,输入成本占 83%(Sonnet 5 口径,来源:Anthropic 官方定价页,2026-08-24),砍上下文通常比换模型见效更直接。
七、把四步法固化成可重复动作
上面四步跑一遍大约半天,真正的工作量在于「每月都要跑」——价格在动、业务线在加、模型在换。为了减少重复劳动,可以把其中两件高频动作做成脚本:一个负责维护单价字典(输入、输出、缓存命中、峰谷档位分列),一个负责按用量日志结算并输出结构化账单。两者都强制标注数据来源与快照日期;遇到字典里没有的价格,脚本应当报错退出,而不是估算一个数值继续跑。
八、数据来源与使用说明
- Claude 系列单价:Anthropic 官方定价页,快照日期 2026-08-24;Sonnet 5 的输入 $2 / 输出 $10 为该日官方确认的标准价;
- DeepSeek V4-Pro:DeepSeek 官方定价页 2026-08-17 新规,高峰 ¥27 / 闲时 ¥13.5(每百万输出 token);
- 文内所有金额与日志均为演示数据,仅用于说明方法;
- 价格处于调整期,请以官方定价页实时数据为准,正式预算前逐项复核。
(内容由 AI 辅助整理,价格以官方定价页为准)
(内容由AI生成,仅供参考)
(内容由AI生成,仅供参考)