19 KiB
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 四层依赖规则
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 模块交互全景
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 目录结构
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):
| 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 |
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 缓存中不存敏感信息的明文。