Files
cpa-plugin/docs/modules/pricing.md
T
chuan 6a461bf1f3 feat: 添加持久化用量计费与管理面板
- 使用 SQLite 保存关联后的请求生命周期和用量记录
- 支持长上下文阶梯价格和可配置的 Fast 计费倍率
- 添加管理接口和可配置列的用量面板
- 保留失败、取消、重试和 compact 请求,便于计费核对
2026-08-14 21:08:02 +08:00

15 KiB
Raw Blame History

价格目录与计价政策模块

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_aliasCPA 路由和别名;
  • selected_model:选择账户后准备执行的模型;
  • upstream_model:上游响应或 Usage 确认的实际模型。

MVP 解析顺序固定为:

  1. 去除已被 CPA 证明只是选项的 thinking suffix,不改变真正模型 ID
  2. 若实际 upstream_model 精确命中已发布模型,使用它;
  3. 否则按类型和 provider 查找显式 ModelAlias
  4. 若管理员明确把 route model 发布为独立计费模型,允许精确命中;
  5. 多个候选同优先级冲突时返回 ambiguous_price_match
  6. 没有可信匹配时返回 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 明确,不能隐藏在代码的 >>= 中。

组合顺序固定为:

  1. 根据总输入判断是否命中长上下文;
  2. 选择完整四段 base rates
  3. 计算四段基础金额;
  4. 对总基础金额应用一次 Fast/tier 倍率;
  5. 最终舍入为 amount_micros

导入 models.dev 时必须保留并核对 tiers。当前 usage-keeper 的自动价格同步忽略长上下文 tiers,不能原样复制。

8. 来源优先级与发布流程

候选展示优先级:

  1. 管理员手工输入并说明来源;
  2. 项目维护的官方来源快照;
  3. models.dev 等外部目录;
  4. 无来源。

优先级只影响 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.gomodels.dev 下载、大小限制、缓存和候选解析;
  • internal/billing/admin.go:目录搜索、覆盖和价格校验;
  • internal/billing/pricing_test.go:四段不重复和阈值测试。

可以复用算法思路,不能复制其 float64 账务表示、自动把目录当 builtin production price、模糊 glob 默认匹配或缺少 Fast 政策的行为。

12.2 cpa-usage-keeper

优先参考:

  • internal/pricing/catalog.gosnapshot.go:完整编译后原子发布只读快照;
  • internal/pricing/fields.goresolver.go:固定枚举维度和条件倍率;
  • internal/repository/pricing*.go:价格与规则持久化;
  • internal/service/pricing_metadata_sync.go:同步预览;
  • internal/helper/usage_cost.go:四段费用拆分。

应吸收 Snapshot、固定字段和完整校验;不能继承动态查询重算历史费用、多条同义 tier 规则相乘、浮点账本和忽略长上下文 tiers。

参考版本:

  • cpa-plugin-key-billing 25b534ae386f830f537cca9215cff5586e630b3a
  • cpa-usage-keeper d62cad3f345ae574089a14a4ac75cca023c7ead6

13. 验收标准

  • 没有已发布可信价格时请求不会免费穿透;
  • GPT-5.6 Sol/Terra/Luna 独立解析且来源可追溯;
  • Fast/priority 在请求、响应或两者同时报告时都只应用一次 2.5×;
  • 长上下文阈值前、边界和边界后结果符合显式 comparison;
  • 四段 Token 价格不重叠,Reasoning 不重复收费;
  • 所有费率、倍率和最终金额确定性计算,无浮点累计漂移;
  • 外部目录刷新不自动改变生产价格;
  • 发布原子可见,失败保持 LKG
  • 回滚生成新版本,旧账单仍引用原版本;
  • alias 冲突和未知模型明确失败,不做模糊猜测;
  • 价格 Golden Tests、版本重放和溢出边界全部通过;
  • 用户侧只看到金额,管理员才能查看价格计算细节。