plan(tree/repair): 三项改造实现计划(遥测加固+断点续跑+并发)
6 个 Task: telemetry 防御加固 → call_id 根因修复 → detector L2/L1 扩展 → progress 管理 → 并发编排+CLI → lint+全量测试
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
---
|
||||
id: question-gen
|
||||
title: 出题模块迁移设计(question_gen)
|
||||
type: design
|
||||
created: 2026-07-07
|
||||
status: 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` 同级。
|
||||
|
||||
```mermaid
|
||||
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` 追加)
|
||||
|
||||
```python
|
||||
@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` 追加)
|
||||
|
||||
```python
|
||||
@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 当错题、对题在前返回顺序、题型保底、池不足报错、种子可复现 |
|
||||
Reference in New Issue
Block a user