docs: document net generation time and mid-stream stall hedging
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
---
|
||||
type: finding
|
||||
node_id: finding:2026-09-11-streaming-stall-validation
|
||||
title: "1.3.8 流式中段沉默:净口径与中段对冲验证报告"
|
||||
date: 2026-09-14
|
||||
---
|
||||
|
||||
# 1.3.8 流式中段沉默:净口径与中段对冲验证报告
|
||||
|
||||
> 计划:`research-wiki/plans/2026-09-11-streaming-stall.md`;设计:`research-wiki/designs/2026-09-11-streaming-stall-design.md`(人类已批准 + 独立审查补遗 B1-B4/N1-N9 全处置)。
|
||||
> 分支 `feature/1.3.8-streaming-stall`;基线 main `a28d944`(1.3.7)+ 设计/计划提交 `97de410`/`6f3a620`;代码提交 `901a193`(T1)/`53bcf43`(T2)/`e0b0be0`(T3)/`2ac05a2`(T4),文档提交见本文件 git 历史。
|
||||
> 所有命令在 `PolyGateway` conda 环境执行,未接管道(退出码不失真)。
|
||||
|
||||
## 1. 红绿证据索引
|
||||
|
||||
证据目录 `tests/outputs/138/{t1,t2,t3,t4}`(不提交,本地留存)。与 137 惯例不同,138 日志**未逐文件落 `EXIT_CODE=` 行**;退出码由日志内 `conda ... failed` ERROR 行(红)与 pytest 汇总行(绿)佐证,如实登记。
|
||||
|
||||
| 任务 | 相位 | 证据文件 | 结果与失败形态 |
|
||||
| --- | --- | --- | --- |
|
||||
| T1 端口事件 + stall 原语 | 红(批次 A) | `t1/red-batch-a.log` | exit≠0:收集期 `ImportError: cannot import name '_stall_from_chunk_times'`(纯函数不存在,非断言值不符) |
|
||||
| T1 | 绿(批次 A) | `t1/green-batch-a.log` | 260 passed(test_openai_compat + test_types) |
|
||||
| T1 | 绿(unit 全套) | `t1/green-unit.log` | 1602 passed,既有断言一行未改 |
|
||||
| T2 净口径记账 | 红(批次 B) | `t2/red-batch-b.log` | exit≠0:6 failed——`CallStats`/`_CallContext` 字段缺失、`generation_ms` 仍得墙钟值、两败轮归因未置 None |
|
||||
| T2 | 绿(批次 B) | `t2/green-batch-b.log` | 40 passed |
|
||||
| T2 | 绿(unit / unit+contracts) | `t2/green-unit.log` / `t2/green-unit-contracts.log` | 1608 passed / 1669 passed, 17 skipped |
|
||||
| T3 遥测两列 | 红(批次 C) | `t3/red-batch-c.log` | exit≠0:14 failed, 224 passed——24 处 AssertionError(冻结清单先扩即红:列数 36≠38、列序末两位、DDL 首现顺序、占位符数)+ 装配闸/落行断言未实现形态 |
|
||||
| T3 | 绿(批次 C) | `t3/green-batch-c.log` | 238 passed |
|
||||
| T3 | 绿(unit+contracts) | `t3/green-unit.log` | 1673 passed, 17 skipped |
|
||||
| T4 中段对冲 + 配置守卫 | 红(批次 D) | `t4/red-batch-d.log` | exit≠0:16 failed, 153 passed——9×`TypeError: RetryMW.__init__() got an unexpected keyword argument 'stall_after_s'`、2×`AttributeError: 'GatewaySettings' object has no attribute ...`、键未识时 `STREAM` 段被当源(`['qwen_1','stream_1'] == ['qwen_1']`)、守卫未抛/默认值未生效断言,均为未实现形态 |
|
||||
| T4 | 绿(批次 D,首轮) | `t4/green-batch-d-first.log` | 169 passed(与红相位 16+153 总量一致) |
|
||||
| T4 | 绿(unit / contracts) | `t4/green-unit.log` / `t4/green-contracts.log` | 1688 passed, 17 skipped / 61 passed, 17 skipped(批次 E 默认关闭回归门:既有语义断言一行未改,例外仅为计划登记的签名/清单同步) |
|
||||
| T4 | lint | `t4/lint.log` | `make lint` 通过,ruff 全绿,import-linter Contracts: 1 kept, 0 broken |
|
||||
| T1 期 ruff 先红 | 红 | `t1/ruff.log` | 1 error(`strict=` 缺显式值),T2 起 `ruff.log` 转 All checks passed |
|
||||
|
||||
## 2. 批次与提交映射
|
||||
|
||||
| 提交 | 计划任务 | 覆盖批次(计划 §5) | 关键断言 |
|
||||
| --- | --- | --- | --- |
|
||||
| `901a193` | T1 观测原语与端口契约 | A | 流式每增量 chunk 置位 content_event;reasoning chunk 计活性;非流式永不置位;None 不观测行为不变;漏传必填 kw 即 TypeError;stall 纯函数(超阈累计/恰等不减/<2 返回 None/尾部不计/空帧不计时);`SourceConfig.stall_threshold_ms` 默认 1000 且 0/负/bool 拒 |
|
||||
| `53bcf43` | T2 净口径记账 | B | 两字段默认 None 旧构造不炸;`generation_ms == wall − stall` 且还原公式成立;clamp 负值归零;stall None 不退减;两败轮归因 None |
|
||||
| `e0b0be0` | T3 遥测两列 | C | 冻结清单 38 列与列序/DDL/占位符同源;SQLite 落行两列恰为传入值;auto_migrate 幂等补列(二次初始化);manual 档点名两条新 ALTER;取消输家行 stall 恒 None;装配闸三负例照旧绿 |
|
||||
| `2ac05a2` | T4 中段对冲 + 守卫 | D、E | 首 token 后沉默触发对冲且赢家净口径;同 pass 复检防误触发;四格独立矩阵;仅 stall+非流式退单路;看门狗掐死主路对冲路存活且记账照常;两败计一次预算;stall 等待中外部取消穿透无残留;对冲路 content_event 恒 None;阈值三层可调;STREAM 保留段;四守卫(有效最小值/deadline 冲突 ValueError、单源 warning、Embedding/OCR B4 warning) |
|
||||
|
||||
## 3. 实测核对(文档承诺 vs 运行实测,T5 交付时本会话复核)
|
||||
|
||||
| 承诺 | 核对方式 | 实测结果 |
|
||||
| --- | --- | --- |
|
||||
| `CallStats` 两新字段默认值 | `CallStats(logical_call_id='x', attempts=1, total_latency_ms=5)` 仅旧参构造 | `generation_ms=0`、`hedges=0`、`hedge_won=False`、`stall_ms=None`、`stall_count=None`,构造不炸 |
|
||||
| 协议字段数 | `inspect.signature(TelemetryRecorder.record_llm_call)` | 38 参(36 → 38,含 self 不计) |
|
||||
| 遥测列 | `telemetry/schema.py::COLUMNS` | `len == 38`,末两位 `('stall_ms', 'stall_count')` |
|
||||
| 两键缺省与三层 | `GatewaySettings.from_env(env=...)` 注入单源最小 env | 不设两键 → `stall_threshold_ms=1000`、`hedge_stall_after_s=None`,且已分发进 SourceConfig(1000);设 `LLM__STREAM__STALL_THRESHOLD_MS=250`/`LLM__HEDGE__STALL_AFTER_S=8` → 250/8.0 |
|
||||
| 值域校验落点 | `dataclasses.replace(src, stall_threshold_ms=0)` | `ValueError: SourceConfig.stall_threshold_ms 必须是正整数`(replace 触发 `__post_init__` 重跑,校验只此一处) |
|
||||
| 版本号不动 | `pyproject.toml` 与 `src/polygateway/__init__.py` | 两处均 1.3.7,本计划不 bump、不 tag、不发布 |
|
||||
|
||||
## 4. 验收命令与退出码(T1–T4 各卡)
|
||||
|
||||
| 任务 | 验收命令(均 `conda run -n PolyGateway`,禁接管道) | 结果 |
|
||||
| --- | --- | --- |
|
||||
| T1 | `pytest tests/unit/test_openai_compat.py tests/unit/test_types.py -q` → `pytest tests/unit -q` | 260 passed → 1602 passed,exit 0 |
|
||||
| T2 | `pytest tests/unit/test_types.py tests/unit/test_retry.py -q` → `pytest tests/unit tests/contracts -q` | 批次 40 passed → 1669 passed, 17 skipped,exit 0 |
|
||||
| T3 | `pytest tests/unit/test_telemetry.py tests/unit/test_ports.py tests/unit/test_hedge.py -q` → `pytest tests/unit tests/contracts -q` | 238 passed → 1673 passed, 17 skipped,exit 0 |
|
||||
| T4 | `pytest tests/unit/test_stall_hedge.py tests/unit/test_config.py -q` → `pytest tests/unit tests/contracts -q` → `make lint` | 169 passed → 1688+61 passed,exit 0;lint 通过 |
|
||||
|
||||
合并前验收门(计划 §6:全套件 + slow 交集子集 + 整分支审查 + verifier)**不在 T5 范围**,随合并/发布流程执行;结果届时补记本节或另文。
|
||||
|
||||
## 5. 豁免索引(未跑项与去向)
|
||||
|
||||
| 未跑项 | 理由 | 去向 |
|
||||
| --- | --- | --- |
|
||||
| PG 实库遥测迁移用例(`tests/integration/test_postgres_telemetry.py`) | 执行期无沙箱可跑(计划 §7 阻塞矩阵已登记此情形);批次 C 的 SQLite 幂等用例(二次初始化)保留未删,PG 侧 `_EXPECTED_COLUMNS` 冻结清单已同步扩展 | **发布门 slow 交集补跑**(CLAUDE.md §4.4.1 第 4 步;本 diff 触及 telemetry schema,PG 实库形态在交集内) |
|
||||
| `tests/e2e/` 与 Redis 时间语义变体(slow) | T1–T4 未动 `streaming.py`/限流 Lua/`Permit` 端口/`backends/**`;但 diff 触及 retry(结算/取消/编排)与公开入口 `complete` 签名 | 发布门按 diff 交集选子集:**Redis 时间语义变体 + 真实网关冒烟届时在交集内**(计划 §6 门 3 已登记) |
|
||||
| `test_thinking_live.py` 全模型能力矩阵 | 本 diff 零触碰 `thinking.py`/能力注册表/相关 e2e 设施 | 按 2026-09-10 生效的交集规则**豁免**,复用 1.3.7 已登记的有效矩阵证据,不外推到已变化路径 |
|
||||
|
||||
## 6. T4 worker 超时恢复实录(提交链完整性说明)
|
||||
|
||||
T4 由前台委派 worker 执行,worker 在收尾阶段(证据落盘后、提交前)会话超时失联。处置:父会话复核 `tests/outputs/138/t4/` 五份证据(red-batch-d 16 红形态均为未实现错误、green-batch-d-first 169 passed 与红相位总量一致、green-unit 1688 / green-contracts 61 全绿、lint 1 kept 0 broken)并核对工作区 diff 精确命中计划 §2.1 表定五文件(`client.py`/`config.py`/`retry.py`/`test_config.py`/`test_stall_hedge.py`,无越界改动)后,代为提交 `2ac05a2`。**教训沉淀**:委派任务的提交动作与证据落盘之间隔着一个会话生命周期,证据先行落盘(计划 §1 证据纪律)使本次恢复无需重跑任何测试。
|
||||
|
||||
## 7. 残余复述(设计/计划已批准口径)
|
||||
|
||||
| 项 | 口径 |
|
||||
| --- | --- |
|
||||
| 中段对冲无业界公开先例 | 调研 §A3 结论,CHANGELOG/README/ARCHITECTURE 三处诚实标注;2A 看门狗链仍是不启用对冲时的默认行为 |
|
||||
| `generation_ms` 语义突变 | 人类「窗口期最小代价」裁决放行的例外(字段发布仅 2 天/消费者唯一/遥测无账本);披露义务已在 T5 三处文档落地 |
|
||||
| 复检竞速窗口 | N3① 复检 `is_set()` 只能把误触发窗口收窄到同一事件循环 pass,不能归零——已是设计批准的最强形态 |
|
||||
| 挂起率统计的不对称 | 看门狗掐死喂熔断 vs 对冲输家取消不喂(1.3.6/1.3.7 各自既有语义,本版不改任一);运维侧统计须知 |
|
||||
|
||||
## 8. 结论
|
||||
|
||||
T1–T4 全部行为变更具备先红后绿证据(§1),默认关闭回归门成立(批次 E:unit 1688 + contracts 61 全绿,既有语义断言一行未改),lint 与 import-linter 契约通过;文档承诺经运行实测核对(§3)。版本 bump、tag、发布与 slow 交集子集**不在本计划内**,按 CLAUDE.md §4.4.1 另行执行(发布时按 §5 豁免索引补跑交集项)。
|
||||
Reference in New Issue
Block a user