docs: add per-project migration documents as design constraints

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.
This commit is contained in:
2026-07-20 01:26:23 -04:00
parent 0df4d365a5
commit 583c012706
5 changed files with 548 additions and 1 deletions
+199
View File
@@ -0,0 +1,199 @@
# Video-Tree-TRM5 迁移文档(迁移即验收)
> **定位**: 本文是 `ARCHITECTURE.md §11.2` 的展开——库建成后如何合并进 Video-Tree-TRM5、替换其哪些内部组件。它既是 M4 迁移的操作指南,也是 M1-M3 设计的反向约束(库公共 API 必须让本文描述的迁移成立)。**与 ARCHITECTURE.md 冲突时以 ARCHITECTURE.md 为准。** 全部结论基于 2026-07-20 对 `reference/Video-Tree-TRM5/` 的代码实测(file:line 均为实测证据)。
---
## 1. 迁移目标与验收定义
**验收公式**: 删除清单文件全删 → import 替换为 `from polygateway import ...` → 项目原业务测试全绿(治理层自身单测随文件一并删除,由库的测试继任)。凡替换不掉的能力 = 库的边界缺口,回补后重验。
| 阶段 | 范围 | 验收 |
|---|---|---|
| **最小接入冒烟**M1 后) | 独立分支上,用 `GatewayClient.from_env()` 装配 SEARCH 角色单 client,替换 `tools/build_trees.py``_build_clients()` 跑通一个小批量建树(LLM+VLM 路径、缓存、遥测落库) | 冒烟脚本成功 + telemetry 落库可查 + 缓存命中率非零(重跑同视频) |
| **全量迁移**M4,需 M3 完成 OCR) | 删除清单全删、8 处装配点统一 `from_env`、OCR 换 `OcrTextPort`、AgentLoop 异常集合改造 | `make test` 业务测试全绿 + 一次完整 train run 对照旧遥测指标无回归 |
---
## 2. 现状盘点
行数为 `wc -l` 实测。治理相关约 2555 行,其中**可删 1296 行**。
| 文件 | 行数 | 职责一句话 | 迁移后命运 |
|---|---|---|---|
| `adapters/llm.py` | 595 | `GovernedLLMClient`:熔断→缓存→重试+SSE 流式→写缓存→遥测五层内联于一个 chat 方法 | **删除**(库 client+middleware 继任) |
| `adapters/breaker.py` | 85 | 进程内熔断器(时钟注入、半开单探针、force_open) | **删除**(库 `backends/memory`,本身即移植蓝本) |
| `adapters/streaming.py` | 131 | 三层活性看门狗纯函数 | **删除**(库 `streaming.py`,近原样移植) |
| `adapters/redis_cache.py` | 128 | sha256 内容寻址响应缓存,TTL 校验、静默降级 | **删除**(库 `backends/redis` 缓存继任) |
| `adapters/telemetry.py` | 229 | SQLite 遥测(单连接+锁+WAL+幂等+to_thread+降级) | **删除**(库 `telemetry/sqlite.py` 继任) |
| `adapters/ocr.py` | 128 | MonkeyOCR `/ocr/text` **裸调**:同步 requests、线程轮询双端点、单帧失败跳过 | **删除**(换库 `OcrTextPort`,升级为全治理;行过滤/拼接逻辑上移业务侧,见 §4) |
| `adapters/vlm.py` | 131 | base64 编码图片并注入最后一条 user message,委托 LLM client | **保留改写**(业务侧封装:`_inject_images` 保留,委托对象换成库 client) |
| `adapters/embedding.py` | 184 | local(sentence-transformers)/remote(OpenAI SDK) 嵌入 | **保留**Q3 未决;remote 实测**无任何重试**且为同步调用,见 §9-R11) |
| `adapters/baseline_diagnosis_store.py` | 183 | 基线诊断结果 SQLite 存储(业务数据) | **保留**(业务侧) |
| `core/protocols.py` | 69 | `LLMProvider`/`VLMProvider`/`TelemetryRecorder` 三 Protocol | **改写**LLMProvider 由库满足;VLMProvider 指向改写后的 vlm.pyTelemetryRecorder 删除,见 §3 |
| `core/types.py` | 182 | `LLMResponse`(11 字段) + 业务类型 | **改写**LLMResponse 改为 re-export 库类型;其余保留) |
| `main.py` `_build_adapters()` | 337(全文件) | Composition Root:手写装配 breaker/cache/telemetry/llm×2/vlm/embed/ocr | **改写**(按角色 `from_env` |
| `app/ports.py` | 173 | 应用层端口,含 `OCRProvider.transcribe_frames` | **改写**OCRProvider 保留但实现改为包装库 `OcrTextPort` 的业务适配器) |
| `tools/{build_trees,repair_trees,generate_questions}.py``app/harness/video_split_cli.py` | 368/492/1462/824 | 各自复制一份手写装配(共 6 处 `GovernedLLMClient(...)` 构造) | **改写**(装配段换 `from_env`,业务编排保留) |
| 治理层单测 `tests/unit/test_{governed_llm,streaming,vlm_adapter,breaker,ocr_adapter,redis_cache,telemetry,infra_settings}.py` 等 | — | 治理栈自身测试 | **删除**(库测试继任);业务测试(假 LLMProvider 注入)保留为验收基准 |
---
## 3. 组件替换映射表(签名实测对比)
| 项目组件 | 库对应物 | 签名兼容性(实测) | shim 形态 |
|---|---|---|---|
| `LLMProvider.chat(messages, *, session_id=None, parent_call_id=None, cache_salt=None)` (`core/protocols.py:22-29`) | `GatewayClient.chat()` | ARCHITECTURE §5.2/§7.8/§7.5 含这三个语义(链路 id 调用方传入、salt 进 key),但 **§5.1 未固化 `chat()` 便捷方法的 kwargs 形态** | 库若原生支持同名 kwargs 则零 shim(首选,见 §9-R1);否则 3 行包装函数 |
| `VLMProvider.chat_with_images(messages, images: list[str\|Path], ...)` (`core/protocols.py:36-44`) | **无库端口**VLM 封装属业务侧,ARCHITECTURE §11.2 | 库 `chat()` 须原生接受多模态 content parts`image_url` data URL 数组,`adapters/vlm.py:112-130` 组装格式) | `vlm.py` 改写为包装库 client:保留 `_encode_image`/`_inject_images``self._llm.chat(...)` 换成库调用,对 33 处业务调用点零改动 |
| `TelemetryRecorder.record_llm_call(15 个关键字参数)` (`core/protocols.py:51-69`) | 库内部遥测(单一 helper 铁律) | 业务侧**无人调用** `record_llm_call`grep 实测仅 Protocol 定义与 adapter 实现);`Runner` 注入 telemetry 后从未使用(`runner.py:738` 赋值即终点,死注入) | 无需 shim:删 Protocol、删 Runner 参数 |
| `LLMResponse`(11 字段) (`core/types.py:19-29`) | 库 `LLMResponse`(§5.1 超集) | 字段逐一比对**完全一致**content/thinking/model/provider/prompt_tokens/completion_tokens/latency_ms/ttft_ms/max_inter_token_ms/cache_hit/call_id);库新增 source_name/cost/usage_source 只增不删 | `core/types.py` 改 re-export`from polygateway import LLMResponse` |
| `CircuitOpenError` (`adapters/llm.py:36`) | 库 `CircuitOpenError`(§6.1) | 业务侧无 except 该异常(grep 实测),仅治理层内部 | 无需 shim |
| `MonkeyOCRClient.transcribe_frames(frame_paths) -> str` (`adapters/ocr.py:88`) | `OcrTextPort.recognize_text(bytes)`(§7.10 | **不兼容**:项目端口收路径列表、返回拼接文本、单帧失败跳过;库端口收单帧 bytes、失败抛异常 | 业务适配器(约 30 行):读文件→逐帧 `recognize_text`→行过滤去重→`"帧N: ..."` 拼接→单帧异常捕获跳过;实现 `app/ports.py:OCRProvider` 不变,`vision.py:105` 调用点零改动 |
| `EmbeddingProvider`(同步 `embed()`) (`app/ports.py:17-36`) | 暂无(Q3 开放) | 若 M2 纳入:库必为异步端口,同步调用点需适配 | 本次迁移不动 |
---
## 4. 调用点清单(grep 实测,33 处 await
业务调用点全部经 Protocol,**迁移后形态不变**(前提是 §3 的 shim 策略成立);需要改动的只有装配点与两个特殊点。
| 分组 | 调用点 (file:line) | 迁移后形态 |
|---|---|---|
| `llm.chat` (16 处) | `core/agent/loop.py:336``core/evolution/evolve.py:566,881,1074,1162``core/evolution/diagnose.py:389,982,1917``app/tree/video_builder.py:1117``app/search/summarizer.py:236``app/tree/repair/{regenerator.py:436,480, supplement.py:460}``app/harness/momentum.py:163``app/question_gen/{gates.py:354,389,423, pipeline_v2.py:753}` | 不变(库 client 直接满足签名) |
| `vlm.chat_with_images` (14 处) | `app/tree/video_builder.py:940,1047,1084``app/search/vision.py:126,148``app/tree/repair/regenerator.py:390``app/question_gen/{adversarial_filter.py:393, distractor_selector.py:150,185, gates.py:314,322, synthesizer.py:720, generator_v2.py:424}` | 不变(改写后的 vlm.py 继续满足 VLMProvider |
| `ocr.transcribe_frames` (1 处) | `app/search/vision.py:105`(外层已有降级边界 105-107:失败 warning 不注入) | 不变(业务适配器满足 OCRProvider);底层升级为多源+重试+熔断 |
| 装配点 (8 处构造) | `main.py:105`(经 `_make_llm` 实例化于 122,130)、`app/harness/video_split_cli.py:328``tools/build_trees.py:216,232``tools/repair_trees.py:154,172``tools/generate_questions.py:265,988,1007` | 全部换 `from_env` 按角色装配(§5 |
**三个必须专门论述的点**
1. **四角色装配与 `evolve_llm = llm` 别名**`main.py:128`):`.env` 定义了 SEARCH/JUDGE/VL/EVOLVE 四组配置(`.env.example:6-23`),但实测 `InfraSettings` 只读 search/vl/evolve 三组(`main.py:24-34`)且 **EVOLVE_LLM_\* 读入后从未使用**——`_build_adapters` 直接 `evolve_llm = llm` 静默共享(配置面看似独立实则死配置);JUDGE 组不在 InfraSettings 里,仅 `tools/generate_questions.py:1008-1010` 直接 `os.environ` 裸读。迁移后按 §7.7 逻辑角色装配,**共享必须显式**:EVOLVE 要么显式声明共享 SEARCH client,要么启用独立配置——需人类拍板(行为变化见 §8)。
2. **`tools/build_trees.py` 跨视频共享 semaphore**`api_sem = asyncio.Semaphore(api_concurrency)`build_trees.py:294`TREE_BUILD_API_CONCURRENCY=16`)注入 `VideoTreeBuilder(api_semaphore=)`,包裹 LLM 与 VLM **两个 client 的每次调用**video_builder.py:365-366)。迁移后由库限流承接而非 `gather_bounded`——`gather_bounded` 限的是任务列表并发,这里限的是"跨两个 client 的全局在途调用数",语义对应库的**全局并发闸**。前提:SEARCH 与 VL 两个 client 共享同一 limiter 后端实例(⚠️ 见 §9-R5);配额满行为配 `wait` 才与 semaphore 等价。
3. **AgentLoop 双层重试**`core/agent/loop.py:84-101,295-315`):步级重试 `step_retries=2, delays=(20,40)`,捕获 `(TimeoutError, OSError)`——即"穿透治理层的瞬时异常"。库单层重试原则(§7.2)下,transport 会把这些异常翻译为领域错误,**`TimeoutError/OSError` 将不再穿出,原配置下步级重试静默失效**(TransientError 不是 OSError 子类)。去留论述:这层的语义实为"任务步兜底"(治理层重试预算耗尽后再给整步一次机会),属业务侧任务级重试,库允许在库外包。**建议保留但必须改造**:利用 AgentLoop 已参数化的 `retryable_exceptions` 注入库的 `(TransientError, AllSourcesExhausted)`;若判定治理层预算已足够则删除该层——二选一,禁止保留原异常集合(静默失效 = 隐性行为删除)。
---
## 5. 配置迁移
### 5.1 .env 键名映射
| 旧键 (`.env.example` 实测) | 库键(形态待 M1 定稿,按 §9 约定) | 语义差异 |
|---|---|---|
| `SEARCH_LLM_{MODEL,BASE_URL,API_KEY}` | `SEARCH__{PROVIDER}__1__{MODEL,BASE_URL,API_KEY}` | 单源→源列表长度 1 的特例;provider 由键名显式给出,消灭 `model.split("-")[0]` 猜测(main.py:109 |
| `JUDGE_LLM_*` | `JUDGE__...` | 现状仅被 generate_questions.py 裸读 os.environ;迁移后归入统一装配 |
| `VL_LLM_*` | `VL__...` | 同 SEARCH |
| `EVOLVE_LLM_*` | `EVOLVE__...` **或删除** | 现状为死配置(§4 点 1);显式共享则删键,独立则启用——人类拍板 |
| `LLM_TIMEOUT` / `LLM_MAX_RETRIES` / `LLM_RETRY_BASE_DELAY` / `LLM_RETRY_MAX_DELAY` / `LLM_CIRCUIT_BREAKER_{THRESHOLD,COOLDOWN}` / `LLM_TTFT_TIMEOUT` / `LLM_INTER_TOKEN_TIMEOUT` | **同名沿用**(§9 承诺) | 熔断阈值 `max(cfg, concurrency*2)` 从 .env 注释手动约定(.env.example:44)变为库内自动计算(§7.4) |
| `REDIS_URL` / `REDIS_CACHE_TTL` | 库缓存后端配置(TTL>0 校验继承,redis_cache.py:27-31 | 语义同 |
| (无) | **缓存 namespace(新增必填)** | §7.5 key 公式新增字段,建议 `video-tree-trm5` |
| `MONKEY_OCR_URLS`(逗号列表) | `OCR__MONKEY__{1,2}__BASE_URL` | 逗号列表→多源配置;轮询升级为选源策略 |
| `TREE_BUILD_API_CONCURRENCY` | 库全局并发限额键 | 语义:跨 SEARCH+VL 两 client 的全局在途上限(⚠️ §9-R5) |
| `ASR_*`Groq whisper | **删除** | 死配置零实现(D10 已确认不作需求证据) |
| `EMBED_API_KEY/URL` | 保留业务侧 | Q3 未决 |
| `no_proxy`/`NO_PROXY` | 保留环境级;OCR 的 `trust_env=False` 需库支持 | ⚠️ §9-R9 |
### 5.2 装配代码 before/after
Before`main.py:_build_adapters`,手写约 110 行,节选骨架):
```python
breaker = CircuitBreaker(fail_threshold=..., cooldown_s=...)
cache = RedisResponseCache(redis=aioredis.from_url(...), ttl_s=ttl) if settings.redis_url else None
telemetry = SQLiteTelemetryRecorder(Path("logs/telemetry.db"))
def _make_llm(model, base_url, api_key, *, thinking):
return GovernedLLMClient(model=..., provider=model.split("-")[0], breaker=breaker,
cache=cache, telemetry=telemetry, ...) # 15 个参数
llm = _make_llm(settings.search_llm_model, ..., thinking=True)
evolve_llm = llm # 静默别名,EVOLVE_LLM_* 配置被忽略
vlm = GovernedVLMClient(_make_llm(settings.vl_llm_model, ..., thinking=False))
ocr = MonkeyOCRClient(urls=settings.monkey_ocr_urls.split(","))
```
After(伪代码,具体 API 以 M1 设计定稿为准):
```python
from polygateway import GatewayClient
llm = GatewayClient.from_env(role="SEARCH")
evolve_llm = llm # 共享必须显式:或 from_env(role="EVOLVE")
vlm = GovernedVLMClient(GatewayClient.from_env(role="VL")) # 业务侧 base64 封装保留
ocr = FrameOcrAdapter(OcrTextPort_from_env()) # 业务适配器包装库 OCR 端口
# breaker/cache/telemetry/limiter 全部由 from_env 按配置内建,共享后端见 §9-R5
```
---
## 6. 迁移步骤(每步一个 commit 作回滚点)
| # | 步骤 | 验证方式 | 回滚 |
|---|---|---|---|
| 0 | M1 后最小接入冒烟(§1,独立分支) | 冒烟脚本 + 遥测/缓存实测 | 丢弃分支 |
| 1 | feature 分支;`.env` 增库键(旧键暂双存) | `from_env` 装配冒烟 | git revert |
| 2 | `core/types.py` LLMResponse 改 re-export`core/protocols.py` 删 TelemetryRecorder、LLMProvider 指库 | `make test`(类型层面无行为变化) | 单 commit revert |
| 3 | `adapters/vlm.py` 改写为包装库 client`main.py:_build_adapters` 换 from_envEVOLVE 决策落地);Runner 删 telemetry 死注入参数 | `make test` + `--mode infer` 小样本跑通 | 单 commit revert |
| 4 | AgentLoop `retryable_exceptions` 注入库异常(或删除步级重试,按 §4 点 3 决策) | 断网/假 transport 注错的集成测试证明步级重试可触发 | 单 commit revert |
| 5 | 删除 `adapters/{llm,breaker,streaming,redis_cache,telemetry}.py` 及其单测 | `make test` 全绿;grep 确认无残留 import | 单 commit revert |
| 6 | 四个工具/CLI 装配点换 from_envbuild_trees 删 semaphore 改库全局并发闸;generate_questions 删 JUDGE 裸读) | 每个工具 `--limit 1` 级小跑 | 逐工具 commit |
| 7 | M3 后)`adapters/ocr.py` 删除,业务适配器接 `OcrTextPort` | vision.py 路径集成测试 + OCR 注入率遥测对照 | 单 commit revert |
| 8 | 一次完整 train run 对照旧遥测(准确率/成本/缓存命中率);删旧 .env 键 | 指标无回归 | 保留分支不合并 |
---
## 7. 旧版行为审计(逐条:保留/替换/修复/有意放弃)
| # | 现有行为(含非功能行为,file:line) | 迁移后 |
|---|---|---|
| 1 | VLM 响应**也走缓存**,且 base64 data URL 整段进 sha256 keyvlm→llm 委托 + redis_cache.py:69-73 全量 messages 进 hash | **替换**:多模态 part 先摘要再 hash(§7.5)——key 全变,见 §8 风险 1 |
| 2 | `cache_salt` 仅非 None 才进 key payload(保证旧键不失效,redis_cache.py:63-71 | **有意放弃**:库 key 公式重构,无旧键兼容义务 |
| 3 | SSE 流耗尽未收 `[DONE]``_SseAnomaly("truncated_no_done")` 判瞬时、不写缓存(llm.py:584-586 | **保留**(归 TransientErrorD2 明确此信号是选手写 transport 的理由) |
| 4 | qwen `<think>` 剥离 + deepseek `reasoning_content`thinking 注入靠 `"qwen" in provider` 字符串猜(llm.py:130-164,357 | **替换**D11 provider 注册表,行为等价、机制显式 |
| 5 | 熔断按 provider 字符串计数;main.py 中 SEARCH 与 VL **共享同一 breaker 实例**main.py:80,111),provider 不同故计数分开 | **替换**:按 source_name 计数(§7.4),语义更细 |
| 6 | 半开只放一个探针防惊群(breaker.py:44-48);401/403 `force_open` 一击即熔(llm.py:410-411 | **保留**(库蓝本即此实现) |
| 7 | 429 一律判瞬时重试,**不细分 insufficient_quota**;无 Retry-After 解析(llm.py:169,185-189 | **修复**429+insufficient_quota → SourceDeadErrorRetry-After 取大者(§6.2 |
| 8 | 缓存命中:独立 call_id、latency_ms=0、遥测必录 cache_hit=Truellm.py:309-341 | **保留**(§4.4 同款) |
| 9 | 遥测降级不冒泡(初始化失败 conn=None、写失败 warning 丢弃,telemetry.py:77-98,126-153);`INSERT OR IGNORE` 幂等 | **保留**(降级方向铁律同向) |
| 10 | 装配时 Redis 不可用 → 降级无缓存(main.py:91-97);TTL≤0 拒绝启动(redis_cache.py:27-31 | **保留** |
| 11 | 重试全部落在同一源退避等待(单源无换源) | **替换**:退避与换源结合(§4.4 步 3),单源配置下行为退化为等价 |
| 12 | 遥测 messages 字段全量 JSON 落 SQLite——**VLM 调用的 base64 图片整段进 telemetry.db**llm.py:330,391 `json.dumps(messages)` | **修复建议**:库遥测对多模态 part 摘要(⚠️ §9-R12ARCHITECTURE 未覆盖) |
| 13 | OCR:同步 requests + 线程局部 Session + `trust_env=False` 绕代理(ocr.py:41-47);双端点加锁轮询(ocr.py:108-109);单帧失败跳过返回空(ocr.py:117-119);行级过滤(len≤1)与帧内去重、`"帧N: "` 拼接(ocr.py:120-128);`check_health` 启动预检(ocr.py:64-70 | 传输/轮询/重试**替换**(库多源全治理);过滤/拼接/单帧跳过**保留业务侧**(§3 适配器);trust_env 与 health 见 ⚠️ §9-R9/R10 |
| 14 | `tools/build_trees.py` 实测 `cache=None`build_trees.py:223,239)——建树不走缓存,断点续跑靠 `progress.json`+完整性双重跳过(build_trees.py:273-279 | 断点续跑纯业务**保留**;迁移后建树可统一开启缓存(行为增强,重跑段内调用免费) |
| 15 | 三处写死 `stream=True`,短请求也走 SSE+看门狗(llm.py:508 | **替换可选**:库放开非流式快路径(§7.1),默认行为不变 |
| 16 | `Runner` 注入 telemetry 从未使用(runner.py:738 死注入);EVOLVE_LLM_* 死配置(§4 点 1 | **修复**(顺带清理,属迁移必要改动非 gold-plating |
| 17 | AgentLoop 步级重试捕 `(TimeoutError, OSError)`loop.py:92 | **改造或删除**(§4 点 3;保留原样 = 静默失效,禁止) |
| 18 | `CancelledError` 全栈穿透(loop.py:280-282 明确不捕获;治理层无 `except BaseException` | **保留**(库铁律同向) |
---
## 8. 行为差异与风险
| # | 差异/风险 | 影响与缓解 |
|---|---|---|
| 1 | **缓存 key 全量变化**key 前缀、namespace、多模态摘要、salt 进 payload 方式均变)→ 迁移瞬间全量缓存失效 | 一次性重付费;TTL 本为 7 天(`REDIS_CACHE_TTL=604800`)自然滚动。缓解:迁移窗口选在训练间隙,首个 run 按无缓存成本预算 |
| 2 | OCR 从裸调升级全治理:失败行为从"静默跳过"变"重试→换源→熔断→最终抛异常" | 延迟分布变化(重试引入等待);`vision.py:105-107` 降级边界保留,最终失败仍不中断推理。收益:双端点一台挂掉不再损失 50% 帧证据 |
| 3 | 429 语义变化:insufficient_quota 从"白烧重试预算"变"立即熔断换源" | 行为更优;单源配置下表现为快速失败——排查配额问题更快但对"等配额恢复"场景需配 wait |
| 4 | semaphore→限流闸:等待语义需显式配 `wait`,且依赖跨 client 共享后端(§9-R5 未闭合前 build_trees 迁移被阻塞) | M1 设计必须先解决 R5,否则该工具只能临时保留 semaphore |
| 5 | 遥测 schema 变化:表结构/字段名(model_name→model)与新增字段 | grep 实测项目内无 telemetry.db 读取方(仅写入),风险低;旧 db 留档即可 |
| 6 | 熔断阈值改库内自动 `max(cfg, concurrency*2)` | 与 .env 注释的手动约定等价,风险低 |
| 7 | EVOLVE 角色决策(共享或独立)可能改变进化步的模型/配额行为 | 迁移前人类拍板并写进配置注释 |
---
## 9. 对库的反向约束清单(本文档最重要产出)
| # | 约束 | 里程碑 | 状态 |
|---|---|---|---|
| R1 | `chat()` 便捷方法必须接受 `session_id` / `parent_call_id` / `cache_salt` 关键字参数(33 处调用点零改动的前提) | M1 | 语义已覆盖(§7.5/§7.8),**签名形态需 M1 设计固化** |
| R2 | `chat()` 原生接受多模态 content parts 数组(`image_url` data URL),VLM 封装才能留业务侧 | M1 | 已覆盖(§2.3/§11.2 |
| R3 | `LLMResponse` 11 字段只增不删不改名 | M1 | 已覆盖(§5.1,实测一致) |
| R4 | 逻辑角色 `from_env(role=...)` 须支持 SEARCH/JUDGE/VL/EVOLVE 四角色 + 显式共享声明 | M1 | 已覆盖(§7.7) |
| R5 | ⚠️ **架构缺口**:多角色 client 须能**共享同一限流/熔断状态后端**(`TREE_BUILD_API_CONCURRENCY` 的全局并发闸横跨 SEARCH+VL 两 clientmain.py 现状 breaker 也跨 client 共享)。`from_env` 按角色装配多个 client 时的后端共享语义 ARCHITECTURE 未定义 | M1 | **缺口** |
| R6 | 非流式快路径、cache salt、`gather_bounded` | M1 | 已覆盖(§7.1/§7.5/D5 |
| R7 | 库错误类型公共导出且可被业务 exceptAgentLoop `retryable_exceptions` 注入 `TransientError`/`AllSourcesExhausted`) | M1 | 基本覆盖(§6),导出面需 M1 确认 |
| R8 | 缓存 namespace 必填对单项目是新增负担 | M1 | 已覆盖(§7.5);建议 from_env 支持项目名默认 |
| R9 | ⚠️ **架构缺口**MonkeyOCR transport 需 per-source `trust_env=False`LAN 直连绕代理,ocr.py:46 实测),§7.10 未提代理/trust_env 配置 | M3 | **缺口** |
| R10 | ⚠️ **架构缺口**OCR 端点健康预检(`check_health`ocr.py:64-70,A/B 实验启动门消费)——库多源配置内聚后业务侧拿不到端点列表自检,库需暴露健康检查或等价能力 | M3 | **缺口** |
| R11 | ⚠️ **架构缺口/勘误**embedding 去向(Q3)——实测 `RemoteEmbeddingProvider` **无任何重试**embedding.py:146-172,同步 OpenAI SDK 裸调),ARCHITECTURE §13-Q3"各有一套独立重试实现"对 Video-Tree 不成立(无治理反而更需要进库);且现端口为同步 `embed()`,库若 M2 纳入必为异步端口,业务调用点需适配 | M2 | **缺口**Q3 决策输入) |
| R12 | ⚠️ **架构缺口**:遥测 `messages` 字段对多模态 part 应摘要——现状 base64 整段进 SQLitellm.py:330),§7.8 未规定,照搬会让库遥测继承 db 膨胀问题 | M1 | **缺口** |
---
*不确定项声明*: 库侧 `from_env(role=...)`、OCR 装配工厂的具体 API 名称以 M1/M3 设计文档定稿为准,本文 §5.2/§6 中相应伪代码届时同步修订;`telemetry.db` 是否存在项目外的离线分析脚本消费方未能实测(项目内 grep 无读取方)。