Files
PolyGateway/research-wiki/migrations/video-tree-trm5.md
T

200 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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) 嵌入 | **remote 路径删除,换 `polygateway.EmbeddingClient`**(Q3 已拍板纳入 M2,2026-07-20;库已交付);local(sentence-transformers)保留。迁移要点: 库为 async `embed(list[str]) -> EmbeddingResponse(list[list[float]])`——VT 同步调用点需自包同步壳(`asyncio.run` 或事件循环内 await),ndarray/tensor 转换自带 adapter(`np.asarray(resp.vectors, dtype=np.float32)`);装配传 `EMBED__NORMALIZE=true` 保 L2 归一化语义 |
| `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`) | `polygateway.EmbeddingClient.embed`(async,M2 已交付) | 同步→异步适配 + list[list[float]]→ndarray 转换留业务侧 adapter;normalize 走库开关 | M4 执行 |
---
## 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 无读取方)。