夜航船 NightShip · 返回主页 · 博客

给AI产品算钱:用量计费从0到1(含可抄的扣费骨架)

2026-10-17 · 林一舟

为什么写这篇

你做了一个AI应用(agent、MCP服务、套壳product都算),第一个真实的商业问题不是模型能力,是:每一次调用你都在花钱,用户不付钱你就亏钱。

订阅制订不通——重度用户一次烧掉你一个月月费;免费额度拍脑袋——给多了被薅,给少了被骂。这篇文章把"用量计费"从架构到落地讲一遍,全部来自我自己给MCP服务接计费的真实过程,代码骨架可以直接抄。

一、先想清楚:你计费的单位是什么

AI产品的计量单位,从粗到细三档:

档位 单位 适合 坑
按次 一次调用/一次任务 工具型、结果可预期 用户觉得"我这条才几个token你收我一次的钱"
按token 原样转嫁模型账单 透明、开发者向 用户看不懂,客服成本高;价格随模型价格浮动
按credit 平台虚拟币,1 credit=约定价值 最主流,兼顾心理账户与成本 汇率要一次定对,改汇率=公关事故

结论:用credit制。原理是把"模型的价格波动"和"用户的付费心智"解耦——你内部按token核算成本,对外只说"这个操作扣10 credit"。改成本时你调内部汇率,不动用户价签。

定价的一个可用锚点:单次典型操作的成本 × 5~8 = credit售价。留出的倍数覆盖:失败重试、免费额度、支付通道费、以及你自己的毛利。

二、计量三件事:计数、聚合、对账

计数:在正确的层拦

不要指望事后从模型厂商的账单API反推每个用户的用量——账单是T+1的,而且粒度不够。在网关层/服务层,把每次调用的输入输出token当场记下来:

// 伪代码:每次LLM调用后的记账
await db.execute(
  `INSERT INTO usage_events (user_id, action, tokens_in, tokens_out, cost_credit, created_at)
   VALUES (?, ?, ?, ?, ?, ?)`,
  [userId, "chat.generate", usage.input_tokens, usage.output_tokens, price, Date.now()]
);

三个必须刻进肌肉记忆的点:

  1. 记账和业务调用同事务语义:调用成功但记账失败=送钱;记账成功但调用失败=坑用户。至少要做到"调用失败不扣费",用try/catch包清楚。
  2. token数从usage字段拿,不要自己数。各家SDK返回体里都有usage,自己估算必错。
  3. 时间戳用UTC存,展示层再转时区。日切对账时你会感谢这条。

聚合:余额不要实时SUM

新手做法是每次扣费前SELECT SUM(cost_credit) FROM usage_events WHERE user_id=?——用量一大这就是慢查询+并发灾难。正确做法:用户表上放一个balance字段,扣费用原子UPDATE:

-- 原子扣费:余额不足直接失败,不存在读到旧值
UPDATE users SET balance = balance - :cost
WHERE id = :uid AND balance >= :cost;
-- 受影响行数=0 → 余额不足,拒绝服务

对账:每天一次,一发现金

usage_events聚合出来的消耗总量,和balance的减少量,每天对一次。差值不为0说明有漏记或重复记——计费系统最怕的不是错,是错了自己不知道。

三、并发与幂等:两个必踩的坑

并发扣减:用户开三个窗口同时发请求,三个请求同时读到balance=100,各扣60,全都"成功"——你亏了80。解法就是上面的原子UPDATE(WHERE balance >= :cost),把判断和扣减合成一条语句,让数据库的行锁替你串行化。

幂等:网络超时后客户端重试,同一个操作扣两次费,用户投诉到怀疑人生。给每次操作发一个request_id,扣费表对其做唯一索引:

CREATE TABLE billing_ledger (
  request_id TEXT PRIMARY KEY,   -- 幂等键:重试同请求不会插第二条
  user_id TEXT NOT NULL,
  cost_credit INTEGER NOT NULL,
  created_at INTEGER NOT NULL
);
-- 扣费=INSERT这条ledger+UPDATE余额,同一事务

INSERT冲突→说明是重试→直接返回上次结果。这两招之后,你的计费就比市面上大量AI套壳产品扎实了——真心话,很多产品连这个都没做。

四、防刷:免费额度是给用户的,不是给脚本的

免费额度上线第一天就会被脚本盯上。最小防线四条,按性价比排序:

  1. 注册即手机/邮箱验证——挡掉最低成本的批量注册。
  2. 设备/IP维度限频:同IP每天新注册≤N个;同设备指纹免费额度只发一次。Redis的INCR+过期时间就够。
  3. 免费额度发得少、发得慢:一次给"够体验3次核心价值"的量,而不是够薅的量。体验完引导付费,转化漏斗反而更健康。
  4. 异常监控:单用户小时级调用量超P99的10倍→自动冻结+人工复核。宁可错杀后快速解封,不可放任烧你的token。

一个真实数字供参考:我见过免费额度上线72小时内,0.3%的账号消耗了41%的token,全是脚本。防线第2条就能拦住它们。

五、收钱的最后一公里:国内没有Stripe metered

Stripe有漂亮的metered billing,但国内个人开发者用不了。现实路径就两条:

预充值的坑只有一个:credit过期策略要想清楚。建议"永不过期但价格随采购成本调整"或者"12个月有效期+到期前三次提醒",别玩"7天过期"——那是给自己上黑名单。

六、可抄的最小骨架(SQLite版)

把上面全部揉在一起,个人开发者一天能写完的最小计费系统:

表结构(3张):
users(id, balance, created_at)
billing_ledger(request_id PK, user_id, delta, reason, created_at)
usage_events(id, user_id, action, tokens_in, tokens_out, created_at)

规则(4条):
1. 扣费 = 事务{ INSERT ledger(幂等) + UPDATE users SET balance=balance-? WHERE balance>=? }
2. 记用量 = usage_events INSERT,与扣费同事务
3. 充值 = ledger INSERT(delta>0, reason='topup:订单号') + balance UPDATE
4. 每日对账 = SUM(delta of ledger) == balance 当前值,逐用户

三张表四条规则,撑到日活几千没问题。等你需要多币种、订阅、团队席位了,再上专业计费(那是一个新文章的话题)。

写在最后

用量计费的本质是把你的成本结构翻译成用户能理解的价格语言。翻译错了,要么亏死要么没人买;翻译对了,用户花钱花得心甘情愿。

我在做的「夜航船」系列里,MCP服务端模板(Kit版)内置了这套计费骨架的完整可运行版:幂等扣费、credit余额、卡密兑换,TypeScript写完带测试。想省事的可以去淘宝搜"夜航船",或者到 nightship.cc 用免费工具先玩一玩。

下一篇写《MCP服务上线后的监控:一眼看出谁在薅你》。

我是林一舟,「夜航船 NightShip」——一个只在夜里发布产品的独立开发者。这篇是夜航手记的原创文章。

🛠 免费工具:MCP 配置生成器 · JSON 转 Schema · MCP 端点测试器

📖 完整《MCP 开发速查手册》(v2 基线 + 迁移速查):淘宝搜「夜航船NightShip」,海外 Gumroad