mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
147 lines
7.6 KiB
Markdown
147 lines
7.6 KiB
Markdown
# ADR-0172:不可变 Model Price Quote 与 Canonical Cost Settlement
|
||
|
||
- 状态:Accepted
|
||
- 日期:2026-07-27
|
||
- 关联:RFC D-12、D-13、D-157、D-160、D-161、D-162;ADR-0167、ADR-0168、ADR-0170、ADR-0171
|
||
|
||
## 背景
|
||
|
||
ADR-0170/0171 已经建立不可变 usage ledger 与 provider I/O 前的 Project quota
|
||
reservation,但费用仍有三个未封闭的权威裂缝:
|
||
|
||
- provider 返回的 `usage.costMicros` 既不可验证,也可能缺失、使用不同币种或被 adapter
|
||
错误换算;
|
||
- model policy 的人工 `maxCostMicros` 只能作为上限,不能证明调用使用了哪一版价格;
|
||
- 价格变化后,如果只保存 model 和最终费用,就无法按历史 revision 重算、审计或精确重放;
|
||
- Cluster 多副本可能在一次调用期间观察到不同价格,本机重启也可能丢失当时的计价输入;
|
||
- 为价格目录、报价和结算各拆 package/service 会放大路由设备的 importer、制品与运维成本,
|
||
却没有独立部署或权限收益。
|
||
|
||
## 决策
|
||
|
||
### 1. Policy 必须选择一个精确、不可变的价格 revision
|
||
|
||
`ModelInvocationPolicy.priceRevision` 与 provider/model 共同选择
|
||
`qinglong/model-price-catalog-entry@v1`。条目固定:
|
||
|
||
- `USD`;
|
||
- 每百万 input/output token 的整数 micro-USD rate;
|
||
- publisher time 与内容摘要;
|
||
- provider、model、price revision 的精确身份。
|
||
|
||
启用单次费用上限或 Project cost quota 时,`priceRevision` 和精确 catalog entry 都是必需
|
||
条件;缺失必须在 provider I/O 前失败关闭。无费用约束且 `priceRevision=null` 的调用继续
|
||
允许 usage cost 为 nullable,不强迫所有部署启用计价。
|
||
|
||
当前 catalog 是有界、注入式 `ModelPriceCatalogResolver`。它冻结运行时消费契约,但不
|
||
宣称已经完成数据库 catalog、签名发布、激活、撤销或管理 UI;这些运营能力必须另行建立
|
||
权限和发布协议。
|
||
|
||
### 2. Provider I/O 前生成不可变最坏情况 Quote
|
||
|
||
Gateway 在 durable admission 前生成
|
||
`qinglong/model-invocation-price-quote@v1`。Quote 绑定 invocation、Project、model policy
|
||
revision、provider/model、price revision、币种、input/output rate、catalog digest、
|
||
token 上限与独立 digest。
|
||
|
||
费用预留按 `maxTotalTokens` 和 `maxOutputTokens` 中合法且最昂贵的 input/output 分配计算,
|
||
不能用调用方估计值。每个 token 维度分别向上取整到整数 micro-USD,并用 `BigInt` 做中间
|
||
运算;越界失败关闭。Quote 的 `reservedCostMicros` 同时是单次费用 fence 和 Project
|
||
cost quota reservation 的权威输入,替代人工复制的 reservation 数值。
|
||
|
||
### 3. Provider 报价不是账单权威
|
||
|
||
provider 返回 usage 后,Gateway 只信任经过既有上限校验的 input/output/total token。
|
||
`usage.costMicros` 无论是 null、错误还是任意值,都由 Quote 重新计算的 canonical cost
|
||
替换。返回给调用方、Completion、UsageLedger、QuotaSettlement 使用同一个 canonical
|
||
usage。
|
||
|
||
`qinglong/model-invocation-price-settlement@v1` 绑定 Quote digest、Completion digest、
|
||
精确 token、canonical cost、settled time 与独立 digest。usage 缺失时不得创建零费用
|
||
Settlement;unknown outcome 保留 Quote 和 quota reservation,但不伪造价格结算。
|
||
|
||
### 4. Quote 与 Settlement 进入既有原子事务
|
||
|
||
准入事务原子提交:
|
||
|
||
1. `StepRun ready → running`、Run/Event/Mutation;
|
||
2. ModelInvocationStart;
|
||
3. PriceQuote;
|
||
4. 可选 QuotaReservation。
|
||
|
||
完成事务原子提交:
|
||
|
||
1. terminal StepRun/Run/Event/Mutation;
|
||
2. Completion;
|
||
3. 可选 UsageLedger;
|
||
4. usage 存在时的 PriceSettlement;
|
||
5. 可选 QuotaSettlement。
|
||
|
||
exact replay 必须复验全部预期事实;Quote/Settlement 缺失、意外存在、digest 或字段漂移均
|
||
失败关闭。持久化 JSON 的一致性比较按字段进行,不依赖 SQLite TEXT 或 PostgreSQL JSONB
|
||
的对象键顺序。
|
||
|
||
### 5. 9004 保持可选、append-only 和双方言一致
|
||
|
||
不改写 9001/9002/9003。新增:
|
||
|
||
- SQLite `9004-ai-model-pricing-snapshots`;
|
||
- PostgreSQL `pg-9004-ai-model-pricing-snapshots`;
|
||
- PriceQuote/PriceSettlement 两张双方言表。
|
||
|
||
PostgreSQL `ql3_runtime` 只有 `SELECT, INSERT`,没有 `UPDATE, DELETE`;其它业务角色与
|
||
PUBLIC 不获得权限。Quote 与 Settlement 不是第二状态机,StepRun 仍是执行权威。
|
||
|
||
### 6. 不新增 package、依赖或后台服务
|
||
|
||
价格 contract、resolver 和计算留在已有 `@qinglong/ai/pricing`;Gateway、coordinator、
|
||
repository 与 Profile 复用现有 AI package。没有新增 workspace package或第三方依赖。
|
||
|
||
AI 禁用时不解析 catalog、不执行 9001–9004、不加载 provider/credential/storage,也不
|
||
创建 timer。edge/standalone 只在显式启用 AI 时把该模块纳入制品;Cluster 可注入未来的
|
||
数据库/配置发布 resolver,但不能把价格管理 authority 放进常驻 runtime。
|
||
|
||
本 ADR 不开放 HTTP/MCP/UI,也不把静态 resolver 解释为产品级价格目录管理。
|
||
|
||
## 被否决方案
|
||
|
||
1. 直接信任 provider `usage.costMicros`:来源、币种、revision 与舍入不可验证。
|
||
2. 只保存最终费用:价格变更后不能历史重算或证明计价输入。
|
||
3. 调用完成后读取最新价格:一次调用可能跨 revision,Cluster 副本会产生不同结果。
|
||
4. 用浮点美元计价:序列化与舍入会产生跨运行时漂移。
|
||
5. 把 unknown usage 结算为零:允许预算绕过并伪造账单事实。
|
||
6. 为 pricing 新建 workspace package/service:没有独立部署、依赖或权限边界,只会增加
|
||
路由器制品、lockfile importer 和 CI 成本。
|
||
7. 立即把 catalog 管理写入 runtime 表:会把运营写 authority 与调用 runtime 合并。
|
||
|
||
## 当前验证
|
||
|
||
- `@qinglong/ai`:70 项测试,69 pass、1 条 PostgreSQL 条件 skip;真实 PostgreSQL 18
|
||
migration/runtime 分角色集成另 1 pass;
|
||
- SQLite 覆盖 exact revision、最坏情况 Quote、provider cost 覆盖、PriceSettlement、
|
||
quota 原子提交、exact replay,以及 priced unknown recovery 保留 Quote/全额 reservation
|
||
且不创建 Settlement;
|
||
- PostgreSQL 真实集成覆盖 JSONB 回读、9004 DDL/ACL、Quote/Completion/Ledger/
|
||
PriceSettlement/QuotaSettlement 同一事务;
|
||
- SQLite 9004 checksum 为
|
||
`572e37d2f44df43a50b51a07c1b4b0bb87fbb22e9cafbd3421ec7ab250036951`;
|
||
PostgreSQL 9004 checksum 为
|
||
`d38b12c2640fdd9fe21dc43a4743fb3480c988fa0a87e210fd81074d87569d2f`;
|
||
- QL3 22 个 package importer 的完整拓扑 build/test 退出 0;dependency audit 覆盖
|
||
`@qinglong/ai` 16 个源码文件且 `findings=[]`;
|
||
- edge-ai 为 4,363,769 bytes/512 files/41 loaded modules,standalone-ai 为
|
||
4,363,841 bytes/512 files/41 modules,均低于 5 MiB/640 files;disabled AI 只加载
|
||
1 个模块、RSS 增量 425,984 bytes,三种 Profile 的 storage/provider loader 均为 0;
|
||
- PostgreSQL 18.4 arm64 physical HA 在 timeline 1→2 promotion 前后精确复验八张
|
||
`ql3_ai` 表、9001–9004 history/checksum 和八表 runtime append-only ACL;
|
||
`optionalAiFeatureSchemaSurvivesPromotion=true` 且总 `passed=true`。
|
||
|
||
## 后续门禁
|
||
|
||
1. 建立签名或受权限保护的 durable catalog publisher、activation/revoke 与配置 ceremony;
|
||
2. 明确非 USD 币种、汇率 revision、cached-input/批处理等 provider-specific 计价维度;
|
||
3. 增加 Quote/Settlement 数据行级 COMMIT-response-loss、promotion 与并发 revision 切换
|
||
fault,而不只验证 schema promotion;
|
||
4. 完成认证、Project Policy、rate limit、低敏 audit 的 pricing/usage/quota API、CLI/UI;
|
||
5. 完成不可变 rollup、coverage receipt、retention 和磁盘耗尽证据。
|