200 lines
24 KiB
Markdown
200 lines
24 KiB
Markdown
# 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.py;TelemetryRecorder 删除,见 §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_env(EVOLVE 决策落地);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_env(build_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 key(vlm→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) | **保留**(归 TransientError,D2 明确此信号是选手写 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 → SourceDeadError;Retry-After 取大者(§6.2) |
|
||
| 8 | 缓存命中:独立 call_id、latency_ms=0、遥测必录 cache_hit=True(llm.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-R12,ARCHITECTURE 未覆盖) |
|
||
| 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 两 client;main.py 现状 breaker 也跨 client 共享)。`from_env` 按角色装配多个 client 时的后端共享语义 ARCHITECTURE 未定义 | M1 | **缺口** |
|
||
| R6 | 非流式快路径、cache salt、`gather_bounded` | M1 | 已覆盖(§7.1/§7.5/D5) |
|
||
| R7 | 库错误类型公共导出且可被业务 except(AgentLoop `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 整段进 SQLite(llm.py:330),§7.8 未规定,照搬会让库遥测继承 db 膨胀问题 | M1 | **缺口** |
|
||
|
||
---
|
||
|
||
*不确定项声明*: 库侧 `from_env(role=...)`、OCR 装配工厂的具体 API 名称以 M1/M3 设计文档定稿为准,本文 §5.2/§6 中相应伪代码届时同步修订;`telemetry.db` 是否存在项目外的离线分析脚本消费方未能实测(项目内 grep 无读取方)。
|