---
id: question-gen-v2-grounded-contrastive
title: question-gen v2 — AR grounded-contrastive 重构（双模式生成 + 独立 VLM grounding + 生成端对抗）
type: design
created: 2026-07-15
status: draft
---

# question-gen v2：AR grounded-contrastive 重构

## 1. 目标与范围

Phase A（grounded selector）落地后，双独立评审（Claude 三 agent + Codex）判定新题"未真正过关"：negative-space 减少但 grounding 不可审计（selector 用生成器自己的 VLM 自评=循环自证）、8/12 仍可被结构性 shortcut 秒杀（逐槽多数投票 / 最模糊-子串项 / 字幕泄答案）、4/12 双正解歧义。根因诊断见 [2026-07-15-question-gen-v2-diagnosis-and-strategy.md]（四层病灶 + 六篇文献回挖）。

v2 从**四层**对症重构，用户锁定三个治本方向：

| 决策 | 选定 |
|------|------|
| 题型 | **双模式对称对比（决策 C）**：可翻转子模式→镜像对+双向 AND；不可翻转→4 选对比集+重去偏 |
| grounding | **独立 VLM（MiniMax-M3）打分**（CLIP 跨模态 embedding 无法服务，退回独立 judge，仍断循环自证） |
| 对抗位置 | **轻量代理内联 + 完整 agent 后置第二道网** |

**范围铁律**：v2 只重构 **Action Recognition** 路径；11 个非 AR 题型走现有 `generate-v2` **零改动**（路径隔离，byte-identical）。代价：题库出现"AR 新格式 + 非 AR 旧格式"混格，评测/训练侧须兼容两种。

> **multi_true 收紧口径（C4 隔离）**：Phase A 的 multi_true 收紧原为 12 类统一门，会破坏"非 AR byte-identical"铁律。v2 将收紧做成 **AR-only**——新增 AR 专属 gate 变体/参数，由 strategy 分流；**非 AR 仍走 Phase A 现有 gate**（不动 rubric，保住 byte-identical）。AR sub-pattern 的改动全部锚定 `app/question_gen/strategy_action_recognition.py`（6 个 SubPattern 定义）与 `app/question_gen/strategy.py::SubPattern`（`supports_flip`/`flip_axis` 字段），**不涉及** `families.py`。

### 路径归属

| 改动 | 位置 | 生效范围 |
|------|------|---------|
| 双模式生成 | 新建 `app/question_gen/contrastive_gen.py` | 仅 AR（strategy 声明开关分流） |
| 独立 VLM grounding | 改 `distractor_selector.py` `_score_options` + 装配加第二 VLM | 仅 AR selector 调用点 |
| 内联对抗探针 | 新建 `app/question_gen/adversarial_probes.py` | 仅 AR |
| 门收紧 + NLI + 歧义门 | AR 专属 multi_true gate 变体 + 新 gate | **仅 AR**（收紧、NLI、歧义门均 AR-only，由 strategy 分流；非 AR 走 Phase A 现有 gate 不动） |
| pair 契约 | `core/types.py` `GeneratedQuestion` + `app/question_gen/loader.py` + `app/harness/batching.py` + `app/harness/inference.py` | 公共类型（新字段默认值兼容非 AR）；QuestionUnit 抽象贯穿采样/分批/聚合三处 |
| 后置双向评分 | 改造 `app/question_gen/adversarial_filter.py` | filter 层，`filter_task_types` 默认 AR |

## 2. Prior-Version Audit（v2 替换/改造的既有行为）

| 既有行为 | v2 处置 |
|---------|---------|
| `distractor_selector` VLM 自评选负项 | **改造**：打分器换独立 MiniMax-M3；区间选择/退火/hard-fail 落库**保留** |
| `adversarial_filter` 翻转门（事后生成镜像 Q） | **改造**：镜像在生成期已产出，不再事后生成；翻转门语义**沿用 Phase B 修正**（[2026-07-14-adversarial-question-gen-phaseB-design.md §4.2]：不判对错，只看 canonical answer 是否随问题翻转），作为对抗有效性 filter（见 §4.5，与 pair correctness 评分正交） |
| `adversarial_filter` 作弊门（后置试答） | **保留**，定位为后置第二道网 |
| `adversarial_filter` 补生成迭代 / 两份 JSON | **保留** |
| `gate_multi_true` 松绑 rubric | **反向收紧**（precision over recall，撤销 Phase A 松绑）；**仅 AR**（AR 专属 gate 变体，非 AR 保持 Phase A rubric 不动） |
| on_accept 逐题追加 / progress 断点 / verdicts 表续跑 / 原子写 | **保留**，pair 需原子成对落盘（见 §7） |
| 4 选项 + 字母答案契约 | **扩展**：加 pair 语义；单题仍 4 选，pair 双向 AND 评分 |
| 11 非 AR generate-v2 全流程 | **零改动** |

未发现隐式删除。唯一有意行为变更：AR 出题机制整体重构；multi_true 收紧限定 **AR-only**（有意，用户批准；非 AR byte-identical 不受影响）。

## 3. 架构与数据流

```mermaid
flowchart TD
  S[采样素材 + 选 sub_pattern] --> F{sub_pattern 可翻转?}
  F -->|temporal/cross_segment| PAIR[生成镜像对 P+Q<br/>同视频翻转轴, 选项 bag-of-words 匹配]
  F -->|semantic/fine_grained/evidence_gap/premature| SET[生成 4 选对比集<br/>单维反事实, 等信息量]
  PAIR --> GRD[独立 VLM MiniMax-M3<br/>对真实帧打视觉可信度分 → 区间选负项]
  SET --> GRD
  GRD --> PROXY[内联轻量对抗探针<br/>text-only 盲答 + MiniMax 快解]
  PROXY -->|被秒杀| RE[回灌失败模式重生]
  RE --> GRD
  PROXY -->|存活| GATE[门: key_verify + multi_true收紧-AR变体<br/>+ NLI禁蕴含 + leak + MiniMax歧义门+NLI]
  GATE -->|失败| RE
  GATE -->|通过| ACC[on_accept 落盘<br/>pair 原子成对写]
  ACC -.批次后置.-> NET2[完整 agent 作弊门第二道网<br/>pair 双向 AND 评对 + 补生成迭代]
  NET2 --> FINAL[accepted_questions_final.json]
```

## 4. 组件设计

### 4.1 双模式生成（`contrastive_gen.py`，仅 AR）

`SubPattern`（`app/question_gen/strategy.py::SubPattern`）已有 `supports_flip`/`flip_axis`（Phase B Task 1），6 个 AR SubPattern 定义在 `app/question_gen/strategy_action_recognition.py`（`temporal`/`cross_segment` 标 `supports_flip=True`）。**本节所有 sub-pattern 改动锚定这两个文件，不涉及 `families.py`。** 生成分流：

**A. 可翻转（temporal→before/after, cross_segment→first/last）— 镜像对**
- VLM 生成题 P（正解 + 干扰项）后，**同素材翻转 flip_axis** 生成镜像 Q（正解天然相反）；
- 选项尽量 **bag-of-words 匹配**（Vinoground：正负选项同词不同序 → 长度/具体度线索结构上消失）；
- 产出 (P, Q) 共享 `pair_id`，评分双向 AND。

**B. 不可翻转（其余 4 个）— 4 选对比集**
- VLM 生成正解 + 3 个**单维反事实**（各改一维、互不重叠 → 防逐槽多数投票）；
- 硬约束（写进 prompt + 后置校验）：**等信息量/等长**、**禁蕴含**（正解非任一干扰项子串、反之亦然）、**动词/事件结构**上反事实（非名词替换，VITATECS 警示名词替换易解）。

> **不可翻转 4 子模式的可量化验收指标（I4）**：1-vs-3 结构仍有残留偏置风险，探针 + 歧义门是**过滤**（拦截）而非**构造消除**，故须以下量化闸门验收（阈值进科研 YAML，见 §10）：
>
> | 指标 | 验收阈值 | 手段 |
> |------|---------|------|
> | text-only 盲答正确率 | 接近随机基线（4 选 ≈ 0.25，超出容差即判可纯文本解） | §4.3 盲答探针 |
> | 选项长度 / 词频均衡 | 各选项 token 长度极差 ≤ 阈值、词频分布通过均衡检查 | 后置校验 |
> | 禁蕴含通过率 | 100%（存在蕴含即 fail） | §4.4 NLI 门 |
> | MiniMax 快解拒绝率 | ≤ 上限（快解率过高说明视觉依赖不足） | §4.3 快解探针 |

### 4.2 独立 VLM grounding（改 `distractor_selector.py`）

- **装配拆分**：`InfraSettings` 新增读 `M3_VL_LLM_*`；adapter 产出**第二个 `GovernedVLMClient`**（scorer_vlm=MiniMax-M3）。生成用主 VLM（qwen），打分用 scorer_vlm。
- `build_grounded_options` 增 `scorer_vlm` 形参；`_score_options` 用 scorer_vlm。区间选择 `[s_correct-δ_high, s_correct-δ_low]` / 退火 / hard-fail 落库**逻辑不变**。
- **早期风险校验**：MiniMax-M3 打分粒度未知（Phase A 观测到 qwen 自评是 1.0/0.1 粗档）。实现第一步抽验 scorer 打分是否够细；过粗则记录为已知局限并考虑候选池扩容补偿。

> **ADR 风格决策说明（I1）——用 MiniMax-M3 当 grounding scorer 偏离 findings 技术建议**：
> - **偏离点**：findings 建议 grounding 不该由生成同源的 VLM 打分，应改用 CLIP 式 **embedding 跨模态度量**（对齐 GroundAttack）。本设计仍用一个 LLM/VLM（MiniMax-M3）作判分器，是**有意决策**——CLIP 在本项目基础设施上无法服务（无跨模态 embedding 通路），退而求其次用一个**独立于生成器**的 judge。
> - **解决了什么**：断掉 Phase A 的"生成器用自己的 VLM 自评 = 循环自证"。
> - **没解决什么**：不等价于 GroundAttack 的 embedding 跨模态度量；判分仍是 LLM 主观打分，非几何相似度。
> - **可观测风险 + 验收阈值**：scorer 打分粒度（是否退化成 1.0/0.1 粗档）为主要风险 → 实现第一步做 **scorer 打分粒度校验**（抽验分档分布），过粗即记为已知局限并触发候选池扩容补偿。
>
> **配置归属（I6，对照 CLAUDE.md §4.5）**：MiniMax API **endpoint/key 属工程配置**（`.env`，`M3_VL_LLM_*`，敏感不提交）；**scorer 粒度阈值、探针阈值、`filter_task_types`** 属科研 YAML 配置，**进 run 快照保证可复现**。

### 4.3 内联轻量对抗探针（`adversarial_probes.py`，仅 AR）

生成候选通过 grounding 后、进正式门前，跑两个**便宜**探针（都不跑完整树搜索 agent）：
- **text-only 盲答探针**：去掉视频/帧，只把 question+options 喂一个 LLM 盲选；答对（对 pair 是双向都对）→ 判"不看视频可解"→ 拒，回灌"该题能被纯文本解，请增强对视觉的依赖"重生。
- **MiniMax 快解探针**：MiniMax-M3 在**不深搜**（只给采样帧+题）设定下试答；秒解 → 拒，回灌失败模式。

探针结果记 run_store 观测（供调参）。

### 4.4 门控收紧 + 独立歧义门

| 门 | v2 处置 |
|----|--------|
| key_verify | 保留 |
| multi_true（收紧，**仅 AR 变体**） | **收紧**：撤销 Phase A 松绑，回到"≥2 选项在题干限定下都完全为真→fail"，且取向 precision over recall（宁杀错）。**仅作用于 AR**（AR 专属 gate 变体/参数，由 strategy 分流）；**非 AR 走 Phase A 现有 multi_true gate 不动**（保 byte-identical） |
| NLI 禁蕴含门（新，仅 AR） | 用 NLI 模型（如 Sentence-BERT / 现有文本 embed 近似）判正解与各干扰项**互不蕴含**；存在蕴含→fail（杀子串/最模糊项） |
| MiniMax 歧义门 + NLI 复核（新，仅 AR） | MiniMax-M3 独立判：是否有干扰项其实**也正确/语义等价于正解**；有→fail（独立于生成器的"错误性/wrongness"核验）。**并加一路独立信号 NLI 文本蕴含复核**：干扰项与正解构成互相蕴含/语义等价（NLI 判定）亦→fail，**不单靠 MiniMax** |
| leak_test | 保留 |

> **单模型多角色风险（I2）**：MiniMax-M3 在本设计里同时承担 **grounding 打分（§4.2）+ 快解探针（§4.3）+ 歧义门（本节）** 三个角色——这是**单模型多角色，不是多裁判**。若 MiniMax 对某类视频存在系统性偏差，三门会**同向失效**（同时误判、无相互纠偏）。缓解：歧义门**至少加一路与 MiniMax 独立的信号（NLI 文本蕴含）**，不把歧义判定完全押在 MiniMax 上；scorer 粒度校验（§4.2）持续监控系统性偏差。

### 4.5 完整 agent 后置第二道网（改造 `app/question_gen/adversarial_filter.py`）

> **两个正交口径（C5，务必分清，勿复用同一句）**：pair correctness 与翻转有效性是**两件事**，Phase B 已把翻转门语义修正为"不判对错、只看答案是否随问题翻转"（[2026-07-14-adversarial-question-gen-phaseB-design.md §4.2]）。v2 显式拆成两条互不复用的口径：

| 口径 | 定义 | 用途 | 判据 |
|------|------|------|------|
| **(a) pair correctness（评测/训练评分）** | P、Q **都答对**才得 1 分，否则 0 | 喂 harness 评分 / 诊断 / 进化（correctness 信号，见 §5） | prediction==answer 的**双向 AND** |
| **(b) 对抗翻转有效性 filter** | canonical answer 是否**随问题翻转**（**不判对错**） | 后置剔除偏置题（沿用 Phase B 修正语义） | 答案随问题翻转=有效保留，否则剔 |

- **作弊者门**：定位为后置第二道网（非唯一裁判）。完整树搜索 agent 试答；套用口径 (a) 对 pair 做**双向 AND**（P、Q 都答对=太简单→剔）。
- **翻转有效性 filter**：镜像已在生成期产出，**不再事后生成**；套用口径 (b)（reuse `judge_flip`）——只看 canonical answer 是否随问题翻转，**不判对错**；不翻转（偏置题）→剔。**与 (a) 分开执行，不复用同一次评分。**
- 补生成迭代 / 难度报告 / 两份 JSON / verdicts 续跑：**保留**。

## 5. 契约改动（评测/训练适配）

核心抽象：引入 **`QuestionUnit`（single | pair）** 作为采样/分批/聚合三处的统一粒度，AgentLoop 仍逐题（MCQ）执行——unit 只活在 unit 层与聚合层，flatten 后喂 AgentLoop。三处必须一致使用同一 QuestionUnit 抽象（见自检）。

### 5.0 类型扩展
- `core/types.py::GeneratedQuestion` 新增：`pair_id: str | None`、`question_role: str`（"single"|"pair_original"|"pair_mirror"，默认 "single"）、`flip_axis: str | None`。默认值保证 11 非 AR + 现有构造点不变。
- 新增 `QuestionUnit` 抽象：`kind`（"single"|"pair"）、`unit_id`、`task_type`、`size`（single=1、pair=2）、`correctness`（用于 batching 混样）、以及所含的 1 或 2 条 `GeneratedQuestion`。
- **`QuestionUnit` 组装规则 + 非法 pair 状态处理（M2）**：同一 `pair_id` **必须恰好 2 条**（1 个 `pair_original` + 1 个 `pair_mirror`），且两题 **video_id / task_type / flip_axis 一致**。不满足（孤儿题、数量≠2、角色重复、字段不一致）→ **hard-fail 或剔除该 pair**（不静默拼半个 unit）；single 题恒组成 size=1 的 unit。

### 5.1 采样按 unit 计数（C2，`app/question_gen/loader.py::stratified_sample`）
真实 loader 是 **`app/question_gen/loader.py`**（不是 `harness/loader.py`）。现 `stratified_sample` 按单个 `GeneratedQuestion` 采样、按 `question_id` 分层、去重补足——会**把 pair 拆开**。改为：
- 采样对象改为 **`QuestionUnit`**；所有**比例 / size / min_per_class 均按 unit 计数**（pair 计 1 个 unit，不是 2）；
- 采样完成后再 **flatten 成逐题列表**喂 AgentLoop（pair 展开为 original+mirror 两条）。

### 5.2 分批按 unit（C3，`app/harness/batching.py`）
真实 batching 是 **`app/harness/batching.py`**（`build_batches`：FFD 按 task_type group 分箱、大类 round-robin 每次放一个 q）——现按单题会**把 pair 拆到不同 batch**。改为：
- batching 输入改为 **unit 列表**（unit 携带 `task_type` / `unit_id` / `size` / `correctness`）；
- **FFD 容量按 `unit.size` 计**（pair 占 2 个容量）；round-robin 以 unit 为分发粒度；
- flatten 成逐题前，保证 **pair 的 original 与 mirror 落在同一 batch**（不跨批割裂）。

### 5.3 pair-level 评分聚合（C1，`app/harness/inference.py::run_inference`）
现 `run_inference` **按单题聚合**（`total=len(records)`、`correct=sum(prediction==answer)`、`per_task_type` 逐题）。pair 要求双向 AND 计 1 个 unit，聚合算法明确改为：

1. **逐题推理不变**：每条 `GeneratedQuestion`（含 pair 的 original / mirror）照常各答一次 MCQ，**per-question prediction 仍逐题写 predictions 表**（保留逐题 prediction 追踪字段，供溯源/诊断）。
2. **合成 pair-level record**：single 题的 per-question record 直接作为 1 条 unit record；pair 先按 `pair_id` **收齐 original + mirror 两条** per-question record，合成 **1 条 pair-level record**——`pair 正确 = (P.prediction==P.answer) AND (Q.prediction==Q.answer)`。
3. **unit 粒度统计**：`InferenceResult.total / correct / per_task_type` 全部按 **unit 粒度**统计（total = single 数 + pair 数；correct 用上面的 unit 正确判据；pair 归入其 task_type 计 1 个 unit）。
4. correctness 信号按 **unit（pair）粒度**喂诊断/进化。
5. **非法 pair 防御**：聚合时若某 `pair_id` 收不齐 2 条（孤儿）→ 按 M2 hard-fail 或剔除该 unit，不计入 total（并告警，不静默）。

### 5.4 序列化边界（I3，全部显式写出/读回 pair 字段）
现有序列化点只到 `sub_pattern`，会丢 pair 字段。以下每处都必须显式写出/读回 `pair_id` / `question_role` / `flip_axis`，读回用 `.get(..., 默认)` 向后兼容旧 JSON：

| 边界 | 位置 | 处置 |
|------|------|------|
| 读回 benchmark | `app/question_gen/loader.py::load_benchmark` | 补读 `pair_id`/`question_role`/`flip_axis`（`.get` 兼容旧 JSON） |
| 记录序列化 | `app/question_gen/adversarial_filter.py::_question_to_record` | 显式写出三字段 |
| final bank 写盘 | `app/question_gen/adversarial_filter.py::write_final_bank` | 显式写出三字段 |
| on_accept 写 JSON | tools 的 `on_accept` 回调（逐题追加落盘） | 显式写出三字段 |
| final JSON 重建 | final JSON 反序列化重建 `GeneratedQuestion` | `.get` 读回三字段并校验 pair 完整 |

- **AgentLoop 本身不改**：仍一次答一道 MCQ；pair / unit 语义只在采样、分批、评分聚合层。

## 6. 错误处理

- grounding hard-fail（凑不齐 grounded 干扰项）→ 重出（reuse 现有机制）；
- 镜像生成失败（造不出合法翻转对）→ 该 slot 退回 4 选对比集模式或重出（不产半个 pair）；
- scorer VLM / 探针 VLM 异常 → slot 级隔离（对齐 Phase A C3 修复：宽异常→重出，不崩整批）；
- NLI/歧义门模型不可用 → 明确报错（不静默跳过，防漏过歧义题）。

## 7. 非功能性需求

> **on_accept 逐题追加 vs pair 原子的协议消歧（I5）**：二者不矛盾——**接受粒度是 unit，落盘粒度是 unit**。pair 先在内存 **pending buffer** 收齐 `pair_original` + `pair_mirror` 两题（single 题恒直接成 unit），**收齐后一次性作为一个 unit 进 accepted 列表**；文件写用**全量 tmp + `os.replace`** 原子替换（不做真正的行内 append，避免半写）；读回时**校验 pair 成对完整、拒绝/剔除孤儿题**（对齐 §5 M2）。即"逐题追加"在语义上是"逐 unit 追加"，pair 永不半落。

| 维度 | 设计 |
|------|------|
| **持久化** | 接受与落盘均以 **unit** 为粒度：pair 在内存 pending buffer 收齐 original+mirror 后一次性进 accepted；文件写全量 tmp + `os.replace`，**pair 原子成对写**——P、Q 同时落盘或都不落，崩溃不留半个 pair。selector/探针观测随题落 run_store |
| **幂等性** | 同 seed→同 slot；镜像由 flip_axis 确定性构造；VLM 非确定性为既有属性，不新增 |
| **断点续跑** | progress（slot→status）；pair 以"整对完成"为 accepted 单位；后置 verdicts 表按 (question_id/hash/stage) 续跑 |
| **原子性** | 逐 slot / 逐 unit 落盘；final JSON 全量 tmp+os.replace；pair 半写检测（读回时校验成对完整，缺半剔除并重生） |

## 8. 测试策略

- 单元：双模式分流（flippable→pair、非 flippable→4 选）；bag-of-words 匹配校验；NLI 禁蕴含判定；text-only 探针与快解探针（mock VLM）；pair 原子写/半写检测；双向 AND 评分。
- 集成：AR 端到端（mock 主 VLM + mock scorer VLM + mock 探针）→ 验证 pair 成对落盘、非 flippable 走 4 选、门收紧生效；后置双向评对。
- 回归：11 非 AR 题型 generate-v2 行为**字节级不变**（现有测试全绿，含 multi_true 非 AR gate 不动）；harness/loader 对混格题库（AR pair + 非 AR single）评分正确。
- 回归（pair 不拆，M3）：
  - `app/question_gen/loader.py::stratified_sample`——自然采样 / 按 ratio / 按 min_per_class 三种口径下，**pair 的 original+mirror 始终同进同出**、不被拆开、比例按 unit 计数正确；
  - `app/harness/batching.py::build_batches`——FFD + round-robin 分批后，**同 pair_id 的两题必落同一 batch**，FFD 容量按 unit.size 计；
  - `app/harness/inference.py::run_inference`——pair-level 聚合：unit 粒度 total/correct 正确、双向 AND 判据正确、孤儿 pair 按 M2 剔除。
- Agent 类测试产出 MD（§4.6 规范）。

## 9. 分期实现（一个设计，writing-plans 分阶段）

> **消除前向依赖（C6）**：原分期把"harness/eval/loader/batching 混格适配"放阶段 5，但阶段 4"后置双向评分"依赖它，构成前向依赖。现将 **pair-unit 的最小 harness/loader/batching 支持前移到阶段 1（契约阶段）**；阶段 4 只**接入阶段 1 已存在的 pair-unit scorer**，不再依赖后置阶段。

1. **契约阶段（QuestionUnit + 最小 pair-unit 支持）**：`GeneratedQuestion` 新字段 + `QuestionUnit` 抽象 + M2 pair 组装/校验 + **最小 pair-unit 支持**（`loader.stratified_sample` 按 unit 采样、`harness/batching.build_batches` pair 同批、`harness/inference.run_inference` pair-level 聚合 scorer）+ 双模式生成骨架（contrastive_gen 分流）+ pair 原子落盘；
2. **独立 VLM grounding**（装配第二 VLM + selector 换 scorer + 粒度校验）；
3. **内联对抗 + 门收紧（AR 变体）+ NLI/歧义门**；
4. **后置双向评分 + 补生成**（改造 `adversarial_filter`：作弊门口径 (a) **直接接入阶段 1 已存在的 pair-unit scorer**、翻转有效性 filter 口径 (b)）；
5. **混格题库全链路验证 + 剩余适配**（AR pair + 非 AR single 端到端评分正确性验收，兜底剩余序列化边界）。

## 10. 风险与回退

> **配置归属（I6）**：本节及 §4 涉及的可调参数——scorer 粒度阈值、探针阈值（盲答/快解容差）、`filter_task_types`、§4.1 各量化验收阈值——均属**科研 YAML 配置**并**进 harness run 快照**（可复现、可对比扫动）；MiniMax API endpoint/key 属**工程配置**（`.env`，`M3_VL_LLM_*`）。二者不混用（对照 CLAUDE.md §4.5）。

| 风险 | 回退 |
|------|------|
| MiniMax-M3 打分过粗/不准 | 扩候选池补偿；或记录为已知局限，后置 agent 门兜底 |
| 网关无 NLI 模型 | 用现有文本 embed 近似蕴含判定；或 LLM few-shot 判蕴含 |
| 混格题库拖累训练 | 分期 5 先验证评分正确性；必要时 AR pair 与非 AR 分池训练 |
| 双模式产量低（4 选非 flippable 侧 hard-fail 高） | 早停 + 补生成迭代兜底（reuse Phase B） |

## 11. 核心算法保真

v2 局限于 question_gen 层，不迁移 `research-wiki/ARCHITECTURE.md §6` 的 12 项核心算法。作弊门复用既有 `run_inference`（AgentLoop 完整树搜索）未改内部。保真校验不适用。
