583c012706
Add research-wiki/migrations/ (govdoc-saas, video-tree-trm5, chsanalyzer): deletion lists, component mappings, call-site inventories, config migration, stepwise rollback plans, legacy behavior audits, and reverse constraints on the library design including flagged architecture gaps.
179 lines
22 KiB
Markdown
179 lines
22 KiB
Markdown
# CHSAnalyzer 迁移文档(迁移即验收)
|
|
|
|
> **定位**: 本文是 `ARCHITECTURE.md §11.3` 的展开——库建成后如何合并进 CHSAnalyzer、替换哪些内部组件。与 ARCHITECTURE.md 冲突时**以 ARCHITECTURE.md 为准**。CHSAnalyzer 是三项目中迁移难度最高、能力对标要求最高的一个:库必须先达到其治理能力**逐项对等**(§1 对标清单),迁移才有动机。全部结论基于 2026-07-20 对 `reference/CHSAnalyzer/` 的代码实测(file:line 为证)。
|
|
|
|
## 1. 迁移目标与验收定义
|
|
|
|
**验收公式**: 删除 §2 标注"删除"的文件 → 业务侧 import 换成 `polygateway` + shim → 原测试全绿(被删组件的自测如 `tests/unit/test_governance.py`、`tests/integration/test_redis_limiter.py` 随组件迁入库侧,不计入"原测试")。
|
|
|
|
| 阶段 | 可替换范围 | 前置里程碑 |
|
|
|---|---|---|
|
|
| M2 后 | VLM scope 治理核心(governance/limiter/scripts/provider_gate/selector/streaming + VLM invoker) | Redis 六道闸+契约测试、多源选源、跨进程熔断、背压 stall |
|
|
| M3 后 | OCR scope(MonkeyOcrParseInvoker → `OcrLayoutPort`)、`core/eval/judge.py` 收编,**全量迁移** | OCR 端口族 + MonkeyOCR transport |
|
|
|
|
**能力对标清单**(库必须逐项对等,任一缺失即库的边界缺口):
|
|
|
|
| # | 能力 | 项目侧证据 |
|
|
|---|---|---|
|
|
| 1 | 六道闸原子限流(全局/单源 × 并发/RPM/TPM),拒绝零副作用 | `app/coordination/scripts.py:6-34` |
|
|
| 2 | TPM 预扣入场、settle 按实际 usage 落回 acquire 窗口多退少补 | `limiter.py:55-62`、`scripts.py:44-48` |
|
|
| 3 | 并发 lease 带 TTL(防进程死亡泄漏)+ release/settle 幂等 | `limiter.py:46-63`、`tests/contracts_limiter.py:66-73` |
|
|
| 4 | 窗口 id 用 Redis 服务器时钟(多进程口径统一) | `limiter.py:95-98` |
|
|
| 5 | 跨进程熔断:单探针半开、探针租约 TTL、epoch fencing、force_open | `scripts.py:82-225`、`provider_gate.py` |
|
|
| 6 | 背压 stall 双条件判卡死(本地等够 + 全局无进展)+ mark_progress 全局活性 | `governance.py:270-281`、`limiter.py:193-209` |
|
|
| 7 | 换源重试:失败跨源累计、退避取 Retry-After 较大值、源冷却备忘 | `governance.py:105-118, 244-261` |
|
|
| 8 | 错误分类:429 body 细分 insufficient_quota、工件级失败不熔断 | `invokers.py:144-166`、`governance.py:237-239` |
|
|
| 9 | scope 级不可用结构化错误(reason 7 种 + retry_after_s),供 worker 延期重投 | `errors.py:140-180`、`workers/tracking.py:406-428` |
|
|
| 10 | 流式三层活性看门狗;thinking token 刷活性不计结果 | `streaming.py`、`invokers.py:55-79` |
|
|
|
|
## 2. 现状盘点(行数 wc -l 实测)
|
|
|
|
| 文件 | 行数 | 职责一句话 | 迁移后命运 |
|
|
|---|---|---|---|
|
|
| `app/providers/governance.py` | 350 | 多源治理编排(选源+限流+熔断+重试+背压) | **删除**(库 middleware 继任;Governed 外壳变 shim) |
|
|
| `app/providers/invokers.py` | 552 | VLM SSE invoker + MonkeyOCR `/parse` invoker + 错误翻译 | **删除**(库 transport 继任) |
|
|
| `app/providers/selector.py` | 42 | round_robin / least_inflight 选源 | **删除**(库 `SourceSelector` 同款) |
|
|
| `app/providers/streaming.py` | 128 | 三层活性看门狗纯函数 | **删除**(库 `streaming.py` 同款移植) |
|
|
| `app/coordination/limiter.py` | 209 | Redis 六道闸限流 + Permit | **删除**(库 `backends/redis`) |
|
|
| `app/coordination/scripts.py` | 245 | 限流/熔断全部 Lua 脚本 | **删除**(随限流/熔断迁库) |
|
|
| `app/coordination/provider_gate.py` | 179 | Redis 跨进程熔断门 | **删除**(库 `RedisBreakerState`) |
|
|
| `tests/contracts_limiter.py` | 79 | 限流器契约(5 条,任一实现须过) | **迁入库**(M2 随实现交付) |
|
|
| `core/eval/judge.py` | 201 | LLM 语义裁判,**同步裸 SDK 无治理**(反面教材) | **改写**(走库,见 §4) |
|
|
| `app/domain/errors.py` | 226 | 领域异常;其中 82-180 行为 Provider 错误族 | **改写**(Provider 族删除换库 `errors.py`;业务异常保留) |
|
|
| `app/config.py` | 574 | Settings + 多源/全局限额/retry/breaker/背压解析 | **改写**(治理配置解析约 43-410 行删除,由库 `from_env` 继任;Settings/S3/Zip/Stage 保留) |
|
|
| `app/container.py` | 511 | 组装根;225-320 行手工装配治理栈 | **改写**(治理装配换库工厂,其余保留) |
|
|
| `app/ports.py` | 704 | 全部端口;其中 396-529 行为限流/熔断端口 | **改写**(限流/熔断端口删除;`VlmProvider`/`TableLocator`/`ProviderOutcome`/`Usage` 保留为业务端口,由 shim 实现) |
|
|
|
|
**明确留在业务侧(零改动或仅换上游类型)**: `position_scheduler.py`(347 行,按 Session 轮转的公平调度,ARCHITECTURE §2.3 明确不进库)、`table_locator.py`(111 行,裁剪/坐标映射/marker 推算等几何映射)、`marker_imaging.py`(96 行,拼图增强)、`preprocessing.py`(64 行)、`extractors.py`(108 行)/`classifiers.py`(47 行)/`vascular_positioner.py`(378 行)的业务解析与编排、`worker_liveness.py`(238 行,任务级活性,非调用治理)、`redis_pool.py`(22 行,arq/调度器仍需)。
|
|
|
|
## 3. 组件替换映射表
|
|
|
|
| CHSAnalyzer | PolyGateway 对应物 | 签名兼容性(实测) | shim 形态 |
|
|
|---|---|---|---|
|
|
| `GovernanceCore.run(call)`(governance.py:200) | 中间件洋葱整体 | 泛型闭包 → `client.chat(request)`,范式不同 | 无需 shim,整体替换 |
|
|
| `VlmProvider.complete(image, instruction)`(ports.py:552-555) | `client.chat(messages)` | **不兼容**:bytes+str vs messages 数组 | `PgwVlmProvider`:组 content 数组(text+image_url base64)→ chat → 映射回 `ProviderOutcome` |
|
|
| `ProviderOutcome{text,source_name,model,usage,raw}`(ports.py:541-548) | `LLMResponse`(ARCH §5.1) | 字段可全映射:text←content、usage.total_tokens←prompt+completion、elapsed_s←latency_ms/1000 | shim 内一次转换 |
|
|
| `Permit{release, settle(actual_tokens:int)}`(ports.py:495-504) | 库 `Permit.settle(actual_usage)`(ARCH §7.3) | 语义同;settle 参数类型 int vs usage 需 M2 定稿 | 库内部端口,业务不触及 |
|
|
| `ConcurrencyLimiter`(ports.py:508-529,含 `mark_progress`/`progress_age_s`/`source_stats`) | 库限流端口(ARCH §7.3 同款契约) | 对等(§7.3 已列 mark_progress/progress_age) | 无 |
|
|
| `ProviderGate` + `ProviderGateDecision`(epoch/is_probe/probe_owner)(ports.py:406-491) | 库熔断端口 + epoch fencing(ARCH §7.4) | 状态机对等;probe TTL 见 §9-G5 | 无 |
|
|
| `TableLocator.locate(image)`(ports.py:577-580) | 业务端口保留,内部改调 `OcrLayoutPort.parse_layout` | 库返回 elements 全量;项目只取首个 table bbox | `table_locator.py` 改写取数逻辑 |
|
|
| 错误三分类 + `OcrResultInvalidError` + `ExtractionParseError` | 库四分类(ARCH §6.1) | Transient/SourceDead/RequestRejected 一一对应;`OcrResultInvalidError` 与 `ExtractionParseError` 合并进 `ResultInvalidError` | worker `_TERMINAL`/重试分支改 except 库异常 |
|
|
|
|
**ProviderUnavailableError 的专门论述**(errors.py:140-180): 这是项目独有的 **scope 级不可用**语义——不是"某次调用失败",而是"整个 VLM/OCR 作用域暂时无源可用,任务应延期且**不消耗业务失败预算**"。它携带 `scope`、7 种受控 `reason`(network_error/timeout/rate_limited/source_dead/circuit_open/retry_exhausted/stalled)、`retry_after_s`(读共享熔断门取全 scope 最早恢复时刻,governance.py:182-198)、`reasons`(per-source 失败原因字典)。消费点 `workers/tracking.py:406-428`:捕获后 `finish_deferred_attempt`(FAILED 但不计失败预算)并 `raise Retry(defer=retry_after_s 派生)`——**arq 级延期重投**。ARCHITECTURE §6.1 的承接:`CircuitOpenError`/`AllSourcesExhausted` 覆盖了"发生了什么",但**未定义结构化字段**(retry_after_s/reason/per-source reasons),而 tracking.py 的延期时长直接依赖 `retry_after_s`。承接方案:库两异常须携带 `retry_after_s`(熔断后端最早恢复时刻)与逐源原因;项目侧留 10 行翻译 shim 把库异常包成 `ProviderUnavailableError`(或 tracking.py 直接改 except 库异常)。字段缺失则该行为不可复现 → **⚠️ 架构缺口 G1**。
|
|
|
|
## 4. 调用点清单(grep 实测)
|
|
|
|
| 调用点 | 现状 | 迁移后形态 |
|
|
|---|---|---|
|
|
| `app/providers/extractors.py:101` | `await self._vlm.complete(cropped, instruction)` | 不变(经 `PgwVlmProvider` shim) |
|
|
| `app/providers/classifiers.py:33` | 同上 | 不变(同上) |
|
|
| `app/providers/vascular_positioner.py:329-330` | `scheduler.acquire(session_id)` 内 `vlm.complete` | 不变;公平调度留业务侧,包在库调用外 |
|
|
| `app/pipeline/extract_table.py:141` | `await self._locator.locate(data)` | 不变(`table_locator` 内部改调 `OcrLayoutPort`) |
|
|
| `app/workers/startup.py:123` | `container.build_extraction_pipeline(...)` | 装配体内部换库工厂(§5) |
|
|
| `app/workers/startup.py:196` | `container.build_vlm_provider(environ)` | 同上 |
|
|
| `app/workers/tracking.py:22,406-428` | `except ProviderUnavailableError` → 延期重投 | except 库异常(或经翻译 shim),**实质改动点之一** |
|
|
| `core/eval/judge.py:34-52` | 同步裸调 anthropic/openai SDK,零治理;JSON 解析手写 `find('{')`+`rfind('}')`(:112-123) | `_call_llm` 改 `asyncio.run(client.chat(...))`(Judge 端口是同步的、runner 无事件循环,桥接安全);解析换库 `JsonRepairStrategy`,**实质改动点之二** |
|
|
|
|
**"judge 是唯一需要业务代码实质改动的点?"——否。** 实质改动共两处:judge(新增治理能力,且默认 provider=anthropic,须经实验室 OpenAI 兼容中转网关接入,或等库增 Anthropic transport——D2 已预留端口但无里程碑)与 `workers/tracking.py` 的异常分支(§3 G1)。其余调用点全部躲在 `VlmProvider`/`TableLocator` 业务端口后,shim 保签名即零改动。**arq worker 与治理层的边界**清晰不变:worker(编排层,任务重试/死信/liveness)只透过这两个端口消费治理层,库恰好站在端口之下,边界无移动。
|
|
|
|
## 5. 配置迁移
|
|
|
|
**多源键名**: 库的 `{SCOPE}__{PROVIDER}__{N}__{FIELD}` 约定即源自本项目(config.py:107-125),`VLM__QWEN__1__*`、`OCR__MONKEY__1__*` 及字段名(BASE_URL/API_KEY/MODEL/MAX_CONCURRENCY/RPM/TPM/EST_TOKENS/TIMEOUT_S/TTFT_TIMEOUT_S/INTER_TOKEN_TIMEOUT_S/ENABLE_THINKING)**原样继承,零改名**。
|
|
|
|
| 项目键(.env.example 实测) | 库对应 | 差异 |
|
|
|---|---|---|
|
|
| `{SCOPE}__{PROVIDER}__{N}__{FIELD}` | 同名继承 | 无;`EST_TOKENS` 见 ⚠️ G2 |
|
|
| `{SCOPE}__GLOBAL__MAX_CONCURRENCY/RPM/TPM`(config.py:249-271) | 全局闸限额 | ARCH §9 未定义 GLOBAL 段命名,M2 设计须定(建议原样继承) |
|
|
| `{SCOPE}__SELECTOR`(round_robin/least_inflight) | `SourceSelector` 策略选择 | 命名待 M1/M2 定稿,建议继承 |
|
|
| `{SCOPE}__RETRY__MAX_ATTEMPTS/BACKOFF_BASE_S/BACKOFF_MAX_S` | RetryPolicy | ⚠️ G4:ARCH §9 只列平铺 `LLM_MAX_RETRIES` 等键,无 per-scope 形态 |
|
|
| `{SCOPE}__BREAKER__FAIL_THRESHOLD/COOLDOWN_S` | BreakerConfig | 同 G4 |
|
|
| `{SCOPE}__BACKPRESSURE__STALL_WINDOW_S/POLL_INTERVAL_S` | 配额满 wait+stall 判定参数 | 同 G4;项目还有废弃键守卫(config.py:374-378)可放弃 |
|
|
| (代码常量)`_LEASE_TTL_MS=1_500_000`(container.py:99) | permit 租约 TTL | 项目是常量+装配守卫,库应配置化并保留守卫(G6) |
|
|
| 新增 | `PGW_LIMITER_BACKEND=redis`、缓存 namespace/TTL、遥测后端等 | 净新增能力的配置 |
|
|
|
|
**装配对比**: container.py:225-320 手工装配(约 96 行:load_sources → 守卫 → Redis 池 → limiter → selector → retry/backpressure/breaker → gate → 每源 httpx client + invoker → GovernanceCore,失败逐层清理)。迁移后:
|
|
|
|
```python
|
|
# after(示意;实际 API 以 M1 设计文档为准)
|
|
vlm_client = GatewayClient.from_env(role="VLM") # 限流/熔断/重试/缓存/遥测/资源生命周期全内置
|
|
ocr_client = GatewayClient.from_env(role="OCR")
|
|
stack = ExtractionProviderStack(
|
|
vlm=PgwVlmProvider(vlm_client), # shim: 实现 app.ports.VlmProvider
|
|
table_locator=BusinessTableLocator(PgwOcrLayout(ocr_client)), # 几何映射留业务
|
|
)
|
|
```
|
|
|
|
`load_single_vlm_capacity`(config.py:274-299,position 调度器容量与治理并发口径一致性校验)保留,改读库的 SourceConfig 聚合结果。
|
|
|
|
## 6. 迁移步骤(生产服务,按 scope 灰度)
|
|
|
|
可灰度性依据:治理栈按 scope 独立装配(container.py:303-320),VLM 与 OCR 的 Redis key 前缀互不相交(`cclimit:{scope}:*`/`provider_gate:{scope}:*` vs 库前缀),且 worker 按角色分进程部署(startup.py:116/189)——**可以一个 scope 一个 scope 迁**。同一 scope 内禁止新旧栈并跑(限额会被双份计数),须整 scope 原子切换。
|
|
|
|
| 步骤 | 内容 | 验证 | 回滚点 |
|
|
|---|---|---|---|
|
|
| S0 | 基线:记录当前测试全绿证据;`chs` 环境安装 `polygateway[redis,...]`;git 提交回滚点 | `make test` 全绿 | git tag |
|
|
| S1(M2) | 库侧先行:`contracts_limiter.py` 5 条契约对库 RedisLimiter 跑通(真实 Redis);项目零改动 | 契约测试全过 | 无风险 |
|
|
| S2(M2) | 引入装配开关(env:`GOVERNANCE_IMPL=legacy\|pgw`,按 scope);实现 `PgwVlmProvider` shim + G1 异常翻译 | 单测 shim 映射;integration 双实现对拍 | 开关切回 legacy |
|
|
| S3(M2) | **VLM scope 灰度**:先 position worker(消费面最小,仅 vascular_positioner)观察,再 pipeline worker(extract) | e2e 提取/定位测试;遥测比对错误率与延迟 | 开关 + 旧代码未删 |
|
|
| S4(M3) | **OCR scope**:`table_locator.py` 改写消费 `OcrLayoutResult`,切 OCR scope | extract_table integration 全绿 | 同上 |
|
|
| S5(M3) | judge 收编:`_call_llm` 走库(异步桥接),解析换 `JsonRepairStrategy` | `tests/unit/test_eval_judge.py` | judge 独立文件,单独回滚 |
|
|
| S6 | 清场:执行 §2 删除清单、errors.py Provider 族删除、tracking.py 定稿、拆开关 | **原测试全绿(验收门)** + 独立 verifier | S5 前的 git 提交 |
|
|
|
|
## 7. 旧版行为审计(逐条:保留/替换/修复/有意放弃)
|
|
|
|
| 现有行为(证据) | 语义 | 处置 |
|
|
|---|---|---|
|
|
| 六道闸全有或全无,拒绝零副作用(scripts.py:6-33) | 任一闸不过 return 0,不加计数 | **保留**(契约测试守护) |
|
|
| token 预扣:成功按实际 settle、transient 按 est 保守结算、400/SourceDead 全额退款(governance.py:219, 252-254, 262-266) | 差异化结算防少计 | **保留** |
|
|
| settle 落回 acquire 时刻窗口(limiter.py:55-62) | 跨窗口退款不污染新窗口 | **保留** |
|
|
| Redis 服务器时钟生成窗口 id(limiter.py:95-98) | 多进程口径统一 | **保留**(ARCH §7.3 已承诺) |
|
|
| lease TTL 1500s + `timeout_s ≤ TTL` 装配守卫(container.py:99-112) | 防慢请求并发槽被提前回收 | **保留**(库配置化,守卫进 from_env,G6) |
|
|
| 单探针半开 + 探针租约 probe_ttl=max(timeout)+5s(scripts.py:96-105, container.py:274-276) | 防惊群 + 探针持有者死亡自动恢复 | **保留**(probe TTL 见 G5) |
|
|
| epoch fencing:迟到结果 applied=False(scripts.py:110-143) | 防旧世代污染新状态 | **保留**(ARCH §7.4 已承诺) |
|
|
| 取消/本地拒绝时 release_probe 不判健康(governance.py:230-243) | 探针让位但源保持 OPEN | **保留**(G5) |
|
|
| mark_progress + 双条件 stall(本地等够 且 全局无进展,双时钟各自比较)(governance.py:270-281) | 防冷启动/多进程误判 | **保留**(ARCH §7.3 已承诺) |
|
|
| 源冷却备忘:gate 拒绝后本地记冷却截止,期内不取 permit(governance.py:105-107, 129-131) | 治标:先 permit 后 gate 导致熔断期白烧 RPM | **修复**:库层序熔断在限流外(ARCH §4.3),开路源不再消耗限流;备忘作为选源优化保留(ARCH §7.4 已列) |
|
|
| `OcrResultInvalid` 熔断记成功、不换源、消耗任务失败预算(governance.py:237-239) | 坏结果≠坏服务 | **保留**(库 `ResultInvalidError`,ARCH §6.3) |
|
|
| RequestRejected 细分:provider 真实响应→gate 记成功;本地拒绝(如坏图格式)→仅释放探针(governance.py:228-236) | HTTP 响应证明服务活着 | **保留**(反向约束 M2) |
|
|
| Retry-After 仅支持秒数形态(invokers.py:127-141) | HTTP-date 返回 None | **有意放弃** date 形态(ARCH §6.2 同款) |
|
|
| 429 body 细分 insufficient_quota → SourceDead(invokers.py:144-166) | 欠费≠限流 | **保留**(ARCH §6.1 已承诺) |
|
|
| 零 content 提前结束 → Transient "early_eof";有 content 缺 [DONE] → 打捞并埋点 "missing_done"(invokers.py:306-313) | 线路级异常定性(D2 的核心价值) | **保留** |
|
|
| usage 缺失按 est_tokens 估算并标 `estimated`,不静默用 0(invokers.py:241-254) | 保守计量 | **保留**(`usage_source` 已进 ARCH §5.1;依赖 G2) |
|
|
| reasoning_content 刷新活性但不计入结果;ttft=首个任意 token(invokers.py:55-79, 336-364) | 防 thinking 模型被看门狗误杀 | **替换+增强**:库把 thinking 收进 `LLMResponse.thinking`(不再丢弃);活性语义必须保留(反向约束 M1) |
|
|
| enable_thinking=True 不注入参数、False 注入关闭参数(invokers.py:230-238) | 与 D11 注册表"声明注入方式"方向相反 | **替换**(provider 注册表须支持"注入关闭参数"形态) |
|
|
| 图片 magic bytes 探测,非 PNG/JPEG 抛 RequestRejected(invokers.py:116-123) | 本地快速拒绝 | **保留**(移入库 transport) |
|
|
| 换源重试失败跨源累计,`fails > max_attempts` → retry_exhausted(governance.py:255-260) | 重试预算是全局的不是 per-source | **保留**(M2 设计明确计数口径) |
|
|
| 整圈 gate 全拒 → circuit_open 快失败;配额满则 poll+jitter 重探(governance.py:210-214, 283-285) | 区分"全熔断"与"配额满" | **保留**(映射 CircuitOpenError vs wait,G1) |
|
|
| MonkeyOCR 两段协议(POST /parse → GET ZIP)+ bbox 有限性/顺序/退化校验(invokers.py:489-552, 427-479) | 数值防御 | **保留**(下沉库,ARCH §7.10 已承诺;首表选取留业务) |
|
|
| 无响应缓存 | 相同图+指令重复付费 | **替换**:迁移后净增缓存(风险见 §8) |
|
|
| judge 同步裸 SDK 零治理(judge.py:34-52) | 反面教材 | **修复**(§4) |
|
|
|
|
## 8. 行为差异与风险
|
|
|
|
| 差异/风险 | 论述与缓解 |
|
|
|---|---|
|
|
| **净增响应缓存**(最大行为变化) | 同图+同指令缓存命中对本项目语义:提取/分类是**确定性期望**任务(信度评估恰恰希望同输入同输出),命中可接受且省费;但科研上做"提取可靠性(信度)"实验时,重复采样**必须**绕过缓存——用 cache salt 按实验 epoch 强制重采样(ARCH §7.5)。医疗合规:缓存值含影像衍生文本,Redis 为项目私有实例,与现状(Redis 已存任务/调度数据)风险面一致。配置:`namespace="chsanalyzer:{scope}"`(单租户,无租户维度),TTL 必填;**灰度期建议 VLM scope 先关 CacheMW**,行为与旧版完全一致,稳定后再开 |
|
|
| 熔断/限流检查顺序变化 | 旧:先 permit 后 gate(有 RPM 白烧缺陷);新:熔断在限流外。行为上开路期间 RPM 消耗下降,属改善;但整体准入顺序依赖 G3 澄清 |
|
|
| thinking 内容保留 | 旧版丢弃 reasoning 文本,新版进 `LLMResponse.thinking` 与遥测——遥测库体积增大,属预期(遥测必录) |
|
|
| Lua 契约漂移 | 库重写 Lua 时语义漂移风险 → `contracts_limiter.py` 5 条契约(并发占用型/RPM/TPM 预扣结算退款/release 幂等/progress 新鲜度)先于项目迁移在库侧对真实 Redis 跑通(S1),并随库永久交付 |
|
|
| Redis key 迁移 | 新旧前缀不同,切换瞬间限流/熔断状态清零(短暂过放行)→ 选低峰切换;旧 key 有 TTL 自然过期,无需清理 |
|
|
| 双栈并跑超限 | 同 scope 新旧并跑限额双计 → 整 scope 原子切换 + 部署脚本禁止混版 worker(§6) |
|
|
|
|
## 9. 对库的反向约束清单(⚠️ = 架构缺口)
|
|
|
|
| # | 约束 | 里程碑 | 状态 |
|
|
|---|---|---|---|
|
|
| R1 | thinking token 刷新看门狗活性、ttft=首个任意 token(invokers.py:55-79) | M1 | ARCH §7.6 未明说,须写进 M1 设计 |
|
|
| R2 | SSE 异常细分(malformed_json/early_eof/missing_done)分类+埋点;missing_done 打捞不失败 | M1 | ARCH §6.2 部分覆盖,细目须进 M1 设计 |
|
|
| R3 | provider 注册表支持"注入关闭参数"形态(enable_thinking=False) | M1 | D11 方向相反,小修 |
|
|
| 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** | ⚠️ `CircuitOpenError`/`AllSourcesExhausted` 未定义结构化字段:须携 `retry_after_s`(全 scope 最早恢复时刻,读熔断后端)、reason(circuit_open/retry_exhausted/stalled)、per-source reasons——否则 tracking.py:406-428 的"延期重投不耗失败预算"不可复现(§3) | M2 | **架构缺口**,修订 §6.1 |
|
|
| **G2** | ⚠️ `est_tokens`(TPM 预扣常量 + usage 缺失兜底,config.py:55)不在 ARCH §7.7 SourceConfig 字段清单;§7.3 `try_acquire(source, est_tokens)` 的 est 来源未定义 | M2 | **架构缺口**,修订 §7.7 |
|
|
| **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 |
|
|
| **G5** | ⚠️ 半开探针租约 TTL(探针持有者死亡后 TTL 过期自动可再探,scripts.py:96-105)与 `release_probe` 操作未见于 ARCH §7.4(只写单探针/epoch fencing);缺失则探针死锁 | M2 | **架构缺口**,修订 §7.4 |
|
|
| G6 | 装配期不变式守卫:`timeout_s*1000 ≤ lease TTL`(container.py:105-112)、`stall_window ≥ 最慢源 ttft/timeout`(container.py:115-124)须进库 from_env;契约中 settle/release **幂等性**须写进 §7.3 契约文字 | M2 | 缺口(轻),M2 设计补 |
|
|
| G7 | judge 默认 provider=anthropic:走实验室 OpenAI 兼容中转即可接入,若须直连 Anthropic 官方 API 则需新 transport(D2 有端口无里程碑)——迁移前与人类确认网关路径 | M3 | 决策项,非缺口 |
|