NewAPI 的额度系统设计

quota:内部计量单位

NewAPI 的用户余额和消费记录并不是直接以美元或人民币存储,而是使用整数 quota 作为内部计量单位。

例如,后台显示余额为 ¥73.00,数据库中对应的 users.quota 可能是 5,000,000。两者之间通过 QuotaPerUnit 和展示汇率进行转换。

// common/constants.go
var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens

默认情况下,50,0000 quota 对应一个系统标准金额单位,即 1 USD。quota 本身不包含币种,它只是系统内部使用的计量单位。

采用整数 quota 有几个好处:避免浮点数直接参与余额累计,方便进行额度扣减、退款和账户余额管理,同时可以将内部计费与前端货币展示分离。

不过,这并不意味着 NewAPI 完全实现了币种无关的计费体系。模型定价和部分计费逻辑仍然以美元价格为基准。

QuotaPerUnit 的来历

代码里没有解释为什么 QuotaPerUnit 定义成 50 万,但提交历史能定位:2023 年 6 月 20 日的 a6cc0226a,标题是 feat: able to display quota in dollar。

2023 年 GPT-3.5 Turbo 的价格为每 1000 Token 0.002 美元,换算下来,每个 Token 的价格是 0.000002 美元。500000 quota 对应 1 美元,恰好与这一价格形成对应关系。
PS:这只是我的推测,没有官方说明作为支撑。

随着模型定价体系不断变化,QuotaPerUnit 逐渐成为内部额度换算刻度。今天它更多承担统一计量单位的作用,而不是代表某个模型的实际 Token 价格。

从 Token 到 quota

对于常见的 Token 计费模型,可以将计算过程简化为:

quota = (
    prompt_tokens
    + completion_tokens × 补全倍率
) × 模型倍率 × 分组倍率

其中,模型倍率决定模型相对于系统标准价格的成本,分组倍率则用于不同用户分组的差异化定价。

例如,倍率为 1 表示采用标准价格,0.5 表示半价,2 表示两倍价格。

按次计费的模型则根据配置的单次价格和相应倍率计算 quota。图片、音频、缓存等模型能力还可能采用不同的计费规则,不能简单套用上述公式。

  • 传统倍率模式不存“每百万 Token 多少钱”,只存无量纲倍率:

    • ModelRatio = 0.5 表示每 token 扣 0.5 个 quota,按 QuotaPerUnit = 50 万 换算即 $1/M 输入;
    • CompletionRatio = 4 表示输出按输入的 4 倍计价,即 $4/M。

    倍率以 Go 的 float64 存放,计费时才转定点数,叠加模型/补全/缓存/音频/分组倍率算出 quota,取整后从整数余额扣减——配置侧是浮点倍率,账本侧是整数 quota。

  • 后加的表达式模式(tiered_expr)反过来,系数直接写每百万 Token 的真实价格(p * 2.5 = $2.50/M),余额仍然是整数 quota。

两者最终都在 quota 上汇合;而倍率/单价的货币含义由 QuotaPerUnit 和站点币种口径决定,格式化之后才变成钱。

三个独立的货币参数

NewAPI 中最容易混淆的是三个参数:

参数 作用 示例
QuotaPerUnit 标准金额与 quota 的换算 500000 (代码写死)
USDExchangeRate 标准金额转换为展示货币 7.3 (默认)
Price 充值一个标准金额单位所需支付的本地货币 7.3 (默认)

PS:这两个 7.3(人民币汇率)应该和上面的 QuotaPerUnit 一样,来自系统开发时的真实数据。

假设 QuotaPerUnit=500000、USDExchangeRate=7.3、Price=7.3,那么:

  • 500000 quota 对应 1 个标准金额单位。
  • 用户界面将其显示为 ¥7.30。
  • 用户充值 1 个标准金额单位,需要支付 ¥7.30。

展示汇率与充值价格虽然默认可能相同,但两者并不等价。

例如,将 Price 设置为 5,而展示汇率保持 7.3,用户充值一个标准金额单位只需要支付 ¥5。这相当于提供了充值折扣。

货币展示与实际支付

NewAPI 将内部额度、货币展示和实际支付分成不同环节。

用户消费时,系统根据模型价格和倍率计算 quota;展示余额时,再将 quota 转换为相应货币;用户充值时,则根据 Price 计算实际支付金额。

因此,人民币展示不意味着必须采用人民币作为内部计价基准。

如果只是希望用户看到人民币余额,通常只需要选择 CNY 展示类型,并配置正确的 USD/CNY 汇率。模型定价和内部 quota 无须因此改变。

充值价格也可以独立配置,不必与展示汇率相等。

设计上的局限

NewAPI 采用的是一套简单的技术方案,所有金额在底层都统一换算成锚定美元的计量单位,然后在交互层根据货币单位进行换算显示。

NewAPI 底层采用美元作为标准计价单位,通过 QuotaPerUnit 将美元转换为整数 quota(数据库设计上就不需要考虑精度问题了),再通过 USDExchangeRate 转换为本地货币展示。这种设计在美元市场比较自然,在人民币等非美元市场存在的问题我做一个列举:

  1. 增加一次换算,少不了又来四舍五入,存在一点精度损失。
  2. USDExchangeRate/Price 只能是固定值,无法根据实际汇率动态调整的。
    如果 USDExchangeRate 调整,现有客户的余额就会变,无法解释。
    如果 Price 调整,比如改成当前实际汇率 6.68,用户充值 668 元,底层算 100 单位(即 50,000,000 quota),再显示页面上变成 730 元。
  1. 模型成本口径不统一。 国内模型供应商通常直接采用人民币报价,海外模型则可能采用美元报价。统一使用美元作为内部基准,需要额外进行货币转换,容易造成成本核算与实际采购金额不一致。

  2. 充值价格与展示汇率容易混淆。 USDExchangeRate 决定展示金额,Price 决定充值实际售价。两者虽然独立,但配置人员容易将其混为一谈,导致充值折扣、实际支付金额与余额显示不一致。

  3. API 兼容性存在隐患。 部分兼容接口沿用 soft_limit_usd 等美元字段名称,但返回值可能已经按照站点展示币种转换。第三方客户端如果仍按美元解释,就会产生金额错误。

  4. 财务核算不够直接。 quota 记录的是内部消费额度,不等于人民币实际收入。充值赠送、支付手续费、退款、汇率差异和上游采购成本都需要额外核算,不能直接根据 quota 统计经营收入和利润。

总体而言,美元作为内部计价基准本身没有问题,问题在于 NewAPI 对非美元市场的适配主要集中在展示和充值环节,缺少完整的多币种成本管理、定价和财务核算机制。 对普通中转站影响有限,但对于面向国内用户的商业化 API 平台,需要额外建设这些能力。

如果你有魔法,你可以看到一个评论框~