- 使用 SQLite 保存关联后的请求生命周期和用量记录 - 支持长上下文阶梯价格和可配置的 Fast 计费倍率 - 添加管理接口和可配置列的用量面板 - 保留失败、取消、重试和 compact 请求,便于计费核对
15 KiB
价格目录与计价政策模块
1. 定位
价格模块负责回答一个问题:在某个不可变价格版本下,这次实际模型与速度层级应该使用什么计价政策。
它拥有:
- 模型价格来源、候选、草稿和已发布版本;
- 模型别名到规范计费模型的显式映射;
- 普通输入、缓存读取、缓存写入和输出四段单价;
- 长上下文阈值与阶梯价格;
- Fast/priority 等速度层级政策;
- 价格预览、发布、回滚和只读运行时快照;
- 无法定价、来源过期和规则冲突诊断。
它不拥有:
- Provider Usage 的 Token 语义归一化,该职责属于 collection.md;
- 最终金额结算、余额、套餐和账本,该职责属于 billing.md;
- 模型调用、OAuth 账户健康和路由选择;
- 按历史 Token 动态改写已经入账的金额;
- 用户侧 Credits 或 Token 额度。用户核心单位始终是金额。
Billing 向本模块提供价格查询主题,本模块返回不可变 ResolvedPricePolicy。Billing 再使用实际规范化 Usage 计算金额并将所用政策完整快照写入账单。
2. 不可违背的原则
2.1 候选价格绝不是生产价格
外部目录、官方网页抓取结果和管理员输入先进入 candidate/draft,只有经过验证和显式发布后才成为生产价格。
生产请求只能读取 published 版本。以下行为禁止:
- models.dev 或其他第三方目录刷新后自动改变生产计费;
- 请求执行过程中读取网络价格;
- 直接修改已发布版本;
- 因价格源暂时不可用而把未知价格当成 0;
- 用“当前最新价格”重算并覆盖历史账单。
2.2 价格和倍率使用确定性表示
结算币种第一版固定为 USD。持久化和领域计算不使用 float64 表示最终费率或金额:
type Rate struct {
MicrosPer1M int64 // 每 1,000,000 Token 的微美元数
}
type Ratio struct {
Numerator int64
Denominator int64
}
例如 Fast 2.5× 保存为 5/2,不是二进制浮点数。计算使用溢出安全的整数/大整数中间值,每个 Execution 的完整 canonical Usage 只在最终总额处执行一次 round-half-up 到 micro USD。Usage revision 通过“新累计应收总额减已入账总额”生成差额,不能对每次增量分别舍入后累加。
2.3 发布后不可变
回滚不是重新编辑旧版本,而是以旧版本内容创建一个新的发布版本。这样每笔账单都能永久引用原始 price_version_id,同时保留谁在何时恢复了哪一版。
2.4 无价格必须显式失败
- 准入时无法解析允许模型的可信价格:
503 billing_price_unavailable,不触达上游; - 执行后才发现实际模型/tier 与准入假设不同且无法定价:保存
unpriced事实、告警并阻止该 Credential/模型的后续请求; - 明确发布的零价格是合法价格,必须通过
is_explicit_free=true与“未解析”区分; - 管理员补发价格后只能追加 settlement/adjustment,不修改原始 Usage 或旧账本。
3. 核心领域对象
3.1 PriceSource
记录价格信息从哪里来:
PriceSource
├─ source_id
├─ kind # official/manual/external
├─ name
├─ source_uri
├─ retrieved_at
├─ effective_hint
├─ content_hash
├─ raw_reference # 受限引用或原始快照位置
└─ verification_status
official 只表示来源类型,不代表内容已自动获准发布。外部内容要保存抓取时间和哈希;来源正文不进入请求关键路径。
3.2 PriceCandidate
候选是某个来源解析出的模型价格建议:
PriceCandidate
├─ candidate_id
├─ source_id
├─ source_model
├─ canonical_model_hint
├─ four_segment_rates
├─ long_context_policy
├─ tier_policy
├─ source_effective_at
├─ parse_warnings[]
└─ status # new/reviewed/rejected/imported
同一模型可以同时存在多个来源候选。冲突必须显示差异,不能用抓取先后顺序静默覆盖。
3.3 PriceVersion
PriceVersion
├─ price_version_id
├─ sequence
├─ currency # USD
├─ state # draft/published/superseded/rejected
├─ parent_version_id
├─ content_hash
├─ change_summary
├─ created_at / created_by
├─ published_at / published_by
└─ source_snapshot[]
任一时刻只有一个运行时 active published version。数据库可以保存未来生效版本,但 MVP 不做定时自动切换;管理员在生效点明确发布,避免 c-shared 内依赖常驻 timer。
3.4 ModelPricePolicy
ModelPricePolicy
├─ price_version_id
├─ canonical_model
├─ provider # MVP 为 openai/codex
├─ input_rate
├─ cache_read_rate
├─ cache_write_rate
├─ output_rate
├─ explicit_free
├─ long_context_policy?
├─ service_tier_policy
├─ source_refs[]
└─ notes
四段费率始终完整保存。来源没有缓存写价格时不能擅自沿用普通输入价格;只有管理员明确确认的 fallback 规则才能编译进版本。
3.5 ModelAlias
ModelAlias
├─ alias
├─ canonical_model
├─ alias_kind # request/route/upstream
├─ provider
├─ valid_from_version
└─ status
MVP 生产匹配只允许精确模型名和显式 alias,不支持任意 glob。key-billing 的 glob 实现可以参考算法,但模糊规则在新增模型后可能意外继承价格,不适合作为金额账本默认策略。
3.6 ResolvedPricePolicy
这是 Pricing 给 Billing 的唯一运行时输出:
ResolvedPricePolicy
├─ price_version_id
├─ canonical_model
├─ matched_value
├─ matched_by # upstream_model/model_alias/route_alias
├─ four_segment_rates
├─ long_context_policy
├─ effective_service_tier
├─ tier_multiplier # Ratio
├─ source_refs
└─ policy_hash
Billing 必须把这些字段复制为账单价格快照,不能只保存一个指向可变当前目录的模型名。
4. 模型解析
一次请求至少可能出现:
requested_model:用户请求的名字;route_model/model_alias:CPA 路由和别名;selected_model:选择账户后准备执行的模型;upstream_model:上游响应或 Usage 确认的实际模型。
MVP 解析顺序固定为:
- 去除已被 CPA 证明只是选项的 thinking suffix,不改变真正模型 ID;
- 若实际
upstream_model精确命中已发布模型,使用它; - 否则按类型和 provider 查找显式
ModelAlias; - 若管理员明确把 route model 发布为独立计费模型,允许精确命中;
- 多个候选同优先级冲突时返回
ambiguous_price_match; - 没有可信匹配时返回 unavailable,绝不按字符串相似度猜测。
准入时还没有最终 upstream model。此时必须锁定本次允许使用的 price_version_id 和候选政策。执行后若模型发生变化,在同一个价格版本内重新解析并结算;不能因调用期间发布了新版本而混用新价格。
GPT-5.6 Sol/Terra/Luna 必须作为不同 canonical model 分别发布。名字相近不代表价格相同,禁止通过 gpt-5.6-* 默认继承同一价格。
5. 四段 Token 价格
价格政策只定义单价;Token 是否重叠由采集/归一化模块决定。Billing 输入必须已经形成四个互斥区段:
普通输入 + 缓存读取 + 缓存写入 + 输出
Reasoning Token 若已经包含在输出中,不增加第五段价格。后台可以展示 reasoning 明细,但不能重复收费。
费率验证至少包括:
- 所有整数非负且不溢出
int64支持范围; - 四段字段不得缺失;
- 显式免费必须带确认标记和审计原因;
- 最坏 Token × 费率 × 倍率的中间结果可安全计算;
- 相同 canonical model 在同一版本中只能有一条 base policy。
6. Fast / priority 政策
Fast 是金额倍率,不是独立余额单位。GPT-5.6 Fast/priority 的当前产品政策保存为版本化 5/2,只应用一次。请求可能使用 OpenAI/Codex 的 service_tier=priority,也可能使用 Anthropic 的 speed=fast。采集层必须把两种输入和最终上游请求中的 Fast 状态归一化为一个 fast_requested 事实。
首先合并请求和响应事实:
effective_service_tier =
响应明确确认的 tier
否则请求明确指定的 tier
否则 standard
effective_service_tier=priority 或请求 speed=fast 都表示 Fast。价格政策中的 fast_pricing_enabled 决定是否应用倍率,因此 Fast 请求可以按标准价格结算。禁止同时配置:
service_tier=priority × 2.5;以及response_service_tier=priority × 2.5
并让二者命中时变成 6.25×。
cpa-usage-keeper 的固定字段规则和不可变 Snapshot 值得复用,但其 resolver 会把每个命中的字段规则连续相乘;这对一般分析倍率合理,对 Fast 请求/响应两个同义观察不合理。本项目必须先归一化为唯一 effective_service_tier。
发布 GPT-5.6 价格版本时必须用当时的 OpenAI 官方资料重新确认:支持的具体模型、倍率、生效时间以及是否有额外限制。文档不硬编码可能过期的具体基础单价。
7. 长上下文政策
LongContextPolicy
├─ threshold_input_tokens
├─ comparison # gt/gte,必须显式
├─ input_rate
├─ cache_read_rate
├─ cache_write_rate
└─ output_rate
判断输入是归一化后的总输入 Token,包含普通输入、缓存读取和缓存写入;阈值边界由 comparison 明确,不能隐藏在代码的 > 或 >= 中。
组合顺序固定为:
- 根据总输入判断是否命中长上下文;
- 选择完整四段 base rates;
- 计算四段基础金额;
- 对总基础金额应用一次 Fast/tier 倍率;
- 最终舍入为
amount_micros。
导入 models.dev 时必须保留并核对 tiers。当前 usage-keeper 的自动价格同步忽略长上下文 tiers,不能原样复制。
8. 来源优先级与发布流程
候选展示优先级:
- 管理员手工输入并说明来源;
- 项目维护的官方来源快照;
- models.dev 等外部目录;
- 无来源。
优先级只影响 review UI,不自动决定发布。标准发布流程:
fetch/import candidate
→ parse + hash
→ match canonical models
→ validate numeric/token/tier semantics
→ diff against active version
→ run golden price cases
→ administrator approve with reason
→ persist immutable version
→ atomically publish compiled Snapshot
→ emit audit + projection event
发布事务必须先完整写入版本、明细、来源引用和审计,再更新 active pointer。运行时 Catalog 使用原子指针替换完整只读 Snapshot;请求不得看见半个新版本。
回滚流程创建新版本并重复相同验证。发布失败时继续使用最后有效版本,readiness 显示 pricing_publish_failed;不得清空当前价格。
9. 持久化建议
| 表 | 用途 |
|---|---|
price_sources |
来源与抓取元信息 |
price_candidates |
未发布候选和解析诊断 |
price_versions |
不可变版本头 |
model_price_policies |
每版模型四段价格 |
model_aliases |
版本化精确别名 |
long_context_policies |
阈值与阶梯价格 |
service_tier_policies |
tier 与有理数倍率 |
price_publications |
发布、回滚和审批审计 |
active version 可以存于 app_settings,但更新必须与版本发布事务一致。旧版本不能随普通归档删除;只要账本仍引用就永久保留。
10. API 与 UI 输出
管理员需要看到:
- active version、发布时间、操作者和来源;
- 当前模型价格、alias、长上下文和 tier 政策;
- candidate 与 active 的逐字段差异;
- 使用中的未定价模型和受影响请求;
- 发布前 golden case 结果;
- 历史版本和回滚入口。
用户 API 只返回已结算金额、币种和必要说明,不返回 Token 单价、来源、规则或倍率。管理员接口路径和 DTO 见 api.md。
11. 运行时与故障策略
- Resolver 只读取内存 Snapshot,不做 SQL、网络或 host callback;
- Snapshot 编译在管理命令/安全 maintenance budget 中完成;
- 外部目录失败只影响 candidate refresh,不影响 active version;
- active version 缺失、损坏或 currency 不匹配时计费 readiness 失败,新请求 fail closed;
- 实际 Usage 无法匹配价格时保存事实并阻断后续同范围请求,不能 panic 或返回会被 CPA 忽略的 interceptor RPC error;
- 热配置不能直接改变历史或 active price content;价格发布走业务 API 和审计事务。
12. 如何参考现有项目
12.1 cpa-plugin-key-billing
优先参考:
internal/billing/pricing.go:四段 Token 价格、模型匹配和长上下文计算;internal/billing/catalog.go:models.dev 下载、大小限制、缓存和候选解析;internal/billing/admin.go:目录搜索、覆盖和价格校验;internal/billing/pricing_test.go:四段不重复和阈值测试。
可以复用算法思路,不能复制其 float64 账务表示、自动把目录当 builtin production price、模糊 glob 默认匹配或缺少 Fast 政策的行为。
12.2 cpa-usage-keeper
优先参考:
internal/pricing/catalog.go、snapshot.go:完整编译后原子发布只读快照;internal/pricing/fields.go、resolver.go:固定枚举维度和条件倍率;internal/repository/pricing*.go:价格与规则持久化;internal/service/pricing_metadata_sync.go:同步预览;internal/helper/usage_cost.go:四段费用拆分。
应吸收 Snapshot、固定字段和完整校验;不能继承动态查询重算历史费用、多条同义 tier 规则相乘、浮点账本和忽略长上下文 tiers。
参考版本:
cpa-plugin-key-billing25b534ae386f830f537cca9215cff5586e630b3a;cpa-usage-keeperd62cad3f345ae574089a14a4ac75cca023c7ead6。
13. 验收标准
- 没有已发布可信价格时请求不会免费穿透;
- GPT-5.6 Sol/Terra/Luna 独立解析且来源可追溯;
- Fast/priority 在请求、响应或两者同时报告时都只应用一次 2.5×;
- 长上下文阈值前、边界和边界后结果符合显式 comparison;
- 四段 Token 价格不重叠,Reasoning 不重复收费;
- 所有费率、倍率和最终金额确定性计算,无浮点累计漂移;
- 外部目录刷新不自动改变生产价格;
- 发布原子可见,失败保持 LKG;
- 回滚生成新版本,旧账单仍引用原版本;
- alias 冲突和未知模型明确失败,不做模糊猜测;
- 价格 Golden Tests、版本重放和溢出边界全部通过;
- 用户侧只看到金额,管理员才能查看价格计算细节。