B1 chunk = any delta (content or reasoning), B2 telemetry plumbing via explicit _emit pass, B3 orchestration entry gate for independent stall hedging; N1-N9 advisory fixes merged. Human decisions recorded in design section 9.
14 KiB
核实完毕。所有关键引用已对照源码验证。以下为审查报告全文。
流式中段沉默设计(1.3.8)独立审查报告
- 审查对象:
designs/2026-09-11-streaming-stall-design.md(已批准,1B' + 2B) - 对照:
findings/2026-09-11-streaming-stall-hedging-research.md、designs/2026-09-10-24-hedged-requests-design.md、maina28d944源码(retry.py / openai_compat.py / streaming.py / types.py / ports.py / telemetry/schema.py / middleware/telemetry.py / config.py / client.py 全部实读) - 立场: 不推翻人类 §9 五项拍板;只找设计与实现层面的漏洞
结论: 有条件实施
总体架构判断正确: 计量与触发共用同一观测原语、复用 1.3.7 准入/记账/取消语义、遥测迁移机制现成,均无方向性问题。但有 3 处阻断级设计缺口(chunk 定义未钉死、遥测透传链路改动面遗漏、独立生效的编排入口遗漏)必须在实施前以设计补遗钉死;另有 9 条建议级修正。均为"补钉"而非"返工"。
九条审查角度逐条裁决
| # | 角度 | 裁决 | 依据(源码实证) |
|---|---|---|---|
| 1 | 沉默计时边界 | 存疑(N1) | 尾部(最后内容 chunk→[DONE]/usage 帧/连接收尾)不是"相邻 chunk 间隔",不会被减,但仍留在墙钟基底里 → 净值含尾部;调研 §B3 的先例(AIPerf 剔除 [DONE]、pi-tps 止于最后 message_update)是"尾部不计",与设计的墙钟基底口径不同,设计未察觉也未声明。TTFT 同理留在净值内(人类原话"减去中段沉默"字面如此,可接受,但须明写)。应然口径: 显式声明"净值 = 墙钟 − 中段沉默,仍含 TTFT 与尾部",README 口径表同步 |
| 2 | 非流式与输家口径 | 成立(建议 N2) | 排除条款(非流式/chunk<2 → None 且不退减)已明写,双口径是设计内选择;建议补一句"非流式 net≈wall(无中段沉默概念),下游预算公式以 stall_ms is None 分支"。输家排除由 _register_hedge 只记赢家 sink 的机制保证(retry.py _register_hedge)✓;但"输家数据留在其 attempt 遥测行可 join 还原"对被取消的输家不成立(无 TransportResult,行是 error 行)→ 见 B2 |
| 3 | P2 与看门狗竞态 | 成立(措辞修正 N9) | 看门狗掐死抛 StreamLivenessTimeout(streaming.py:24-50),在 transport 边界翻译为 TransientError ... from exc(openai_compat.py:476-477),不是 CancelledError。编排层无需区分两种主路之死: _attempt_hedged Phase 5 的"先了结的是失败则等另一路"分支对任何 _Failed 一律等副路,两败走 _combine_failures(retry.py:171-176)计一次预算——现有机制完整表达设计语义 ✓。但设计"主路之死不算失败"措辞不精确: attempt 级记账照常(breaker record_failure("timeout")、attempt_fails、reasons、遥测 error 行),"不算失败"的只是本轮超级尝试 |
| 4 | 触发信号并发安全 | 存疑(N3) | asyncio.Event 在单循环内 check→clear 之间无 await 即无交错,但超时到期后触发对冲前必须复检 is_set()——否则"chunk 与超时同一事件循环 pass 到达"会误触发。1.3.7 的 _past_hedge_window 是一次性等待(retry.py:542-568),stall 需要循环等-清-重等,设计只写"等该事件超时",未钉循环模式。等待者只有一个(编排),对冲路恒传 None 是 1.3.7 约定(retry.py _attempt_hedged Phase 4),但设计未声明该约定延伸到新的 chunk Event 参数 |
| 5 | 取消穿透 | 成立(并入 N3) | 外部取消到达 → _attempt_hedged 的 except BaseException 两任务同消、尽力收口、原异常上抛,与 1.3.7 逐字一致 ✓;stall 等待循环每轮迭代自建 waiter,须逐字套用 _past_hedge_window 的 waiter 收口 + cancelling() 穿透纪律(retry.py:556-568),设计补一句即可 |
| 6 | 计量正确性 | 存疑(N4) | 分层可行: 沉默在 transport 由 chunk 时间戳序列算出(openai_compat.py:580-590 循环邻域),净值在 retry 由"墙钟−stall"得。但两处的钟不同源: retry 用注入钟 self._now(retry.py:332/346,client.py:242 默认 time.monotonic),transport 硬编码 time.monotonic()(openai_compat.py:562/582)且 transport 不接收注入钟(client.py:508 构造只传 registry/capabilities)。生产同为 monotonic 无事;测试注入 FakeClock 时净值可为负/失真。赢家 sink 与 1.3.7 generation_sink 机制协调成立(改记净值即可)✓ |
| 7 | 遥测 DDL 迁移 | 成立(编辑点清单见 N7) | schema.py 有成熟迁移机制: SQLITE_BACKFILL/_PG_BACKFILL_DECLS 保序追加、recorder "先探测后 ALTER"(auto 档)、telemetry_schema_sql() 给 manual 档同源 SQL。设计引用的迁移纪律(#12/#13/#15)与现状吻合 ✓。"遥测 DB 从未存过 generation_ms"经核实属实(COLUMNS 36 项无此字段)✓ |
| 8 | 配置守卫完备性 | 存疑(N5) | 三段键解析无冲突: _load_sources 以 len(parts) != 4 跳过(config.py:431),STREAM 进 _RESERVED_SEGMENTS(config.py:57)防 4 段 {SCOPE}__STREAM__{N}__* 撞 provider 段,与 HEDGE 先例同型 ✓。但守卫有缺口: ①设计写 stall_after_s ≥ inter_token_timeout_s,未写有效值回落——运行时是 inter_token_timeout_s or timeout_s(openai_compat.py:573),inter_token 未设时守卫对象应是 timeout_s;②严重度与 1.3.7 不一致: 同类"永不触发"条件 hedge_after_s ≥ min(timeout_s) 是 ValueError(config.py:820-824),而 stall 场景非流式无残留收益(1.3.7 ttft warning 留有非流式收益,stall 没有),只给 warning 论证不足;③stall_after_s ≥ call_deadline_s 的 ValueError"沿用 1.3.7"须点名落进 check_hedge_assembly(config.py:772)同函数 |
| 9 | 内部一致性 | 存疑(N7) | 无硬矛盾,但有三处错位: ①§4.2 改动面写 config.py:46 邻域——:46 是 _SOURCE_FIELDS 表(per-source 4 段键注册表),而 §6 明写 {SCOPE}__STREAM__STALL_THRESHOLD_MS 是 3 段 scope 级键,两处矛盾,正确先例是 _load_hedge(config.py:745-769)同款 loader + GatewaySettings 字段;②§4.2 写 types.py:417-418 邻域(CallStats 增两字段)——CallStats 在 types.py:296-324,417-418 邻域是 LLMResponse;③§5.2 写端口加参"须进批准项",但 §9 五项无对应条目(整体批准可视为覆盖,但设计自拟的程序未闭环) |
发现列表
阻断级(实施前必须以设计补遗钉死)
B1 | 设计 §3/§4.2/§5.2 | "内容 chunk"定义未钉死,reasoning 增量归属不明
- 问题: 源码现状是
_iter_sse_deltas对content与reasoning_content都产出(openai_compat.py:65-85),看门狗与first_token_event都把思考增量当存活证据(openai_compat.py:585-588 注释"思考增量同样是存活证据,与看门狗活性口径一致")。若按"内容 chunk"字面只计is_content=True的增量,则 reasoning 模型长 thinking 阶段(只流 reasoning_content)会被误计为沉默: P1 的stall_ms虚高、净口径失真;P2 在正常推理中误触发对冲。本库 reasoning 是一等特性,这不是边缘情形。 - 修法: §3 明写"chunk = 任何增量产出(content 或 reasoning_content),与看门狗/首 token 活性口径一致;仅空 chunk/usage 帧/SSE keepalive 不计时"。排除条款的"内容 chunk <2"同步按此口径。
B2 | 设计 §4.2 改动面/§7 契约表 | 遥测两列的透传链路改动面遗漏,且"输家行可 join"表述不成立
- 问题: attempt 行的用量字段由 emitter
_AttemptUsage.of(response)从 LLMResponse 读取(middleware/telemetry.py:216-236)。stall 两列要进 attempt 行,必须二选一: (a) LLMResponse 增stall_ms/stall_count(公共类型,只增带默认值合法,但改动面与 §7 契约表均未列)+_build_response搬运;或 (b)_emit显式传 TransportResult 两值。同时ports.py的TelemetryRecorder.record_llm_call签名必须加两参——这是第三个端口签名变更,§7 只列了Transport.complete与TransportResult;emitter 的装配闸signature.bind以该协议签名为判据(telemetry.py:338),漏改协议直接装配期炸。另外 §4.2"输家数据留在其 attempt 遥测行可 join 还原"对被取消的输家不成立: 取消的输家无 TransportResult,其行是hedge_cancellederror 行,stall 恒 None;只有"竞速落选但已完成"的输家才有数据。 - 修法: §4.2/§7 补全链路(选定 (a) 或 (b);ports.py recorder 签名 + emitter + 两 recorder 实现 + schema.py 四处编辑点);修正输家表述为"已完成输家的沉默在其 attempt 行;被取消输家无沉默数据(传输未返回),其存在性由
hedge_cancelled行表达"。
B3 | 设计 §5.2 改动面 | "独立于 AFTER_S 生效"所需的编排入口改动遗漏
- 问题: 现状
RetryMW.__call__仅在self._hedge_after_s is not None时进对冲路径(retry.py__call__分支)。§9-④ 拍板独立生效意味着hedge_after_s=None + stall_after_s=25必须也能进对冲路径——__call__入口门须改为"两阈值任一已设且(对 stall)请求为流式"。同时: ①非流式 + 仅 stall_after_s → 无中段沉默概念,应逐字走单路(等价未配置),设计未声明;②_past_hedge_window须从一次性等待重构为两阶段(首 token 前 AFTER_S 仅当已设 / 首 token 后 stall 循环),改动面只写"扩为"偏轻;③纯非流式部署设了 stall_after_s 是否给装配 warning,设计未涉及。 - 修法: §5.2 改动面补
__call__入口门与非流式退化声明;§6 配置表补"stall_after_s 仅对流式生效,纯非流式 scope 设置时 warning(或静默,须拍板一句)"。
建议级
N1 | §3/§4.2 | 沉默计时边界未明写(TTFT 与尾部归属)
- 尾部(末 chunk→[DONE]/usage 帧)不是相邻间隔、不会被减,但留在墙钟基底 → 净值含尾部;TTFT 同理。调研 §B3 先例是"流终止信号后不计",与墙钟基底口径不同。修法: 显式声明"净值 = 墙钟 − 中段沉默,仍含 TTFT 与尾部;尾部归属活跃侧,与 pi-tps'止于最后更新'的差异是有意的(墙钟基底),尾部通常 ≈0";README 口径表同步。
N2 | §4.2 排除条款 | 非流式双口径补一句显式声明
- "非流式场景 net≈wall(无中段沉默概念),下游预算公式以
stall_ms is None为分支条件"。
N3 | §5.2 触发机制 | stall Event 循环的并发与取消纪律未钉
- ①超时到期后触发对冲前复检
is_set();②check→clear 之间不得有 await;③对冲路 chunk Event 恒 None(1.3.7 首 token 约定延伸);④每轮 waiter 收口 +cancelling()穿透逐字套_past_hedge_window(retry.py:556-568);⑤等待者唯一(编排),Event 无需多等待者语义。或改用"最后 chunk 时间戳 + 计算剩余时长"模式免 clear 竞态——二选一,设计钉一种。
N4 | §4.2 改动面 | 钟同源纪律与负值防御
- retry 墙钟(注入
now)与 transport 沉默钟(硬编码time.monotonic())生产同源、测试分叉。修法: 设计声明"沉默序列与墙钟必须同为 monotonic 语义;net = max(0, wall − stall)clamp;FakeClock 测试用假 transport 自控 stall 值,真实 transport 测试不得注入假钟"。
N5 | §5.2/§6 | 守卫完备性三缺口
- ①
stall_after_s ≥ inter_token_timeout_s须用有效值inter_token_timeout_s or timeout_s(openai_compat.py:573 回落同款);②"永不触发"严重度对照 1.3.7 的 ValueError(config.py:820-824)需论证为何只 warning(stall 无非流式残留收益,升 ValueError 的论据其实更强);③stall_after_s ≥ call_deadline_sValueError 点名落进check_hedge_assembly。
N6 | §4.2 stall_count 定义 | "连续停顿合并一次"在 gap 口径下无所指
- 间隔口径里任意两个超阈值 gap 之间至少隔一个 chunk,不存在可合并的"连续停顿";该子句移植自 pi-tps 的事件流语义,在本口径下无操作定义。修法: 直接定义"
stall_count= 超阈值相邻间隔个数",删合并子句。
N7 | §4.2/§5.2/§6/§7 | 引用勘误与编辑点清单
types.py:417-418实为 LLMResponse 邻域(max_inter_token_ms 在 :406),CallStats 在 :296-324;config.py:401-407实为 :427-431;config.py:46是_SOURCE_FIELDS(与 §6 三段键矛盾,见裁决 9);openai_compat.py:605是 max_inter_token_ms 装配处,max_gap 循环在 :580-590;schema.py:43/85指向 ttft_ms 行,实际编辑点 = DDL 末尾(created_at 之后,schema.py 头注释纪律)+SQLITE_BACKFILL/_PG_BACKFILL_DECLS保序对齐 +COLUMNS+ 列数断言测试 +telemetry_schema_sql同源再生成;§5.2"须进批准项"在 §9 无对应条目,补一条批准记录或删该句。
N8 | §4.2 vs §7 | 类型换算点未钉
- TransportResult
stall_ms: float | Nonevs CallStatsint | None: 钉一句换算顺序(建议 retry 层int(wall_ms) − int(stall_ms)或先减后取整,二选一),避免各实现 ±1ms 漂移。
N9 | §5.2 与看门狗的交互 | 措辞修正 + 一句哲学论证
- "主路之死不算失败"改为"本轮超级尝试不因主路之死而败(等副路);attempt 级记账(breaker failure/
attempt_fails/遥测 error 行)照常"。并补一句论证: 看门狗掐死喂熔断失败 vs 对冲输家取消不喂熔断,同为源沉默事件却不同处置——前者是既有看门狗语义(1.3.6 起),后者是 1.3.7 已批准语义,本设计不改任一,但运维侧"挂起率"统计须知道这一不对称。
核实记录(引用抽查,全部实读源码)
| 设计引用 | 实况 | 结论 |
|---|---|---|
retry.py:542-562 _past_hedge_window |
:542 起,返回式逐字一致 | ✓ |
| retry.py:~335/~346 计时点 | :332/:346 | ✓(邻域) |
| openai_compat.py:573 回落 | :573 逐字一致 | ✓ |
| openai_compat.py:605 max_gap | :580-590 循环,:605 装配 | 偏位(N7) |
| types.py:417-418 | CallStats 在 :296-324 | 错位(N7) |
| config.py:46/57/401-407 | :46 ✓ :57 ✓ :401-407 实为 :427-431 | 部分偏位 |
| 遥测无 generation_ms 列 | COLUMNS 36 项核实 | ✓ |
| 看门狗抛 TransientError | streaming.py + openai_compat.py:476 | ✓ |
结论行: 有条件实施——3 条阻断级(B1 chunk 定义、B2 遥测透传链路、B3 编排入口)+ 9 条建议级(N1-N9),均为设计补遗级修正,无需推翻 §9 拍板。
发现计数: B1-B3(阻断级 3 条),N1-N9(建议级 9 条)。