type, node_id, title, date
type
node_id
title
date
finding
finding:2026-09-11-streaming-stall-validation
1.3.8 流式中段沉默:净口径与中段对冲验证报告
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 豁免索引补跑交集项)。
5. 合并前独立验证补跑(2026-09-14,verifier P2-1/P2-2 缺口闭合)
项
证据
结果
全套件(含 integration)
tests/outputs/138/verify/full-suite2.log
1764 passed, 23 skipped, exit=0
PG 遥测 30 用例
pg-integration-rerun2.log
30 passed exit=0(首轮 12 failed → 根因: _minimal_fields 测试 helper 未同步 1.3.8 两列,postgres.py:450 按物理列取值 KeyError;修复 helper + test_pre_135 期望字典,同类 B1 测试同步缺口,非生产缺陷——生产路径由装配闸强制全签名)
关键用例重跑
key-reruns.log
10 passed exit=0(净口径精确值/复检防误触发/看门狗掐死汇合/遥测迁移幂等)
make lint
verify/lint.log
exit=0,Contracts: 1 kept, 0 broken