docs: fold round-2 review fixes into completion verdict design

This commit is contained in:
2026-10-14 22:46:56 -04:00
parent 95104af2a5
commit 607523ef5c
@@ -7,7 +7,7 @@ date: 2026-10-14
# #30/#31 统一完成判定设计
> 状态: 方向已获人类批准(2026-10-14,四项取舍与五个拍板点按推荐);人类授权"Claude 与独立 subagent 确认无误后直接执行",不再走人类设计审阅。第一轮独立审查 3 阻塞 + 9 应修已逐条核实并修订(见 §11)。
> 状态: 方向已获人类批准(2026-10-14,四项取舍与五个拍板点按推荐);人类授权"Claude 与独立 subagent 确认无误后直接执行",不再走人类设计审阅。两轮独立审查已闭合(R1 3 阻塞 + 9 应修;R2 通过,2 应修已并入),见 §11。
> 基线: `22609c9`(1.3.9);目标版本 **1.3.10**;分支 `fix/issue30-31-completion-verdict`。
> 需求: [#30](https://gitea.iomgaa.online/iomgaa/PolyGateway/issues/30)、[#31](https://gitea.iomgaa.online/iomgaa/PolyGateway/issues/31)。
> 修订对象: [1.3.9 保真设计](2026-10-01-issue29-openai-fidelity-design.md) §4.1/§4.2/§5/§6、ARCHITECTURE §6.2/§7/§14。
@@ -76,7 +76,7 @@ date: 2026-10-14
| truncated | `length` |
| filtered | `content_filter`、`sensitive` |
| upstream_error | `network_error`、`error` |
| unknown | 其余非空字符串: 按 natural 交付、**不可缓存**、按源+值告警一次 |
| unknown | 其余非空字符串: 按 natural 交付、**不可缓存**、按源+值告警一次(文案写明"该值不缓存",便于下游定位命中率下降);`end_turn`/`stop_sequence` 等无证据不预先归类 |
| absent | None |
### 4.2 第一步: 流完整性(仅流式)
@@ -88,7 +88,7 @@ date: 2026-10-14
| 干净 EOF、无 DONE、finish 已见、finish 帧**同帧或之后**收到的 usage 被 `_resolve_usage` 判为 measured | `eof_after_finish`,usage 记 measured(不降级) |
| 干净 EOF、其余情形 | 截断: openai → Transient `missing_done`(仍不打捞);text → 原 `_check_done` 语义(零产物 early_eof、`retry` 抛、`salvage` 记 `salvaged`) |
聚合器每尝试记录 `finish_seen` 与 `usage_after_finish`: 收到非 null usage 时,若 `finish_seen` 已真或同帧带 finish,则以该 usage 置后者;usage-only 帧(`choices: []`)在 finish 之后到达是常规形态,正确置位。状态随重试/对冲每尝试新建。
聚合器每尝试记录 `finish_seen` 与 `usage_after_finish`: 收到非 null usage 时,若 `finish_seen` 已真或同帧带 finish,则置后者为真;是否 measured 以**最后一个**非 null usage(与计费所用 `sink["usage"]` 同一对象)在流结束时判定;usage-only 帧(`choices: []`)在 finish 之后到达是常规形态,正确置位。状态随重试/对冲每尝试新建。
诚实边界: 中转站在上游出错后主动正常结束响应,与真完整在传输层不可区分;防线是"finish 必须存在、不属 upstream_error、且其后有 measured usage",文档如实写明。
@@ -118,13 +118,13 @@ ResultInvalid 理由: `retry.py:409-414` 对其"不重试、不换源、熔断
### 4.4 文本模式 reasoning 别名
投影 `thinking`: 本次尝试出现过 `reasoning_content` 则只用它,否则用 `reasoning`。保真 `completion.message` 保留上游原键不改名。两模式活性、产物摘要、`thinking_observation` 均基于该投影。
投影 `thinking`: 本次尝试出现过**非空** `reasoning_content` 则只用它,否则用 `reasoning`。保真 `completion.message` 保留上游原键不改名。两模式活性、产物摘要、`thinking_observation` 均基于该投影。
## 5. 字段合并与活性
### 5.1 null 与已知字段
null 规则**优先于**已知字段类型校验: 任意层任意键值为 null 视为缺省;键从未出现时保留 `key: null`(含 `tool_calls`、`refusal`、`role`、`function.arguments`)。保真成功仍须最终 role=assistant(全程只有 `role: null` 即缺 role,照旧失败)。其余已知字段(content/reasoning_content/role/tool_calls 及工具 id/type/function.name/arguments)非 null 时语义逐字沿用 1.3.9。
null 规则**优先于**已知字段类型校验: chunk、choice、delta、工具条目、function 五层任意键值为 null 视为缺省,数组中的 null 元素(如 `tool_calls: [null]`)跳过;键从未出现时保留 `key: null`(含 `tool_calls`、`refusal`、`role`、`function.arguments`)。保真成功仍须最终 role=assistant(全程只有 `role: null` 即缺 role,照旧失败)。其余已知字段(content/reasoning_content/role/tool_calls 及工具 id/type/function.name/arguments)非 null 时语义逐字沿用 1.3.9。
### 5.2 未知字段(delta、`tool_calls[]` 条目、`function` 三层统一)
@@ -142,7 +142,7 @@ null 规则**优先于**已知字段类型校验: 任意层任意键值为 null
### 5.3 活性
已知字段活性逐字沿用 1.3.9(非空 content/reasoning_content、工具 name/arguments 非空增量、首次建立工具身份)。未知字段仅当合并后可见字符串内容增长、且键不属标签集合时算进展(`reasoning` 由此计入);同帧只记一次。文本模式同口径。README 中"增量 chunk = content 或 reasoning_content"的 stall/TTFT 口径表述同步更新为本节口径。
已知字段活性逐字沿用 1.3.9(非空 content/reasoning_content、工具 name/arguments 非空增量、首次建立工具身份)。未知字段仅当本帧在非标签键上并入非空字符串片段(含追加型数组中的字符串元素)时算进展(`reasoning` 由此计入);同帧只记一次。残余风险如实登记: 上游每帧重复下发的非标签字符串(如 `status`)会被拼接并持续刷新看门狗。文本模式同口径。README 中"增量 chunk = content 或 reasoning_content"的 stall/TTFT 口径表述同步更新为本节口径。
### 5.4 告警节流
@@ -165,7 +165,7 @@ null 规则**优先于**已知字段类型校验: 任意层任意键值为 null
| 读 | `_is_cacheable` 在命中覆盖(`cache.py:224` `fields.update`)**之前**、对存储原值判定;不可缓存 → miss + warning |
| 读: 非法 `stream_end` | 不在 Literal 域内 → miss + warning(关乎内容完整性,故与 `_coerce_observation` 的宽容方向相反) |
| 读: 顶层无 finish_reason | 回退 `completion.finish_reason`(1.3.9 保真 length 条目因此可识别);文本旧条目两字段缺省 → None → 仍可命中 |
| 命中返回 | `stream_end=None`;`finish_reason` 保留存储值 |
| 命中返回 | `stream_end=None`;`finish_reason` 取判定所用值(顶层缺省时为回退值),非字符串视 miss |
| 序列化 | `_RESPONSE_FIELDS` 由 `dataclasses.fields` 自动派生,无需改动 |
旧 salvage/length 文本污染无法辨认: CHANGELOG 继续要求受影响集合迁移 namespace/salt。
@@ -243,3 +243,5 @@ null 规则**优先于**已知字段类型校验: 任意层任意键值为 null
| R1 | S5 无产物 + length 与 #30 原文冲突 | 采纳(人类授权代决): 保真交付、文本 ResultInvalid、stop/absent 仍 Transient |
| R1 | S6-S9 缓存顺序、遥测落点、原因码、null 优先 | 采纳: §6/§7/§4.3/§5.1 |
| R1 | 建议 1-7 | 采纳 1/2/3/5/6/7;建议 4 不标不可缓存(上游确定性,非毒化) |
| R2 | 结论通过;N1 measured 以最后 usage 判定、N2 非空 reasoning_content | 采纳: §4.2/§4.4 |
| R2 | 建议 1-4: 命中回退值、未知 finish 告警文案、数组字符串活性与残余风险、null 五层与数组 null 元素 | 全部采纳: §6/§4.1/§5.3/§5.1 |