QUOTA_PER_UNIT = 500000.0

计价模型

内核不卖流量,它把上游算力折算成整数额度。这一页写清折算公式、倍率来源、预扣与退回的时机,以及一个跑着同一份公式的计算器。

01计价单位

全链路只用一个整数:quota。浮点只出现在展示层换算,落库与判定永远是 i64。

常量值含义
QUOTA_PER_UNIT500000.01 USD 对应的 quota 数
quota = -1−1不限量(用户余额 / 令牌子额度 / 房间池通用此约定)
展示换算q / 500000quota_usd、used_usd、today_usd 等视图字段

之所以不存浮点:并发扣减需要原子 CAS,浮点没有可靠的比较交换语义;而且 $0.000002 级的单次费用用浮点累加会漂。

02公式

五个倍率,两个夹取,一次向上取整。

src/store.rs
quota = ceil(
  ( (prompt − cached − cache_write)     // 未命中输入,原价
  + cached        × cdr                 // 缓存命中,打折
  + cache_write   × cwr                 // 缓存写入,可能溢价
  + completion    × cr                  // 补全
  ) × mr × gr                           // 模型总价倍率 × 分组倍率
)
  • 子集夹取:cached 先夹到 [0, prompt],cache_write 再夹到 [0, prompt − cached]。上游把三者报重了也不会算出负数或重复计费。
  • 口径归一:Anthropic 的 input_tokens 不含缓存读写,OpenAI 的 prompt_tokens 含命中缓存。内核统一按「prompt 为全集」记账,与 Anthropic 的 inclusive 语义对齐。
  • 按渠道协议计费:跨协议时,用量取自上游(渠道)协议的 usage,倍率也用该模型的倍率——不是按客户端协议猜。
  • 向上取整:ceil 意味着宁可多收 1,不放过零头。

怎么自证

本页公式与网关扣费同口径:同一笔用量在任何协议入口得到的 quota 一致。可先用下方计算器算一遍,再与控制台「使用日志」的逐笔金额核对。

03计算器

与内核同一份公式,输入用量与倍率,输出整数 quota 与美元。

输入

tokens · ratios

结果

同公式
0quota =$0
构成
预扣 → 结算—

以上四项加权求和后,再乘模型倍率与分组倍率并向上取整。
预扣按估算的 completion 计算,真实用量返回后多退少补。

04预扣 · 结算 · 退回

额度在请求生命周期里走三步,任何一步失败都有明确的账务结果。

预扣 pre_consume

用估算的 prompt token 与配置的 prealloc_completion_tokens 算出 pre_quota,对内存原子量做 CAS 扣减。扣不动直接 402,请求不会打到上游,因此不会产生任何真实费用。

结算 settle

拿到真实 usage 后按公式重算实际额度,把 pre_quota 释放、按实际值正式扣除,并写一条消费日志(含四种 token 分项、延迟、状态、渠道)。

退回 refund

上游失败、重试耗尽或请求在转发前被拒时,把预扣整笔退回。日志仍会写,状态标为失败,便于排查上游抖动。

场景账务结果响应
正常完成按真实 usage 扣 ceil(...),多退少不补(少则追加扣减)协议原生响应
预扣时余额不足不扣402 insufficient_quota
房间池不足不扣个人,房间池也退402
房间已冻结不扣403 room_forbidden
全部渠道失败整笔退回502 upstream_error
429 后换渠道成功只按最终成功的 usage 扣一次协议原生响应

05三种账务形态

同一套公式,三种池子。区别只在「扣谁」。

Personal

个人账户

用户余额 −1 = 不限量

最普通的形态:一个用户一个余额,令牌跟随用户,扣减按「用户 + 令牌」双层原子检查。

  • 令牌 remain_quota = -1 时跟随用户余额
  • 用户禁用即全部令牌失效
  • 分组倍率 gr 挂在用户上

Token

令牌子额度

从用户余额里切一块

给某个项目或某把外发的 Key 单独设上限与过期时间,超了就 402,不影响同账号其他令牌。

  • model_limits 逗号白名单
  • expired_at = -1 永不过期
  • 一次性明文展示,面板只回显一次

Room

房间共享池

≤5 人 · 独立池

房间 Key 的消费只扣房间池,个人账户完全不参与;房间可用独享渠道,个人 Key 调度不到。

  • 入房名额由 SQL 原子抢占
  • 池尽 402,冻结 403
  • 日志带房间归属

06倍率从哪来

倍率是数据,不是代码。控制面用 pricing 端点改,内核侧在内存里读,改完即刻生效。

写入一个模型的四倍率
curl -X PUT https://<你的部署地址>/admin/pricing/models/deepseek-flash \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{
    "model_ratio": 0.14,          # mr:把 token 折算成美元尺度的总倍率
    "completion_ratio": 4,        # cr:补全相对输入的倍数
    "cached_discount_ratio": 0.1, # cdr:命中缓存打折
    "cache_write_ratio": 1.25     # cwr:写入缓存溢价
  }'

# 分组倍率挂在用户所属分组上,用于给不同人群整体打折或加价
curl -X PUT https://<你的部署地址>/admin/pricing/groups/vip \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{ "ratio": 0.8 }'

缺省即 1.0

模型没有配置倍率时,四个倍率与分组倍率全部按 1.0 处理——也就是「按 token 原样扣」。新接上游记得先写 pricing,否则计费尺度和你想的不一样。

07自部署账

内核自身不收钱:它按你自己的倍率给下游记账,上游账单来自你配的渠道密钥。

项需要什么说明
运行时Rust 工具链 · 单个二进制Axum + Tokio + sqlx,无外部服务依赖
存储一个 SQLite 文件WAL 模式,busy_timeout 5 秒,连接池 32
配置一份 TOMLadmin_token 必填,转发参数全有默认值
上游你自己的渠道密钥密钥只进不出:写进去,任何响应字段都不返回它
验证三个脚本schemaguard 校契约 · roomtest 校房间 · realchannel 打真实上游
cargo build --release
./target/release/opennext-kernel -c config.toml
# → 监听 0.0.0.0:3000 · 首次启动自动建表并写入 root 用户与令牌
curl http://127.0.0.1:3000/health   # ok