docs(tree): add tree module vertical slice design and implementation plan
This commit is contained in:
@@ -0,0 +1,440 @@
|
|||||||
|
---
|
||||||
|
type: design
|
||||||
|
id: tree-module-vertical-slice
|
||||||
|
title: "建树模块竖切设计:数据结构 + 建树 + 修复 + 迁移"
|
||||||
|
created: 2026-07-07
|
||||||
|
status: approved
|
||||||
|
---
|
||||||
|
|
||||||
|
# 建树模块竖切设计
|
||||||
|
|
||||||
|
## 1. 背景与动机
|
||||||
|
|
||||||
|
TRM5 的三大模块(建树、训练 harness、新题构建)中,建树是一切的地基——搜索 Agent、训练循环、检索器全部依赖树结构。当前 `app/tree/` 目录为空,需要从 reference 代码和 TRM4 迁移建树能力。
|
||||||
|
|
||||||
|
### 1.1 现状
|
||||||
|
|
||||||
|
| 资产 | 位置 | 状态 |
|
||||||
|
|------|------|------|
|
||||||
|
| TreeIndex 数据结构 | `reference/video_tree_trm/tree_index.py` | 简单 description 字段,需扩展 card |
|
||||||
|
| VideoTreeBuilder | `reference/video_tree_trm/video_tree_builder.py` | L2 轴心策略,需适配 TRM5 Protocol |
|
||||||
|
| EmbeddingModel | `reference/video_tree_trm/embeddings.py` | 双后端,需拆分到 adapters/ |
|
||||||
|
| TreeEnvironment | TRM4 `core/tree/environment.py` | 基于 flat dict,需改为 TreeIndex |
|
||||||
|
| 树增强管线 | TRM4 `core/tree/enhance/` | verify/supplement 逻辑可复用 |
|
||||||
|
| 已建树数据 | TRM4.zip `store/videos/` | 300 视频,原始卡片,质量良好 |
|
||||||
|
|
||||||
|
### 1.2 关键决策
|
||||||
|
|
||||||
|
| 决策 | 结论 | 理由 |
|
||||||
|
|------|------|------|
|
||||||
|
| 树格式 | 原始卡片(L3 6字段、L2 7字段、L1 7字段,非增强 10 字段) | 数据分析显示原始卡片质量很好;增强新增的 4 字段价值有限——people 空率 42%、emotion_cues 空率 64% |
|
||||||
|
| 数据结构 | 扩展 TreeIndex 统一承载 card + embedding | 一棵树服务两个消费者(Agent 用 card、Retriever 用 embedding),避免双格式同步问题 |
|
||||||
|
| 迁移来源 | TRM4.zip 原始树 | 未经 merge/verify 增强的干净数据,与 VideoTreeBuilder 输出格式一致 |
|
||||||
|
| 修复模式 | 独立 `app/tree/repair/` 目录,可整目录删除 | 修复是历史遗留需求,后续不再需要 |
|
||||||
|
|
||||||
|
## 2. 数据结构
|
||||||
|
|
||||||
|
### 2.1 Card 体系(frozen dataclass)
|
||||||
|
|
||||||
|
三级卡片与 VideoTreeBuilder VLM 输出对齐。L3 为 6 字段,L2 为 7 字段,L1 为 7 字段(与 TRM4 原始树一致):
|
||||||
|
|
||||||
|
| Card 类型 | 字段数 | 字段 | 类型 |
|
||||||
|
|-----------|--------|------|------|
|
||||||
|
| **L3Card** | 6 | `frame_summary` | `str` |
|
||||||
|
| | | `visible_entities` | `list[str]` |
|
||||||
|
| | | `ongoing_actions` | `list[str]` |
|
||||||
|
| | | `visible_text` | `list[str]` |
|
||||||
|
| | | `spatial_layout` | `str` |
|
||||||
|
| | | `visual_attributes` | `dict[str, Any]` |
|
||||||
|
| **L2Card** | 7 | `event_description` | `str` |
|
||||||
|
| | | `entities` | `list[str]` |
|
||||||
|
| | | `actions` | `list[str]` |
|
||||||
|
| | | `action_subjects` | `list[str]` |
|
||||||
|
| | | `visible_text` | `list[str]` |
|
||||||
|
| | | `spatial_relations` | `str` |
|
||||||
|
| | | `state_changes` | `str \| None` |
|
||||||
|
| **L1Card** | 7 | `scene_summary` | `str` |
|
||||||
|
| | | `main_setting` | `str` |
|
||||||
|
| | | `key_entities` | `list[str]` |
|
||||||
|
| | | `main_actions` | `list[str]` |
|
||||||
|
| | | `topic_keywords` | `list[str]` |
|
||||||
|
| | | `visible_text` | `list[str]` |
|
||||||
|
| | | `temporal_flow` | `str` |
|
||||||
|
|
||||||
|
所有 Card 均为 `frozen=True`,变更时整体替换(创建新 Card 实例赋给节点的 `card` 属性)。
|
||||||
|
|
||||||
|
### 2.2 节点结构
|
||||||
|
|
||||||
|
继承 reference 嵌套关系,扩展 card。每个节点均有 `id: str` 字段:
|
||||||
|
|
||||||
|
```
|
||||||
|
TreeIndex
|
||||||
|
├── metadata: IndexMeta
|
||||||
|
└── roots: list[L1Node]
|
||||||
|
├── id: str
|
||||||
|
├── card: L1Card
|
||||||
|
├── embedding: ndarray | None
|
||||||
|
├── time_range: tuple[float, float] | None
|
||||||
|
└── children: list[L2Node]
|
||||||
|
├── id: str
|
||||||
|
├── card: L2Card
|
||||||
|
├── embedding: ndarray | None
|
||||||
|
├── time_range: tuple[float, float] | None
|
||||||
|
└── children: list[L3Node]
|
||||||
|
├── id: str
|
||||||
|
├── card: L3Card
|
||||||
|
├── embedding: ndarray | None
|
||||||
|
├── frame_path: str | None
|
||||||
|
├── timestamp: float | None
|
||||||
|
└── subtitle: str | None
|
||||||
|
```
|
||||||
|
|
||||||
|
**ID 规则**:建树时由 VideoTreeBuilder 生成,格式 `l1_{i}_l2_{j}_l3_{k}`。迁移时从 TRM4 flat JSON 的 `node_id` 字段读取。反序列化时校验 ID 唯一性。
|
||||||
|
|
||||||
|
**embedding 文本源**通过 property 派生:
|
||||||
|
|
||||||
|
| 节点 | property | 来源 |
|
||||||
|
|------|----------|------|
|
||||||
|
| L3Node | `description` | `card.frame_summary` |
|
||||||
|
| L2Node | `description` | `card.event_description` |
|
||||||
|
| L1Node | `summary` | `card.scene_summary` |
|
||||||
|
|
||||||
|
**IndexMeta** 与 reference 一致:`source_path`、`modality`、`embed_model`、`embed_dim`、`created_at`。
|
||||||
|
|
||||||
|
### 2.3 序列化
|
||||||
|
|
||||||
|
- **主格式**:JSON(`save_json` / `load_json`),card 自动转 dict / 从 dict 恢复
|
||||||
|
- **embedding**:可选包含(base64 编码),默认不含
|
||||||
|
- **断点续跑**:`save_l1_json` / `load_l1_json` 保存单个 L1 子树中间结果
|
||||||
|
|
||||||
|
## 3. VideoTreeBuilder
|
||||||
|
|
||||||
|
从 reference 迁移,核心算法保真(ARCHITECTURE.md §6 算法 #1、#2、#3)。
|
||||||
|
|
||||||
|
### 3.1 改造点
|
||||||
|
|
||||||
|
| 维度 | reference | TRM5 |
|
||||||
|
|------|-----------|------|
|
||||||
|
| VLM 依赖 | `LLMClient` | `VLMProvider` Protocol |
|
||||||
|
| LLM 依赖 | 同上 | `LLMProvider` Protocol |
|
||||||
|
| 输出格式 | `L3Node.description`(字符串) | `L3Node.card: L3Card`(6 字段) |
|
||||||
|
| VLM Prompt | 返回字符串数组 | 返回结构化 JSON 对象数组 |
|
||||||
|
| 字幕输入 | 无 | 可选 `srt_entries` 参数,注入 VLM prompt |
|
||||||
|
| L2 代表帧 | 独立提取(均匀采样) | 复用 L3 帧:先提帧阶段提取所有 L3 帧,L2 从同 clip 内的 L3 帧中均匀采样 `l2_representative_frames` 个 |
|
||||||
|
| 日志 | `utils/logger_system.py` | loguru |
|
||||||
|
|
||||||
|
**治理约束**:所有 VLM/LLM 调用必须经过 `GovernedLLMClient`(四层治理栈)+ `TelemetryRecorder` 遥测。VideoTreeBuilder 通过 `VLMProvider` / `LLMProvider` Protocol 接收已治理的客户端实例,不直接接触 adapter 层。调用方(CLI 或测试)负责注入治理实例。
|
||||||
|
|
||||||
|
### 3.2 建树流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. [前置] 字幕完整性检查(可选)
|
||||||
|
2. [前置] 解析 SRT → list[SRTEntry](可选)
|
||||||
|
3. 时间切分:视频 → L1 时间区间列表
|
||||||
|
4. 帧提取:按 l3_fps 提取所有帧到缓存目录(ffmpeg 线程池并发,已存在则跳过)
|
||||||
|
5. L2 先行:从同 clip 内 L3 帧中均匀采样 l2_representative_frames 个代表帧 + 对应时段字幕 → VLM → L2Card
|
||||||
|
6. L3 向下:每帧 + L2 描述上下文 + 对应时刻字幕 → VLM → L3Card
|
||||||
|
7. L1 向上:L2 描述聚合 → LLM → L1Card
|
||||||
|
8. 字幕分配:Voronoi 中点策略将 SRT 写入 L3Node.subtitle
|
||||||
|
9. 组装 TreeIndex
|
||||||
|
10. 原子保存:先写最终 TreeIndex JSON,成功后清理 progress + intermediate 文件
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 VLM Prompt 修改策略
|
||||||
|
|
||||||
|
**增量修改,不做简化**。在 reference 原始 prompt 基础上追加:
|
||||||
|
|
||||||
|
- **L3 批量 prompt**:保留原始指令,追加结构化 JSON 输出格式要求 + 字幕上下文(有字幕时)
|
||||||
|
- **L3 单帧 fallback**:同上
|
||||||
|
- **L2 prompt**:保留原始指令,追加结构化输出 + 字幕
|
||||||
|
- **L1 prompt**:保留原始指令,追加结构化输出
|
||||||
|
|
||||||
|
**结构化 card JSON 解析**:VLM 返回 JSON 对象数组时,逐字段校验类型(str/list/dict)。字段缺失或类型错误时,该批次整体走逐帧 fallback(与算法 #2 一致)。单帧 fallback 解析失败时,使用空字符串/空列表填充缺失字段并记录 warning 日志。
|
||||||
|
|
||||||
|
### 3.4 保真项
|
||||||
|
|
||||||
|
| # | 算法 | 保真方式 |
|
||||||
|
|---|------|---------|
|
||||||
|
| 1 | L2 轴心建树策略 | L2 先行 → L3 向下 → L1 向上,asyncio 链式并发 |
|
||||||
|
| 2 | VLM 批量帧描述 + JSON fallback | `_L3_BATCH_SIZE=5`,解析失败逐帧 fallback |
|
||||||
|
| 3 | 断点续跑机制 | `progress.json` + L1 中间 JSON,按段恢复;最终 JSON 成功写入后清理中间文件 |
|
||||||
|
|
||||||
|
## 4. 字幕模块
|
||||||
|
|
||||||
|
位置:`app/tree/subtitle.py`。从 TRM4 `enhance/merge.py` 和 TRM3 `tools/generate_subtitles.py` 提取。
|
||||||
|
|
||||||
|
### 4.1 接口
|
||||||
|
|
||||||
|
| 函数 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| `parse_srt(path) → list[SRTEntry]` | 解析 SRT 文件,剥离 HTML 标签 |
|
||||||
|
| `check_subtitle_completeness(entries, duration, min_coverage) → SubtitleReport` | 检查覆盖率、最大空白段、条目数 |
|
||||||
|
| `extract_subtitle_for_range(entries, time_range) → str` | 提取时间范围内字幕,供 VLM prompt 注入 |
|
||||||
|
| `assign_subtitles_voronoi(index, entries) → None` | Voronoi 中点策略将字幕分配到 L3 节点 |
|
||||||
|
|
||||||
|
### 4.2 SRTEntry
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SRTEntry:
|
||||||
|
start: float
|
||||||
|
end: float
|
||||||
|
text: str
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 SubtitleReport
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SubtitleReport:
|
||||||
|
total_entries: int
|
||||||
|
coverage_ratio: float # SRT 覆盖时长 / 视频总时长
|
||||||
|
max_gap_sec: float # 最大连续无字幕间隔
|
||||||
|
usable: bool # coverage_ratio >= min_coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
**决策行为**:`usable=True` 时正常注入 VLM prompt;`usable=False` 时记录 warning 日志并以无字幕模式建树(不 raise,不阻断)。解析失败(文件损坏)时 raise,由调用方决定是否降级。
|
||||||
|
|
||||||
|
### 4.4 建树时机
|
||||||
|
|
||||||
|
字幕在建树**前**解析完成,作为 `VideoTreeBuilder.build()` 的可选输入。有字幕时注入 VLM prompt 上下文,同时在建树完成后通过 Voronoi 分配写入 `L3Node.subtitle`。
|
||||||
|
|
||||||
|
## 5. 质量校验
|
||||||
|
|
||||||
|
位置:`app/tree/verify.py`。建树模式和修复模式共用。
|
||||||
|
|
||||||
|
### 5.1 校验项
|
||||||
|
|
||||||
|
| 字段 | 层级 | 逻辑 |
|
||||||
|
|------|------|------|
|
||||||
|
| `visible_text` | L1 | 每条须在下属 L2/L3 的 visible_text 中有出处 |
|
||||||
|
| `visible_text` | L2 | 每条须在下属 L3 的 visible_text 中有出处 |
|
||||||
|
| `key_entities` | L1 | 交叉校验 L2/L3 文本语料 |
|
||||||
|
| `entities` | L2 | 交叉校验 L3 文本语料(visible_text + subtitle + frame_summary) |
|
||||||
|
|
||||||
|
### 5.2 匹配算法
|
||||||
|
|
||||||
|
模糊子串匹配:忽略大小写、去除标点。不实现编辑距离容忍(简单子串匹配已足够)。
|
||||||
|
|
||||||
|
### 5.3 接口
|
||||||
|
|
||||||
|
```python
|
||||||
|
def verify_tree(index: TreeIndex) -> VerifyStats:
|
||||||
|
"""校验树节点卡片,删除不可靠内容,返回统计信息。
|
||||||
|
|
||||||
|
Card 为 frozen dataclass,校验时创建新 Card 实例(过滤后)
|
||||||
|
赋给节点的 card 属性。TreeIndex 和节点本身可变,Card 不可变。
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
`VerifyStats` 记录各字段的保留/删除数量。
|
||||||
|
|
||||||
|
## 6. Embeddings
|
||||||
|
|
||||||
|
Embedding 实现遵循 Clean Architecture 依赖方向:`app/tree/` 通过 `EmbeddingProvider` Protocol 使用 embedding 能力,具体实现在 `adapters/embedding.py`。
|
||||||
|
|
||||||
|
### 6.1 依赖分层
|
||||||
|
|
||||||
|
| 层 | 位置 | 内容 |
|
||||||
|
|---|------|------|
|
||||||
|
| Protocol | `app/ports.py` | `EmbeddingProvider`:`embed(texts) → ndarray [N, D]`、`dim → int` |
|
||||||
|
| 实现 | `adapters/embedding.py` | 双后端(local sentence-transformers / remote OpenAI 兼容 API),L2 归一化 |
|
||||||
|
| 消费 | `app/tree/index.py` | `TreeIndex.embed_all(embed_fn, model_name, dim)` 接受 `embed_fn` 参数 |
|
||||||
|
|
||||||
|
### 6.2 从 reference 迁移改造
|
||||||
|
|
||||||
|
- reference 的 `EmbeddingModel` 类拆分:接口 → `app/ports.py`,实现 → `adapters/embedding.py`
|
||||||
|
- 日志从 `utils/logger_system.py` 改为 loguru
|
||||||
|
- 配置从 `EmbedConfig` 改为从 `config/default.yaml` 的 `embed:` 段读取
|
||||||
|
|
||||||
|
## 7. TreeEnvironment
|
||||||
|
|
||||||
|
位置:`app/tree/environment.py`。从 TRM4 迁移,改造为基于 TreeIndex。
|
||||||
|
|
||||||
|
### 7.1 职责边界
|
||||||
|
|
||||||
|
TreeEnvironment 是**纯数据访问层**,不含 LLM 调用。LLM 摘要和 Agent 工具分发属于 `app/search/` 模块(本次竖切不含)。
|
||||||
|
|
||||||
|
### 7.2 接口
|
||||||
|
|
||||||
|
| 方法 | 职责 | 依赖 |
|
||||||
|
|------|------|------|
|
||||||
|
| `view_node(node_id, anchor=False)` | 返回节点卡片 + 子节点概览;`anchor=True` 时为卡片字段添加行锚标 `[c1]` `[s1]` 供引用验证 | 纯数据 |
|
||||||
|
| `search_similar(query, top_k, embed_fn)` | 语义搜索 + 祖先去重 | `embed_fn` 参数 |
|
||||||
|
| `get_subtitle(node_id)` | 返回节点字幕 | 纯数据 |
|
||||||
|
| `resolve_frame_paths(node_ids)` | node_id → 帧文件路径 | 纯数据 |
|
||||||
|
|
||||||
|
### 7.3 算法 #12 保真:语义搜索
|
||||||
|
|
||||||
|
ARCHITECTURE.md §6 算法 #12 要求保真"分块 embedding、祖先去重、锚定验证"。TRM5 的实现方式:
|
||||||
|
|
||||||
|
| 原算法要素 | TRM4 实现 | TRM5 实现 | 变更理由 |
|
||||||
|
|-----------|----------|----------|---------|
|
||||||
|
| 分块 embedding | 卡片全文按 4000 字符分块,每块独立 embedding | 每节点一个 embedding(基于 description property) | TreeIndex 已有 per-node embedding,分块是 flat-dict 时代的替代方案;per-node embedding 语义更准确 |
|
||||||
|
| 祖先去重 | 搜索结果中,若某节点的祖先已在结果中则去重 | **保持不变** | — |
|
||||||
|
| 锚定验证 | `view_node(anchor=True)` 为卡片行添加 `[c1]` `[s1]` 等锚标 | **保持不变**,在 `view_node` 中实现 | — |
|
||||||
|
|
||||||
|
**分块→单节点 embedding 的变更属于核心算法修改**,需在实现 PR 的 commit message 中标注"算法 #12 变更"并说明理由。
|
||||||
|
|
||||||
|
### 7.4 与 TRM4 的其他差异
|
||||||
|
|
||||||
|
- 底层从 flat dict 改为 TreeIndex 嵌套结构
|
||||||
|
- `view_node()` 不调 LLM——纯数据返回卡片内容(LLM 摘要移至 `app/search/`)
|
||||||
|
- 通过 `id → (l1_idx, l2_idx, l3_idx)` 索引映射实现 O(1) 节点查找,映射在 TreeEnvironment 构造时一次性构建
|
||||||
|
|
||||||
|
## 8. 修复模式
|
||||||
|
|
||||||
|
位置:`app/tree/repair/`,独立可拆卸(`rm -rf app/tree/repair/` + 删除调用入口 = 零残留)。
|
||||||
|
|
||||||
|
### 8.1 文件布局
|
||||||
|
|
||||||
|
```
|
||||||
|
app/tree/repair/
|
||||||
|
├── __init__.py
|
||||||
|
├── detector.py # 检测缺失/低质量节点
|
||||||
|
├── regenerator.py # VLM 重新生成 + 向上级联
|
||||||
|
└── supplement.py # Q&A 反向补全
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 修复流程(底向上)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 检测:扫描所有节点,标记 NodeIssue
|
||||||
|
2. L3 修复:VLM 重新描述帧(复用现有 L2 描述作上下文 + 字幕)
|
||||||
|
3. L2 重生成:受影响 L2 从全部 L3 children 聚合(LLM)
|
||||||
|
4. L1 重生成:受影响 L1 从全部 L2 children 聚合(LLM)
|
||||||
|
5. verify_tree()
|
||||||
|
6. supplement(Q&A 反向补全,仅修复模式)
|
||||||
|
```
|
||||||
|
|
||||||
|
**治理约束**:修复模式的所有 VLM/LLM 调用同样必须经过 `GovernedLLMClient` + `TelemetryRecorder`,通过 Protocol 参数注入。
|
||||||
|
|
||||||
|
### 8.3 与建树模式的顺序差异
|
||||||
|
|
||||||
|
| | 建树模式 | 修复模式 |
|
||||||
|
|---|---------|---------|
|
||||||
|
| 前提 | 从零开始 | 已有树,局部损坏 |
|
||||||
|
| 顺序 | L2 先行 → L3 向下 → L1 向上 | L3 修复 → L2 重生成 → L1 重生成 |
|
||||||
|
| L2 上下文 | 必须先建 L2 才有上下文 | 复用现有 L2 描述 |
|
||||||
|
| 级联 | 向下再向上 | 仅向上 |
|
||||||
|
|
||||||
|
### 8.4 检测项(detector.py)
|
||||||
|
|
||||||
|
| 检查项 | 层级 | 判定条件 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| 必填字段为空 | L3 | card 中 frame_summary / visible_entities 等为空 |
|
||||||
|
| 帧文件缺失 | L3 | frame_path 指向的文件不存在 |
|
||||||
|
| 无子节点 | L2/L1 | children 列表为空 |
|
||||||
|
| 时间空洞 | L2 | 相邻 L2 clips 时间范围不连续 |
|
||||||
|
|
||||||
|
### 8.5 Q&A 反向补全(supplement.py)
|
||||||
|
|
||||||
|
从 TRM4 `enhance/supplement.py` 迁移,仅修复模式使用(建树模式不含此步骤)。
|
||||||
|
|
||||||
|
| 特性 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 注入类别白名单 | `person_name`、`location`、`score_number`、`object_name` |
|
||||||
|
| 禁止注入 | 情感、因果、时序推理 |
|
||||||
|
| LLM 调用 | 每题一次,分析缺失事实 + 搜索已有 + 注入缺失 |
|
||||||
|
| 注入前去重 | 大小写归一后去除重复条目 |
|
||||||
|
|
||||||
|
## 9. 迁移
|
||||||
|
|
||||||
|
一次性操作,完成后归档迁移工具。
|
||||||
|
|
||||||
|
### 9.1 迁移资产清单
|
||||||
|
|
||||||
|
| 资产 | TRM4 来源 | TRM5 目标 |
|
||||||
|
|------|----------|----------|
|
||||||
|
| 原始树 JSON | TRM4.zip → `store/videos/<vid>/tree.json` | `store/videos/<vid>/tree.json`(转换为 TreeIndex JSON) |
|
||||||
|
| 未压缩帧(1280×720) | TRM4.zip → `store/videos/<vid>/frames/` | `store/videos/<vid>/frames/` |
|
||||||
|
| SRT 字幕 | `data/Video-MME/subtitle/*.srt` | `data/Video-MME/subtitle/` |
|
||||||
|
| 原始视频压缩包 | `data/Video-MME/original_data/*.zip` | `data/Video-MME/original_data/` |
|
||||||
|
| 原始视频 MP4 | `data/Video-MME/videos/`(如果存在) | `data/Video-MME/videos/` |
|
||||||
|
| Benchmark 问题 | `store/questions/benchmarks/Video-MME/*.json` | `store/questions/benchmarks/Video-MME/` |
|
||||||
|
|
||||||
|
### 9.2 迁移步骤
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 解压 TRM4.zip 到临时目录
|
||||||
|
2. 拷贝原始资产(帧、SRT、视频、问题)
|
||||||
|
3. 格式转换:flat tree.json → TreeIndex JSON(一次性 Python 脚本)
|
||||||
|
4. 验证(见 §9.3)
|
||||||
|
5. 清理临时目录
|
||||||
|
6. 归档转换脚本到 tools/archived/
|
||||||
|
```
|
||||||
|
|
||||||
|
格式转换脚本 `tools/convert_flat_to_treeindex.py` 是一次性工具,仅 CLI 调用,`app/`/`core/`/`adapters/` 不 import 该脚本。迁移完成后移至 `tools/archived/`。
|
||||||
|
|
||||||
|
### 9.3 迁移验收
|
||||||
|
|
||||||
|
| 验收项 | 条件 |
|
||||||
|
|--------|------|
|
||||||
|
| 视频数 | 300 个视频目录均存在 |
|
||||||
|
| tree.json | 每个视频目录有 tree.json,可正常反序列化为 TreeIndex |
|
||||||
|
| frames | 每个 L3 节点的 frame_path 对应文件存在,JPEG 可读 |
|
||||||
|
| SRT | `data/Video-MME/subtitle/` 下 SRT 文件数 ≥ 290(部分视频无字幕为已知情况) |
|
||||||
|
| 问题 | 每个视频有对应 question JSON |
|
||||||
|
|
||||||
|
**失败处理**:迁移脚本输出缺失资产报告(视频 ID + 缺失项),非零缺失时以 exit code 1 退出但不回滚已迁移的资产(允许手动补充后重跑验证)。
|
||||||
|
|
||||||
|
## 10. 文件布局与依赖
|
||||||
|
|
||||||
|
### 10.1 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
app/tree/
|
||||||
|
├── __init__.py
|
||||||
|
├── index.py # TreeIndex, L1/L2/L3Node, L1/L2/L3Card, IndexMeta
|
||||||
|
├── video_builder.py # VideoTreeBuilder(asyncio, VLMProvider)
|
||||||
|
├── subtitle.py # SRT 解析 + 完整性检查 + Voronoi 分配
|
||||||
|
├── verify.py # 质量校验(建树/修复共用)
|
||||||
|
├── environment.py # TreeEnvironment(运行时数据访问)
|
||||||
|
└── repair/ # 修复模式(独立可拆卸)
|
||||||
|
├── __init__.py
|
||||||
|
├── detector.py # 检测缺失/低质量节点
|
||||||
|
├── regenerator.py # VLM 重新生成 + 向上级联
|
||||||
|
└── supplement.py # Q&A 反向补全
|
||||||
|
|
||||||
|
app/ports.py # EmbeddingProvider Protocol(新增)
|
||||||
|
|
||||||
|
adapters/
|
||||||
|
├── embedding.py # EmbeddingProvider 实现(local/remote 双后端)
|
||||||
|
└── vlm.py # VLMProvider 实现(最小可用版本)
|
||||||
|
|
||||||
|
tools/
|
||||||
|
├── migrate_from_trm4.sh # 迁移主脚本
|
||||||
|
└── convert_flat_to_treeindex.py # 格式转换(迁移后归档至 tools/archived/)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 依赖方向
|
||||||
|
|
||||||
|
```
|
||||||
|
app/tree/ → core/protocols.py (VLMProvider, LLMProvider, TelemetryRecorder)
|
||||||
|
app/tree/ → app/ports.py (EmbeddingProvider)
|
||||||
|
app/tree/ ✗ adapters/(只通过 Protocol)
|
||||||
|
app/tree/repair/ → app/tree/index.py, verify.py, subtitle.py(内部依赖)
|
||||||
|
adapters/ → core/protocols.py, app/ports.py(实现 Protocol)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 竖切边界
|
||||||
|
|
||||||
|
本次竖切包含 `adapters/embedding.py` 和 `adapters/vlm.py` 的**最小可用实现**,确保竖切可端到端运行(CLAUDE.md §4.4:"开始实现就必须完成到可运行状态")。完整的治理集成(GovernedVLMClient 等)在需要时增量添加。
|
||||||
|
|
||||||
|
| 模块 | 状态 |
|
||||||
|
|------|------|
|
||||||
|
| `app/tree/` 全部文件 | 本次实现 |
|
||||||
|
| `adapters/embedding.py` | 本次实现(最小可用) |
|
||||||
|
| `adapters/vlm.py` | 本次实现(最小可用) |
|
||||||
|
| `text_builder.py` | 后续实现 |
|
||||||
|
| `app/search/` tool dispatch + LLM 摘要 | Agent 层,下个竖切 |
|
||||||
|
|
||||||
|
## 11. 被拒方案
|
||||||
|
|
||||||
|
| 方案 | 拒绝理由 |
|
||||||
|
|------|---------|
|
||||||
|
| 双格式共存(TreeIndex + flat JSON) | 数据重复、同步风险、维护两套序列化 |
|
||||||
|
| 使用增强 10 字段卡片 | 数据分析显示增强字段价值有限:people 空率 42%、emotion_cues 空率 64% |
|
||||||
|
| 从当前 TRM4 store 迁移增强树 | 增强过程引入了格式偏差,原始树更干净 |
|
||||||
|
| 富卡片建树代码复用 | 代码已丢失(远端服务器仅存与 reference 相同的简单版本) |
|
||||||
|
| EmbeddingModel 放在 app/tree/ | 违反 Clean Architecture 依赖方向,外部 SDK 实现应在 adapters/ |
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
type: design
|
||||||
|
node_id: design:tree-module-vertical-slice
|
||||||
|
title: "建树模块竖切设计:数据结构 + 建树 + 修复 + 迁移"
|
||||||
|
date: 2026-07-07
|
||||||
|
---
|
||||||
|
|
||||||
|
# 建树模块竖切设计:数据结构 + 建树 + 修复 + 迁移
|
||||||
|
|
||||||
@@ -15,6 +15,16 @@
|
|||||||
"id": "plan:core-agent-adapters-llm",
|
"id": "plan:core-agent-adapters-llm",
|
||||||
"label": "core/agent/ + adapters/llm 基础设施实现计划",
|
"label": "core/agent/ + adapters/llm 基础设施实现计划",
|
||||||
"type": "plan"
|
"type": "plan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "design:tree-module-vertical-slice",
|
||||||
|
"label": "建树模块竖切设计:数据结构 + 建树 + 修复 + 迁移",
|
||||||
|
"type": "design"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plan:tree-module-vertical-slice",
|
||||||
|
"label": "建树模块竖切实现计划",
|
||||||
|
"type": "plan"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [
|
"links": [
|
||||||
@@ -24,6 +34,13 @@
|
|||||||
"relation": "implements",
|
"relation": "implements",
|
||||||
"evidence": "计划实现设计文档中定义的全异步 AgentLoop + 四层治理栈",
|
"evidence": "计划实现设计文档中定义的全异步 AgentLoop + 四层治理栈",
|
||||||
"added": "2026-07-07T02:25:32.349931+00:00"
|
"added": "2026-07-07T02:25:32.349931+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "plan:tree-module-vertical-slice",
|
||||||
|
"target": "design:tree-module-vertical-slice",
|
||||||
|
"relation": "implements",
|
||||||
|
"evidence": "计划逐 Task 实现设计文档中的 11 个模块",
|
||||||
|
"added": "2026-07-07T05:27:02.953166+00:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
# Research Wiki 索引
|
# Research Wiki 索引
|
||||||
|
|
||||||
> 自动生成,更新时间:2026-07-07 02:25 UTC
|
> 自动生成,更新时间:2026-07-07 05:27 UTC
|
||||||
|
|
||||||
## design (1)
|
## design (3)
|
||||||
- [2026-07-06-core-agent-adapters-llm-design](designs/2026-07-06-core-agent-adapters-llm-design.md) `design:2026-07-06-core-agent-adapters-llm-design`
|
- [2026-07-06-core-agent-adapters-llm-design](designs/2026-07-06-core-agent-adapters-llm-design.md) `design:2026-07-06-core-agent-adapters-llm-design`
|
||||||
|
- [建树模块竖切设计:数据结构 + 建树 + 修复 + 迁移](designs/2026-07-07-tree-module-design.md) `design:2026-07-07-tree-module-design`
|
||||||
|
- [建树模块竖切设计:数据结构 + 建树 + 修复 + 迁移](designs/tree-module-vertical-slice.md) `design:tree-module-vertical-slice`
|
||||||
|
|
||||||
## plan (3)
|
## plan (5)
|
||||||
- [2026-07-06-core-agent-adapters-llm](plans/2026-07-06-core-agent-adapters-llm.md) `plan:2026-07-06-core-agent-adapters-llm`
|
- [2026-07-06-core-agent-adapters-llm](plans/2026-07-06-core-agent-adapters-llm.md) `plan:2026-07-06-core-agent-adapters-llm`
|
||||||
|
- [2026-07-07-tree-module-vertical-slice](plans/2026-07-07-tree-module-vertical-slice.md) `plan:2026-07-07-tree-module-vertical-slice`
|
||||||
- [core/agent/ + adapters/llm 基础设施实现计划](plans/core-agent-adapters-llm.md) `plan:core-agent-adapters-llm`
|
- [core/agent/ + adapters/llm 基础设施实现计划](plans/core-agent-adapters-llm.md) `plan:core-agent-adapters-llm`
|
||||||
|
- [建树模块竖切实现计划](plans/tree-module-vertical-slice.md) `plan:tree-module-vertical-slice`
|
||||||
- [项目基础设施初始化计划](plans/infrastructure-setup.md) `plan:infrastructure-setup`
|
- [项目基础设施初始化计划](plans/infrastructure-setup.md) `plan:infrastructure-setup`
|
||||||
|
|||||||
@@ -8,3 +8,7 @@
|
|||||||
- [2026-07-07 02:25 UTC] 新增 plan: core/agent/ + adapters/llm 基础设施实现计划 (plan:core-agent-adapters-llm)
|
- [2026-07-07 02:25 UTC] 新增 plan: core/agent/ + adapters/llm 基础设施实现计划 (plan:core-agent-adapters-llm)
|
||||||
- [2026-07-07 02:25 UTC] 新增边: plan:core-agent-adapters-llm --implements--> design:core-agent-adapters-llm
|
- [2026-07-07 02:25 UTC] 新增边: plan:core-agent-adapters-llm --implements--> design:core-agent-adapters-llm
|
||||||
- [2026-07-07 02:25 UTC] 重建索引: 4 篇页面
|
- [2026-07-07 02:25 UTC] 重建索引: 4 篇页面
|
||||||
|
- [2026-07-07 05:26 UTC] 新增 design: 建树模块竖切设计:数据结构 + 建树 + 修复 + 迁移 (design:tree-module-vertical-slice)
|
||||||
|
- [2026-07-07 05:26 UTC] 新增 plan: 建树模块竖切实现计划 (plan:tree-module-vertical-slice)
|
||||||
|
- [2026-07-07 05:27 UTC] 新增边: plan:tree-module-vertical-slice --implements--> design:tree-module-vertical-slice
|
||||||
|
- [2026-07-07 05:27 UTC] 重建索引: 8 篇页面
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
type: plan
|
||||||
|
node_id: plan:tree-module-vertical-slice
|
||||||
|
title: 建树模块竖切实现计划
|
||||||
|
date: 2026-07-07
|
||||||
|
---
|
||||||
|
|
||||||
|
# 建树模块竖切实现计划
|
||||||
|
|
||||||
Reference in New Issue
Block a user