docs: clear review residue from the est_tokens design
The value-domain table still listed the OCR endpoint as a producer of "unavailable" while section 3.3 had just decided to keep its "measured" label -- an implementer following the normative table would have redone the change that was explicitly dropped, and the guard test would fail. Also corrects the derivation call-site count to five, qualifies the retained conservative settlement to the non-dead transient branch only, and pins the gap metric to "AND cache_hit = false" so cache hits, which carry cost 0.0 by design, do not inflate it.
This commit is contained in:
@@ -34,7 +34,7 @@ CHS 原版 `config.py:55` 把它定义为"须 ≥ 最坏情形 token"——按
|
|||||||
|
|
||||||
| 方案 | 做法 | 权衡 |
|
| 方案 | 做法 | 权衡 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **A(选定)** | 记 `0/0`,`usage_source` 扩一个 `unavailable`,cost 记 NULL | 缺数据可被统计:`SUM(cost)` 跳过 NULL,`COUNT(*) WHERE usage_source='unavailable'` 能量化账的缺口。代价:公共值域变更,需进 CHANGELOG |
|
| **A(选定)** | 记 `0/0`,`usage_source` 扩一个 `unavailable`,cost 记 NULL | 缺数据可被统计:`SUM(cost)` 跳过 NULL,`COUNT(*) WHERE usage_source='unavailable' AND cache_hit = false` 能量化账的缺口(**必须带 `cache_hit` 限定**:按 §3.2 #5 的裁决,缓存命中行可以既是 `unavailable` 又有 `cost=0.0`,它们本无账目缺口,不加限定就会灌水——与 §3.3 剔出 OCR 用的是同一把尺子)。代价:公共值域变更,需进 CHANGELOG,且该查询口径要一并写进 wiki(§8) |
|
||||||
| B | 记 `0/0`,沿用 `estimated` | 改动最小(等于把 `est_tokens>0` 路径统一到 `est_tokens=0` 的现状行为)。**否决**:cost 算出 `0.0`,"免费"与"未知"在数据上不可区分,缺口不可量化 |
|
| B | 记 `0/0`,沿用 `estimated` | 改动最小(等于把 `est_tokens>0` 路径统一到 `est_tokens=0` 的现状行为)。**否决**:cost 算出 `0.0`,"免费"与"未知"在数据上不可区分,缺口不可量化 |
|
||||||
| C | 保留 est 兜底,只修 `prompt`/`completion` 分配比例 | 保住 CHS"保守计量"意图。**否决**:比例是又一个没有正确取值的魔数,且未触及"拿上界当实测"这个根因,仍高估约 9 倍 |
|
| C | 保留 est 兜底,只修 `prompt`/`completion` 分配比例 | 保住 CHS"保守计量"意图。**否决**:比例是又一个没有正确取值的魔数,且未触及"拿上界当实测"这个根因,仍高估约 9 倍 |
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ CHS 原版 `config.py:55` 把它定义为"须 ≥ 最坏情形 token"——按
|
|||||||
|
|
||||||
| 方案 | 权衡 |
|
| 方案 | 权衡 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **`SourceConfig.effective_est_tokens()`(选定)** | 纯方法只读自身字段,落 `types.py` 内核不违反依赖铁律;零装配变更、零端口变更;三个调用点(`QuotaGate`、retry/embedding 失败结算)共用一份 |
|
| **`SourceConfig.effective_est_tokens()`(选定)** | 纯方法只读自身字段,落 `types.py` 内核不违反依赖铁律;零装配变更、零端口变更;5 个调用点(`QuotaGate` 入场 + retry/embedding 各自的成功侧与失败侧结算)共用一份 |
|
||||||
| 注入 `GlobalLimits` 到 `QuotaGate`,派生取全局与单源 tpm 的较紧者 | 能覆盖"单源 `tpm=0` 而全局 `tpm>0`"的场景。**否决**:需改三处装配(`retry.py:186`/`embedding.py:116`/`ocr.py:119`),且它修的是一个**既有**缺口(见 §7),超出本任务范围 |
|
| 注入 `GlobalLimits` 到 `QuotaGate`,派生取全局与单源 tpm 的较紧者 | 能覆盖"单源 `tpm=0` 而全局 `tpm>0`"的场景。**否决**:需改三处装配(`retry.py:186`/`embedding.py:116`/`ocr.py:119`),且它修的是一个**既有**缺口(见 §7),超出本任务范围 |
|
||||||
| 派生下沉到两个 limiter 后端 | **否决**:`try_acquire(source_key, est_tokens)` 的入参会变成谎言(后端忽略它),且逻辑要写两遍,违反 D3"语义契约只有一份"与 P7"决策与存储分离" |
|
| 派生下沉到两个 limiter 后端 | **否决**:`try_acquire(source_key, est_tokens)` 的入参会变成谎言(后端忽略它),且逻辑要写两遍,违反 D3"语义契约只有一份"与 P7"决策与存储分离" |
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ CHS 原版 `config.py:55` 把它定义为"须 ≥ 最坏情形 token"——按
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `measured` | usage 帧完整可信 | 正常路径 | 按 token 换算 |
|
| `measured` | usage 帧完整可信 | 正常路径 | 按 token 换算 |
|
||||||
| `estimated` | 有实测数字但可信度降级 | 打捞路径(收到 usage 帧但流被截断) | 按 token 换算 |
|
| `estimated` | 有实测数字但可信度降级 | 打捞路径(收到 usage 帧但流被截断) | 按 token 换算 |
|
||||||
| `unavailable` | 用量信息不可得 | usage 帧缺失、失败尝试、终态失败、OCR 端点 | **NULL** |
|
| `unavailable` | 用量信息不可得 | usage 帧缺失、失败尝试、终态失败 | **NULL**(缓存命中行例外,见 §3.2 #5) |
|
||||||
|
|
||||||
`estimated` 保留且有真实生产者(打捞),同时保证历史库里既有的 `estimated` 行读兼容。
|
`estimated` 保留且有真实生产者(打捞),同时保证历史库里既有的 `estimated` 行读兼容。
|
||||||
|
|
||||||
@@ -84,12 +84,12 @@ CHS 原版 `config.py:55` 把它定义为"须 ≥ 最坏情形 token"——按
|
|||||||
| 7 | `middleware/ratelimit.py:26` | `source.est_tokens` → `source.effective_est_tokens()` |
|
| 7 | `middleware/ratelimit.py:26` | `source.est_tokens` → `source.effective_est_tokens()` |
|
||||||
| 8 | `retry.py:370`、`embedding.py:294` | **失败侧**保守结算改用 `effective_est_tokens()`。必须同改:预扣派生值而结算退 `est_tokens=0` 会让 `delta` 为负、退掉全部押金,丢掉"失败可能已被计费"的保守意图 |
|
| 8 | `retry.py:370`、`embedding.py:294` | **失败侧**保守结算改用 `effective_est_tokens()`。必须同改:预扣派生值而结算退 `est_tokens=0` 会让 `delta` 为负、退掉全部押金,丢掉"失败可能已被计费"的保守意图 |
|
||||||
| 9 | `retry.py:338`、`embedding.py:271` | **成功侧**结算:`usage_source == "unavailable"` 时按 `effective_est_tokens()` 结算,而非 `prompt+completion`(此时恒为 0)。**这条是保持既有行为、不是新增保守**:改前 `_resolve_usage` 恰好返回 `est_tokens`,使 `actual == 预扣量`、`delta == 0`、押金留存;#3 把它改成 `(0, 0)` 后若不同改,成功调用的押金会被整笔退回,对"从不返回 usage 帧的网关源"构成系统性 TPM 计量失效——闸门退化成进门即放行、出门即清账,正是降级方向铁律要防的击穿 |
|
| 9 | `retry.py:338`、`embedding.py:271` | **成功侧**结算:`usage_source == "unavailable"` 时按 `effective_est_tokens()` 结算,而非 `prompt+completion`(此时恒为 0)。**这条是保持既有行为、不是新增保守**:改前 `_resolve_usage` 恰好返回 `est_tokens`,使 `actual == 预扣量`、`delta == 0`、押金留存;#3 把它改成 `(0, 0)` 后若不同改,成功调用的押金会被整笔退回,对"从不返回 usage 帧的网关源"构成系统性 TPM 计量失效——闸门退化成进门即放行、出门即清账,正是降级方向铁律要防的击穿 |
|
||||||
| 10 | `embedding.py:383,390` | 二值合并扩为三态:任一批 `unavailable` → 整体 `unavailable`;否则任一 `estimated` → `estimated`;否则 `measured` |
|
| 10 | `embedding.py:383,390` | 二值合并扩为三态:任一批 `unavailable` → 整体 `unavailable`;否则任一 `estimated` → `estimated`;否则 `measured`。同步更新 `types.py:273` 的行内注释 `# measured | estimated`,内核里不留与三态矛盾的注释 |
|
||||||
| 11 | `embedding.py:397` `_total_cost` | 存在 `unavailable` 批时整体 cost 记 NULL(逐批求和会给出一个偏低却看似有效的金额) |
|
| 11 | `embedding.py:397` `_total_cost` | 存在 `unavailable` 批时整体 cost 记 NULL(逐批求和会给出一个偏低却看似有效的金额) |
|
||||||
|
|
||||||
### 3.3 明确不改的
|
### 3.3 明确不改的
|
||||||
|
|
||||||
`retry.py`/`embedding.py` 失败路径按预扣量做**限流**结算的行为保留——那是限流语境,保守方向正确(失败请求可能已被网关计费),且该值只流向 `_settle_and_release`,不进遥测。`SourceConfig.est_tokens` 字段与 `{SCOPE}__{PROVIDER}__{N}__EST_TOKENS` 环境键**保留不删不改名**(迁移兼容硬约束,ARCHITECTURE §5.1)。`RateLimiter` 端口签名不变。
|
**非 dead 的瞬时失败路径**(`retry.py:369` 的 `if not dead` 分支)按预扣量做**限流**结算的行为保留——那是限流语境,保守方向正确(失败请求可能已被网关计费),且该值只流向 `_settle_and_release`,不进遥测。其余三条失败分支(`RequestRejectedError`/`ResultInvalidError`/`SourceDeadError`)的 `actual` 停在初值 0(`retry.py:329`),属既有行为,本次**不动**——#8 已把行号钉死,实现时不要顺手把这三条也改成保守结算。`SourceConfig.est_tokens` 字段与 `{SCOPE}__{PROVIDER}__{N}__EST_TOKENS` 环境键**保留不删不改名**(迁移兼容硬约束,ARCHITECTURE §5.1)。`RateLimiter` 端口签名不变。
|
||||||
|
|
||||||
**`ocr.py:411` 的 `usage_source="measured"` 保留不改**(初稿曾列为改动项,独立审查后剔出)。库既有立场是 OCR 的 0 token 属**事实**而非未知——`types.py:51` "token 用量;OCR 等无计费调用填 0"、`ocr.py:9` "settle 恒为 0(OCR 无 token 计费)"——故 `measured` 是准确陈述。改成 `unavailable` 还会反噬 §2.1 的核心度量:`COUNT(*) WHERE usage_source='unavailable'` 本用于量化账目缺口,灌进本无缺口的 OCR 行就失去意义。
|
**`ocr.py:411` 的 `usage_source="measured"` 保留不改**(初稿曾列为改动项,独立审查后剔出)。库既有立场是 OCR 的 0 token 属**事实**而非未知——`types.py:51` "token 用量;OCR 等无计费调用填 0"、`ocr.py:9` "settle 恒为 0(OCR 无 token 计费)"——故 `measured` 是准确陈述。改成 `unavailable` 还会反噬 §2.1 的核心度量:`COUNT(*) WHERE usage_source='unavailable'` 本用于量化账目缺口,灌进本无缺口的 OCR 行就失去意义。
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ CHS 原版 `config.py:55` 把它定义为"须 ≥ 最坏情形 token"——按
|
|||||||
|
|
||||||
否决 issue 建议的 p90 自估,主论据是 **`TelemetryRecorder` 目前是纯只写端口,自估需要新增读接口并强制所有后端(含 `none`)实现**,公共 API 扩张远大于它要省掉的一个可选字段,且尚无实测证据表明派生默认值不够用(§8)。初稿曾论证"那会把两条方向相反的降级铁律焊在一起",此论据经审查后**撤回**:p90 方案完全可以在遥测读失败时回退到纯派生值,限流侧仍能保持 fail-closed,故并非必然冲突。结论不变,理由收窄。
|
否决 issue 建议的 p90 自估,主论据是 **`TelemetryRecorder` 目前是纯只写端口,自估需要新增读接口并强制所有后端(含 `none`)实现**,公共 API 扩张远大于它要省掉的一个可选字段,且尚无实测证据表明派生默认值不够用(§8)。初稿曾论证"那会把两条方向相反的降级铁律焊在一起",此论据经审查后**撤回**:p90 方案完全可以在遥测读失败时回退到纯派生值,限流侧仍能保持 fail-closed,故并非必然冲突。结论不变,理由收窄。
|
||||||
|
|
||||||
**幂等与重复**: `Permit.settle()`/`release()` 的幂等 flag 语义不变。#8 使预扣与结算取自同一派生函数,同一请求重复结算仍是 no-op。
|
**幂等与重复**: `Permit.settle()`/`release()` 的幂等 flag 语义不变。#8 与 #9 使预扣与结算取自同一派生函数,同一请求重复结算仍是 no-op。
|
||||||
|
|
||||||
**持久化与原子性**: 无 DDL 变更(两 schema 的 `cost` 列已可空);无新增落盘点;Redis Lua 脚本不改(仍接收调用方算好的 est)。历史数据不迁移:旧行的 `estimated` 语义在新值域中依然合法可读。
|
**持久化与原子性**: 无 DDL 变更(两 schema 的 `cost` 列已可空);无新增落盘点;Redis Lua 脚本不改(仍接收调用方算好的 est)。历史数据不迁移:旧行的 `estimated` 语义在新值域中依然合法可读。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user