CodeGateway 计费精度口径白皮书
这份白皮书公开 CodeGateway 的完整计费口径:用什么单位记账、在哪里取整、流式请求怎么计量、中途断开怎么结算、tier 加价怎么算、充值与退款怎么入账。每一个口径都是生产环境的实际行为,附代码落点(文件:符号,行号为 2026-10 参考值)与迁移号佐证;历史上出过的精度事故也如实列出,包括已修复、已退还与仍在跟踪中的。
我们只描述已经上线的行为,不描述计划中的能力。
1. 计价单位:整数 micro-dollars 双层体系
CodeGateway 不用浮点数记账。整个计费链路有两层单位,换算的单一事实源(SoT)是 backend/src/balance-units.ts:
层 | 单位 | 换算 | 落点 |
|---|---|---|---|
账本与热路径 | 整数 micro-dollars(µ) | 1¢ = 10⁸ µ;$1 = 10¹⁰ µ |
|
展示与钱包 | 整数美分(cents),micro 的 floor 镜像 | $1 = 100¢ |
|
为什么是 10⁸ 而不是 10⁴:1 美分 = 100,000,000 micro。历史上两次把换算系数写成 10_000(差 10,000 倍)的事故(见 §8 事故披露)之后,所有跨 cents↔micro 边界的换算都必须经过 balance-units.ts 的常量与函数,不允许各写各的——git grep MICRO_PER_CENT 可以列出全部换算点。大额运算走 bigint,避免超过 2⁵³ 后丢精度(balance-units.ts:27-29)。
取整规则三分法
三个方向各司其职,这是本白皮书最重要的口径(balance-units.ts:66-149):
函数 | 规则 | 用途 | 为什么 |
|---|---|---|---|
| 向零截断(truncate) | 一般换算 | 迁移 0141 的 legacy 余额回填同样采用向零截断( |
| 向下取整(floor,toward −∞) | 客户可见余额镜像 | 客户可见的 cents 绝不能高于真实 micro 余额——这是 #2818 修复定下的原则(见 §6、§8) |
| 向上取整(ceil) | 仅遗留兼容入口 | 亚分成本至少收 1¢,防长尾漏扣;当前扣费主路径已不经过它(见下) |
扣减不再经过 cents 取整
#2818 之后,逐请求扣费传 costMicro 原值、直接对 micro 账本做原子扣减(workspace-balance.ts:210 deductWorkspaceBalanceMicro,单条 SQL UPDATE 带 WHERE balance >= cost 守卫)——亚分精度全程不丢失。"逐请求 ceil 到分"这个曾造成系统性多收的环节在主路径上已不存在(workspace-balance.ts:31-32 注释)。
2. 价格来源与成本公式
双源一致:模型价格的事实源是 D1 的 model_catalog / model_costs(由 admin portal 管理);backend/src/pricing.ts 的硬编码 MODEL_PRICES 是计费兜底。CI(model-prices-parity.yml)强制每个公开 model id 两边都有且一致。
成本公式(pricing.ts:1186-1201 calculateCostMicro):
usd = (inputTokens × rate.input
+ outputTokens × rate.output
+ cacheCreationTokens × rate.cacheCreate
+ cacheReadTokens × rate.cacheRead) / 1,000,000
costMicro = Math.round(usd × markup × 10¹⁰)四路 token 分别计价(输入 / 输出 / 缓存写 / 缓存读),费率按每百万 token 美元计。最终取整是 Math.round(四舍五入),不是向上取整——代码注释原文 "no ceil overcharge"。旧版 cents 入口 calculateCostCents 用 Math.ceil,仅保留给旧测试,生产路径不走(pricing.ts:1204-1219)。
长上下文加价(pricing.ts:1158-1183 applyLongContextSurcharge):当次请求的总提示 token(input + cacheRead + cacheCreation)超过该模型的 thresholdTokens 时,按三元组(thresholdTokens / inputMultiplier / outputMultiplier)对 input、output、cacheRead 以及 cacheCreate 四路费率乘系数(WHI-1387)。逐模型的长上下文阈值在价格页与 D1 费率中同源。
3. Tier 加价与 90 天滚动重算
当前档位表(tier-config.ts:73-117 DEFAULT_CONFIG,须与线上 operational_config 行一致):
档 | 90 天消费门槛 | token 加价 | 限流(rpm/tpm) |
|---|---|---|---|
tier1 | $0+ | 1.07× | 各档独立上限(#1390) |
tier2 | $20+ | 1.07× | ↑ |
tier3 | $50+ | 1.07× | ↑ |
tier4 | $200+ | 1.07× | ↑ |
tier5 | $500+ | 1.07× | ↑ |
2026-10-05「方案3」起,token 侧加价拉平为 1.07×,货币化移到充值侧 2% 服务费(见 §7);tier 保留的意义是限流档位(tier-config.ts:61-65)。档位事实源是 D1 operational_config(admin portal 维护),KV 缓存 300 秒,代码内默认表仅作配置面不可达时的兜底,且必须与线上行一致(tier-config.ts:56-60,#2514 教训)。
90 天口径:滚动窗口 windowDays=90,消费 = SUM(request_logs.cost_micro)——只由实际请求成本构成,充值本身不改变它(90 天聚合函数 get90DaySpend 在 tier-config.ts:537-545,聚合 SUM(cost_micro);tier-writeback.ts:151 是同义注释)。
重算的两条路径(tier-writeback.ts 头注释):
充值后即时重算(
recomputeTierOnTopup,Stripe webhook 触发):只升不降。应该档位高于当前档 → 立即升级;永不在这里降级。每日 02:00 扫描(
dailyTierSweep):升级立即生效;降级先挂起、过 7 天宽限才生效(TIER_DEMOTION_GRACE_DAYS=7)。宽限期内档位进一步下降时只更新目标档、不重置计时器(防计时器滥用)。宽限期内消费回升则自动清除挂起。
折扣(#940):支持按档与全局两类促销折扣,同时生效时取对用户更优的一档(tier-config.ts:20-42)。
配置漂移监测(tier-config-drift.ts,#2516):绕开 admin 的 200 包装直接读 D1,区分"行存在但与兜底值分叉"(row_diverges)与"行丢失"(row_missing)两种静默漂移;该模块只读、不自动修复。另有workspace 池账本漂移事项(属另一类,不是 tier 配置漂移)按 GitHub #3472 跟踪,见 §8。
4. 流式与非流式计量
流式(SSE):metering/stream.ts 的 createMeteringStream 逐事件解析上游 SSE,增量累积 token usage:
每个
message_delta触发onProgress——用于渐进释放预扣留(见 §5);message_stop触发onUsage最终结算;每 15 秒注入心跳注释防客户端超时,同时
onHeartbeat让预留保活(#1356,防 Durable Object 回收在途流的预留)。
非流式:从响应体的 usage 字段一次结算,同一套 calculateCostMicro 公式。两条路径的差别只在 usage 的获取方式,成本公式与单位完全一致。
5. 预扣留、中止路径与错误路径结算
一次请求的结算生命周期:
预扣留(preDeduct):请求进入流水线前按预估成本预留余额(
computePreDeductEstimate在backend/src/preDeduct.ts:254:input 按实测 token 计价,剩余额度换算成最大输出 token)。余额不足返回 402,payload 附「把 max_tokens 降到 N 可通过」提示——提示用与预留同一公式、同一实测 input token 计算(#2536),不会出现"照做仍然 402"。渐进释放:流式过程中每个
message_delta释放一部分预留。恰好一次结算(#2501):无论流以哪种方式结束——正常
message_stop、上游截断关闭、管道错误、客户端 abort——都结算且仅结算一次,按当时已累积的 usage 计费(metering/stream.ts:23-50、metering/stream-settle.ts)。客户端断开由请求 signal 捕获,是唯一可靠的断连信号。
诚实披露:#2501 之前,中止或出错的流完全不入账(不写 balance_events 也不写 request_logs)——上游 token 我们已经付了钱,但没有向用户计费。修复后按已流出的 tokens 正常结算。
错误路径:成本公式由 usage 驱动,错误响应没有 usage 就不计费(cost = 0)。例如我方上游批发额度耗尽(CF AI Gateway code 2021)产生的 402,生产实测全部 0 计费(upstream-error-rate-alert.ts:14 的取证记录)。流失败同时释放限流 TPM 预留,不留幻影占用(#1668)。
6. 池钱包与账本镜像
账本(事实层):用户层
balance_events、workspace 层workspace_balance_events,以及 micro 余额缓存(users.balance_cache_micro、workspaces.balance_usd_micro),全部整数 micro;D2(#2860)起 workspace 的 micro 列是写侧权威。钱包(workspace 层):
workspaces.balance_usd_micro是权威值;workspaces.balance_usd_cents是客户可见的 cents 镜像,在同一条 UPDATE 内写入(Sprint 9 workspace 多租户起,余额按 workspace 隔离)。镜像规则:
cents = floor(micro / 10⁸)(balance-units.ts:115-138)。迁移 0172:217 新增balance_usd_micro列,0172:222-227 由balance_usd_cents回填 micro;此后 cents 是 floor 镜像而非事实源(workspace-balance.ts:46-48)。原则:展示值永远不高于真实余额——宁可显示少一分的保守值。原子性:扣减是单条
UPDATE … WHERE balance >= cost带守卫的原子操作,并发请求不会把余额扣穿。对账兜底:每日对账 cron 复核取整与扣减异常(
balance-units.ts:90-92注释承诺的兜底路径)。
7. 充值、赠送金与退款
三条充值通道——Stripe 一次性支付、自动充值(SetupIntent 绑卡 + cron 触发)、银行转账——全部入账 workspaces.balance_usd_micro(D2/#2860 起的写侧权威);cents 列是同一条 UPDATE 内写入的 floor 镜像。
2% 充值服务费(方案3,2026-10-05 起):token 侧加价拉平 1.07× 后,货币化移到充值侧,platformFeeRate = 0.02(dashboard/lib/payment-config.ts:20、backend/src/payment-config.ts:33)。实收金额 = 入账面额 + 服务费 + Stripe 手续费 gross-up;余额入账 = 面额 + 赠送金,平台服务费与 Stripe 手续费不入账(stripe-handlers/manual-checkout.ts:231-235、:598-601 传 amountMicro;auto-topup-charge.ts:661)。
预付赠送:预付更高档位得赠送金——充 $100 得 10%、充 $200 得 10%、充 $500 得 12%(dashboard/lib/topup-bonus.ts:39-43)。赠送金与充值同通道入账。
首充加赠(已上线):用户首次成功充值在面额之外额外赠送——充 $10 赠 $2、充 $20 赠 $5、充 $50 赠 $12(FIRST_TOPUP_BONUS_TIERS,dashboard/lib/topup-bonus.ts:71-75);与上述预付档位不重叠。
自动充值:
webhook 是绑卡事实源(SetupIntent 以
POST /api/webhooks/stripe/auto-topup落库为准,浏览器侧保存只是乐观路径,auto-topup-webhook.ts头注释);扣款入账幂等:以
auto_topup_events.balance_event_id IS NULL判重,重复 webhook 不会重复入账;连续失败达阈值自动暂停配置并邮件告知;3DS 挑战时挂起(
suspended_3ds)直到用户在 dashboard 完成 SCA。
退款与纠偏:
平台侧纠偏性退还直接写池账本:迁移
0197_pool_ceil_overcharge_credit.sql按请求复算 #2818 多收(ceil(cost_micro/1e8)×1e8 − cost_micro,0197:43),按 workspace 汇总结转(0197:213-220,同时回写 cents floor 镜像)。这是池账本内部结转,不是对外退款;迁移文件头明确披露 class-(a) 行在池被抽干、带守卫的扣减被静默丢弃时可能高估金额(见 §8);退款/扣减可产生负的亚分残差(
balance-units.ts:69-71)——此时 truncate 与 floor 结果不同(−0.5¢ 截断为 0¢、floor 为 −1¢),账本以 micro 原值保存,展示层按 §1 的规则换算;前端余额显示用
microToUsdDisplay(截断到分、负数带号,balance-units.ts:159-166)。
8. 事故披露与已知事项(如实记录)
透明度不是"从没出过错",而是错了什么、怎么修的、钱怎么退的,全都可查:
事故 | 内容 | 状态 |
|---|---|---|
#1345 | 自动充值阈值的 cents→micro 换算写成 ×10⁴(差 10,000 倍),候选集几乎恒空,自动充值长期静默停摆 | 已修复:换算统一收口到 |
#2075 | workspace shadow-deduct 用本地 | 已修复:同一 SoT 收口 |
#2818 | 逐请求把成本 ceil 到分再扣,系统性多收(每请求至多多收不到 1¢,长尾累积) | 已修复:扣减改传 |
#2861 | 写侧早已切 micro,但读侧(402 闸门 / dashboard 展示 / balance-drift)仍按 cents 换算,出现跨单位偏差(D3) | 已修复:读路径统一切到 micro,消除跨单位不一致 |
#2514 | 线上 | 已修复:代码兜底值对齐线上行($20/$50/$200/$500);只读漂移监测由 #2516 另行加入 |
#3472 | workspace 池账本与账户账本之间的漂移 | 部分修复:迁移 |
附:复算口径(数据声明如何验证)
本白皮书正文只声明机制与常数,不引用即时业务数据。如需复算任何消费类数据,统一口径为:
-- 某用户 90 天消费(USD):request_logs 按 workspace/user 聚合
SELECT SUM(cost_micro) / 1e10 AS spend_usd FROM request_logs WHERE ...;
-- 验证取整镜像:派生 cents 必须等于 floor(micro/1e8)。
-- 除数写成 100000000.0(浮点):若用整数除法,SQLite 会先向零截断再 FLOOR,负亚分余额会算错。
SELECT balance_usd_cents, FLOOR(balance_usd_micro / 100000000.0) FROM workspaces WHERE ...;任何对外发布的量化声明(如事故影响笔数、退还总额)发布前须经独立复算并在发布页附取数口径。
口径变更随代码走:本文每个声明都有上述代码/迁移锚点,代码变了本文必须同 PR 更新。代码锚点指向我们的内部仓库,issue 编号为内部引用。来源:内部 issue GH#2294。