24 KiB
24 KiB
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) |
三个必须专门论述的点:
- 四角色装配与
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)。 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 等价。- 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 行,节选骨架):
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 设计定稿为准):
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(per-source 键)与 health(逐源 dict,resp.ok 收紧为 2xx)已按 M3 设计 §10.1 落地,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 | ✅ 已闭(M3): SourceConfig.trust_env + {SCOPE}__{PROVIDER}__{N}__TRUST_ENV 键 M1 已落地,MonkeyOcrTransport 与 openai_compat 同款尊重;LAN 直连配 OCR__MONKEY__N__TRUST_ENV=false |
M3 | 已闭 |
| R10 | ✅ 已闭(M3): OcrClient.check_health() -> dict[str, bool] 逐源并发预检(2xx=True,取消穿透);启动门语义 = all(值),业务可逐源定位坏端点打日志 |
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 无读取方)。