From 77f9260189483dadac8624200e0aa978235843e2 Mon Sep 17 00:00:00 2001 From: iomgaa Date: Thu, 6 Aug 2026 05:04:55 -0400 Subject: [PATCH] docs: publish which errors reach callers and which the library absorbs TransientError and SourceDeadError read like caller-facing contracts in the taxonomy table, but the retry loop catches both and repackages them as AllSourcesExhausted, so they never arrive. That is only discoverable by reading middleware/retry.py, and a downstream project wrote a whole design section on the false premise before checking. The new table states the split outright, including that GovernanceBackendError now sits on the caller-facing side and SourceNotConfiguredError deliberately does not join the retryable family. --- README.md | 15 +++++++++++++++ research-wiki/migrations/chsanalyzer.md | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2494b0f..3ac3171 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,21 @@ except RequestRejectedError: 预算耗尽/全源熔断时抛 `GatewayUnavailableError` 族(`CircuitOpenError` / `AllSourcesExhausted`),携带 `scope` / `reason` / `retry_after_s` / `per_source_reasons`,供任务队列做延期重投。 +### 哪些异常会到达调用方 + +上表的"库内行为"一列描述的是**治理动作**,不是调用方要处理的东西。四类里有两类**根本到不了调用方**——它们被重试循环接住,预算耗尽时统一包成 `AllSourcesExhausted`。这个区分只看类型树和 docstring 是读不出来的,曾让下游据此写错整段设计文档,故在此列明: + +| 会到达调用方 | 库内吸收(不必 catch) | +|---|---| +| `GatewayUnavailableError` 族——`CircuitOpenError` / `AllSourcesExhausted` / `GovernanceBackendError` | `TransientError`(退避后换源重试,耗尽即转为 `AllSourcesExhausted`) | +| `RequestRejectedError` | `SourceDeadError`(立即熔断该源并换源,同上) | +| `ResultInvalidError` | | +| `SourceNotConfiguredError` | | + +**`GovernanceBackendError` 属于第一列**: 限流/熔断的状态后端(如 Redis)自身故障时库 fail-closed——一个请求都发不出去,这就是"整个 scope 暂时不可用"。它继承 `GatewayUnavailableError`,所以 §4 那段 `except GatewayUnavailableError` 一条即覆盖完整,无需为它单列分支。`retry_after_s` 默认 5 秒(后端恢复时间不可知,取 0 会让积压任务零延迟冲击已挂掉的后端)。 + +**`SourceNotConfiguredError` 有意不在第一列的族内**: 源名不在限流后端的配置字典中是**装配缺陷**而非暂时故障,它应当消耗失败预算、进死信、让人看见——归入可重投家族只会让配置写错的任务永远重投且无人告警。 + ## 配置参考 配置只有两条装配路径:`from_env()`(读 `.env`/环境变量)或构造函数全量注入(测试/高级);库内部任何组件不自读环境变量。键名全集见 [.env.example](.env.example),约定速览: diff --git a/research-wiki/migrations/chsanalyzer.md b/research-wiki/migrations/chsanalyzer.md index a0d5f5d..5d6db1c 100644 --- a/research-wiki/migrations/chsanalyzer.md +++ b/research-wiki/migrations/chsanalyzer.md @@ -181,7 +181,7 @@ stack = ExtractionProviderStack( | R4 | 六道闸+契约 5 条、服务器时钟窗口、settle 落 acquire 窗口、transient 按 est 保守结算 | M2 | §7.3 大体覆盖 | | R5 | RequestRejected 二分(真实响应记成功/本地拒绝释放探针);换源重试跨源计数口径 | M2 | 须进 M2 设计 | | R6 | OCR ZIP 协议 + bbox 数值防御下沉;OCR Usage=0;glm 白名单预留 | M3 | §7.10 已覆盖 | -| **G1** | ✅ 已闭(M3 核实): 库 `GatewayUnavailableError` 一族自 M1 起携 `scope/reason/retry_after_s/per_source_reasons`(errors.py:74-105),chat/embedding/OCR 三循环抛出点均已填充且有契约测试钉住;项目侧仅剩约 10 行翻译 shim(库异常 → ProviderUnavailableError)或 tracking.py 直接 except 库异常 | M2 | 已闭 | +| **G1** | ✅ 已闭(M3 核实): 库 `GatewayUnavailableError` 一族自 M1 起携 `scope/reason/retry_after_s/per_source_reasons`(errors.py:74-105),chat/embedding/OCR 三循环抛出点均已填充且有契约测试钉住;项目侧仅剩约 10 行翻译 shim(库异常 → ProviderUnavailableError)或 tracking.py 直接 except 库异常。**2026-08-06 补(issue #7,库 1.1.0)**: 治理后端故障(`GovernanceBackendError`,Redis 挂等 fail-closed 情形)此前**不在**该族内,`except GatewayUnavailableError` 接不住,会落进 `_TERMINAL` 兜底而消耗业务失败预算;现已归入该族(`reason=governance_backend_down`,`retry_after_s` 默认 5.0),tracking.py 一条 except 即覆盖完整,**无需为它单列分支**。同批新增的 `SourceNotConfiguredError`(源名与配置不匹配的装配缺陷)**有意在族外**,应当落进 `_TERMINAL` 让配置错误浮出水面 | M2 | 已闭 | | **G2** | ✅ 已闭(2026-07-30 核实): `est_tokens` 已进 ARCH §7.7 SourceConfig 字段清单,且 §7.3 `try_acquire` 的 est 来源已定义为 `SourceConfig.effective_est_tokens()`(显式值优先,否则按 `tpm // 60` 派生)。双职责一并拆开:该字段只剩 TPM 预扣的可选调优覆盖,usage 缺失不再由它兜底(见 §7 行 151 的推翻判定) | M2 | 已闭 | | **G3** | ⚠️ §4.3 层序图文矛盾:图示 熔断→限流→重试(重试最内),但理由要求"每次重试重新过限流闸"且熔断/限流是 per-source 的、选源在重试循环内(governance.py:120-167 实践为每次尝试执行 选源→冷却备忘→permit→熔断门)。洋葱不澄清"逐次准入"机制则多源语义无法成立 | M2 | **架构缺口**,澄清 §4.3/§4.4 | | **G4** | ⚠️ per-scope 韧性配置命名(`{SCOPE}__RETRY__*`/`BREAKER__*`/`BACKPRESSURE__*`/`SELECTOR`/`GLOBAL__*`)未进 ARCH §9,现文只有平铺 `LLM_*` 键;CHSAnalyzer 的 VLM/OCR 两 scope 参数各异,平铺键无法表达 | M2 | **架构缺口**,修订 §9 |