# 额度与计费 ## 模块定位 额度与计费负责回答三个问题:一次请求是否还可以开始、请求实际产生了多少成本、该用户当前还剩多少可用额度。 每个用户 Key 拥有独立的美元额度账户、重置周期和并发上限。系统没有套餐、订阅或充值订单,管理员直接分配额度。 ## 当前能力 | 能力 | 当前实现 | | --- | --- | | 用户额度 | 每个 Key 独立设置本周期美元额度 | | 请求扣费 | 根据最终 Token Usage 和模型价格结算 | | 自动重置 | 支持不重置、每日、每周和每月 | | 手动重置 | 管理员可立即开始新额度周期 | | 并发限制 | 每个 Key 独立限制同时执行的请求数 | | 价格规则 | 支持基础价格、长上下文价格和 Fast 倍率 | | 参考价格 | 搜索并显式导入 models.dev 供应商级价格,刷新前展示差异 | | 计费账目 | 永久记录扣费、额度调整和周期重置 | | 请求拦截 | 余额、并发或价格不满足时在上游调用前拒绝 | ## 额度账户 每个 Key 创建时同时创建一个额度账户,默认值为: | 属性 | 默认值 | | --- | --- | | 本周期额度 | `$0` | | 已用金额 | `$0` | | 可用余额 | `$0` | | 自动重置 | 不重置 | | 并发上限 | 4 | 三项金额的关系始终为: ```text 可用余额 = 本周期额度 - 本周期已用金额 ``` 金额在数据库中以微美元整数保存,管理接口使用最多六位小数的美元字符串,避免浮点累计误差。 修改额度只改变本周期额度,不会清空已经产生的消费。例如额度为 `$10`、已用 `$3` 时,将额度改为 `$5`,余额会变为 `$2`。 ## 请求准入与结算 计费采用“请求前准入、请求后结算”的软限制: 1. 请求开始前读取当前额度周期。 2. 如果已经到达重置时间,先执行周期重置。 3. 余额必须大于零。 4. 当前并发必须小于用户并发上限。 5. 为 Request ID 创建并发占用后放行请求。 6. 请求终态到达时释放并发占用。 7. 最终 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 的计算方式为: ```text 普通输入 = 输入 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 的身份认证和模型权限; - 上游服务本身的账单对账。