--- id: maintenance-pool title: 训练池 Maintenance 正确题自动补入机制 type: design created: 2026-07-14 status: approved --- # 训练池 Maintenance 正确题自动补入机制 ## 1. 目标 在 `PerCategoryPoolStrategy` 的池构建阶段,baseline infer 完成后、分层划分前,自动检测每个 task_type 分组的正确/错误比例。若正确题占比低于 `batch_correct_ratio`,从 `test_questions_dir`(VME benchmark)中补入该 task_type 的已验证正确题,使训练集有足够的正确题满足 batch 构建需求。 ### 驱动场景 SubPattern 靶向出题(如 AR30)刻意针对 agent 失败模式,agent 在 baseline infer 中大部分答错。`batch_correct_ratio=0.5` 要求 mini-batch 一半正确一半错误,但 train 池正确题不足会导致 batch 构建失败或质量下降。 ## 2. 算法 在 `PerCategoryPoolStrategy.build()` 的 Phase 2(按 task_type 分组)和 Phase 3(分层划分)之间插入 Phase 2.5。 ### 候选筛选 当前 baseline correctness dict 仅包含训练题(如 AR30),**不包含 benchmark 题**。因此补入候选的正确性判定需要额外查询 DB 中的历史推理记录: ``` # 从 DB 查询所有 run 中对 benchmark 题的评估结果(取最新一次) benchmark_correctness = query_db( "SELECT question_id, prediction, answer FROM predictions WHERE question_id IN (benchmark_qids) ORDER BY timestamp DESC" ) # 每个 question_id 取最新记录判定正确性 ``` 若 DB 中无该题的历史记录,该题不作为候选(不假设正确)。 ### 补入流程 ``` 对每个 task_type 分组: c = 该组正确题数(来自当前 baseline correctness) w = 该组错误题数 r = batch_correct_ratio if batch_correct_ratio 为 None: 跳过 ratio = c / (c + w) if ratio > 1 - r: logger.warning( "类别 {} 正确率 {:.1%} 过高(阈值 {:.1%}),出题可能太简单", task_type, ratio, 1 - r, ) if ratio >= r: 跳过(正确题已充足) k = ceil((r * w - (1 - r) * c) / (1 - r)) candidates = test_questions_dir 中同 task_type 的题 ∩ DB 历史推理中正确的 − 已在当前组中的 question_id actual = min(k, len(candidates)) 补入 actual 道: - 构造新 GeneratedQuestion(clone,family="VME_MAINTENANCE") - correctness[qid] = True 合并到该 task_type 分组 ``` ### 接口传参 `PerCategoryPoolStrategy.build` 新增 `db_path: Path | None = None` 参数,用于查询历史 benchmark 推理记录。调用方 `build_or_load_pools` 已持有 `db_path`,直接透传。 ### 边界情况 | 场景 | 行为 | |------|------| | 正确率已满足 r | 不补入 | | 候选不足(如需 30 道但只有 41 道可用) | 补入全部可用,接受比例不完美 | | `batch_correct_ratio` 为 None | 跳过补入 | | `test_questions_dir` 为 None | 跳过补入 | | DB 中无 benchmark 历史记录 | 无候选,不补入(日志警告) | | 补入题与 test 池重复 | 允许 — test 池保持完整不动 | | `_split_one_category` 的 floor 造成 ±1 误差 | 接受 — 比例不完美是预期行为 | | 正确率 > 1 - r(如 r=0.5 时正确率 > 50%) | `logger.warning` 警告出题可能太简单,不阻断 | ## 3. 接口变更 | 组件 | 变更 | |------|------| | `PoolConfig` | 新增字段 `batch_correct_ratio: float \| None = None` | | `_to_pool_config` | 从 `RunConfig.batch_correct_ratio` 映射(`RunConfig` 已有该字段,默认 0.5) | | `PerCategoryPoolStrategy.build` | 新增 `db_path` 参数;Phase 2 和 Phase 3 之间插入 Phase 2.5 | | `build_or_load_pools` | 透传 `db_path` 给 `strategy.build()` | ### 不变的部分 - `_split_one_category` 不改 — 补入后的分组正常走分层划分 - test 池加载(Phase 4)不改 — 63 道 VME AR 完整保留 - `GlobalPoolStrategy` 不改 - pipeline / runner / batching 不改 ## 4. 补入题的标记与追溯 | 字段 | 值 | 说明 | |------|-----|------| | `family` | `"VME_MAINTENANCE"` | 区分于 `"ACTION_RECOGNITION"` 生成题 | | `skill_target` | 保持原题值 | 从 benchmark 原始数据继承 | | correctness | `True` | 来源定义:从"已验证正确"池选出 | ## 5. 数据流 ``` baseline infer (30 道 AR30) ↓ correctness = {qid: True/False} (如: 5 correct, 25 wrong) ↓ PerCategoryPoolStrategy.build() Phase 1: 按 task_types 过滤 Phase 2: 按 task_type 分组 Phase 2.5: ←── 新增 maintenance 补入 检查: c=5, w=25, r=0.5 → 需补 k=20 从 VME benchmark 加载 AR 正确题 (41 道) 排除已有 → 补入 min(20, 41) = 20 道 组总量: 50 (25 correct, 25 wrong) Phase 3: _split_one_category (train_ratio=0.667) train: 33 题 (≈17 correct + 16 wrong) val: 17 题 (≈8 correct + 9 wrong) Phase 4: test 池 (63 道 VME AR,完整不动) ``` ## 6. 非功能性需求 | 维度 | 设计 | |------|------| | 持久化 | 补入题写入 pools.json 冻结,与原生题一视同仁 | | 幂等性 | pools.json 存在则直接加载,不重复补入 | | 断点续跑 | 不影响 — pools.json 冻结后训练可断点恢复 | | 原子性 | 不适用 — 池构建是一次性操作 |