OpenAI API 成本监控实战:如何把「乱收费」变成「清清楚楚」

"OpenAI 告诉你花了多少钱,但不告诉你花在哪。所以我自己写了一套监控系统。"

这是开发者 alimafana 在构建多租户 AI 产品时遇到的真实痛点。他的产品 Provia 是一个面向阿拉伯电商的 AI 销售机器人,每天触发大量 OpenAI API 调用。某天他看到账单显示花了 $4.27,但完全不知道这笔钱是怎么产生的——是某次昂贵的图片分析?某个店铺的异常调用?还是某个对话陷入了死循环?

没有答案。

本文记录我复盘这个方案的全过程,聊聊怎么用三个文件搭起一套完整的 OpenAI API 成本监控系统。


1. 问题本质:OpenAI 账单为什么「看不清」

登录 platform.openai.com/usage,你能看到的只有:

  • 每天的总花费
  • 按模型分类的 token 数量(gpt-4o、gpt-4o-mini 等)
  • Token 总数

没了。

对于个人项目,这够用。对于 production 环境,缺的东西太多了:

你想知道 OpenAI 给你的
哪个功能产生了这笔费用 ❌ 不知道
哪个用户/租户触发了调用 ❌ 不知道
哪次对话超出了预算 ❌ 不知道
失败的调用是否还在收费 ❌ 不知道
延迟和成本之间的关联 ❌ 不知道

这不是 OpenAI 的锅——它本来就是个通用的 API 提供商,没有义务理解你的业务逻辑。但作为开发者,如果你不做这层监控,就等于在瞎子摸象。


2. 方案概览:三文件监控架构

alimafana 的方案出奇地简洁,一共三个文件:

openai-logger.ts    ← 包装 OpenAI 调用,记录一切
api_logs            ← Supabase 数据表,存储每次调用的明细
dashboard           ← 可视化界面,按租户、功能、模型分析成本

核心思路:不要直接调用 OpenAI,通过一个 wrapper 代理所有请求,把每次调用的元数据全部记下来。


3. 文件一:Wrapper 的设计

3.1 定价表是关键

OpenAI API 返回 token 数量,但不直接返回费用。你需要自己查表计算:

// 最近检查:2026-04-15 — https://openai.com/pricing
const PRICING: Record<string, { input: number; output: number }> = {
  "gpt-4o": { input: 2.50, output: 10.00 },       // 每 100 万 token
  "gpt-4o-mini": { input: 0.15, output: 0.60 },   // 每 100 万 token
};

这里有个关键数字:gpt-4o 比 gpt-4o-mini 贵约 16 倍。如果某个场景 gpt-4o-mini 就能搞定,你用了 gpt-4o,那就是在烧钱。监控的目的之一就是让这种浪费变得可视化。

费用计算公式:

const cost = (
  tokens.prompt_tokens * rates.input +
  tokens.completion_tokens * rates.output
) / 1_000_000;

3.2 Wrapper 的实现

import OpenAI from "openai";
import { createAdminClient } from "@/lib/supabase/admin";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

const PRICING: Record<string, { input: number; output: number }> = {
  "gpt-4o": { input: 2.50, output: 10.00 },
  "gpt-4o-mini": { input: 0.15, output: 0.60 },
};

interface LogMeta {
  storeId: string;
  conversationId?: string;
  leadId?: string;
  endpoint: string;
  functionCalled?: string;
  searchQuery?: string;
  productsFound?: number;
}

export async function loggedChatCompletion(
  params: OpenAI.Chat.Completions.ChatCompletionCreateParams,
  meta: LogMeta
) {
  const start = Date.now();
  const result = await openai.chat.completions.create(params);
  const duration = Date.now() - start;

  const tokens = result.usage;
  const rates = PRICING[params.model as string] || PRICING["gpt-4o-mini"];
  const cost = tokens
    ? (tokens.prompt_tokens * rates.input +
        tokens.completion_tokens * rates.output) / 1_000_000
    : 0;

  // Fire-and-forget log — never blocks the response
  const supabase = createAdminClient();
  supabase
    .from("api_logs")
    .insert({
      store_id: meta.storeId,
      conversation_id: meta.conversationId,
      lead_id: meta.leadId,
      endpoint: meta.endpoint,
      model: params.model,
      prompt_tokens: tokens?.prompt_tokens,
      completion_tokens: tokens?.completion_tokens,
      total_tokens: tokens?.total_tokens,
      cost,
      duration_ms: duration,
      function_called: meta.functionCalled,
      search_query: meta.searchQuery,
      products_found: meta.productsFound,
      status: "success",
    })
    .then(() => {})
    .catch(() => {}); // Silent fail — monitoring never breaks the app

  return { result, cost, duration };
}

3.3 最重要的设计模式:Fire-and-Forget

这行代码是整个监控系统的精髓:

.then(() => {}).catch(() => {}); // Silent fail

日志插入是不等待的。如果数据库挂了、网络抖了、表还不存在——用户的响应照常返回。

实测数据:
- 日志插入耗时:15–40ms
- Chat completion 耗时:800–2500ms

如果你 await 日志,每次请求增加 2–5% 的延迟,而用户完全感知不到这个监控的价值。

监控永远不能拖慢它要监控的东西。 这是唯一重要的原则。

alimafana 运行这套系统几周下来,千次调用中大约丢失 2–3 条日志。这个损失可以接受。

3.4 迁移成本:10 分钟

使用方式几乎没变:

// Before
const response = await openai.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: customerMessage }],
});

// After
const { result, cost, duration } = await loggedChatCompletion(
  {
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: customerMessage }],
  },
  {
    storeId: store.id,
    conversationId: conversation.id,
    leadId: lead.id,
    endpoint: "chat",
  }
);

同一个接口,加一个 meta 参数。全局搜索替换,10 分钟搞定。


4. 文件二:数据表设计

CREATE TABLE api_logs (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  store_id UUID REFERENCES stores(id),
  conversation_id UUID,
  lead_id UUID,
  endpoint TEXT NOT NULL,
  model TEXT,
  prompt_tokens INT,
  completion_tokens INT,
  total_tokens INT,
  cost DECIMAL(10,8),          -- 8位小数,单次调用约 $0.00013
  duration_ms INT,
  function_called TEXT,
  search_query TEXT,
  products_found INT,
  status TEXT DEFAULT 'success',
  error TEXT,
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_api_logs_store ON api_logs(store_id);
CREATE INDEX idx_api_logs_created ON api_logs(created_at);
CREATE INDEX idx_api_logs_endpoint ON api_logs(endpoint);

4.1 每一列回答一个业务问题

store_id → "哪个租户最贵?"
多租户 SaaS 里,一个店铺的费用可能是另一个的 10 倍。不记录这个维度,你永远发现不了。

endpoint → "是聊天贵,还是图片分析贵?"
拆开看才知道资源消耗在哪里。

conversation_id + lead_id → "这个对话/客户花了多少钱?"
精细化到单个用户维度的成本分析。

function_called + search_query + products_found → 调试列
当客户说"给我看红色裙子"但返回空结果时,你可以查:搜索函数调用了吗?用的什么 query?返回了多少商品?这省了我好几个小时的调试时间。

duration_ms → 延迟
Dashboard 里用颜色标记:绿色 < 1s,黄色 1–3s,红色 > 3s。

error → 失败调用也在收费
OpenAI 对失败的调用仍然收取 prompt token 的费用。这个字段让你知道"哪些错误在烧钱"。

4.2 DECIMAL(10,8) 不能省

这是最容易忽略的细节:

gpt-4o-mini 单次调用 ≈ $0.00013

如果用 DECIMAL(10,2),所有调用都会 round 到 $0.00,你的总和永远是一片 0。

到了规模化阶段,分分厘厘都是钱。


5. 文件三:Dashboard 要展示什么

alimafana 没有给出完整的 Dashboard 代码,但根据数据表的字段设计,核心视图应该包括:

5.1 必看指标

指标 业务含义
每日/每周/每月总成本 整体花费趋势
按租户拆分的成本分布 找到"贵的那个店铺"
按 endpoint 拆分的成本 聊天 vs 搜索 vs 图片分析
按模型拆分的成本 gpt-4o vs gpt-4o-mini 是否用对了
失败调用的成本占比 错误是否在持续烧钱
P99 延迟 vs 成本 贵是否因为慢

5.2 一个真实发现

第一次打开 Dashboard,他发现两个功能(本来以为成本差不多)的实际费用相差 100 倍。

这就是监控的价值——你以为的和你以为的,往往不是真相。


6. 这个方案的扩展思路

alimafana 的实现针对的是 Supabase + TypeScript 环境,但思路可以迁移到任何技术栈:

6.1 日志存储选型

方案 适用场景
Supabase/PostgreSQL 已有 Supabase 或习惯 SQL 查询
MongoDB 需要快速迭代字段
ClickHouse 调用量极大,需要 OLAP 能力
Prometheus + Grafana 已有 K8s 监控体系
纯文件 + cron 聚合 轻量级,不想加数据库依赖

6.2 多模型支持

如果你的产品同时接入了 OpenAI、Anthropic、Google 的 API,可以统一日志格式:

interface UnifiedLog {
  provider: "openai" | "anthropic" | "google";
  model: string;
  inputTokens: number;
  outputTokens: number;
  cost: number;           // 统一换算成美元
  latencyMs: number;
  status: "success" | "error";
  errorType?: string;
}

6.3 告警能力

在成本监控之上加一层告警:

// 当单日成本超过阈值时告警
if (dailyCost > threshold) {
  await sendAlert({
    channel: "slack",
    message: `⚠️ OpenAI 今日成本已达 $${dailyCost},超过阈值 $${threshold}`
  });
}

7. 我的感受

读完 alimafana 的方案,我最强烈的感受是:这才是工程师应该做的事——把看不见的东西变透明。

很多团队抱怨 AI 成本高,但连最基本的 per-call 成本数据都没有。你说成本高,高在哪?哪个功能?哪个用户?哪个模型?答不上来。

监控系统不需要多复杂。三个文件,一个下午,就能让你从"看天吃饭"变成"心里有数"。

如果你也在做 AI 产品,还没有监控 API 成本的手段,今天就可以开始搭。 从包装一个 OpenAI 调用开始,从一张记录每次调用的表开始。


8. 相关资源


标签: AI, OpenAI, 成本优化, 监控, 实战, DevOps

字数: 约 3200 字

预计阅读: 10 分钟