跳转到主要内容
/admin/cost 是一个单页运营视图,告诉你 AI provider 现在在烧多少钱。它读 ai_call 表(你 app 的每一次 AI 调用都会写一行,记录 token 数、成本(微分)、 延迟),汇总过去 30 天,按 provider/model 拆开。layout 用 requireAdmin() 鉴权——只有 admin 能进。

前置条件

  • AI 调用已经走 provider 抽象层了(Phase 2)。第一次调用前看板是空的。
  • 你的 user 记录 role = 'admin'。用 SQL 或 admin user 页改。
  • ai_call 表存在——pnpm db:push 时根据 src/db/ai.schema.ts 创建。
  • 价格表里有该模型的条目(否则成本显示 0——见坑 3)。

一步步用

  1. 用 admin 账号在 /login 登录,然后访问 /admin/cost
  2. 看顶部四张卡
    • 总调用数(过去 30 天)
    • 总 token 数(输入 + 输出)
    • 总花费(<CostBadge> 渲染,把微分转美元)
    • 成功率(status = 'success' 的占比)
  3. 下面是按模型拆分的表。按花费降序排。列:provider/model、调用数、 输入 token、输出 token、成本(CostBadge)、平均 TTFT (ms)、错误率 (%)。
  4. 抓有问题的模型——错误率高、TTFT 慢、花费失控。
  5. /admin/orders 排查,或者用 pnpm db:studio 直接查 ai_call

汇总怎么算的

页面跑两条 SQL 聚合 ai_call
// 顶部卡的总数
const totals = await db
  .select({
    calls: sql<number>`count(*)::int`,
    successCalls: sql<number>`count(*) filter (where status = 'success')::int`,
    inputTokens: sql<number>`coalesce(sum(input_tokens), 0)::int`,
    outputTokens: sql<number>`coalesce(sum(output_tokens), 0)::int`,
    cost: sql<number>`coalesce(sum(cost_micro_cents), 0)::int`,
  })
  .from(aiCall)
  .where(gte(aiCall.createdAt, since));
按模型的表 group by (provider, model),按 sum(cost_micro_cents) desc 排。 两条都按 createdAt >= now() - 30d 过滤。

验证生效

  1. 从 app 任意地方触发一次 AI 调用(比如跑个测试 prompt)。
  2. 刷新 /admin/cost——调用数 +1,token 数对应请求/响应的体量,成本非零 (前提是模型在价格表里)。
  3. 确认表里出现该模型,provider 字段对得上。
  4. 如果配了多个 provider,挨个跑几次调用;行按花费排,最贵的会冒到顶部。

常见坑

  1. 没调用就是空的。新装当然是空的——页面只读 ai_call,没行就没数据。
  2. 成本显示 0。你的 cost_micro_cents 列是 0,因为该模型没在价格表里。 加到 src/ai/pricing.ts(Phase 2)——或者你只关心 token 数也行。
  3. 微分换算。成本用微分存,避免浮点误差。<CostBadge> 除以 1,000,000 渲染美元。直接查表时要记得:cost_micro_cents / 1_000_000.0 得到 USD。
  4. 大数据集查询慢ai_call 行数过百万后 group-by 会变慢。给 Postgres 加 (created_at, provider, model) 索引,或者按月分区。
  5. 30 天窗口是硬编码的。想换时间窗口?改 src/app/[locale]/(app)/admin/cost/page.tsx 顶部的 WINDOW_MS。 目前没有 query-string 开关。

官方文档