Files
Video-Tree-TRM5/research-wiki/ARCHITECTURE.md
T

373 lines
19 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 架构设计
**版本** 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-controlledLLM 自主调工具),其余三步是 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 # VideoTreeBuilderL2 轴心 + 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 # PromptManagerprompt 加载与拼装)
│ │ ├── skills.py # SkillRegistry + discover_skills
│ │ ├── summarizer.py # 两轮 LLM 摘要(view_node / search_similar 用)
│ │ ├── vision.py # observe_frameVLM 两轮 + 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`):**
| Protocol | 关键方法 | 职责 |
|----------|---------|------|
| `SkillStore` | `read_skill()`, `write_skill()`, `list_versions()` | 版本化技能存储 |
| `PromptStore` | `read_prompt()`, `write_prompt()` | 版本化提示词存储 |
| `RunLog` | `insert()`, `query()` | 实验日志 |
### 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` | localsentence-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_tokentoken 间隔)、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 缓存中不存敏感信息的明文。