23 KiB
id, title, type, created, status
| id | title | type | created | status |
|---|---|---|---|---|
| question-gen-v2-grounded-contrastive | question-gen v2 — AR grounded-contrastive 重构(双模式生成 + 独立 VLM grounding + 生成端对抗) | design | 2026-07-15 | 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. 架构与数据流
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,聚合算法明确改为:
- 逐题推理不变:每条
GeneratedQuestion(含 pair 的 original / mirror)照常各答一次 MCQ,per-question prediction 仍逐题写 predictions 表(保留逐题 prediction 追踪字段,供溯源/诊断)。 - 合成 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)。 - unit 粒度统计:
InferenceResult.total / correct / per_task_type全部按 unit 粒度统计(total = single 数 + pair 数;correct 用上面的 unit 正确判据;pair 归入其 task_type 计 1 个 unit)。 - correctness 信号按 unit(pair)粒度喂诊断/进化。
- 非法 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,不再依赖后置阶段。
- 契约阶段(QuestionUnit + 最小 pair-unit 支持):
GeneratedQuestion新字段 +QuestionUnit抽象 + M2 pair 组装/校验 + 最小 pair-unit 支持(loader.stratified_sample按 unit 采样、harness/batching.build_batchespair 同批、harness/inference.run_inferencepair-level 聚合 scorer)+ 双模式生成骨架(contrastive_gen 分流)+ pair 原子落盘; - 独立 VLM grounding(装配第二 VLM + selector 换 scorer + 粒度校验);
- 内联对抗 + 门收紧(AR 变体)+ NLI/歧义门;
- 后置双向评分 + 补生成(改造
adversarial_filter:作弊门口径 (a) 直接接入阶段 1 已存在的 pair-unit scorer、翻转有效性 filter 口径 (b)); - 混格题库全链路验证 + 剩余适配(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 完整树搜索)未改内部。保真校验不适用。