feat(question_gen): add generate-v2 CLI subcommand and experiment script

- Add generate-v2 subparser with --config, --store-dir, --db-path,
  --seed, and --dry-run arguments to tools/generate_questions.py
- Implement _run_generate_v2 async handler: config loading, video
  discovery, DI client construction, TreeIndex loading, pipeline
  invocation, and result persistence
- Add scripts/generate_questions_v2.sh following build_trees.sh
  conventions (source .env, conda run python path, MODE=mock support)
- Update app/question_gen/__init__.py to export full v2 public API:
  run_pipeline_v2, PipelineConfig, PipelineResult, QuestionFamilySpec,
  ALL_FAMILIES, CandidateQuestion, generate_one_v2, GateReport, run_gates
- Add QuestionGenStore.load_progress() for pipeline resumption
- Add integration tests for CLI help and dry-run behavior
- Update test_question_gen_api to match expanded __all__

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 00:04:42 -04:00
parent f46e87258c
commit eecb86e27a
6 changed files with 533 additions and 14 deletions
+16 -1
View File
@@ -1,6 +1,10 @@
"""出题模块 — benchmark 加载、分层采样赛题合成。"""
"""出题模块 — benchmark 加载、分层采样赛题合成与 v2 出题管线"""
from app.question_gen.families import ALL_FAMILIES, QuestionFamilySpec
from app.question_gen.gates import GateReport, run_gates
from app.question_gen.generator_v2 import CandidateQuestion, generate_one_v2
from app.question_gen.loader import load_benchmark, stratified_sample
from app.question_gen.pipeline_v2 import PipelineConfig, PipelineResult, run_pipeline_v2
from app.question_gen.synthesizer import (
TASK_TYPE_LEVEL_MAP,
AnchorContext,
@@ -9,10 +13,21 @@ from app.question_gen.synthesizer import (
)
__all__ = [
# v1 接口
"load_benchmark",
"stratified_sample",
"TASK_TYPE_LEVEL_MAP",
"AnchorContext",
"generate_one",
"sample_anchor",
# v2 接口
"run_pipeline_v2",
"PipelineConfig",
"PipelineResult",
"QuestionFamilySpec",
"ALL_FAMILIES",
"CandidateQuestion",
"generate_one_v2",
"GateReport",
"run_gates",
]
+40
View File
@@ -380,6 +380,46 @@ class QuestionGenStore:
heavy_sampled=row[3] or 0,
)
def load_progress(self) -> dict[str, str]:
"""加载已完成 slot 的进度映射(用于断点续跑)。
从最近一次 running 状态的批次中,读取所有 final_status 非 pending 的 item
聚合为 slot_id → "accepted"|"rejected" 映射。
若存在同一 slot_id 的多条记录(多次重出),取最终状态:
- 任一条 accepted → accepted
- 全部 rejected → rejected
Returns
-------
dict[str, str]
{slot_id: "accepted"|"rejected"} 映射。无进度时返回空 dict。
"""
# 取最近一次未结束的 run_id
row = self._conn.execute(
"SELECT run_id FROM question_gen_runs WHERE status='running' "
"ORDER BY started_at DESC LIMIT 1",
).fetchone()
if row is None:
return {}
run_id = row[0]
rows = self._conn.execute(
"SELECT slot_id, final_status FROM question_gen_items "
"WHERE run_id=? AND final_status != 'pending'",
(run_id,),
).fetchall()
progress: dict[str, str] = {}
for slot_id, status in rows:
if status == "accepted":
progress[slot_id] = "accepted"
elif slot_id not in progress:
progress[slot_id] = "rejected"
return progress
def close(self) -> None:
"""关闭数据库连接。"""
self._conn.close()