ai_call 写一行 —— 成功、失败、取消都写 ——
包括 token 数、成本、延迟、以及驱动这次调用的 prompt 版本。这一张表撑起了
/admin/cost 后台,也是你定位失控模型或挂掉 provider 唯一需要看的地方。
前置条件
- 数据库已迁移(
pnpm db:push),ai_call表存在。 - 实际跑过几次 AI 调用 —— 不然表是空的。
- 看一遍
src/ai/manager.ts的recordCall(),每行数据都是从这里诞生的。
关键字段
userId、(provider, model)、status、createdAt、promptId 上的索引覆盖了后台
要跑的所有查询。
为什么用 micro-cents?
某些 OpenAI 模型 1k 输入 token 收 $0.0001,200 token 也就 0.002 美分。用「美分」存 就被迫上浮点和精度丢失。micro-cents = 百分之一美分:- $1.00 = 100¢ = 10_000 micro-cents
- $0.0001 = 0.01¢ = 1 micro-cent
- 显示为美元:
microCents / 1_000_000
<CostBadge /> 已经替你做了这步,别再自己写一遍。
步骤:写一份用量报告
三条查询:日成本、按模型的错误率、p95 延迟。全部直接打ai_call。
近 30 天日花费
各模型错误率
各模型 p95 总延迟
后台仪表盘
/admin/cost(被 adminActionClient → user.role === 'admin' 守护)读
ai_call 渲染上面三条查询,再加一个花费排行榜。要加自己的小组件就在
src/app/[locale]/(app)/admin/cost/ 下放一个 server component,import db,跑查询,
渲染,不需要额外接线。
验证生效
- 发一次 chat 调用 —— 任意模型任意 provider 都行。
psql跑SELECT provider, model, status, cost_micro_cents, total_ms FROM ai_call ORDER BY created_at DESC LIMIT 1;—— 应该有一行。- 用 admin 身份打开
/admin/cost。「今日」总额应该反映出这次调用。 - 故意造个错误:传一个不存在的 model id。再查表,会多出一行
status = 'error',errorMessage有内容。
常见坑
- 价目表漏配 → 成本 = 0。
getTokenPrice会回退到mock:any,能跑出极小的数, 但凡是要真收钱的模型都该有显式一行。审计一下:SELECT model, SUM(cost_micro_cents) FROM ai_call GROUP BY model HAVING SUM(cost_micro_cents) < 100; - micro-cents 和 cents 弄混了(差 100 倍)。 花了两小时纳闷毛利怎么这么好看? 你除的是 10_000 而不是 1_000_000。
- 忽略失败调用。 失败也要钱 —— Anthropic 对超过 TTFT 后失败的流照样收费。
「成功的占比」可以过
status = 'success',算成本要全部加起来。 - 缓存命中看着像免费。 Manager 给缓存行打
cached = true、costMicroCents = 0。 这是对的(你没付钱),但做容量规划时也要把缓存行从延迟报表里排除。 ttftMs在非流式时为空。 只有operation = 'chat_stream'才填ttftMs。 对普通 chat 行聚合ttftMs全是NULL。
官方文档
- PostgreSQL 聚合函数:postgresql.org/docs/current/functions-aggregate.html
percentile_cont:postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-ORDEREDSET-TABLE- Drizzle 原生 SQL:orm.drizzle.team/docs/sql
- 源码:
src/db/ai.schema.ts、src/ai/manager.ts