docs: add TaskTypeStrategy design + Plan A framework plan
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
# Per-Category Pool Strategy 设计
|
||||
|
||||
> **状态**: 已批准
|
||||
> **日期**: 2026-07-12
|
||||
> **关联**: [app-harness-design](2026-07-07-app-harness-design.md)、[question-gen-v2-design](2026-07-11-question-gen-v2-design.md)
|
||||
|
||||
## 1. 动机
|
||||
|
||||
v2 题目生成管线为 12 个 task_type 各生成 30 题(共 360 题)。需要支持:
|
||||
|
||||
| 需求 | 说明 |
|
||||
|------|------|
|
||||
| 按类增量 baseline | 类别生成完成后立即可跑 baseline inference,无需等全量完成 |
|
||||
| Per-category 分层划分 | 每类 30 题按 correctness 2:1 分为 20 train / 10 val |
|
||||
| 按类训练 | `--task-types` 控制训练作用域,快速验证单类别效果 |
|
||||
| 外部 test 池 | test 用 Video-MME 900 道真题中同类题目,不从生成题中抽取 |
|
||||
| 全局训练兼容 | 全类别联合训练时 system/tool 慢进化看全量 val 信号 |
|
||||
|
||||
## 2. 方案选型
|
||||
|
||||
| 方案 | 描述 | 取舍 |
|
||||
|------|------|------|
|
||||
| A. 最小改动 | 在 `build_pools` 内加 if 分支 | 改动小但两条路径耦合 |
|
||||
| B. 替换为纯 per-category | 删除全局模式 | 简洁但丢失 Video-MME 900 题训练能力 |
|
||||
| **C. Pool 工厂模式** ✓ | `PoolStrategy` Protocol + 具体策略 | 符合 Clean Architecture DIP/OCP;策略可独立测试替换 |
|
||||
|
||||
**选择 C**:池分割是真正易变的接缝(已有两种策略,未来可扩展),满足 CLAUDE.md 抽象引入条件。
|
||||
|
||||
## 3. 核心抽象
|
||||
|
||||
### 3.1 PoolStrategy Protocol
|
||||
|
||||
```python
|
||||
# app/ports.py 新增(应用层端口,非 core 层——因为返回值 Pools 定义在 app/harness/)
|
||||
|
||||
class PoolStrategy(Protocol):
|
||||
"""池构建策略端口。"""
|
||||
|
||||
def build(
|
||||
self,
|
||||
questions: list[GeneratedQuestion],
|
||||
correctness: dict[str, bool],
|
||||
config: PoolConfig,
|
||||
) -> Pools: ...
|
||||
|
||||
def build_incremental(
|
||||
self,
|
||||
new_task_types: list[str],
|
||||
questions: list[GeneratedQuestion],
|
||||
correctness: dict[str, bool],
|
||||
config: PoolConfig,
|
||||
) -> dict[str, dict[str, list[str]]]:
|
||||
"""增量构建新类别的 train/val 划分。
|
||||
|
||||
返回 {task_type: {"train": [qid, ...], "val": [qid, ...]}}。
|
||||
仅 PerCategoryPoolStrategy 有实质实现;GlobalPoolStrategy
|
||||
不支持增量,调用时抛出 NotImplementedError。
|
||||
"""
|
||||
...
|
||||
```
|
||||
|
||||
> **层级决策**:`PoolStrategy` 放在 `app/ports.py` 而非 `core/protocols.py`,因为返回类型 `Pools` 定义在 `app/harness/pools.py`。若放入 core 会导致 core → app 的逆向依赖,违反 Clean Architecture 四层分层。
|
||||
>
|
||||
> **注**:`PoolConfig` 的两组字段(Global 系列 / PerCategory 系列)由两个策略各取所需,未使用的字段被忽略。这避免了为每个策略定义专属 config 子类的过度抽象。
|
||||
|
||||
### 3.2 PoolConfig
|
||||
|
||||
```python
|
||||
# core/types.py 新增
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PoolConfig:
|
||||
task_types: tuple[str, ...] | None # None = 全部类别
|
||||
seed: int # 随机种子
|
||||
baseline_run_id: str # 基线 run 标识(用于读 correctness、写入 pools 指纹)
|
||||
# GlobalStrategy 使用
|
||||
diag_size: int
|
||||
diag_correct_ratio: float
|
||||
val_size: int
|
||||
val_correct_ratio: float
|
||||
test_size: int
|
||||
eval_min_per_class: int
|
||||
# PerCategoryStrategy 使用
|
||||
train_ratio: float # train/(train+val),默认 2/3
|
||||
test_questions_dir: Path | None # 外部 test 题源路径(与 RunConfig.test_questions 对应)
|
||||
```
|
||||
|
||||
### 3.3 两个具体策略
|
||||
|
||||
**GlobalPoolStrategy**:封装现有 `build_pools` 逻辑,test → val → diag 全局三分。
|
||||
|
||||
**PerCategoryPoolStrategy**:
|
||||
|
||||
```
|
||||
对每个 task_type (共 N 题,目标 train=20, val=10):
|
||||
correct 题 (Nc 个) → shuffle(seed)
|
||||
train_correct = floor(Nc * 20/N) # 按总量 20/10 比例分配
|
||||
val_correct = Nc - train_correct
|
||||
wrong 题 (Nw 个) → shuffle(seed)
|
||||
train_wrong = 20 - train_correct # 确保 train 总量恰好 20
|
||||
val_wrong = Nw - train_wrong
|
||||
assert train_correct + train_wrong == 20
|
||||
assert val_correct + val_wrong == 10
|
||||
|
||||
diagnosis = 所有选中类别的 train 合并
|
||||
validation = 所有选中类别的 val 合并
|
||||
test = load_benchmark(test_questions_dir) 按 task_types 过滤
|
||||
```
|
||||
|
||||
**边界场景处理**:
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| 某类别全部 correct(0 wrong) | 退化为非分层 random 20/10,记录 WARNING |
|
||||
| 某类别全部 wrong(0 correct) | 同上,退化为非分层 random 20/10 |
|
||||
| 某类别 correctness 不完整(部分 qid 无 baseline) | fail-fast,列出缺失 qid |
|
||||
| Video-MME 中某 task_type 无题 | test 池该类别为空,记录 WARNING |
|
||||
|
||||
## 4. RunConfig 变更
|
||||
|
||||
| 新增字段 | 类型 | 默认值 | 归属 |
|
||||
|---------|------|--------|------|
|
||||
| `task_types` | `tuple[str, ...] \| None` | `None` | CLI 临时覆盖 |
|
||||
| `pool_split_mode` | `Literal["global", "per_category"]` | `"global"` | 科研 YAML |
|
||||
| `train_ratio` | `float` | `0.667` | 科研 YAML |
|
||||
| `test_questions` | `str` | `"benchmarks/Video-MME"` | 科研 YAML |
|
||||
|
||||
`default.yaml` 新增:
|
||||
|
||||
```yaml
|
||||
harness:
|
||||
pool_split_mode: global
|
||||
train_ratio: 0.667
|
||||
test_questions: "benchmarks/Video-MME"
|
||||
```
|
||||
|
||||
## 5. 增量 Baseline Inference
|
||||
|
||||
### 5.1 流程
|
||||
|
||||
```bash
|
||||
# 类别 A、B 完成 → 跑 baseline
|
||||
python main.py harness --mode infer \
|
||||
--run-id baseline_v2 --questions generated-v2-360 \
|
||||
--task-types "Object Recognition" "Scene Understanding"
|
||||
|
||||
# 类别 C 完成 → 追加到同一 run_id
|
||||
python main.py harness --mode infer \
|
||||
--run-id baseline_v2 --questions generated-v2-360 \
|
||||
--task-types "Action Reasoning"
|
||||
|
||||
# promote 为 seed
|
||||
python main.py harness --mode promote \
|
||||
--run-id baseline_v2 --seed baseline_v2_seed
|
||||
|
||||
# 按类训练
|
||||
python main.py harness --mode train --fresh \
|
||||
--seed baseline_v2_seed --run-id train_obj_recog_01 \
|
||||
--task-types "Object Recognition" --pool-split-mode per_category
|
||||
```
|
||||
|
||||
### 5.2 增量写入规则
|
||||
|
||||
| 表 | 唯一键 | 同 run_id 多次 infer 行为 |
|
||||
|---|--------|-------------------------|
|
||||
| `_runs` | `run_id` | upsert:更新 `updated_at`,保留原始 `created_at` |
|
||||
| `predictions` | `(run_id, question_id)` | INSERT;主键冲突报错(防重复推理) |
|
||||
| `traces` | `trace_id` | 正常 INSERT(UUID 天然唯一) |
|
||||
|
||||
**重跑同类别**:若需重新推理某类别,先 DELETE 该类别的 predictions(`WHERE run_id=? AND question_id IN (?)`),再重新 infer。
|
||||
|
||||
**promote 读取规则**:`promote_to_seed` 读取指定 `run_id` 下全部 predictions,不区分追加批次。promote 前应确保所有目标类别均已完成 baseline。
|
||||
|
||||
## 6. 池冻结格式
|
||||
|
||||
### 6.1 per_category 格式
|
||||
|
||||
```json
|
||||
{
|
||||
"split_mode": "per_category",
|
||||
"train_ratio": 0.667,
|
||||
"baseline_run_id": "baseline_v2",
|
||||
"categories": {
|
||||
"Object Recognition": {
|
||||
"train": ["qid_1", "qid_2", "..."],
|
||||
"val": ["qid_21", "..."]
|
||||
}
|
||||
},
|
||||
"test_source": "benchmarks/Video-MME",
|
||||
"questions": [...]
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 增量更新
|
||||
|
||||
```python
|
||||
def build_or_load_pools(
|
||||
config: RunConfig,
|
||||
strategy: PoolStrategy,
|
||||
db_path: Path,
|
||||
) -> Pools:
|
||||
"""构建或加载三池。
|
||||
|
||||
参数:
|
||||
config: 运行配置(含 task_types, pool_split_mode, baseline_run_id 等)。
|
||||
strategy: 池构建策略实例。
|
||||
db_path: harness.db 路径(用于读取 baseline correctness)。
|
||||
|
||||
流程:
|
||||
1. 若 pools.json 存在 → 加载
|
||||
a. 若 config.task_types 中有类别不在已冻结的 categories → 增量构建并追加
|
||||
b. 若 pools.json 的 seed/train_ratio/baseline_run_id 与当前 config 不一致 → 报错
|
||||
2. 若不存在 → 从 DB 加载 correctness → strategy.build() → 冻结
|
||||
3. 按 config.task_types 从 categories 中组装 Pools 返回
|
||||
"""
|
||||
```
|
||||
|
||||
**冻结一致性校验**:加载已有 pools.json 时,若 `split_mode`、`seed`、`train_ratio`、`baseline_run_id` 与当前 config 不一致,直接报错并提示删除 pools.json 重建。已冻结类别的划分不可变,新类别可追加。
|
||||
|
||||
### 6.3 向后兼容
|
||||
|
||||
旧格式 `pools.json`(无 `split_mode` 字段)自动识别为 global 模式。
|
||||
|
||||
## 7. task_types 作用域贯穿
|
||||
|
||||
策略在池构建阶段完成所有过滤,下游无需感知 task_types:
|
||||
|
||||
| 阶段 | 行为 |
|
||||
|------|------|
|
||||
| 池构建 | strategy 只处理指定类别 |
|
||||
| mini-batch | 消费 scoped 后的 diagnosis 池,无改动 |
|
||||
| per-skill gate | 池中只有指定类别的题,自然 scoped |
|
||||
| 慢进化 | 用 scoped 后的 val 池评估,照常进化 |
|
||||
| held-out eval | test 池已是 Video-MME 同类题 |
|
||||
|
||||
## 8. main.py train 接线
|
||||
|
||||
```python
|
||||
# composition root 伪代码
|
||||
elif config.mode == "train":
|
||||
strategy = (PerCategoryPoolStrategy()
|
||||
if config.pool_split_mode == "per_category"
|
||||
else GlobalPoolStrategy())
|
||||
pools = build_or_load_pools(config, strategy)
|
||||
await runner.train(pools)
|
||||
```
|
||||
|
||||
## 9. 文件改动地图
|
||||
|
||||
| 文件 | 改动类型 | 内容 |
|
||||
|------|---------|------|
|
||||
| `app/ports.py` | 新增 | `PoolStrategy` Protocol(应用层端口,避免 core → app 逆向依赖) |
|
||||
| `core/types.py` | 新增 | `PoolConfig` dataclass |
|
||||
| `app/harness/pools.py` | 重构 | `GlobalPoolStrategy` + `PerCategoryPoolStrategy` + 增量逻辑 |
|
||||
| `app/harness/config.py` | 修改 | RunConfig 新增 4 字段 + 校验;`pool_split_mode` 用 Literal 类型 |
|
||||
| `config/default.yaml` | 修改 | 3 个新配置项 |
|
||||
| `main.py` | 修改 | `task_types` 纳入 `cli_overrides` + RunConfig;train 接线;strategy 组装 |
|
||||
| `app/harness/log.py` | 微调 | `_runs` 表 `INSERT OR IGNORE` 改为 `ON CONFLICT DO UPDATE`(增量 infer 更新时间戳) |
|
||||
| `tests/unit/test_pools.py` | 新增 | 两种策略单元测试 |
|
||||
| `tests/integration/test_pool_strategy.py` | 新增 | 端到端集成测试 |
|
||||
|
||||
### 不改动
|
||||
|
||||
- `app/harness/batching.py` — 消费 scoped 池,无需感知策略
|
||||
- `core/evolution/` — 进化引擎与池来源解耦
|
||||
- `app/harness/inference.py` — 推理逻辑不变
|
||||
- `app/question_gen/` — 题目生成与池构建解耦
|
||||
@@ -0,0 +1,265 @@
|
||||
---
|
||||
id: task-type-strategy
|
||||
title: 出题管线 TaskTypeStrategy 拆分设计(Clean Architecture)
|
||||
type: design
|
||||
created: 2026-07-14
|
||||
status: approved
|
||||
---
|
||||
|
||||
# 出题管线 TaskTypeStrategy 拆分设计
|
||||
|
||||
## 1. 目标
|
||||
|
||||
借鉴 Clean Architecture 思想,将出题管线从 5 个粗粒度 Family 替换为 12 个题型级别的 TaskTypeStrategy,实现题型独立的出题策略。第一个特化实现为 ActionRecognitionStrategy(含 6 个失败子模式靶向出题)。
|
||||
|
||||
### 设计驱动
|
||||
|
||||
| 问题 | 数据来源 |
|
||||
|------|---------|
|
||||
| v2-360 全部 30 道 AR 题 100% 单帧可答 | `research-wiki/findings/2026-07-14-question-quality-gap-analysis.md` |
|
||||
| 12/12 题型存在 CRITICAL 或 HIGH 差距 | 同上 |
|
||||
| 同一 task_type 跨 family 随机选择导致行为不确定 | `app/question_gen/families.py` — AR 同时在 RETRIEVAL 和 VISUAL |
|
||||
| 5 个 prompt 模板零题型分支 | `store/prompts/question_gen/*.md` |
|
||||
|
||||
## 2. 架构
|
||||
|
||||
### 2.1 替换关系
|
||||
|
||||
```
|
||||
之前:pipeline → get_family_for_slot() → QuestionFamilySpec(5 个,随机选择)
|
||||
之后:pipeline → get_strategy(task_type) → TaskTypeStrategy(12 个,确定性查找)
|
||||
```
|
||||
|
||||
### 2.2 类层次
|
||||
|
||||
```
|
||||
TaskTypeStrategy (Protocol)
|
||||
│
|
||||
├── BaseTaskTypeStrategy (类)
|
||||
│ └── 封装现有 family 行为,确定性绑定一个 QuestionFamilySpec
|
||||
│ └── select_sub_pattern → None
|
||||
│ └── extra_gates → []
|
||||
│ └── 11 个题型用此类
|
||||
│
|
||||
└── ActionRecognitionStrategy (类)
|
||||
└── 自包含采样/prompt/SubPattern
|
||||
└── 仍提供 strategy_name / skill_target / leak_probe_template(gate/store 合约)
|
||||
└── 6 个 SubPattern + maintenance_pool
|
||||
```
|
||||
|
||||
### 2.3 接口定义
|
||||
|
||||
```python
|
||||
class TaskTypeStrategy(Protocol):
|
||||
"""题型出题策略 — pipeline 的唯一接口。"""
|
||||
|
||||
@property
|
||||
def task_type(self) -> str: ...
|
||||
|
||||
# ── 采样 ──
|
||||
@property
|
||||
def sampling_level(self) -> int: ...
|
||||
@property
|
||||
def sampling_constraint(self) -> SamplingConstraint: ...
|
||||
|
||||
# ── Prompt ──
|
||||
@property
|
||||
def prompt_template(self) -> str: ...
|
||||
def select_sub_pattern(self, rng: Random) -> SubPattern | None: ...
|
||||
def build_prompt_context(self, material: MaterialContext, sub_pattern: SubPattern | None) -> dict: ...
|
||||
|
||||
# ── Gate / Store 合约 ──
|
||||
@property
|
||||
def strategy_name(self) -> str:
|
||||
"""策略标识名,写入 store family 字段(如 "RETRIEVAL"、"ACTION_RECOGNITION")。"""
|
||||
@property
|
||||
def skill_target(self) -> str:
|
||||
"""目标失败机制编号,写入 store(如 "M1"、"AR")。"""
|
||||
@property
|
||||
def leak_probe_template(self) -> str:
|
||||
"""泄漏检测 prompt 模板文件名,供 leak_test gate 使用。"""
|
||||
|
||||
def extra_gates(self, candidate: CandidateQuestion) -> list[GateResult]:
|
||||
"""题型专属的额外验证(base 返回空列表)。"""
|
||||
```
|
||||
|
||||
`strategy_name` / `skill_target` / `leak_probe_template` 替代了 pipeline 中对 `slot.family.name` / `slot.family.skill_target` / `slot.family.leak_profile.probe_template` 的访问,确保 gate 和 store 合约不破坏。
|
||||
|
||||
## 3. SubPattern 数据结构
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class SubPattern:
|
||||
"""出题子模式 — 靶向特定失败机制。"""
|
||||
name: str
|
||||
weight: float
|
||||
sampling_level_override: int | None
|
||||
constraint_override: SamplingConstraint | None
|
||||
instruction: str # 核心指令(1-3 句话)
|
||||
positive_examples: list[dict] # VME 原题 few-shot
|
||||
negative_examples: list[dict] # 反面示例
|
||||
distractor_rules: str # 干扰项构造规则
|
||||
```
|
||||
|
||||
## 4. ActionRecognitionStrategy 的 6 个 SubPattern
|
||||
|
||||
来源:22 道错题双分类器仲裁分析。
|
||||
|
||||
| SubPattern | 权重 | 采样 | 错题# | 失败机制 |
|
||||
|-----------|------|------|-------|---------|
|
||||
| premature_evidence_anchoring | 0.20 | L1 | #3,4,12,17 | 找到一个证据就停搜,未验证全部选项 |
|
||||
| temporal_reasoning_failure | 0.20 | L1 | #8,9,15,16 | 事件排序错误 / 无法定位第 N 次事件 |
|
||||
| semantic_rigidity | 0.15 | L2 | #1,2,22 | 要求字面匹配,拒绝同义词释义 |
|
||||
| fine_grained_visual_action | 0.15 | L2 | #6,13,14,18 | 识别"做了什么"但分不清"怎么做的" |
|
||||
| cross_segment_entity_tracking | 0.15 | L1 | #7,19,20 | 单段正确但无法跨段合并 |
|
||||
| evidence_gap_confabulation | 0.15 | L2 | #5,10,11,21 | 缺证据时编造因果链 |
|
||||
|
||||
### ActionRecognitionStrategy 的 gate/store 合约值
|
||||
|
||||
| 字段 | 值 | 说明 |
|
||||
|------|-----|------|
|
||||
| strategy_name | `"ACTION_RECOGNITION"` | store 的 family 字段 |
|
||||
| skill_target | `"M1_AR"` | 继承自 RETRIEVAL 的 M1 + AR 后缀区分 |
|
||||
| leak_probe_template | `"gate_leak_retrieval.md"` | 复用 RETRIEVAL 的泄漏检测模板 |
|
||||
| sampling_level(默认) | 2 | L2 事件级(从 L3 提升,有意变更) |
|
||||
| sampling_constraint(默认) | `min_subtitles=3, min_l3_nodes=5, require_frames=True, cross_l2_span=True` | 加强约束(有意变更) |
|
||||
|
||||
SubPattern 可通过 `sampling_level_override` / `constraint_override` 进一步覆盖默认值。
|
||||
|
||||
### 4.1 能力保持机制
|
||||
|
||||
capability_maintenance 不是生成子模式,而是**采样来源**:从 Video-MME AR 的 41 道正确题中采样,直接进训练集。通过 `batch_correct_ratio` 控制混合比例。
|
||||
|
||||
```
|
||||
训练 batch 组成:
|
||||
├── 错误题来源: 6 个 SubPattern 生成的新题
|
||||
└── 正确题来源: maintenance_pool (41 道已验证正确的 VME 原题)
|
||||
```
|
||||
|
||||
## 5. pipeline 集成
|
||||
|
||||
### 5.1 调用链变更
|
||||
|
||||
```
|
||||
之前: slot.family → sample_material_v2(family_spec=...) → generate_one_v2(family_spec=...)
|
||||
→ run_gates(family_spec=...) → store.record_item(family=slot.family.name)
|
||||
|
||||
之后: strategy = get_strategy(slot.task_type)
|
||||
sub_pattern = strategy.select_sub_pattern(rng)
|
||||
level = sub_pattern.sampling_level_override or strategy.sampling_level
|
||||
constraint = sub_pattern.constraint_override or strategy.sampling_constraint
|
||||
material = sample_material_v2(level=level, constraint=constraint, ...)
|
||||
prompt_ctx = strategy.build_prompt_context(material, sub_pattern)
|
||||
candidate = generate_one_v2(prompt_ctx=prompt_ctx, ...)
|
||||
report = run_gates(candidate, leak_template=strategy.leak_probe_template, ...)
|
||||
extra = strategy.extra_gates(candidate)
|
||||
store.record_item(..., family=strategy.strategy_name, skill_target=strategy.skill_target,
|
||||
sub_pattern=sub_pattern.name if sub_pattern else None)
|
||||
```
|
||||
|
||||
### 5.2 接口影响面
|
||||
|
||||
| 文件 | 变更 | 影响范围 |
|
||||
|------|------|---------|
|
||||
| `sampler_v2.py` | `sample_material_v2` 签名:`family_spec` → `level: int` + `constraint: SamplingConstraint` | 函数签名 + 内部 `constraint = family_spec.sampling` 行 |
|
||||
| `pipeline_v2.py` | `_process_one_slot`:`slot.family` → `strategy`;`SlotAssignment.family` 字段移除 | `_assign_slots` / `_process_one_slot` / `_process_wrapper` |
|
||||
| `generator_v2.py` | `generate_one_v2` 签名:新增 `prompt_ctx: dict` 参数,替代内部读 `family_spec.prompt_template` | `_build_v2_prompt` / `generate_one_v2` |
|
||||
| `gates.py` | `run_gates` 签名:`family_spec` → `leak_template: str` | `_run_leak_test` 参数变更 |
|
||||
| `run_store.py` | `record_item` 新增 `sub_pattern: str | None` 参数 | DDL 加 `sub_pattern TEXT` 列 |
|
||||
|
||||
### 5.3 不变的部分
|
||||
|
||||
后处理(postprocess)、四门 gate 内部逻辑、去重(embedding)、重量抽检(heavy_check)、on_accept 回调、progress 断点续跑 — 全部不动。
|
||||
|
||||
## 6. Strategy 注册
|
||||
|
||||
```python
|
||||
_STRATEGY_REGISTRY: dict[str, TaskTypeStrategy] = {}
|
||||
|
||||
def get_strategy(task_type: str) -> TaskTypeStrategy:
|
||||
if task_type in _STRATEGY_REGISTRY:
|
||||
return _STRATEGY_REGISTRY[task_type]
|
||||
return _build_default_strategy(task_type)
|
||||
```
|
||||
|
||||
### 6.1 BaseTaskTypeStrategy family 消歧绑定表
|
||||
|
||||
消除多 family 随机性。每个 task_type 确定性绑定一个 family。
|
||||
|
||||
| task_type | 旧合法 families | 新绑定 | 被移除行为 | 理由 |
|
||||
|-----------|---------------|--------|-----------|------|
|
||||
| Action Recognition | RETRIEVAL, VISUAL | **特化策略** | 不走 family | 完全自包含 |
|
||||
| Object Recognition | RETRIEVAL | RETRIEVAL | 无 | 唯一 |
|
||||
| Object Reasoning | RETRIEVAL, REASONING | **REASONING** | RETRIEVAL 的 L3 单帧 + 禁推理 prompt | 需要推理;RETRIEVAL 产出识别题是已知 bug |
|
||||
| Action Reasoning | REASONING | REASONING | 无 | 唯一 |
|
||||
| Attribute Perception | RETRIEVAL, VISUAL | **VISUAL** | RETRIEVAL 的 require_frames=False | 属性感知需要帧 |
|
||||
| OCR Problems | RETRIEVAL, VISUAL | **VISUAL** | RETRIEVAL 的 require_frames=False | OCR 需要帧 |
|
||||
| Counting Problem | ENUMERATION, VISUAL | **ENUMERATION** | VISUAL 的 L3 单帧采样 | 计数需要跨帧 |
|
||||
| Information Synopsis | REASONING, ENUMERATION | **REASONING** | ENUMERATION 的 cross_l2_span=False | 综述需要跨段 |
|
||||
| Temporal Reasoning | ENUMERATION | ENUMERATION | 无 | 唯一 |
|
||||
| Temporal Perception | ENUMERATION | ENUMERATION | 无 | 唯一 |
|
||||
| Spatial Reasoning | SPATIAL | SPATIAL | 无 | 唯一 |
|
||||
| Spatial Perception | SPATIAL | SPATIAL | 无 | 唯一 |
|
||||
|
||||
### 6.2 多归属题型约束变化明细
|
||||
|
||||
确定性绑定会改变多归属题型的采样约束,这是**有意的行为变更**(旧行为是随机混合,本身就是不可控的)。
|
||||
|
||||
| task_type | 旧约束(随机选择) | 新约束(确定绑定) | 变化 |
|
||||
|-----------|-----------------|-----------------|------|
|
||||
| Object Reasoning | RETRIEVAL(L3,no-frame,no-cross) 或 REASONING(L2,no-frame,cross) | REASONING 固定 | 不再退化为 L3 识别题 |
|
||||
| Attribute Perception | RETRIEVAL(no-frame) 或 VISUAL(frame) | VISUAL 固定 | 始终要求帧 |
|
||||
| OCR Problems | RETRIEVAL(no-frame) 或 VISUAL(frame) | VISUAL 固定 | 始终要求帧 |
|
||||
| Counting Problem | ENUMERATION(no-frame) 或 VISUAL(frame) | ENUMERATION 固定 | 不再混入 VISUAL 的 L3 采样 |
|
||||
| Information Synopsis | REASONING(cross) 或 ENUMERATION(no-cross) | REASONING 固定 | 始终跨段 |
|
||||
|
||||
## 7. 文件结构
|
||||
|
||||
| 操作 | 文件 | 职责 |
|
||||
|------|------|------|
|
||||
| 新建 | `app/question_gen/strategy.py` | Protocol + SubPattern + BaseTaskTypeStrategy + 注册表 |
|
||||
| 新建 | `app/question_gen/strategy_action_recognition.py` | ActionRecognitionStrategy 实现 + 6 个 SubPattern 定义 |
|
||||
| 修改 | `app/question_gen/pipeline_v2.py` | `_process_one_slot` / `_assign_slots` 改为通过 strategy |
|
||||
| 修改 | `app/question_gen/sampler_v2.py` | 签名 `family_spec` → `level` + `constraint` |
|
||||
| 修改 | `app/question_gen/generator_v2.py` | 签名 `family_spec` → `prompt_ctx` |
|
||||
| 修改 | `app/question_gen/gates.py` | `run_gates` 签名 `family_spec` → `leak_template` |
|
||||
| 修改 | `app/question_gen/run_store.py` | `record_item` 新增 `sub_pattern` 参数 + DDL 加列 |
|
||||
| 不动 | `app/question_gen/families.py` | 保留,BaseTaskTypeStrategy 内部使用 |
|
||||
| 不动 | `app/question_gen/postprocess.py` | 后处理不变 |
|
||||
|
||||
## 8. 非功能性需求
|
||||
|
||||
| 维度 | 设计 |
|
||||
|------|------|
|
||||
| 持久化 | 不变 — on_accept 逐题回调。SubPattern 名写入 store 的 sub_pattern 列以便追溯 |
|
||||
| 幂等性 | 不变 — 同 seed → 同 slot 分配。strategy 查找确定性,sub_pattern 选择由 rng 控制 |
|
||||
| 断点续跑 | 不变 — progress 机制在 pipeline 层,strategy 无状态 |
|
||||
| 原子性 | 不变 — 逐题落库 |
|
||||
| store 迁移 | `question_gen_items` 表新增 `sub_pattern TEXT` 列,默认 NULL(BaseTaskTypeStrategy 写 NULL) |
|
||||
|
||||
## 9. 行为保真检查清单
|
||||
|
||||
| # | 行为 | 状态 | 说明 |
|
||||
|---|------|------|------|
|
||||
| 1 | 12 种 task_type 列表 | 保留 | pipeline 不变 |
|
||||
| 2 | slot round-robin 视频 | 保留 | pipeline 不变 |
|
||||
| 3 | family 加权随机选择 | **有意变更** | 消除不确定性 → 确定性绑定。详见 §6.2 |
|
||||
| 4 | _TASK_TYPE_TO_LEVEL 映射 | **有意变更** | BaseTaskTypeStrategy 从绑定 family 读取原值(唯一归属题型数值不变);AR 特化策略从 L3→L2(有意提升);多归属题型因消歧而变化(详见 §6.2) |
|
||||
| 5 | SamplingConstraint 约束 | **有意变更** | 同 #4 逻辑:唯一归属不变,多归属因消歧变化,AR 有意加强 |
|
||||
| 6 | 重出循环 + 换视频 | 保留 | |
|
||||
| 7 | 后处理洗牌 + verbatim | 保留 | |
|
||||
| 8 | 四门 gate | 保留 | leak_test 通过 strategy.leak_probe_template 获取模板 |
|
||||
| 9 | embedding 去重 | 保留 | |
|
||||
| 10 | 重量抽检 | 保留 | |
|
||||
| 11 | on_accept 回调 | 保留 | |
|
||||
| 12 | progress 断点续跑 | 保留 | |
|
||||
| 13 | reject_reason 传入重出 | 保留 | |
|
||||
| 14 | QuestionGenStore 记录 | 保留 | family → strategy_name;新增 sub_pattern 列 |
|
||||
|
||||
## 10. 渐进替换路径
|
||||
|
||||
```
|
||||
Phase 1 (当前): ActionRecognitionStrategy 特化 + 11 个 BaseTaskTypeStrategy
|
||||
Phase 2 (按需): 逐个替换表现差的题型为特化策略
|
||||
Phase N (最终): 12 个特化策略,families.py 可移除
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
type: design
|
||||
node_id: design:per-category-pool-strategy
|
||||
title: "Per-Category Pool Strategy 设计"
|
||||
date: 2026-07-13
|
||||
---
|
||||
|
||||
# Per-Category Pool Strategy 设计
|
||||
|
||||
Reference in New Issue
Block a user