# ActionRecognitionStrategy 特化实现计划 (Plan B) > **For agentic workers:** REQUIRED SUB-SKILL: Use subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 实现 ActionRecognitionStrategy 特化策略(6 个 SubPattern + AR 专属 prompt),替换 Plan A 中 AR 的临时 VISUAL_FAMILY 绑定,使 AR 出题靶向 22 道错题的 6 种失败子模式。 **Architecture:** 新建 `strategy_action_recognition.py`(ActionRecognitionStrategy 类 + 6 个 SubPattern 定义),新建 `action_recognition.md` prompt 模板,在 `strategy.py` 的模块初始化中注册。Pipeline 无需改动 —— Plan A 已接好全部 sub_pattern 接口(`instruction`、`sampling_level_override`、`constraint_override`)。 **Tech Stack:** Python 3.11, pytest, Protocol (typing) **关联设计:** `research-wiki/designs/2026-07-14-task-type-strategy-design.md` §4 **范围:** 仅 Plan B(ActionRecognitionStrategy 特化)。不改 pipeline、sampler、generator、gates、store。 --- ### Task 1: AR 专属 prompt 模板 **Files:** - Create: `store/prompts/question_gen/action_recognition.md` - [ ] **Step 1: 创建 AR 专属 prompt 模板** ```markdown You are a question generator for video understanding benchmarks, specializing in **Action Recognition**. Your task: Generate a multiple-choice question that tests whether the answerer can accurately recognize, distinguish, and reason about **actions and behaviors** observed across multiple segments of the video. ## Action Recognition Guidelines - The question MUST require watching multiple segments or the full video — single-frame-answerable questions are failures. - Focus on **dynamic actions**: what someone does, how they do it, the sequence of actions, or which action is absent. - The correct answer must be grounded in observable behavior (body movements, interactions, operations), NOT in static visual attributes or text/OCR. - Questions should target action details that require temporal tracking: order of events, manner of execution, repetition counts, or cross-segment entity behavior. ## Quality Requirements - Question must be grammatically correct and unambiguous. - All four options must be parallel in structure and length. - The correct answer must not be identifiable from linguistic cues alone. - Each option must begin with "A. ", "B. ", "C. ", or "D. ". ## Prohibited Patterns - Do NOT generate questions answerable from a single frame or screenshot — if pausing the video at one moment gives the answer, the question is too easy. - Do NOT generate OCR/text-reading questions disguised as action recognition — reading jersey numbers, scoreboards, or on-screen text is NOT action recognition. - Do NOT fabricate actions not observable in the provided material. - Do NOT construct options where the correct answer is obvious from common sense or world knowledge alone. - Do NOT write questions where multiple options could reasonably be correct. ## Output Respond with ONLY a valid JSON object. No additional text. ``` - [ ] **Step 2: 验证模板可加载** ```bash conda run -n Video-Tree-TRM python -c " from app.question_gen.generator_v2 import _load_prompt_template content = _load_prompt_template('action_recognition.md') assert 'Action Recognition' in content print('OK: action_recognition.md loaded successfully') " ``` 预期:打印 OK 消息,无异常。 - [ ] **Step 3: 提交** ```bash git add store/prompts/question_gen/action_recognition.md git commit -m "feat(question_gen): add Action Recognition specialized prompt template" ``` --- ### Task 2: ActionRecognitionStrategy 类 + 6 个 SubPattern **Files:** - Create: `app/question_gen/strategy_action_recognition.py` - Test: `tests/unit/test_strategy_action_recognition.py` - [ ] **Step 1: 写失败测试** ```python # tests/unit/test_strategy_action_recognition.py """ActionRecognitionStrategy 单元测试。""" from __future__ import annotations import random import pytest from app.question_gen.strategy_action_recognition import ( AR_SUB_PATTERNS, ActionRecognitionStrategy, ) class TestActionRecognitionStrategy: """ActionRecognitionStrategy 属性和行为。""" @pytest.fixture def strategy(self) -> ActionRecognitionStrategy: """创建策略实例。""" return ActionRecognitionStrategy() def test_task_type(self, strategy: ActionRecognitionStrategy) -> None: """task_type 固定为 Action Recognition。""" assert strategy.task_type == "Action Recognition" def test_strategy_name(self, strategy: ActionRecognitionStrategy) -> None: """strategy_name 为 ACTION_RECOGNITION。""" assert strategy.strategy_name == "ACTION_RECOGNITION" def test_skill_target(self, strategy: ActionRecognitionStrategy) -> None: """skill_target 为 M1_AR。""" assert strategy.skill_target == "M1_AR" def test_sampling_level(self, strategy: ActionRecognitionStrategy) -> None: """默认采样层级为 L2。""" assert strategy.sampling_level == 2 def test_sampling_constraint(self, strategy: ActionRecognitionStrategy) -> None: """采样约束加强:min_subtitles=3, min_l3_nodes=5, require_frames=True, cross_l2_span=True。""" c = strategy.sampling_constraint assert c.min_subtitles == 3 assert c.min_l3_nodes == 5 assert c.require_frames is True assert c.cross_l2_span is True def test_prompt_template(self, strategy: ActionRecognitionStrategy) -> None: """使用 AR 专属 prompt 模板。""" assert strategy.prompt_template == "action_recognition.md" def test_leak_probe_template(self, strategy: ActionRecognitionStrategy) -> None: """复用 RETRIEVAL 的泄漏检测模板。""" assert strategy.leak_probe_template == "gate_leak_retrieval.md" def test_extra_gates_empty(self, strategy: ActionRecognitionStrategy) -> None: """当前版本无额外 gate。""" assert strategy.extra_gates(None) == [] class TestSubPatternSelection: """select_sub_pattern 行为。""" @pytest.fixture def strategy(self) -> ActionRecognitionStrategy: return ActionRecognitionStrategy() def test_returns_sub_pattern(self, strategy: ActionRecognitionStrategy) -> None: """select_sub_pattern 返回 SubPattern 而非 None。""" rng = random.Random(42) sp = strategy.select_sub_pattern(rng) assert sp is not None assert sp.name in [p.name for p in AR_SUB_PATTERNS] def test_deterministic_with_same_seed(self, strategy: ActionRecognitionStrategy) -> None: """相同 seed 返回相同 SubPattern。""" sp1 = strategy.select_sub_pattern(random.Random(42)) sp2 = strategy.select_sub_pattern(random.Random(42)) assert sp1.name == sp2.name def test_distribution_covers_all_patterns(self, strategy: ActionRecognitionStrategy) -> None: """足够多次采样应覆盖全部 6 个子模式。""" rng = random.Random(123) names = {strategy.select_sub_pattern(rng).name for _ in range(200)} assert len(names) == 6 def test_sub_pattern_has_instruction(self, strategy: ActionRecognitionStrategy) -> None: """每个 SubPattern 都有非空 instruction。""" for sp in AR_SUB_PATTERNS: assert sp.instruction.strip(), f"{sp.name} 的 instruction 为空" def test_sub_pattern_has_distractor_rules(self, strategy: ActionRecognitionStrategy) -> None: """每个 SubPattern 都有非空 distractor_rules。""" for sp in AR_SUB_PATTERNS: assert sp.distractor_rules.strip(), f"{sp.name} 的 distractor_rules 为空" class TestSubPatternOverrides: """SubPattern 的 sampling 覆盖。""" def test_l1_patterns_override_level(self) -> None: """L1 子模式覆盖默认的 L2 采样层级。""" l1_names = {"premature_evidence_anchoring", "temporal_reasoning_failure", "cross_segment_entity_tracking"} for sp in AR_SUB_PATTERNS: if sp.name in l1_names: assert sp.sampling_level_override == 1, f"{sp.name} 应覆盖为 L1" else: assert sp.sampling_level_override is None, f"{sp.name} 不应覆盖采样层级" def test_weights_sum_to_one(self) -> None: """权重总和为 1.0。""" total = sum(sp.weight for sp in AR_SUB_PATTERNS) assert abs(total - 1.0) < 1e-9 def test_individual_weights_match_design(self) -> None: """每个子模式权重与设计文档一致。""" expected = { "premature_evidence_anchoring": 0.20, "temporal_reasoning_failure": 0.20, "semantic_rigidity": 0.15, "fine_grained_visual_action": 0.15, "cross_segment_entity_tracking": 0.15, "evidence_gap_confabulation": 0.15, } for sp in AR_SUB_PATTERNS: assert abs(sp.weight - expected[sp.name]) < 1e-9, f"{sp.name} 权重不匹配" def test_all_six_patterns_defined(self) -> None: """定义了 6 个子模式。""" assert len(AR_SUB_PATTERNS) == 6 expected_names = { "premature_evidence_anchoring", "temporal_reasoning_failure", "semantic_rigidity", "fine_grained_visual_action", "cross_segment_entity_tracking", "evidence_gap_confabulation", } actual_names = {sp.name for sp in AR_SUB_PATTERNS} assert actual_names == expected_names ``` - [ ] **Step 2: 运行测试验证失败** ```bash conda run -n Video-Tree-TRM pytest tests/unit/test_strategy_action_recognition.py -v ``` 预期:ImportError — `app.question_gen.strategy_action_recognition` 不存在。 - [ ] **Step 3: 实现 strategy_action_recognition.py** ```python # app/question_gen/strategy_action_recognition.py """Action Recognition 特化出题策略 — 靶向 6 种失败子模式。 来源:22 道 VME AR 错题双分类器仲裁分析。 每个 SubPattern 定义独立的 instruction、采样覆盖、干扰项构造规则, 由 pipeline 在出题时注入到 prompt 的 Special Focus 区段。 设计文档: research-wiki/designs/2026-07-14-task-type-strategy-design.md §4 """ from __future__ import annotations from typing import TYPE_CHECKING, Any from app.question_gen.families import SamplingConstraint from app.question_gen.strategy import SubPattern if TYPE_CHECKING: import random # --------------------------------------------------------------------------- # 默认采样约束(设计文档 §4 定义) # --------------------------------------------------------------------------- _AR_DEFAULT_CONSTRAINT = SamplingConstraint( min_subtitles=3, min_l3_nodes=5, require_frames=True, cross_l2_span=True, ) # --------------------------------------------------------------------------- # 6 个 SubPattern 定义 # --------------------------------------------------------------------------- _SP_PREMATURE_EVIDENCE_ANCHORING = SubPattern( name="premature_evidence_anchoring", weight=0.20, sampling_level_override=1, constraint_override=None, instruction=( "Generate a question where the correct answer requires verifying evidence across " "ALL options before committing — not just finding one matching piece of evidence. " "The video must contain a plausible-looking but incorrect match that appears early " "or prominently, while the true answer is confirmed only by cross-referencing " "multiple segments. The question should punish an agent that stops searching after " "the first evidence match." ), positive_examples=[ { "question": "Which of the following tasks did the heroine not complete while her baby was sleeping?", "answer": "D. Doing laundry", "why": "Requires checking all 4 options against all segments; stopping at first match misses the negation.", }, { "question": "Which acrobatic skill is absent from this video?", "answer": "D. Somersault", "why": "Must verify every option against every segment to confirm absence.", }, ], negative_examples=[ { "question": "What color jersey does the player wear?", "why": "Single-frame answerable, no need to verify across segments.", }, ], distractor_rules=( "Place the most visually salient or early-appearing action as a distractor (not the answer). " "Make one distractor partially correct (happens in a different segment or by a different person). " "The correct answer should require exhaustive verification across segments." ), ) _SP_TEMPORAL_REASONING_FAILURE = SubPattern( name="temporal_reasoning_failure", weight=0.20, sampling_level_override=1, constraint_override=None, instruction=( "Generate a question that requires precise temporal ordering or locating the Nth " "occurrence of an event. The video must contain the same or similar action happening " "multiple times, and the question must specify a temporal anchor (e.g., 'after X happens', " "'the second time', 'at the beginning'). The correct answer depends on getting the " "sequence order right." ), positive_examples=[ { "question": "In the video after feeding the ducks, what did the male protagonist do after riding his bike?", "answer": "A. Went jogging in the park", "why": "Requires precise temporal chain: feeding → biking → next action.", }, { "question": "What happened to the team on the counterattack after Sabonis' first steal?", "answer": "D. They scored a three-pointer", "why": "Must locate the FIRST steal (not second) and track what follows.", }, ], negative_examples=[ { "question": "What does the person do in the video?", "why": "No temporal anchor, any observation suffices.", }, ], distractor_rules=( "Include actions that genuinely occur in the video but at a different time point. " "One distractor should be what happens before the anchored moment. " "Another should be what happens after the Nth+1 occurrence (off-by-one trap)." ), ) _SP_SEMANTIC_RIGIDITY = SubPattern( name="semantic_rigidity", weight=0.15, sampling_level_override=None, constraint_override=None, instruction=( "Generate a question where the correct answer option uses a synonym, paraphrase, or " "semantic equivalent of what is shown in the video — NOT the exact words from subtitles. " "The agent must recognize that a rephrased description matches the observed action. " "Include a distractor that uses near-verbatim subtitle wording but describes a " "different or incorrect action." ), positive_examples=[ { "question": "What are the magic tricks about?", "answer": "B. Sleight of hand with everyday objects", "why": "Video shows card and coin manipulation; correct answer paraphrases rather than quoting subtitles.", }, ], negative_examples=[ { "question": "According to the narrator, what is the main topic?", "why": "Invites verbatim subtitle matching, not semantic understanding.", }, ], distractor_rules=( "One distractor must reuse exact subtitle phrasing but apply it to the wrong action/context. " "Another distractor should use a semantically related but distinct action verb " "(e.g., 'cutting' vs 'slicing' vs 'chopping' when only one is correct). " "The correct answer must be a valid semantic equivalent, not a stretch." ), ) _SP_FINE_GRAINED_VISUAL_ACTION = SubPattern( name="fine_grained_visual_action", weight=0.15, sampling_level_override=None, constraint_override=None, instruction=( "Generate a question that distinguishes between visually similar actions — " "the MANNER of how something is done, not just WHAT is done. " "The video must show a specific technique, method, or style of performing an action, " "and the question must test whether the agent can differentiate it from similar alternatives. " "Frames are essential — the answer cannot come from subtitles alone." ), positive_examples=[ { "question": "How does the chef prepare the garlic in this recipe?", "answer": "C. Crushes it with the flat side of a knife", "why": "All options are valid garlic preparations; only visual observation distinguishes.", }, { "question": "What does the man with a laughing face do at the beginning of the video?", "answer": "C. Clasps his hands together and bows", "why": "Specific gesture detail requires frame-level observation.", }, ], negative_examples=[ { "question": "Does the person cook in the video?", "why": "Binary yes/no, no manner distinction needed.", }, ], distractor_rules=( "All four options must describe the same general category of action " "(e.g., all are ways of cutting, all are types of greetings). " "Distractors must be visually plausible alternatives that could occur in the same context. " "The distinction must be observable only from frames, not from subtitles." ), ) _SP_CROSS_SEGMENT_ENTITY_TRACKING = SubPattern( name="cross_segment_entity_tracking", weight=0.15, sampling_level_override=1, constraint_override=None, instruction=( "Generate a question that requires tracking a specific entity (person, object, or group) " "across multiple video segments and merging observations. The correct answer depends on " "information from at least two separate segments — a single segment gives only a partial " "or misleading picture. The entity must appear in different contexts or states across segments." ), positive_examples=[ { "question": "In the video, what happened in the car when the heroine came home from shopping?", "answer": "D. The car wouldn't start and she had to call for help", "why": "Must track heroine across shopping segment → car segment → resolution.", }, ], negative_examples=[ { "question": "What is the person wearing?", "why": "Single-segment observation, no cross-segment tracking needed.", }, ], distractor_rules=( "One distractor should be correct for the entity in a DIFFERENT segment (right entity, wrong time). " "Another distractor should be correct for a DIFFERENT entity in the same segment (right time, wrong entity). " "The correct answer must require merging observations from multiple segments." ), ) _SP_EVIDENCE_GAP_CONFABULATION = SubPattern( name="evidence_gap_confabulation", weight=0.15, sampling_level_override=None, constraint_override=None, instruction=( "Generate a question about an action where the video evidence is INCOMPLETE — " "the full causal chain is not directly shown. The correct answer is the one that " "stays faithful to what IS observable, while distractors fill in the gap with " "plausible but unsupported causal narratives. The agent must resist inventing " "explanations for unobserved transitions." ), positive_examples=[ { "question": "How were the Sawtooth ranges formed?", "answer": "D. The video describes geological uplift but does not show the formation process", "why": "Video describes result but not process; agent must not confabulate mechanism.", }, ], negative_examples=[ { "question": "Why did the person leave the room?", "why": "If the reason is explicitly stated in dialogue, no evidence gap exists.", }, ], distractor_rules=( "Distractors must be plausible causal narratives that COULD explain the outcome but " "are NOT supported by the video evidence. Each distractor should fill the evidence gap " "with a different invented mechanism. The correct answer must be the one that " "either: (a) states only what is directly observable, or (b) acknowledges the limitation." ), ) # 导出 tuple(不可变,按名称排序供测试) AR_SUB_PATTERNS: tuple[SubPattern, ...] = ( _SP_PREMATURE_EVIDENCE_ANCHORING, _SP_TEMPORAL_REASONING_FAILURE, _SP_SEMANTIC_RIGIDITY, _SP_FINE_GRAINED_VISUAL_ACTION, _SP_CROSS_SEGMENT_ENTITY_TRACKING, _SP_EVIDENCE_GAP_CONFABULATION, ) # --------------------------------------------------------------------------- # Strategy 实现 # --------------------------------------------------------------------------- class ActionRecognitionStrategy: """Action Recognition 特化出题策略。 自包含 —— 不依赖 QuestionFamilySpec,直接定义采样约束、prompt、gate 合约。 6 个 SubPattern 按权重随机选择,各自可覆盖默认采样层级。 设计文档: research-wiki/designs/2026-07-14-task-type-strategy-design.md §4 """ @property def task_type(self) -> str: """固定为 Action Recognition。""" return "Action Recognition" @property def sampling_level(self) -> int: """默认 L2 事件级(从 L3 提升,有意变更)。""" return 2 @property def sampling_constraint(self) -> SamplingConstraint: """加强约束:要求字幕、帧、跨段。""" return _AR_DEFAULT_CONSTRAINT @property def prompt_template(self) -> str: """AR 专属 prompt 模板。""" return "action_recognition.md" @property def strategy_name(self) -> str: """store 的 family 字段。""" return "ACTION_RECOGNITION" @property def skill_target(self) -> str: """M1_AR — 继承 RETRIEVAL 的 M1 + AR 后缀区分。""" return "M1_AR" @property def leak_probe_template(self) -> str: """复用 RETRIEVAL 的泄漏检测模板。""" return "gate_leak_retrieval.md" def select_sub_pattern(self, rng: random.Random) -> SubPattern: """按权重随机选择一个子模式。 参数: rng: 可控随机数生成器。 返回: 选中的 SubPattern 实例。 """ names = [sp.name for sp in AR_SUB_PATTERNS] weights = [sp.weight for sp in AR_SUB_PATTERNS] chosen_name = rng.choices(names, weights=weights, k=1)[0] return next(sp for sp in AR_SUB_PATTERNS if sp.name == chosen_name) def build_prompt_context(self, material: Any, sub_pattern: SubPattern | None) -> dict: """返回 AR 的 prompt 上下文。 参数: material: 采样素材上下文。 sub_pattern: 选中的子模式(AR 策略下始终非 None)。 返回: 上下文字典。 """ return { "family_name": "ACTION_RECOGNITION", "prompt_template": "action_recognition.md", "sub_pattern": sub_pattern.name if sub_pattern else None, } def extra_gates(self, candidate: Any) -> list: """当前版本无额外 gate,预留接口。 参数: candidate: 候选题目。 返回: 空列表。 """ return [] ``` - [ ] **Step 4: 运行测试验证通过** ```bash conda run -n Video-Tree-TRM pytest tests/unit/test_strategy_action_recognition.py -v ``` 预期:全部 PASS - [ ] **Step 5: 格式和 lint 检查** ```bash conda run -n Video-Tree-TRM ruff format app/question_gen/strategy_action_recognition.py tests/unit/test_strategy_action_recognition.py conda run -n Video-Tree-TRM ruff check app/question_gen/strategy_action_recognition.py tests/unit/test_strategy_action_recognition.py conda run -n Video-Tree-TRM radon cc app/question_gen/strategy_action_recognition.py -n C -s ``` 预期:零 error,无 C 级以上复杂度。 - [ ] **Step 6: 提交** ```bash git add app/question_gen/strategy_action_recognition.py tests/unit/test_strategy_action_recognition.py git commit -m "feat(question_gen): add ActionRecognitionStrategy with 6 SubPatterns" ``` --- ### Task 3: 注册 AR 策略 + 移除临时绑定 **Files:** - Modify: `app/question_gen/strategy.py:107` - Test: `tests/unit/test_strategy.py`(追加测试) - [ ] **Step 1: 写测试 — AR 注册后 get_strategy 返回特化策略** 在 `tests/unit/test_strategy.py` 末尾追加: ```python class TestActionRecognitionRegistration: """AR 策略注册后 get_strategy 返回特化实例。""" def test_get_strategy_returns_ar_strategy(self): """get_strategy('Action Recognition') 返回 ActionRecognitionStrategy。""" from app.question_gen.strategy_action_recognition import ActionRecognitionStrategy s = get_strategy("Action Recognition") assert isinstance(s, ActionRecognitionStrategy) assert s.task_type == "Action Recognition" assert s.strategy_name == "ACTION_RECOGNITION" def test_ar_not_base_strategy(self): """get_strategy('Action Recognition') 不再返回 BaseTaskTypeStrategy。""" s = get_strategy("Action Recognition") assert not isinstance(s, BaseTaskTypeStrategy) def test_other_types_still_base(self): """其他题型仍返回 BaseTaskTypeStrategy。""" for tt in ("Object Recognition", "Temporal Reasoning", "Spatial Reasoning"): s = get_strategy(tt) assert isinstance(s, BaseTaskTypeStrategy), f"{tt} 应该是 BaseTaskTypeStrategy" ``` - [ ] **Step 2: 运行测试验证失败** ```bash conda run -n Video-Tree-TRM pytest tests/unit/test_strategy.py::TestActionRecognitionRegistration -v ``` 预期:`test_get_strategy_returns_ar_strategy` FAIL(返回 BaseTaskTypeStrategy)。 - [ ] **Step 3: 在 get_strategy 中添加延迟注册** 修改 `app/question_gen/strategy.py` 中的 `get_strategy` 函数,在首次调用时触发特化策略注册(避免循环导入): ```python _BUILTIN_REGISTERED = False def get_strategy(task_type: str) -> TaskTypeStrategy: """获取题型策略。未注册的自动创建 BaseTaskTypeStrategy。 首次调用时延迟注册内建特化策略(避免循环导入)。 参数: task_type: 题型名。 返回: TaskTypeStrategy 实例。 异常: KeyError: task_type 不在消歧绑定表和注册表中。 """ global _BUILTIN_REGISTERED # noqa: PLW0603 if not _BUILTIN_REGISTERED: _BUILTIN_REGISTERED = True _register_builtin_strategies() if task_type in _STRATEGY_REGISTRY: return _STRATEGY_REGISTRY[task_type] return _build_default_strategy(task_type) def _register_builtin_strategies() -> None: """注册内建的特化策略。由 get_strategy 首次调用时延迟执行。""" from app.question_gen.strategy_action_recognition import ActionRecognitionStrategy register_strategy(ActionRecognitionStrategy()) ``` 同时更新 `_TASK_TYPE_TO_FAMILY` 中 AR 的注释: ```python # 修改前: "Action Recognition": VISUAL_FAMILY, # Plan A 临时绑定;Plan B 替换为特化策略 # 修改后: "Action Recognition": VISUAL_FAMILY, # fallback — 注册表中已被 ActionRecognitionStrategy 替换 ``` - [ ] **Step 4: 运行测试验证通过** ```bash conda run -n Video-Tree-TRM pytest tests/unit/test_strategy.py tests/unit/test_strategy_action_recognition.py -v ``` 预期:全部 PASS。 - [ ] **Step 5: 提交** ```bash git add app/question_gen/strategy.py tests/unit/test_strategy.py git commit -m "feat(question_gen): register ActionRecognitionStrategy, replace temp VISUAL binding" ``` --- ### Task 4: 全量回归测试 + lint - [ ] **Step 1: lint** ```bash conda run -n Video-Tree-TRM ruff format app/question_gen/ tests/ && conda run -n Video-Tree-TRM ruff check app/question_gen/ --fix ``` - [ ] **Step 2: 全量测试** ```bash conda run -n Video-Tree-TRM pytest tests/unit/ tests/integration/ -v --tb=short ``` 预期:1173+ 全部 PASS - [ ] **Step 3: 提交(如有 lint 修复)** ```bash git add -A && git commit -m "chore: lint and format Plan B changes" ``` --- ## 行为保真检查清单 | # | 行为 | 状态 | 说明 | |---|------|------|------| | 1 | 11 个非 AR 题型行为不变 | 保留 | get_strategy 对非 AR 类型仍返回 BaseTaskTypeStrategy | | 2 | AR 从 VISUAL_FAMILY(L3) 变为 AR 特化(L2) | **有意变更** | 设计文档 §4 明确标注 | | 3 | AR 采样约束加强 | **有意变更** | 设计文档 §4: min_subtitles=3, cross_l2_span=True | | 4 | AR prompt 从 visual.md 变为 action_recognition.md | **有意变更** | 专属 prompt 靶向动作识别 | | 5 | AR strategy_name 从 "VISUAL" 变为 "ACTION_RECOGNITION" | **有意变更** | store 中新记录可区分 | | 6 | AR skill_target 从 "M4" 变为 "M1_AR" | **有意变更** | 设计文档 §4 | | 7 | select_sub_pattern 返回 SubPattern(非 None) | **有意变更** | AR 始终有 sub_pattern | | 8 | sub_pattern.instruction 注入到 prompt | 保留 | Plan A 已接线(generator_v2.py:176-177) | | 9 | sub_pattern.sampling_level_override 覆盖 level | 保留 | Plan A 已接线(pipeline_v2.py:359-360) | | 10 | sub_pattern.name 写入 store | 保留 | Plan A 已接线(pipeline_v2.py:425) | | 11 | pipeline 重出循环/后处理/四门 gate 不变 | 保留 | Plan B 不改 pipeline | | 12 | 断点续跑 | 保留 | pipeline 的 progress 机制不变 | ## 核心算法保真校验 本计划不涉及核心算法迁移,保真校验不适用。 ## 非功能性需求 | 维度 | 设计 | |------|------| | 持久化 | 不变 — on_accept 逐题回调。sub_pattern 名已在 Plan A 中接入 store | | 幂等性 | 不变 — strategy 查找确定性,sub_pattern 选择由 rng 控制 | | 断点续跑 | 不变 — progress 机制在 pipeline 层,strategy 无状态 | | 原子性 | 不变 — 逐题落库 |