AI 预付 Credits 账本:原子预留、实际结算与可追踪退款
只维护一个 credits 余额会在并发请求、超时和重试下超卖。本文用双重分录、批次有效期、原子 hold、实际 settle、释放与补偿退款构建资金状态机。
本文要点
- 主题聚焦:AI、大模型、独立开发
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
Credits 看起来只是 balance = balance - cost,但并发流式请求会立即打破这个模型。两个请求同时看到余额充足后都开始运行,最终成本可能超过余额;worker 超时后清理任务释放 hold,而模型其实仍在生成;支付回调重投又可能发两次额度。余额列即使加锁,也无法说明额度来自哪次购买、何时过期、为何退款。
AI 产品定价模型 解释 Credits 适用场景,AI 平台租户配额 管理资源公平。本篇把 Credits 当产品计费单位的账务状态机,不把它宣传成现金或通用会计结论;税务、收入确认和未消费余额义务仍由财务与当地规则决定。
不可变分录是余额的事实来源
定义最小整数单位和固定产品版本,禁止浮点。购买赠送、促销、消耗、过期和退款使用不同账户或 reason code;每笔 transaction 至少两条方向相反的 entry,借贷净和为零。可缓存 available,但它必须能从分录重建,修正只能追加补偿交易。
grant: issuer_liability -1000 | customer_available +1000
reserve: customer_available -120 | customer_held +120
settle: customer_held -83 | service_consumed +83
release: customer_held -37 | customer_available +37
service_refund: service_consumed -83 | original_lots +83
这只是守恒关系示例,不代表 Credits 与法定货币兑换。grant 按批次记录来源、适用产品、effectiveAt、expiresAt 和限制;消费顺序例如先到期先用必须成为版本化政策。促销额度和购买额度若退款权利不同,不能混成一个余额。
执行前原子预留最坏成本
请求准入时由服务端估算允许的最大消费,而不是相信客户端 max_tokens。同一数据库事务锁定账户与将被占用的 grant lots,确认可用、未过期和产品匹配,创建唯一 operation 与 hold,再移动分录。使用 SERIALIZABLE、行锁或带版本的条件更新都可以,但受影响行数必须精确;幂等键相同且参数不同直接冲突。
预留成功后才调用模型。流式或 Agent 长任务按小段 capture:每段以 (operationId, usageEventId) 唯一,事务校验 hold 仍开放、原子增加 settled 并减少 remainingHold,重复段返回原结果。接近上限时停止新一轮工具或输出;需要增加上限则走新的 top-up reservation 和明确授权。MCP 超时、重试与幂等 的 operation ID 贯穿模型执行、分录和外部计费。
AVAILABLE -> OPEN -> PARTIALLY_SETTLED -> SETTLED
\-------------------------> RELEASED
SETTLED -> PARTIALLY_REFUNDED -> REFUNDED
每段 capture 写入 actor、reason、usage event、策略版本和时间,并保持 captured + remainingHold = originalHold + topUps。只有带 final 标记且所有已知 usage 已入账后,才在同一事务把剩余部分 release 并关闭 hold;重复 final 返回原结果。这样首个小分段不会提前释放后续任务仍需要的额度。
hold 创建时固定被占用 lot 与到期判断。合法预留后 lot 即使跨过 expiresAt 仍可 settle;若 release 时原 lot 已过期,差额转入 expired 而非重新变 available,防止用长任务延长额度寿命。
TTL 不是自动退款证明
hold 可以有 lease,但每次 claim 必须通过 CAS 取得单调递增的 fence/attempt。dispatch、heartbeat、usage capture 和 final settle 都携带并校验当前 fence,旧 worker 恢复后只能被拒绝;外部调用还复用稳定 provider request ID。lease 到期不能仅凭旧 heartbeat 释放:结果未知就进入 RECONCILING,不释放也不重复调用;只有权威证据确认未 dispatch/未执行才 release,已执行则按实际 usage settle。
取消也按实际消耗结算:已经发送的输入、生成的输出和不可撤销工具成本仍可计入,未使用部分释放。产品应在用户操作前说明计算规则,不把内部供应商失败全部自动转嫁,也不承诺所有取消必然零成本。
退款是受限补偿,不是删除扣款
服务 Credits 返还引用原 settle allocation,使用唯一 refund ID 并原子检查累计不超过可退净额。分录按原比例恢复各 grant lot:仍有效的回到 available,已经到期的进入 expired;若决定补偿,另发带独立来源、权限和期限的新 grant,不能借退款复活旧额度。现金 refund、chargeback 和服务 Credits 返还采用不同 transaction type;已消费后的支付撤销进入欠款或停用流程,不伪装成普通余额变化。外部支付或 Stripe Credit Grant 回调同样去重,并明确唯一事实源持续对账。
PostgreSQL 事务隔离 可用于验证并发扣减,测试还要覆盖一百个请求争抢最后余额、分段 capture 重投、final 重放、旧 fence worker 恢复、未知供应商结果、退款跨 lot 到期和退款超过原额。断言任何时点 available + held + consumed + expired 与全部 grant/adjustment 守恒,且没有负 available。
上线先对旧余额生成开账分录,双读比较但只由旧系统扣减;差异为零后切新 reservation。回滚时停止新 hold,允许已有 operation settle/reconcile,恢复旧准入但不删除新分录。错误通过带审批的补偿交易修正。Credits 系统可靠的标准不是余额页面看起来正确,而是每一次预留、消费、释放和退款都能对应唯一业务事实并在崩溃后重放。
延伸阅读
Related