Files
Video-Tree-TRM5/research-wiki/designs/fix-diagnosis-tree-data-link.md

9.7 KiB
Raw Permalink Blame History

type, node_id, title, date
type node_id title date
design design:fix-diagnosis-tree-data-link 修复诊断 tree_data 断链 bugTRM4→TRM5 迁移 regression 2026-07-15

修复诊断 tree_data 断链 bugTRM4→TRM5 迁移 regression

诊断编排全程传 tree_data={},导致 ground_truth 恒空、error_type 归因 100% 坍缩成 extraction_failure。本设计接通断掉的树加载环,补齐 P5 fail-loud,双注入点(离线诊断 + 训练循环)一并修复。

状态:已含 Codex 独立审查修订(2 Critical + 8 Important 全部核验并采纳;展平实现从"复用 to_dict"改为"遍历 json"level 改由遍历深度赋值,补视频覆盖 fail-loud,重冻 test 成员变化诚实化)。

1. 根因(已坐实)

环节 位置 事实
CLI 注入空树 app/harness/video_split_cli.py:335 tree_data={},注释谎称"由诊断管线内部按需加载"
训练循环同病 app/harness/runner.py:2180 tree_data={} + 同一句假注释
编排层无加载 core/evolution/diagnose.py:2068-2074 if tree_data and "nodes"...{} 为假 → else 分支 tree_data_by_video={}根本无加载逻辑
ground_truth 恒空 diagnose.py:736,755 {}.get("nodes",{}) → node card 取空 → ""
span 评估无参照 diagnose.py:509-515 judge 看不到"应提取什么"extraction_completeness 系统性偏低,解析失败还默认 0.0
归因瀑布短路 diagnose.py:928 avg_completeness < 0.5 恒真 → 100% extraction_failure

实测:82 个 T2 全部 extraction_failureevolution_target 全 tool48 格多样性退化成 11 格(error_type 维坍缩成单值)。judge_missed_nodestree_content 同样为空,search_failure 分支永不触发。

2. Regression 溯源:TRM4 无此 bug

层面 TRM4(正常) TRM5(坏了)
tree.json 结构 扁平 {"nodes": {id: {node_id,level,time_range,parent_id,children_ids,card}}} 嵌套 {"metadata", "roots": [{id,card,time_range,children}]}
tree_cache 填充 diagnose.py:1677 _load_json(tree.json) 直接得 nodes tree_data={},填充逻辑被删
诊断拿 ground_truth _load_json(...)["nodes"] 直接可用 恒空

双重 regression:(a) 建树模块重写把产物格式从扁平 nodes 演进成嵌套 roots(合理架构升级);(b) 诊断代码迁移时既未适配新格式、又把加载逻辑删成空 dict。因此 TRM5 需要一个 TRM4 不需要的格式桥接展平器roots → nodes)。

3. 影响范围

维度 是否污染 原因
tier 分层 T0/T1/T2/uncertain 干净 classify_defect_vs_lapse 不吃 tree_data
floor_k / test 代表性 干净 按 task_type 总数,与 error_type 无关
error_type 归因 全坍缩 ground_truth 恒空
evolution_target 路由 全 tool error_type 的下游派生
48 格多样性覆盖 ⚠️ 退化 error_type 维失效 → 实际仅按 task_type 单维
judge_missed_nodes ⚠️ 退化 tree_content 空

结论:已冻结 pools.json 的 tier/floor/代表性可信,仅 error_type 维及其下游被污染。

4. 修复设计

4.1 单元拆分(单一职责)

单元 位置 职责 依赖
树展平器 app/harness/tree_nodes.py(新) load_tree_nodes(store_dir, video_id) -> dict递归遍历 tree.json 的 roots(嵌套 dict,按遍历深度赋 levelroot=1/child=2/孙=3),抽取每节点 id/card/time_range,组装成扁平 {"nodes": {node_id: {card, level, time_range}}} 只读文件 + json不走 TreeIndex 对象层,见 §4.2 核验)
离线注入 app/harness/video_split_cli.pybuild_diagnosis_deps/run_pipeline wrong_ids → 涉及 video 去重加载成 {video_id: {"nodes":...}},填充 DiagnosisDeps.tree_databaseline_diagnosis 仅透传) ← 展平器
训练注入 app/harness/runner.py_run_diagnosis 由 batch question_ids → 涉及 video 同法加载注入,删 tree_data={} 假注释 ← 展平器

4.2 关键决策

边界 决策 理由
加载职责 app 层video_split_cli / runner),core 只消费 dict Clean Architecturecore 不碰文件系统;run_diagnosis else 分支已支持 {video_id:...} 形态,core 零改动
展平实现 递归遍历 tree.json roots dict(原始 json,card 已是 dict 直接取),不走对象层 核验:仅 L1Nodeto_dictindex.py:260),L2/L3 是其内部闭包;且 to_dict 输出无 level、L3 无 time_range(用 timestamp)。遍历 json 更省且零改建树模块
level 字段 由遍历深度直接赋值root=1/child=2/孙=3),不解析 node_id node_id 累积式 ..._L1_..._L2_..._L3_,正则首匹配会把 L2/L3 误判成 1(Codex I7
预加载范围 只加载被诊断题涉及的 video,按 video 缓存去重 YAGNI236 错题涉及 <200 视频,避免加载无关树
fail-loud tree.json 缺失 → FileNotFoundError(沿用 factory.py:87 先例);展平后 nodes 为空 → ValueError本次诊断涉及的每个 video 必须被 tree_data 覆盖run_diagnosis.get(vid, {}) 静默回退(diagnose.py:2144-2145)改为缺失即 raise 补 P5:空树/漏加载本应报错,不再静默退化
诊断瀑布本身 不动algo §4.7 #7 修复只接通输入,不改归因逻辑

L3 无 time_range 的已知次要行为node.to_dict 语义下 L3 节点用 timestamp_load_tree_contenttime_range 时 L3 退化为默认 [0,0],仅影响 judge_missed_nodes 文本里 L3 的时间显示,不影响 ground_truthcard)。展平器可选:从 L3 timestamp 合成 [timestamp, timestamp]

5. 非功能性四维

维度 结论
持久化 展平器纯内存只读,不落盘;诊断结果仍逐题 upsert(不变)
幂等性 展平确定性(同 tree.json → 同 nodes);诊断 upsert 幂等(不变)
断点续跑 done_question_ids 续跑机制不变;fingerprint 因代码改动而变 → 全量重跑一次
原子性 无新写操作;pools.json 重冻仍走既有原子写

6. 重跑与重冻衔接

代码改动 → git short SHA 变 → diag_fingerprint 变 → 全量重跑 236 题诊断。

  • classify_defect_vs_lapse / evidence / bias / skill judge 输入不含 tree_data → 预期 Redis 缓存命中,tier 分层 T2/T1 应不变。但这是"大概率"而非"必然":缓存键内容未逐字段核证,且存在非 tree_data 的不稳定源(如 C3 异常吞并 attributiondiagnose.py:2186)。重跑后须实测校验 T2=82/T1=152 是否保持,偏差需归因。
  • evaluate_spanground_truth 由空变有)+ judge_missed_nodestree_content 由空变有)缓存失效 → 重算(约半数 LLM 调用,~1h)。
  • 诊断完成后重跑 Phase 2 重新冻结覆盖 pools.jsonerror_type 恢复判别力会改变多样性阶段的选择顺序cells 按 (task_type, error_type) 排序,split_selection.py:139/193)→ trainval 组成变 → test 补集成员随之变化,不保证稳定。floor 与代表性 ε 约束仍保证 test 代表性合格,但具体成员会变——用户已接受重冻覆盖,这是预期结果而非风险。

7. 测试策略

测试 类型 验证点
展平器正确性 unit 真实 tree.json → {"nodes":{...}},节点数=递归总数,node 含 card(dict)/level/time_range
展平器 level 赋值 unit 遍历深度 → level 1/2/3,与节点嵌套层级一致(不依赖 node_id 解析)
展平器 fail-loud unit tree.json 缺失 → FileNotFoundError;空树/空 nodes → ValueError
视频覆盖校验 unit 诊断涉及的 video 未被 tree_data 覆盖 → raise(不静默回退)
ground_truth 接通 integration 对真实 T2 样本注入真实树,断言 evaluate_span 收到非空 ground_truth(充分条件);观察 error_type 恢复多值(软验收——分布是否脱离单值取决于 judge,非硬保证)
core 依赖方向 结构检查 core/evolution/diagnose.py 不 import app.tree

8. 算法保真声明

本修复触及核心算法 §4.7 #7 诊断瀑布:仅接通其输入(tree_data),不改 attribute_error 归因分支、severity 函数或 defect/lapse 判定逻辑。参考 TRM4 core/harness/diagnose.py 的 tree_cache 语义对照,确保 TRM5 诊断拿到与 TRM4 一致的 ground_truth。

9. 被拒方案

方案 拒因
复用 TreeIndex.load_json + node.to_dict 走对象层 核验否决:仅 L1Nodeto_dictindex.py:260),L2/L3 为其内部闭包;输出无 level、L3 无 time_range,补了也要后处理
补 L2/L3Node.to_dict 再走对象层 改核心建树模块 index.py(推理/save_json/建树全链路依赖,algo §4.7 #1-3);为诊断读取需求反向改生产方接口,违反 YAGNI
让 core 诊断直接吃 TreeEnvironment 对象 破坏依赖方向(core 依赖 app.tree);且需构建 env(要 frames_dir)过重
保留现有 pools.json 不重冻 train 多样性仍基于污染的 error_type,违背修复初衷
只修离线诊断、不修 runner 训练循环 backward 每轮复现坍缩,进化盲目只改 tool

关联

  • 影响指标:metric:split-cell-coverage48 格覆盖)、metric:split-signal-tier-distribution
  • 数据表:schema:baseline-diagnosis
  • 上游设计:design:results-driven-video-split
  • 参考实现:TRM4 core/harness/diagnose.py:1677tree_cache 填充)