8.7 KiB
额度与计费
模块定位
额度与计费负责回答三个问题:一次请求是否还可以开始、请求实际产生了多少成本、该用户当前还剩多少可用额度。
每个用户 Key 拥有独立的美元额度账户、重置周期和并发上限。系统没有套餐、订阅或充值订单,管理员直接分配额度。
当前能力
| 能力 | 当前实现 |
|---|---|
| 用户额度 | 每个 Key 独立设置本周期美元额度 |
| 请求扣费 | 根据最终 Token Usage 和模型价格结算 |
| 自动重置 | 支持不重置、每日、每周和每月 |
| 手动重置 | 管理员可立即开始新额度周期 |
| 并发限制 | 每个 Key 独立限制同时执行的请求数 |
| 价格规则 | 支持基础价格、长上下文价格和 Fast 倍率 |
| 参考价格 | 搜索并显式导入 models.dev 供应商级价格,刷新前展示差异 |
| 计费账目 | 永久记录扣费、额度调整和周期重置 |
| 请求拦截 | 余额、并发或价格不满足时在上游调用前拒绝 |
额度账户
每个 Key 创建时同时创建一个额度账户,默认值为:
| 属性 | 默认值 |
|---|---|
| 本周期额度 | $0 |
| 已用金额 | $0 |
| 可用余额 | $0 |
| 自动重置 | 不重置 |
| 并发上限 | 4 |
三项金额的关系始终为:
可用余额 = 本周期额度 - 本周期已用金额
金额在数据库中以微美元整数保存,管理接口使用最多六位小数的美元字符串,避免浮点累计误差。
修改额度只改变本周期额度,不会清空已经产生的消费。例如额度为 $10、已用 $3 时,将额度改为 $5,余额会变为 $2。
请求准入与结算
计费采用“请求前准入、请求后结算”的软限制:
- 请求开始前读取当前额度周期。
- 如果已经到达重置时间,先执行周期重置。
- 余额必须大于零。
- 当前并发必须小于用户并发上限。
- 为 Request ID 创建并发占用后放行请求。
- 请求终态到达时释放并发占用。
- 最终 Usage 到达后计算成本、增加已用金额并写入扣费账目。
系统不会在请求开始前估算并冻结最大费用,因此最后一个或多个并发请求可能让余额变成负数。负余额会被保留,之后的新请求不再放行,直到管理员提高额度或开始新周期。
同一个 Request ID 的重复准入不会重复占用并发;同一条 Usage 的重复回调也不会重复扣费。插件重新启动时会释放上次进程遗留的未关闭并发占用。
并发限制
并发上限可以设置为 1–64。计数基于已经准入且尚未收到请求终态的 Request ID。
- 达到上限时,新请求返回 HTTP 429 和
billing_concurrency_exceeded。 - 成功、失败、拒绝或取消的请求进入终态后都会释放名额。
- 重复或迟到的终态回调不会重复释放或破坏计数。
并发限制只控制同时执行数量,不限制单位时间请求次数或 Token 数量。
额度重置
| 重置方式 | 行为 |
|---|---|
none |
永不自动重置 |
daily |
按天开始新周期 |
weekly |
每七天开始新周期 |
monthly |
按月开始新周期 |
| 手动重置 | 立即结束当前周期并开始新周期 |
未指定首次重置时间时,系统以 Key 创建时间作为周期锚点计算下一次重置。周期计算使用 Asia/Shanghai 时区;月度锚点在较短月份不存在时使用该月最后一天。
重置后:
- 本周期已用金额归零;
- 新周期额度沿用当前额度设置;
- 可用余额恢复为完整额度;
- 旧周期余额不结转;
- 历史周期和账目继续保留。
自动重置在读取额度状态或执行请求准入时检查,不依赖常驻定时任务。
模型价格
每个模型使用精确模型名称维护一套价格策略,价格单位为 $ / 1M Token。
基础价格
| 价格项 | 计费对象 |
|---|---|
| 输入 | 排除缓存读取和缓存写入后的普通输入 Token |
| 缓存读取 | Cache Read Token |
| 缓存写入 | Cache Write/Creation Token |
| 输出 | Output Token |
普通输入 Token 的计算方式为:
普通输入 = 输入 Token - 缓存读取 Token - 缓存写入 Token
长上下文价格
价格策略可以设置输入 Token 门槛,并选择“大于”或“大于等于”。请求达到门槛时,输入、缓存读取、缓存写入和输出四项价格整体切换到长上下文价格,不与基础价格混用。
Fast 价格
当请求标记为 Fast、Priority 或同等快速服务档位,并且该模型启用 Fast 计价时,系统对完整请求成本应用一次倍率。倍率以精确分数保存,默认界面值为 2.5。
系统先选择基础或长上下文档位,再应用 Fast 倍率,最后对整次请求执行一次四舍五入。
models.dev 参考目录
models.dev 只提供候选参考价格,不直接参与在线计费。插件仅在管理员点击“更新目录”时下载 catalog.json,把能够由当前计费模型准确表达的供应商级 Token 价格规范化后保存到本地缓存。用户请求、认证、额度检查和最终扣费都不会访问外网。
管理员可以把任意目录价格导入任意本地模型,本地模型名与参考模型名不必一致。导入后,本地价格保存其 models.dev 供应商、模型、目录 revision 和获取时间;实际计费仍读取 SQLite 中已经确认的本地价格。
目录映射规则:
- 输入和输出价格必须存在;
- 缓存价格缺失时使用输入价格,避免把缓存错误计为免费;
- 只支持一个可完整表示的长上下文档位;
- 独立音频或推理价格无法由当前四类 Token 表达时不提供导入;
- 输入、输出和缓存全部为零的条目不自动认定为免费;
- Fast 倍率是本地业务规则,导入和刷新不会改变它。
刷新目录会先返回所有已关联价格的变化和已下架来源。页面只有在管理员确认后才更新发生变化的本地价格;取消确认不会改动账单。人工保存某个模型价格会把来源切换为“手动配置”,以后的目录刷新不再跟随它。
下载或解析失败时继续使用最后一次成功缓存,本地有效价格保持不变。目录下载有超时和体积限制,解析、写缓存和发布新索引成功后才替换旧版本。
价格缺失
允许访问的模型必须存在价格配置。系统在 CPA 已经选择上游后、真正访问模型服务前再次检查实际模型价格。
价格不存在时返回 HTTP 503 和 billing_price_unavailable,不会把未知成本的请求发送到上游。历史迁移数据仍可能因为当时没有价格而显示为成本不可用。
管理员首次补充某个模型价格时,系统按固定小批次补算该模型尚未定价的历史 Usage。补算使用缺失成本专用索引,不会一次性把全部历史载入内存;每批独立提交,在线认证、额度检查和新用量写入可以继续执行。
不可变账目
| 账目类型 | 含义 | 金额方向 |
|---|---|---|
charge |
一次最终 Usage 产生的扣费 | 负数 |
quota_change |
管理员修改本周期额度 | 增加为正,减少为负 |
cycle_reset |
自动或手动开始新周期 | 新周期完整额度 |
每条账目保存用户 Key、额度周期、变动金额、变动后余额和发生时间,请求扣费还会保存模型。账目结构预留 Request ID 和 Execution ID,但当前目标 CPA 的 Usage 契约不提供这两个字段,因此实际扣费账目通常为空,不使用时间推测结果改写不可变账目。
账目只追加、不修改、不删除,并通过唯一事件标识避免重复写入。额度状态用于快速读取当前余额,账目用于解释余额变化过程。
拒绝结果
| 条件 | HTTP 状态 | 错误码 |
|---|---|---|
| 余额小于或等于零 | 429 | billing_quota_exhausted |
| 并发达到上限 | 429 | billing_concurrency_exceeded |
| 额度数据库不可用 | 503 | billing_unavailable |
| 模型没有价格 | 503 | billing_price_unavailable |
这些拒绝发生在访问实际模型服务之前,并进入请求终态记录。
模块边界
本模块负责:
- 模型价格和确定性成本计算;
- 参考价格目录缓存、显式导入和确认更新;
- 用户额度、余额和额度周期;
- 并发请求准入;
- 请求结束后的实际扣费;
- 不可变计费账目。
本模块不负责:
- 套餐、订阅、充值、支付和退款;
- 按分钟、按日或按月的额外速率限制;
- 请求开始前的费用预估或资金冻结;
- 用户 Key 的身份认证和模型权限;
- 上游服务本身的账单对账。