diff --git a/research-wiki/designs/issue8-stall-budget.md b/research-wiki/designs/issue8-stall-budget.md new file mode 100644 index 0000000..e308569 --- /dev/null +++ b/research-wiki/designs/issue8-stall-budget.md @@ -0,0 +1,46 @@ +--- +type: design +node_id: design:issue8-stall-budget +title: "stall 判定改为非生产性等待口径" +date: 2026-08-06 +--- + +# stall 判定改为非生产性等待口径 + +**全文**: `designs/2026-08-06-issue8-stall-budget-design.md`(已批准 2026-08-06)|**来源**: Gitea issue #8 |**实施**: [[plan:issue8-stall-budget-plan]] + +## 问题 + +`timeout_s ≥ stall_window_s` 时,一次耗满超时的请求即判 scope 死,`max_attempts` **静默失效**(无报错无 warning)。`stall_window_s` 默认 300 极易被 `TIMEOUT_S` 追平,"只配 timeout 不配 stall"这种最常见写法正好踩中。 + +## 根因 + +**两个预算重叠计费**:真实尝试的耗时同时向重试预算(`max_attempts`)与 stall 预算(`stall_window_s`)计费,而后者更小,必然先耗尽。 + +## 选定方案 + +`StallClock` 让 stall 只累计非生产性等待。**划分依据是"谁消耗重试预算"**,不是"是否发出请求"——烧 `max_attempts` 的时间不烧 `stall_window_s`,不烧 `max_attempts` 的时间(含 429 尝试本身)归 stall 治理。 + +关键理由: + +- **消除耦合而非守护耦合**。`stall_window_s` 与 `timeout_s` 自此无关系,配置方不必心算 `stall > timeout × retries`。 +- **`inf` 语义因此不必改**。新口径下"非生产性排队耗满窗口且 scope 从未出餐"判死本就正当,`inf` 从"有害恒真"回归为"正确的保守默认"。一次改动解决问题,优于两次改动互相牵制。 +- **取补集实现**(总时间减 `_attempt` 耗时)而非逐处标记 sleep:埋点 7 处降到 3 处,且将来新增等待路径自动计入 stall,默认安全。 + +## 被否决的备选 + +| 备选 | 否决理由 | +|---|---| +| **装配期校验 `stall_window_s > max(timeout_s)`**(issue 建议方向 1) | 治标:把缺陷固化成配置契约。且约束值须为 `timeout × max_attempts`(本机 900s),使 stall 兜底迟钝到近乎失效——修好一个洞挖开另一个。仍挡不住残余情形 | +| **`inf` 不参与判死**(issue 建议方向 2) | 新口径下 `inf` 已无害。单独改它会制造冷启动兜底真空(429 免预算无其他兜底),并反转 `test_both_windows_exceeded_raises_stalled` 钉住的行为、与 CHS 蓝本分叉 | +| **逐处标记 sleep** | 埋点 7 处且默认危险:新增等待路径忘记标记即成 stall 盲区 | +| **给 embedding/ocr 补主循环 stall 判定** | 前提不成立。429 免预算是 chat 独有,embedding/ocr 无条件 `fails += 1`,两条循环路径均已封闭,补齐等于凭空新增判死路径 | +| **删除既有 ttft 装配校验** | 其理由虽已消失(TTFT 属生产性时间),但校验无害且不误拒合理配置;删除需动 ARCHITECTURE §7.3 契约 G6,超出本 issue 范围(人类定夺:保留并改注释) | + +## 实施期订正(§3.6) + +初稿按"是否发出请求"划分,使 429 尝试**两个预算都不烧**(429 免重试预算,其耗时又算生产性)。排队型网关持满 timeout 才回 429 时实测挂 **25.2 小时**(301 次尝试),而改前只有 301s——**把一个 bug 换成了更严重的 bug**。由独立验证发现。订正为按"谁消耗重试预算"划分,429 尝试耗时退还 stall 账,实测回到 301s。 + +## 不变量 + +双条件结构、`progress_age_s()` 的 `inf` 语义、429 免预算、退避与 jitter 公式、`fail_fast` 分支、`AllSourcesExhausted` 字段与 `reason` 取值全部未动——**错误面零变更**。 diff --git a/research-wiki/graph/edges.json b/research-wiki/graph/edges.json index afd8060..0df7778 100644 --- a/research-wiki/graph/edges.json +++ b/research-wiki/graph/edges.json @@ -140,6 +140,16 @@ "id": "plan:governance-backend-error", "label": "实现计划: 治理后端故障归位为 scope 级不可用(Issue #7)", "type": "plan" + }, + { + "id": "design:issue8-stall-budget", + "label": "stall 判定改为非生产性等待口径", + "type": "design" + }, + { + "id": "plan:issue8-stall-budget-plan", + "label": "issue #8 实施计划: stall 非生产性等待口径", + "type": "plan" } ], "links": [ @@ -254,6 +264,13 @@ "relation": "implements", "evidence": "T1-T5 逐任务实现设计 §3 的五项决策与 §8 影响面清单", "added": "2026-08-06T08:08:51.865565+00:00" + }, + { + "source": "plan:issue8-stall-budget-plan", + "target": "design:issue8-stall-budget", + "relation": "implements", + "evidence": "T1-T6 实施该设计,含 §3.6 订正", + "added": "2026-08-06T14:58:01.673693+00:00" } ] } \ No newline at end of file diff --git a/research-wiki/index.md b/research-wiki/index.md index 2e475b3..2cb4641 100644 --- a/research-wiki/index.md +++ b/research-wiki/index.md @@ -1,8 +1,8 @@ # Research Wiki 索引 -> 自动生成,更新时间:2026-08-06 08:11 UTC +> 自动生成,更新时间:2026-08-06 14:58 UTC -## design (23) +## design (25) - [2026-07-20-m1-core-design](designs/2026-07-20-m1-core-design.md) `design:2026-07-20-m1-core-design` - [2026-07-20-m2-distributed-design](designs/2026-07-20-m2-distributed-design.md) `design:2026-07-20-m2-distributed-design` - [2026-07-21-m25-resilience-design](designs/2026-07-21-m25-resilience-design.md) `design:2026-07-21-m25-resilience-design` @@ -14,6 +14,7 @@ - [2026-07-31-response-observability-fields-design](designs/2026-07-31-response-observability-fields-design.md) `design:2026-07-31-response-observability-fields-design` - [2026-07-31-sampling-params-design](designs/2026-07-31-sampling-params-design.md) `design:2026-07-31-sampling-params-design` - [2026-08-06-governance-backend-error-design](designs/2026-08-06-governance-backend-error-design.md) `design:2026-08-06-governance-backend-error-design` +- [2026-08-06-issue8-stall-budget-design](designs/2026-08-06-issue8-stall-budget-design.md) `design:2026-08-06-issue8-stall-budget-design` - [est_tokens 解耦: 拆分限流预扣与遥测用量兜底(issue #2)](designs/est-tokens-decoupling.md) `design:est-tokens-decoupling` - [GatewaySettings 装配校验补齐(第二轮)](designs/settings-invariants-round-2.md) `design:settings-invariants-round-2` - [GatewaySettings 跨字段不变量守卫的生效范围](designs/settings-invariant-guards.md) `design:settings-invariant-guards` @@ -22,6 +23,7 @@ - [M2.5 治理韧性: 半死源隔离与健康感知调度](designs/m25-resilience.md) `design:m25-resilience` - [M3 OCR 端口族设计](designs/m3-ocr.md) `design:m3-ocr` - [M4 迁移验证设计(GovDoc→CHS,发 v1.0)](designs/m4-migration.md) `design:m4-migration` +- [stall 判定改为非生产性等待口径](designs/issue8-stall-budget.md) `design:issue8-stall-budget` - [响应可观测字段扩展(Issue #3)](designs/response-observability-fields.md) `design:response-observability-fields` - [推理开关能力建模与 reasoning_tokens 采集(issue #5 + #6)](designs/2026-08-02-thinking-capability-design.md) `design:2026-08-02-thinking-capability-design` - [治理后端故障归位为 scope 级不可用(Issue #7)](designs/governance-backend-error.md) `design:governance-backend-error` @@ -41,7 +43,7 @@ - [P7 OCR soak 验收: 99.73% 与 13 不变量全 PASS](findings/p7-ocr-soak.md) `finding:p7-ocr-soak` - [推理开关与 reasoning_tokens: 供应商实测与业界做法](findings/2026-08-02-thinking-switch-and-reasoning-tokens.md) `finding:2026-08-02-thinking-switch-and-reasoning-tokens` -## plan (19) +## plan (21) - [2026-07-20-m1-core-plan](plans/2026-07-20-m1-core-plan.md) `plan:2026-07-20-m1-core-plan` - [2026-07-20-m2-distributed-plan](plans/2026-07-20-m2-distributed-plan.md) `plan:2026-07-20-m2-distributed-plan` - [2026-07-21-m25-resilience-plan](plans/2026-07-21-m25-resilience-plan.md) `plan:2026-07-21-m25-resilience-plan` @@ -51,7 +53,9 @@ - [2026-07-31-response-observability-fields](plans/2026-07-31-response-observability-fields.md) `plan:2026-07-31-response-observability-fields` - [2026-07-31-sampling-params](plans/2026-07-31-sampling-params.md) `plan:2026-07-31-sampling-params` - [2026-08-06-governance-backend-error-plan](plans/2026-08-06-governance-backend-error-plan.md) `plan:2026-08-06-governance-backend-error-plan` +- [2026-08-06-issue8-stall-budget](plans/2026-08-06-issue8-stall-budget.md) `plan:2026-08-06-issue8-stall-budget` - [est_tokens 解耦实施计划](plans/est-tokens-decoupling.md) `plan:est-tokens-decoupling` +- [issue #8 实施计划: stall 非生产性等待口径](plans/issue8-stall-budget-plan.md) `plan:issue8-stall-budget-plan` - [M1 核心里程碑实现计划](plans/m1-core-plan.md) `plan:m1-core-plan` - [M2 分布式实现计划](plans/m2-distributed.md) `plan:m2-distributed` - [M2.5 治理韧性实现计划](plans/m25-resilience.md) `plan:m25-resilience` diff --git a/research-wiki/log.md b/research-wiki/log.md index 4684e4f..08d31e2 100644 --- a/research-wiki/log.md +++ b/research-wiki/log.md @@ -90,3 +90,7 @@ - [2026-08-06 08:08 UTC] 新增边: plan:governance-backend-error --implements--> design:governance-backend-error - [2026-08-06 08:08 UTC] 重建索引: 57 篇页面 - [2026-08-06 08:11 UTC] 重建索引: 57 篇页面 +- [2026-08-06 14:57 UTC] 新增 design: stall 判定改为非生产性等待口径 (design:issue8-stall-budget) +- [2026-08-06 14:58 UTC] 新增 plan: issue #8 实施计划: stall 非生产性等待口径 (plan:issue8-stall-budget-plan) +- [2026-08-06 14:58 UTC] 新增边: plan:issue8-stall-budget-plan --implements--> design:issue8-stall-budget +- [2026-08-06 14:58 UTC] 重建索引: 61 篇页面 diff --git a/research-wiki/plans/issue8-stall-budget-plan.md b/research-wiki/plans/issue8-stall-budget-plan.md new file mode 100644 index 0000000..127123a --- /dev/null +++ b/research-wiki/plans/issue8-stall-budget-plan.md @@ -0,0 +1,35 @@ +--- +type: plan +node_id: plan:issue8-stall-budget-plan +title: "issue #8 实施计划: stall 非生产性等待口径" +date: 2026-08-06 +--- + +# issue #8 实施计划: stall 非生产性等待口径 + +**全文**: `plans/2026-08-06-issue8-stall-budget.md` |**实现**: [[design:issue8-stall-budget]] |**分支**: `feat/issue-8-stall-budget` + +## 交付 + +| 任务 | 内容 | 提交 | +|---|---|---| +| T1 | `StallClock` 落地 + chat 路径改造 + 8 条测试 | `02c3d06` | +| T2 | embedding 路径复用 | `6d0f3c9` | +| T3 | ocr 路径复用 | `0477d95` | +| T4 | `config.py` docstring 与 `.env.example` 注释对齐(无逻辑变更) | `d05114e` | +| T5 | 全套件回归 + `ARCHITECTURE.md` §7.3 与 `CHANGELOG` 同步 | `3645e57` | +| T6 | 独立验证(全新上下文 verifier)+ 三个问题的修复 | `bc4683d`、`a0a5cf7` | + +## 测试证据(先失败后通过) + +三条路径的失效链条各有一条回归用例,改前均转红于 `reason="stalled"`:`retry.py:218`、`embedding.py:250`、`ocr.py:275`。issue 只记录了 chat 路径,embedding/ocr 两条为本次核出。 + +## 独立验证发现的三个问题(均已修) + +1. **429 缝隙(中)**: 初稿使 429 尝试两个预算都不烧,慢 429 场景实测挂 25.2 小时——**修复引入的回归**。见设计 §3.6。 +2. **测试假证据(中)**: 并发用例用了两个 `RetryMW` 实例,实例级共享被对象隔离掩盖,clock 提升为实例属性时 7 条用例全部逃逸。改为复用同一 `mw` 并补"两次调用间空转超窗"用例,变异测试确认可抓。 +3. **文档遗漏(轻)**: 计划要求的 `test_backpressure.py` docstring 订正漏做。 + +## 保真校验 + +治理主循环为 CHS `governance.py:200-285` 移植物,但 `reference/` 不在工作区,故以设计 §4 行为审计表 9 条 + 代码内 CHS 行号注释为基准。核对结果:标"保留"的 8 条在 `git diff` 中零出现,唯一"替换"项为条件 A 度量口径。