# Spec-3：出题管线 v2（失败机理靶向 + 逐题质量门）

- **日期**: 2026-07-11
- **状态**: 已批准（用户确认：双标签体系、轻量档全量 + 重量档 15% 抽检）
- **依据**: 三轮分析——`findings/2026-07-11-question-gen-calibration-analysis.md`（生成题缺陷与根因）、`findings/2026-07-11-benchmark-failure-taxonomy.md`（242 错题机理分类与 11 种题型规格）
- **系列**: Spec-1/2/3 三件套之一；Spec-1 修好的推理环境是本 spec 重量抽检的前置

## 1. 目标重定义

出题目标从"难度与 benchmark 一致"改为：**覆盖已证实的失败机理（M1-M5）+ 逐题质量门**。

| 决策 | 内容 |
|------|------|
| calibrate 降级 | 仅作观测性报表，不再是验收门 |
| 评分协议不变 | 标准四选一按字母判分；harness/推理侧零改动。题型规格中"附证据节点 id"等要求降级为**构造时验证材料**，存题目元数据供 diagnose 分析 |
| 双标签体系 | 主标签 `task_type`（Video-MME 12 类，进化循环 mini-batch/gate/skills/diagnose 零改动）+ 附加字段 `skill_target`（M1-M5/题族，仅用于出题配比、质检、覆盖率统计） |
| 质检两档 | 轻量四门全量逐题；重量档（盲 Agent 全树试答）15% 抽检 + 难度标签 |

## 2. 流水线架构

重构 `app/question_gen/synthesizer.py` + `tools/generate_questions.py` generate 子命令：

```mermaid
graph LR
    S[采样器<br/>按题族选素材] --> G[生成器<br/>题族 prompt 模板<br/>双标签输出]
    G --> P[确定性后处理<br/>shuffle+答案重映射<br/>指代黑名单<br/>verbatim 检测]
    P --> Q{轻量四门 全量}
    Q -->|拒| R[带拒因重出<br/>同 slot 最多 3 次]
    R --> G
    Q -->|过| H[重量抽检 15%<br/>盲 Agent 试答→difficulty_steps]
    H --> W[入库 generated-v2]
```

### 2.1 五题族（11 种题型的落地归并）

每族一个采样器 + 一个 prompt 模板，题型（shape）作为模板参数：

| 题族 | 覆盖题型 | 采样约束 | 治什么 | 默认配比 |
|------|---------|---------|--------|---------|
| 检索族 | 证据埋深/反"不存在"、ASR 实体对齐、属性辨析 | 证据取自 entities/visible_entities/visible_text/长字幕单句；配镜像题（真不存在） | M1 负证据幻觉（最大杠杆） | 30% |
| 推理族 | 转述还原、NOTA 校准对 | 从字幕单句做一步语义变换（蕴含/虚拟语气/序数映射）；NOTA 成对生成（半正解半陷阱） | M2 字面匹配 | 25% |
| 枚举族 | 多实例锚定、序数枚举、覆盖率权重 | **source_nodes ≥ 3 跨 L1**；同类事件 ≥2 次；首个表面匹配必须是错的 | M3 锚定/盘点 | 20% |
| 视觉族 | 状态演化多帧、实例消歧数字 | 证据仅在帧内、不在任何 card 文本；多时刻真实读数做干扰项 | M4 视觉验证 | 15% |
| 空间族 | 参照系空间 | spatial_layout 字段 + 时间锚；摄像机/被摄者双参照系 | T5 空间 | 10% |

**题族 × task_type 兼容矩阵**（Codex 审查补充；✓=合法组合，采样器按此分配双标签，保证 12 类各 20 题的硬约束可满足）：

| 题族 | 合法 task_type |
|------|---------------|
| 检索族 | Object Recognition、Object Reasoning、Action Recognition、Attribute Perception、OCR Problems |
| 推理族 | Action Reasoning、Object Reasoning、Information Synopsis |
| 枚举族 | Counting Problem、Temporal Reasoning、Temporal Perception、Information Synopsis |
| 视觉族 | Attribute Perception、Counting Problem、OCR Problems、Action Recognition |
| 空间族 | Spatial Perception、Spatial Reasoning |

每个 task_type 至少落入一个题族；Spatial 两类仅由空间族供给。**"不出 T1 类素材题"是素材形态禁用**（不采瞬时动作/记分牌瞬时数值/无对白因果类素材），不删除任何 Video-MME task_type。

### 2.2 确定性后处理（零 LLM 成本，通用硬约束）

1. **选项 shuffle** + 答案字母重映射（修复 v1 答案 57% 在 A 的偏斜）
2. **指代黑名单**（正则）：禁 this segment / this frame / this clip / the current frame / the scene / frame summary 等；题干必须含 L1 time_range 时间锚（"between 10:52 and 21:44"式）或全局限定语（"in the entire video"）
3. **verbatim 检测**：题干+正确项 vs 源节点文本的 n-gram 重合门（检索族豁免正确项检测——其证据本来就在文本，见 2.3）
4. **出题禁区**：不出 T1 类素材题（瞬时动作/记分牌时序/无对白因果）；不复刻 T7 噪声模式（选项重复、口径含糊的计数边界）

### 2.3 轻量四门（全量逐题，约 4 次单轮 LLM 调用/题）

| 门 | 判定 | 杀什么 |
|----|------|--------|
| 键验证 | 拿出题依据（source_nodes 原文）判标注答案是否成立 | 标注幻觉（v1 CP 组 2 例无出处） |
| 盲答测试 | 不给任何视频信息裸答，答对即拒 | 常识可解题、干扰项秒排题 |
| 多真测试 | 拿全树素材判是否 >1 选项可为真 | 歧义多解题（v1 SR 组 5/8） |
| 泄漏测试 | **按题族条件化**（见下） | 一跳检索捷径 |

**泄漏门的题族捷径画像**（"信息不对称化"的落地）：

| 题族 | 捷径画像（该捷径必须失败才放行） |
|------|--------------------------------|
| 检索族 | top-5 语义搜索片段裸答必须失败（全树 card 文本裸答**允许**成功——考的是检索深度） |
| 视觉族 | 全树 card 文本裸答必须失败（逼 observe_frame） |
| 推理/枚举/空间族 | 锚点节点文本裸答必须失败（需变换/跨节点） |

### 2.4 重出循环与重量抽检

- 拒题 → 拒因回填到生成 prompt → 同 slot 重出，最多 3 次；3 次仍拒则该 slot 换素材重采样
- 通过四门的题按 15% 抽样跑盲 Agent 全树试答（复用 Spec-1 修复后的推理管线）：验证真实可答性，产出 `difficulty_steps` 难度标签

## 3. 数据与版本

| 项 | 决策 |
|----|------|
| 旧 240 题 | 原地保留 `store/questions/generated/`（infer_gen240 run 引用它，保可复现） |
| 新题集 | `store/questions/generated-v2/`，布局与 v1 一致：`{video_id}.json` 平铺（无子目录）。CLI 示例：生成 `python tools/generate_questions.py generate --output-dir store/questions/generated-v2 ...`；推理 `python main.py --mode infer --questions generated-v2 --run-id gen240v2` |
| 题目元数据新增 | `skill_target`、`source_nodes`（已有）、`gate_report`（四门判定）、`difficulty_steps`（抽检题） |

**元数据承载方式（Codex 审查修正）**：现有 `GeneratedQuestion`（`core/types.py`）为固定 8 字段，`load_benchmark` 丢弃未知 JSON 字段，池快照只存固定字段。约定：

| 字段 | 承载 | 进训练链路 |
|------|------|-----------|
| `skill_target`、`difficulty_steps` | 扩展 `GeneratedQuestion` 为可选字段（默认 None，benchmark 题不受影响）；loader/pools 同步保留 | 是（diagnose 可按 skill_target 聚合报表） |
| `gate_report` | 只存在于题目 JSON（溯源用）与生成期 SQLite；loader **不加载**（体积大且训练不需要） | 否 |
| 规模 | 默认 240 题；**task_type 均匀（20/类）为硬约束**（进化循环分层需要），**族配比为软目标（±5%）**——采样器按"族 × task_type 兼容矩阵"（如枚举族→Counting/Temporal 类）分配每题的双标签；YAML 可扫 |

## 4. 配置归属（D7 规则）

- **科研配置**（per-experiment YAML）：族配比、门阈值（n-gram 窗口、多真判定温度）、抽检率、重出上限、规模
- **工程配置**（`.env`）：LLM/VLM 端点、超时、熔断——沿用现有

## 5. 运行时数据与治理

- 每题的门判定记录（哪门拒、拒因文本、重出轮次、最终状态）落 SQLite。表结构在设计批准后走 `structured-logging` skill 单独设计（本 spec 只约定：记录必须逐题可追溯、可聚合出各门拦截率报表）
- **质量门 LLM 调用治理（Codex 审查补充）**：轻量四门与生成器的全部 LLM/VLM 调用必须经 `GovernedLLMClient`/`GovernedVLMClient` + `TelemetryRecorder`（CLAUDE.md §4.8/§4.9），严禁裸调 SDK；门执行器通过依赖注入接收客户端实例，session_id 用生成批次 id、parent_call_id 链接到题目生成调用

## 5.5 核心接口概要（Codex 审查补充，完整签名留给 plan）

| 类型/函数 | 职责 |
|----------|------|
| `QuestionFamilySpec` | 题族声明：采样约束、prompt 模板、泄漏门捷径画像、合法 task_type 集合 |
| `CandidateQuestion` | 生成器输出：题面 + 双标签 + source_nodes + 构造验证材料（未过门） |
| `GateReport` | 四门判定结果：每门 pass/reject + 拒因文本 |
| `run_gates(candidate, deps) -> GateReport` | 门执行器（依赖注入 LLM/树环境） |
| `generate_one` 迁移 | v1 签名（只收 task_type）废弃，v2 收 `(family_spec, task_type, slot_seed)` 返回 `CandidateQuestion` |

## 6. 不做什么（YAGNI）

- 不改判分协议、不改 harness/推理侧、不改 mini-batch/gate/diagnose 的 task_type 分组
- 不做对抗式加难迭代（留到自进化循环跑通后，须固定出题对手版本）
- 不做 T1 树增强（用户已确认本次不做）
- 不引入树外信息源（原始视频重新抽帧出题）——视觉族用现有帧缓存即可

## 7. 验证

1. Smoke：12 个视频 × 每族 2-3 题，验证四门拦截率、重出收敛（≤3 轮）、双标签与元数据完整性
2. 全量：生成 240 题 → `bash scripts/infer_generated.sh`（指向 generated-v2）→ calibrate 报表观测
3. 回归断言：新题集答案位置分布均匀（卡方检验）；指代黑名单零命中；泄漏门画像全部通过
4. 单测：后处理层纯函数（shuffle 重映射、黑名单、n-gram 门）

## 8. 被否方案

- **纯 prompt 补丁**：第二轮设计层分析证明 7 类缺陷中 5 类会换形复发（信息闭环是结构问题）
- **失败机理作为主标签**：进化循环全链改造，收益不明确；先以元数据形式观察其价值
- **重量档全量**：240 题 ≈ 一次完整推理实验（4-5 小时），轻量四门已拦截三轮发现的全部缺陷类型
- **难度一致性作为验收门**：三轮分析证明分数一致性是坏代理指标（假难/假易双向失真）
