6 个 Task: telemetry 防御加固 → call_id 根因修复 → detector L2/L1 扩展 → progress 管理 → 并发编排+CLI → lint+全量测试
7.2 KiB
id, title, type, created, status
| id | title | type | created | status |
|---|---|---|---|---|
| question-gen | 出题模块迁移设计(question_gen) | design | 2026-07-07 | approved |
出题模块迁移设计
1. 目标
从 TRM4 core/harness/question_gen.py 迁移出题数据结构与采样逻辑到 TRM5 Clean Architecture,同时预留 LLM 驱动出题的 Protocol 接口。
| 维度 | 说明 |
|---|---|
| 迁移范围 | benchmark 加载 + 分层采样(纯函数,180 行) |
| 预留接口 | QuestionGenerator Protocol(不实现,后续参考 TRM4 research-wiki/designs/2026-07-06-question-gen-synth-design.md) |
| 不做 | LLM 出题实现、校准脚本、去重机制 |
2. Clean Architecture 分层决策
2.1 类型放置
GeneratedQuestion 被 core/evolution/(diagnose、validate)和 app/harness/(runner、batching、pools、inference)跨层使用。按依赖方向(core 不可依赖 app),必须放 core/types.py,与 LLMResponse 同级。
flowchart LR
CT["core/types.py\nGeneratedQuestion"] --> CE["core/evolution/\ndiagnose · validate"]
CT --> AH["app/harness/\nrunner · batching · pools"]
CT --> AQ["app/question_gen/\nloader"]
2.2 模块结构
core/types.py ← 追加 GeneratedQuestion
app/ports.py ← 追加 QuestionGenerator Protocol
app/question_gen/
├── __init__.py ← 公开 API re-export
└── loader.py ← load_benchmark() + stratified_sample()
否决方案:
| 方案 | 否决理由 |
|---|---|
GeneratedQuestion 放 app/question_gen/types.py |
core/evolution/ 无法 import app/ 层,违反依赖方向 |
| loader / sampler 拆两文件 | sampler 仅 ~100 行,不值得独立文件 |
Protocol 放 app/question_gen/protocols.py |
与 EmbeddingProvider 在 app/ports.py 的既有模式不一致 |
3. 类型定义
3.1 GeneratedQuestion(core/types.py 追加)
@dataclass(frozen=True)
class GeneratedQuestion:
"""单条生成/加载的题目。跨层共享类型。"""
question_id: str
video_id: str
task_type: str
question: str
options: tuple[str, ...]
answer: str
source_nodes: tuple[str, ...]
difficulty: str
与 TRM4 的有意变更:
| 变更 | 理由 |
|---|---|
options: list → tuple |
配合 frozen=True 不可变语义 |
source_nodes: list → tuple |
同上 |
difficulty 移除默认值 "medium" |
显式传入(§4.1 P4: 显式优于隐式) |
移除 QuestionGenResult:TRM5 无消费者,YAGNI。
3.2 QuestionGenerator Protocol(app/ports.py 追加)
@runtime_checkable
class QuestionGenerator(Protocol):
"""LLM 驱动的题目生成端口(预留接口)。"""
async def generate(
self,
video_id: str,
task_type: str,
tree: TreeIndex,
*,
exemplars: list[GeneratedQuestion],
) -> GeneratedQuestion: ...
接口设计参考 TRM4 仓库 research-wiki/designs/2026-07-06-question-gen-synth-design.md(位于 /home/iomgaa/Projects/Video-Tree-TRM4/,不复制到 TRM5)中的"题型-层级映射 + few-shot exemplar"模式。tree 参数提供锚节点上下文,exemplars 提供风格示例。具体实现在后续 tools/generate_questions.py(一次性脚本)中完成,通过 adapters/ 层的 Protocol 实现注入。
4. 函数接口
4.1 load_benchmark
load_benchmark(questions_dir: Path) -> list[GeneratedQuestion]
从指定目录 glob *.json,每个文件以 stem 为 video_id,解析为 GeneratedQuestion 列表。JSON 格式与 store/questions/benchmarks/Video-MME/*.json 完全一致。
与 TRM4 对比:算法 100% 保真。options 和 source_nodes 转为 tuple。
difficulty 字段处理规则:现有 benchmark JSON(store/questions/benchmarks/Video-MME/)不含 difficulty 字段,这是 legacy schema 特征。加载时按如下规则显式转换(非默认值掩盖):
| JSON 情况 | 处理 |
|---|---|
有 difficulty 字段 |
取 JSON 值 |
无 difficulty 字段 |
赋 _LEGACY_DEFAULT_DIFFICULTY = "medium" 常量 |
常量集中定义在 loader.py 顶部,测试用例覆盖两种情况。
4.2 stratified_sample
stratified_sample(
questions: list[GeneratedQuestion],
correctness: dict[str, bool],
size: int,
correct_ratio: float | None,
task_types: list[str] | None,
seed: int,
min_per_class: int | None,
) -> list[GeneratedQuestion]
所有参数显式传入,无默认值(§4.1 P4)。
算法保真清单(逐一比对 TRM4):
| 逻辑点 | TRM4 行为 | TRM5 保持 |
|---|---|---|
task_types 过滤 |
task_types 非 None 时,先过滤 pool 只保留指定题型 |
保持 |
correct_ratio=None |
自然分布分支,随机抽样 size 道 |
保持 |
correct_ratio 有值 |
按对错比例分层,对题 round(size * ratio) |
保持 |
correctness.get(id, False) |
未知 correctness 的题统一当错题处理 | 保持 |
| 分层返回顺序 | 对题在前、错题在后 | 保持 |
| 池不足 | ValueError 报错,不静默降级 |
保持 |
min_per_class 补足 |
遍历 pool 全部题型(非仅 sampled 命中的),按首次出现顺序确定性枚举 | 保持 |
| 补足不足时 | 全取,不报错 | 保持 |
| 随机种子 | random.Random(seed) 局部实例 |
保持 |
内部辅助函数 _ratio_stratified_sample 和 _backfill_per_class 完整保留。
5. 职责边界
| 组件 | 职责 | 位置 | 谁 import 谁 |
|---|---|---|---|
GeneratedQuestion |
题目数据结构 | core/types.py |
被所有层 import |
load_benchmark / stratified_sample |
加载 + 采样 | app/question_gen/loader.py |
被 app/harness/ import |
QuestionGenerator Protocol |
LLM 出题接口定义 | app/ports.py |
被未来 adapters/ 实现 |
tools/generate_questions.py(未来) |
LLM 出题一次性脚本 | tools/ |
独立工具,不被其他模块 import |
tools/generate_questions.py 未来可实例化 QuestionGenerator 的 adapter 实现,但 tools/ 本身不被 app/ import(§5 硬性规则)。
6. 文档同步
以下章节需要更新:
| 文档 | 章节 | 变更 |
|---|---|---|
ARCHITECTURE.md §1 表格 |
DataLoader 行 app/question_gen/generator.py |
→ app/question_gen/loader.py |
ARCHITECTURE.md §2.2 Mermaid |
QGEN 节点 generator.py |
→ loader.py |
CLAUDE.md §1.5 表格 |
DataLoader 行 app/question_gen/generator.py |
→ app/question_gen/loader.py |
不变更:ARCHITECTURE.md §6 核心算法保真清单 — stratified_sample 是采样工具函数,不属于 13 项核心算法(那些是建树 + 训练的关键算法)。
7. 测试策略
| 测试 | 路径 | 覆盖点 |
|---|---|---|
GeneratedQuestion 冻结性 |
tests/unit/test_core_types.py(追加) |
frozen 不可变、字段完整性 |
load_benchmark |
tests/unit/test_question_loader.py |
正常加载、空目录、JSON 格式异常 |
stratified_sample |
tests/unit/test_question_loader.py |
自然分布、分层采样、题型过滤、未知 correctness 当错题、对题在前返回顺序、题型保底、池不足报错、种子可复现 |