# Video-Tree-TRM5 架构设计 **版本** v1 · **状态** 提案 · **读者** 工程团队 本文分两部分。**Part 1「架构」与具体技术栈无关**,只描述边界、依赖方向、接缝与核心算法——它是长期承诺,预期能跨越多次技术选型而不变。**Part 2「韧性与运维」描述 LLM 调用治理与可观测规范**——它随实现演进,但当前约束所有实现必须满足。 --- ## §1 核心定位 **项目目标**:在层次化视频树上构建可自我进化的搜索 Agent + 可训练的递归检索器,实现长视频理解。目标会议 EMNLP 2026。 **系统类比**——自进化循环对标 PyTorch 训练: | PyTorch | 本项目 | 代码位置 | |---------|--------|----------| | DataLoader | 出题 question_gen | `app/question_gen/loader.py` | | model.forward() | 推理 inference | `app/harness/inference.py` + `core/agent/loop.py` | | loss.backward() | 诊断 diagnose | `core/evolution/diagnose.py` | | optimizer.step() | 进化 evolve | `core/evolution/evolve.py` | | nn.Parameter | Skills + Prompts(版本化) | `store/skills/`, `store/prompts/` | | training loop | 外层循环 runner | `app/harness/runner.py` | | checkpoint | Store 版本快照 | `app/harness/workspace.py` | 只有 inference 是 agent-controlled(LLM 自主调工具),其余三步是 code-controlled workflow。 **三大模块**: | 模块 | 目录 | 一句话定义 | |------|------|-----------| | 建树 | `app/tree/` | 离线预处理——VLM 生成三层 TreeIndex(L1段落→L2片段→L3帧),支持字幕注入和后增强 | | 训练 | `app/harness/` + `core/` | 自进化循环(推理→诊断→进化)+ RecursiveRetriever 参数训练 | | 新题构建 | `app/question_gen/` | 生成 Video-MME 风格训练题,原始 benchmark 作 held-out 泛化评测 | --- ## §2 分层架构(Clean Architecture) ### 2.1 四层依赖规则 ```mermaid flowchart TB subgraph 外部实现层 AD_LLM["adapters/llm.py\nGovernedLLMClient"] AD_VLM["adapters/vlm.py"] AD_EMB["adapters/embedding.py"] AD_CACHE["adapters/redis_cache.py"] AD_TEL["adapters/telemetry.py"] AD_OCR["adapters/ocr.py"] AD_ASR["adapters/asr.py"] end subgraph 应用层 TREE["app/tree/\n建树模块"] HARNESS["app/harness/\n训练循环"] QGEN["app/question_gen/\n新题构建"] SEARCH["app/search/\nAgent 装配"] RET["app/retriever/\n可训练检索器"] PORTS["app/ports.py"] end subgraph 可提取内核 AGENT["core/agent/\nAgentLoop 引擎"] EVO["core/evolution/\n诊断+进化引擎"] CPROTO["core/*/protocols.py"] end AD_LLM & AD_VLM & AD_EMB & AD_CACHE & AD_TEL -->|实现| CPROTO & PORTS HARNESS --> AGENT & EVO SEARCH --> AGENT TREE & RET & QGEN --> PORTS AGENT & EVO -->|定义| CPROTO ``` 依赖只能向内,源码 import 方向严格单向。内层不认识外层:`core/` 不知道自己被哪个 `app/` 模块组合,`app/` 不知道 Protocol 背后是哪个 adapter。判据:**任意一个 `core/` 模块,搬到没有 adapters 的环境里,用假实现替换 Protocol 即可原样运行**。 ### 2.2 模块交互全景 ```mermaid flowchart TD CLI["main.py CLI"] --> RUNNER["app/harness/runner.py\n训练循环编排"] CLI --> BUILD["app/tree/video_builder.py\n建树"] CLI --> QGEN["app/question_gen/loader.py\n新题构建"] CLI --> TRAIN_RET["app/retriever/train.py\n检索器训练"] RUNNER --> INF["app/harness/inference.py\n推理 step"] RUNNER --> DIAG["core/evolution/diagnose.py\n诊断"] RUNNER --> EVOL["core/evolution/evolve.py\n进化"] RUNNER --> BATCH["app/harness/batching.py\nmini-batch"] RUNNER --> GATE["core/evolution/gate.py\nCE-Gate"] RUNNER --> WS["app/harness/workspace.py\nStore + Workspace"] INF --> LOOP["core/agent/loop.py\nAgentLoop"] LOOP --> SEARCH["app/search/\nprompt + skills"] LOOP --> LLM["adapters/llm.py\nGovernedLLMClient"] BUILD --> VLM["adapters/vlm.py"] BUILD --> EMB["adapters/embedding.py"] ``` ### 2.3 目录结构 ```text project_root/ ├── core/ # 可提取内核(不依赖 app/、adapters/) │ ├── protocols.py # 共享端口:LLMProvider, VLMProvider, TelemetryRecorder │ ├── agent/ # 【可提取包】AgentLoop 引擎 │ │ ├── loop.py # Thinking+JSON 推理循环,pluggy hook │ │ ├── types.py # Step, LoopResult │ │ └── protocols.py # Agent 专属:ToolDispatcher, AgentLoopSpec │ ├── evolution/ # 【可提取包】诊断+进化引擎 │ │ ├── diagnose.py # 两阶段诊断管线 │ │ ├── evolve.py # patch/rewrite 进化 │ │ ├── gate.py # CE-Gate e-process │ │ ├── validate.py # 块顺序验证 │ │ ├── types.py # DiagnosisResult, EvolutionRecord, ... │ │ └── protocols.py # Evolution 专属:SkillStore, PromptStore, RunLog │ └── types.py # 跨模块共享类型 │ ├── app/ # 应用层(组合 core + adapters,领域特化) │ ├── tree/ # 模块1:建树 │ │ ├── index.py # TreeIndex 数据结构(L1/L2/L3Node + Card) │ │ ├── video_builder.py # VideoTreeBuilder(L2 轴心 + asyncio 并发) │ │ ├── config.py # TreeConfig 配置 dataclass │ │ ├── subtitle.py # SRT 解析 + Voronoi 字幕分配 │ │ ├── verify.py # 交叉校验(entities/visible_text) │ │ ├── environment.py # 树环境语义搜索(分块 embedding + 祖先去重) │ │ └── repair/ # 后修复管线 │ │ ├── detector.py # 缺陷检测(空字段/缺帧/时间间隙) │ │ ├── regenerator.py # VLM 重描述 + 自底向上级联修复 │ │ └── supplement.py # Q&A 反向补全 │ ├── harness/ # 模块2:训练 harness │ │ ├── runner.py # 训练循环编排(对标 Trainer) │ │ ├── inference.py # 推理 step │ │ ├── batching.py # mini-batch 构建 │ │ ├── pools.py # 三池切分(数据加载已移至 question_gen/loader.py) │ │ ├── gate_ladder.py # 信息阶梯 │ │ ├── momentum.py # 慢速动量 │ │ ├── config.py # RunConfig │ │ ├── log.py # HarnessLog (SQLite) │ │ └── workspace.py # Store + Workspace 版本管理 │ ├── question_gen/ # 模块3:出题(加载 + 采样 + 未来 LLM 生成) │ │ └── loader.py # benchmark 加载、分层采样 │ ├── search/ # 搜索 Agent 装配 │ │ ├── prompt.py # PromptManager(prompt 加载与拼装) │ │ ├── skills.py # SkillRegistry + discover_skills │ │ ├── summarizer.py # 两轮 LLM 摘要(view_node / search_similar 用) │ │ ├── vision.py # observe_frame(VLM 两轮 + OCR 注入) │ │ └── tools.py # SearchToolDispatcher(实现 ToolDispatcher) │ ├── retriever/ # 可训练检索器 │ │ ├── recursive.py # RecursiveRetriever (CrossAttention+ACT) │ │ ├── losses.py # NavigationLoss + ACTLoss │ │ └── train.py # 两阶段训练入口 │ └── ports.py # 应用层特有端口 │ ├── adapters/ # 外部实现层 │ ├── llm.py # GovernedLLMClient(四层治理栈) │ ├── streaming.py # 流式三层看门狗(TTFT/inter_token/total) │ ├── breaker.py # CircuitBreaker(内存级熔断器) │ ├── vlm.py # VLM 客户端 │ ├── embedding.py # Embedding 服务实现 │ ├── redis_cache.py # Redis 响应缓存 │ ├── ocr.py # MonkeyOCR 客户端 │ ├── asr.py # ASR (Whisper) 客户端 │ └── telemetry.py # SQLite 遥测记录实现 │ ├── config/ # 声明性配置(YAML,禁止 .py) ├── store/ # 版本化资源(skills/prompts/questions/videos) ├── workspaces/ # 实验工作区 ├── prompts/ # 诊断 prompt(不参与进化,是评估标尺) ├── tests/ # 测试 ├── data/ # 数据(不提交 Git) ├── logs/ # 日志(不提交 Git) ├── results/ # 实验结果 ├── main.py # CLI 入口 └── research-wiki/ # 单一事实源 ``` ### 2.4 依赖方向硬性规则 | 层 | 可依赖 | 禁止依赖 | |---|--------|---------| | `core/` | 标准库、typing、pluggy、json_repair | `app/`、`adapters/`、任何框架 | | `app/` | `core/`、标准库 | `adapters/`(只通过 Protocol) | | `adapters/` | `core/`、`app/ports.py`、第三方库 | `app/` 内部模块 | ### 2.5 可提取内核 `core/agent/` 和 `core/evolution/` 是两个独立可提取包,只依赖 Protocol 接口。将它们连同 `core/types.py` 复制到另一个项目,提供 Protocol 实现即可运行——这是验证依赖方向是否守住的判据。 --- ## §3 接缝清单(Protocol 端口) 只在真正易变、需替换或需造测试替身处引入接口,其余写直白的领域函数。 ### 3.1 核心端口(`core/` 内,可提取) **共享端口(`core/protocols.py`,跨子包):** | Protocol | 关键方法 | 职责 | |----------|---------|------| | `LLMProvider` | `chat()` | LLM 文本调用(全异步) | | `VLMProvider` | `chat_with_images()` | VLM 图文调用(全异步) | | `TelemetryRecorder` | `record_llm_call()` | LLM 调用遥测 | **Agent 专属端口(`core/agent/protocols.py`):** | Protocol | 关键方法 | 职责 | |----------|---------|------| | `ToolDispatcher` | `dispatch(tool_name, args, context)` | Agent 工具调度 | | `AgentLoopSpec` | `before_step/after_tool/after_step/on_finish` | pluggy 生命周期 hook | **Evolution 专属端口(`core/evolution/protocols.py`):** core/ 侧 Protocol 只读——core/ 返回结果 dataclass,写入由 app/harness/ 编排层执行。写方法保留在 app/ 侧的实现类中。 | Protocol | 关键方法 | 职责 | |----------|---------|------| | `SkillStore` | `read_skill()`, `list_skill_files()` | 版本化技能读取(只读) | | `PromptStore` | `read_prompt()`, `list_prompt_files()` | 版本化提示词读取(只读) | | `RunLog` | `get_predictions()`, `get_traces()` | 实验日志查询(只读) | ### 3.2 应用层端口(`app/ports.py`) | Protocol | 关键方法 | 职责 | 当前实现 | |----------|---------|------|---------| | `EmbeddingProvider` | `embed(texts)` | 文本嵌入 | `adapters/embedding.py` | | `TreeCache` | `get()`, `set()` | 树索引缓存 | `adapters/redis_cache.py` | | `ASRProvider` | `transcribe(audio_path)` | 语音识别 | `adapters/asr.py` | | `OCRProvider` | `transcribe_frames(frame_paths: list[Path]) -> str` | 帧文字转录 | `adapters/ocr.py` | 判据:这块代码会不会被换实现、或需要在测试里替换成假的?不会,就别抽象。 ### 3.3 当前实现映射 | Protocol | adapter 实现 | 说明 | |----------|-------------|------| | `LLMProvider` | `adapters/llm.py` `GovernedLLMClient` | OpenAI 兼容 API,内置治理栈(§5) | | `VLMProvider` | `adapters/vlm.py` | Qwen VL 等 OpenAI 兼容 VLM API | | `ToolDispatcher` | `app/search/tools.py` `SearchToolDispatcher` | 搜索 Agent 工具分发(view_node / search_similar / observe_frame / submit_answer / read_skill) | | `SkillStore` / `PromptStore` | `app/harness/workspace.py` | 文件系统版本化存储(`store/skills/v{N}/`) | | `RunLog` | `app/harness/log.py` `HarnessLog` | SQLite 持久化 | | `TelemetryRecorder` | `adapters/telemetry.py` | SQLite `telemetry.db` | | `EmbeddingProvider` | `adapters/embedding.py` | local(sentence-transformers)/ remote 双后端 | | `TreeCache` | `adapters/redis_cache.py` | Redis 键值缓存 | | `ASRProvider` | `adapters/asr.py` | Groq Whisper API | | `OCRProvider` | `adapters/ocr.py` | MonkeyOCR 自建端点 | --- ## §4 Agent 遥测规范 每次 LLM/VLM 调用必须经过 `TelemetryRecorder` 记录以下字段: | 字段 | 类型 | 说明 | |------|------|------| | `call_id` | str | UUID,本次调用唯一标识 | | `parent_call_id` | str? | 父调用(agent step → LLM call 链路) | | `session_id` | str | epoch/step/question 关联 ID | | `model_name` | str | 使用的模型名 | | `provider` | str | API 端点标识 | | `messages` | str (JSON) | 原始输入 | | `response` | str | 原始输出 | | `thinking` | str | thinking/reasoning 内容 | | `prompt_tokens` | int | 输入 token 数 | | `completion_tokens` | int | 输出 token 数 | | `latency_ms` | int | 延迟毫秒 | | `ttft_ms` | float? | 首 token 延迟(流式测量) | | `max_inter_token_ms` | float? | 最大 token 间隔(流式测量) | | `cache_hit` | bool | 是否命中 Redis 缓存 | | `error` | str? | 异常信息(正常为 null) | **存储**:SQLite `telemetry.db`,`llm_calls` 表。`adapters/telemetry.py` 实现 `TelemetryRecorder` Protocol,`adapters/llm.py` 的 `GovernedLLMClient` 在每次调用后自动写入。 --- ## §5 LLM 调用韧性 所有 LLM/VLM 调用必须经过 `GovernedLLMClient`(`adapters/llm.py`)治理,禁止裸调 OpenAI SDK。治理栈包含四层: | 层 | 机制 | 说明 | |---|------|------| | 1 | 熔断器 | 连续 N 失败 → 短路 M 秒 → 探针恢复(`adapters/breaker.py`) | | 2 | Redis 响应缓存 | content-addressed:`hash(model + messages)` → response | | 3 | 流式三层看门狗 | TTFT / inter_token / total 超时保护(`adapters/streaming.py`) | | 4 | 指数退避重试 | `max_retries`、`base_delay`、`max_delay`(可配置) | **流式三层看门狗**:GovernedLLMClient 内部始终使用 `stream=True` 消费 SSE,通过 `stream_with_liveness_timeouts()` 实现三层超时保护——TTFT(首 token,含 thinking token)、inter_token(token 间隔)、total(总时长)。thinking token 刷新看门狗但不计入 content,确保长时间思考不会被误判为连接挂死。 熔断参数(`LLM_CIRCUIT_BREAKER_THRESHOLD`、`LLM_CIRCUIT_BREAKER_COOLDOWN`)、超时(`LLM_TIMEOUT`、`LLM_TTFT_TIMEOUT`、`LLM_INTER_TOKEN_TIMEOUT`)通过 `.env` 工程配置管理。 **治理流程伪代码**: ``` chat(messages) → if breaker.is_open: raise CircuitOpen # 层1: 熔断 call_id = uuid4() if cache.get(model, messages): # 层2: 缓存 telemetry.record(..., cache_hit=True) return cached retry loop: # 层4: 重试 stream = httpx.stream(POST, stream=True) guarded = stream_with_liveness_timeouts( # 层3: 三层看门狗 sse_deltas, ttft, inter_token, total) content, thinking, ttft_ms = consume(guarded) breaker.record_success() cache.set(model, messages, response) telemetry.record(...) return response except transient → backoff + breaker.record_failure() except 401/403 → breaker.force_open(); raise ``` --- ## §6 核心算法保真清单 迁移时逐一比对参考代码,不可简化。建树 4 项 + 训练 9 项 = 13 项: | # | 算法 | 参考文件 | 核心逻辑 | |---|------|---------|---------| | 1 | L2 轴心建树策略 | `reference/video_tree_trm/video_tree_builder.py` | L2 先行→L3 向下→L1 向上,asyncio 链式并发 | | 2 | VLM 批量帧描述 + JSON fallback | `reference/video_tree_trm/video_tree_builder.py` | `_L3_BATCH_SIZE=5` 批量调用,解析失败逐帧 fallback | | 3 | 断点续跑机制 | `reference/video_tree_trm/video_tree_builder.py` | `progress.json` + L1 中间 JSON,按段恢复 | | 4 | RecursiveRetriever | `reference/docs/architecture.md §5` | Cross-Attention 选择器 + ACT halt + z 状态累积 | | 5 | CE-Gate e-process | TRM4 `core/harness/eprocess.py` | 截断 Beta 混合、四出口门控 | | 6 | 信息阶梯 | TRM4 `core/harness/gate_ladder.py` | 冷启动 2:1、gamma-EMA、反泄漏 | | 7 | 块顺序验证 | TRM4 `core/harness/validate.py` | 基线缓存、INFRA 护栏、配对翻转 | | 8 | 诊断瀑布 | TRM4 `core/harness/diagnose.py` | 错误归因级联、缺陷 vs 失误、D1-D5 | | 9 | 进化 patch 引擎 | TRM4 `core/harness/evolve.py` + `patch.py` | 保护跨度、rank-and-clip、附录/动量 | | 10 | Mini-batch 构建 | TRM4 `core/harness/batching.py` | FFD + round-robin + 正确率混合 | | 11 | Agent Loop | TRM4 `core/loop.py` | Thinking+JSON、json_repair、pluggy hook | | 12 | 树环境语义搜索 | TRM4 `core/tree/environment.py` | 分块 embedding、祖先去重、锚定验证 | | 13 | 训练循环编排 | TRM4 `core/harness/runner.py` | 三级嵌套、慢更新10步、断点续训 | > **TRM4** 指 `/home/iomgaa/Projects/Video-Tree-TRM4/`,**reference** 指 `/home/iomgaa/Projects/Video-Tree-TRM5/reference/`。 --- ## §7 配置管理(双模式) 系统配置分两套,按用途隔离: | 模式 | 载体 | 适用 | |------|------|------| | 工程配置 | `pydantic-settings` + `.env` | 系统运行所需、少变或敏感(API 密钥、Redis URL、LLM 超时等) | | 科研实验配置 | per-experiment YAML + harness run 快照 | 会在实验中反复扫动/对比的参数(gate 阈值、batch 大小、评估口径等) | **D7 归属判定规则(防串台,强制遵守)**:某参数是否会在科研实验中被反复扫动/对比? - **是** → 科研配置(per-experiment YAML + harness run 快照,保证可复现)。 - **否**(系统运行所需、少变、或敏感)→ 工程配置(`pydantic-settings` + `.env`)。 CLI args 仅用于单次临时覆盖,不作为任何配置的常驻来源。两类载体之外,严禁在代码中散落硬编码默认值;缺失关键配置应直接报错而非兜底。 **参数归属示例**: | 参数 | 归属 | 理由 | |------|------|------| | `SEARCH_LLM_API_KEY` | 工程 `.env` | 敏感,不变 | | `LLM_TIMEOUT` | 工程 `.env` | 系统运行所需,少变 | | `gate_e_confirm` | 科研 YAML | CE-Gate 阈值,实验中反复调优 | | `batch_size` | 科研 YAML | mini-batch 大小,实验中反复扫动 | | `embed.model_name` | 科研 YAML | 嵌入模型选型,实验中对比 | | `REDIS_URL` | 工程 `.env` | 基础设施地址,少变 | --- ## 附:横切原则 - **溯源**:每条推理链(Agent 看了哪些节点、如何汇总出答案)都带来源。遥测(§4)记录全部 LLM 调用,Store 版本化记录 skill/prompt 变更历史。 - **可复现**:每次 harness run 冻结当前 `config/` YAML + Store 版本快照(`app/harness/workspace.py`),结果可重算。 - **安全/合规**:API 密钥走 `.env`,不提交 Git;Redis 缓存中不存敏感信息的明文。