Files
Video-Tree-TRM5/app/question_gen/strategy_action_recognition.py
T
iomgaa d0a8019fe1 feat(question_gen): add ActionRecognitionStrategy with 6 SubPatterns
Self-contained strategy targeting 6 Agent failure modes in Action
Recognition: premature_evidence_anchoring, temporal_reasoning_failure,
semantic_rigidity, fine_grained_visual_action,
cross_segment_entity_tracking, evidence_gap_confabulation.

- L2 default sampling (upgrade from L3) with 3 patterns overriding to L1
- Weighted random SubPattern selection (0.20/0.20/0.15/0.15/0.15/0.15)
- Each SubPattern includes instruction, examples, distractor rules
- Satisfies TaskTypeStrategy Protocol without extending BaseTaskTypeStrategy
- 32 unit tests covering all properties, definitions, and selection behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-07-14 06:49:03 -04:00

334 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Action Recognition 特化出题策略。
靶向 Agent 在动作识别类题目上的 6 种典型失败机制,通过加权随机
SubPattern 选择为 VLM 出题提供聚焦指令。
与 BaseTaskTypeStrategy 完全自包含,不依赖 QuestionFamilySpec。
采样从 L3 提升至 L2(跨段推理需要更广视野),3 个 SubPattern
进一步覆盖至 L1 以测试全局追踪能力。
"""
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
# ---------------------------------------------------------------------------
# 6 个 SubPattern 定义
# ---------------------------------------------------------------------------
_PREMATURE_EVIDENCE_ANCHORING = SubPattern(
name="premature_evidence_anchoring",
weight=0.20,
sampling_level_override=1,
constraint_override=None,
instruction=(
"设计一道需要考察视频中完整证据链的动作识别题。"
"正确答案的关键证据出现在视频后半段或跨多个片段,"
"但视频前段包含一个看似合理的局部匹配——"
"Agent 若仅凭首条匹配停止搜索就会出错。"
),
positive_examples=[
{
"question": "视频中厨师最终采用了哪种烹饪方式?",
"answer": "B. 蒸",
"why": "厨师先演示了炒(前段),但最终菜品使用蒸制(后段),"
"锚定首段证据的 Agent 会错选'炒'。",
},
],
negative_examples=[
{
"question": "视频开头厨师在做什么?",
"why": "答案仅需前段信息,不会触发过早锚定失败。",
},
],
distractor_rules=(
"将视频前段出现的局部匹配动作设为强干扰项。"
"其余干扰项使用语义相近但未出现的动作,"
"确保仅看前段片段的 Agent 有高概率选错。"
),
)
_TEMPORAL_REASONING_FAILURE = SubPattern(
name="temporal_reasoning_failure",
weight=0.20,
sampling_level_override=1,
constraint_override=None,
instruction=(
"设计一道要求正确排序或定位第 N 次出现的动作识别题。"
"题目需要 Agent 追踪事件的时间先后顺序,"
"或准确识别某动作在视频中第几次出现。"
"打乱时序或错误计数即会答错。"
),
positive_examples=[
{
"question": "运动员第三次尝试起跳前做了什么准备动作?",
"answer": "C. 深蹲热身",
"why": "需要准确定位'第三次'起跳而非其他次,"
"时序推理失败的 Agent 会混淆不同次尝试的准备动作。",
},
],
negative_examples=[
{
"question": "运动员在视频中做了什么?",
"why": "不涉及时序排序或计数,Agent 无需追踪顺序。",
},
],
distractor_rules=(
"干扰项使用其他次出现时的关联动作(如第 1 次或第 2 次的准备动作)。"
"确保各选项在视频中确实出现过,只是对应的时间点不同。"
),
)
_SEMANTIC_RIGIDITY = SubPattern(
name="semantic_rigidity",
weight=0.15,
sampling_level_override=None,
constraint_override=None,
instruction=(
"设计一道动作识别题,正确选项使用与视频原始描述不同的同义表达。"
"Agent 需理解语义等价而非依赖字面匹配——"
"例如视频字幕说'奔跑',正确选项写作'快速移动'。"
),
positive_examples=[
{
"question": "工人对墙面进行了什么操作?",
"answer": "A. 涂覆保护层",
"why": "视频中字幕描述为'刷漆',正确答案改写为'涂覆保护层'"
"依赖字面匹配的 Agent 会因找不到完全一致的表述而错选。",
},
],
negative_examples=[
{
"question": "工人在刷漆吗?",
"why": "选项直接复用视频原文,不考察语义理解。",
},
],
distractor_rules=(
"干扰项使用与正确答案表面相似但语义不同的表达。"
"保留一个与视频原始字幕字面接近但含义偏移的选项作为陷阱。"
),
)
_FINE_GRAINED_VISUAL_ACTION = SubPattern(
name="fine_grained_visual_action",
weight=0.15,
sampling_level_override=None,
constraint_override=None,
instruction=(
"设计一道需要区分细粒度动作方式的识别题。"
"题目聚焦于 HOW(怎么做)而非 WHAT(做什么),"
"如区分'搅拌'与'翻炒'、'拧'与'拉'等视觉上相似但方式不同的动作。"
),
positive_examples=[
{
"question": "维修人员是如何拆卸螺丝的?",
"answer": "D. 用扳手逆时针旋转",
"why": "需要区分拆卸的具体方式(扳手 vs 螺丝刀、顺时针 vs 逆时针),"
"粗粒度识别只能判断'在拆螺丝',无法区分方式。",
},
],
negative_examples=[
{
"question": "维修人员在做什么?",
"why": "只需粗粒度动作识别('拆螺丝'),不考察具体方式。",
},
],
distractor_rules=(
"干扰项使用同一大类动作的不同执行方式(同一动词的不同修饰)。"
"确保所有选项描述的都是合理的执行方式,仅细节不同。"
"避免使用明显不相关的动作作为干扰。"
),
)
_CROSS_SEGMENT_ENTITY_TRACKING = SubPattern(
name="cross_segment_entity_tracking",
weight=0.15,
sampling_level_override=1,
constraint_override=None,
instruction=(
"设计一道需要跨视频段落追踪同一实体动作的识别题。"
"目标实体在不同片段中外观、称呼或上下文发生变化,"
"Agent 需要将多段信息合并才能正确回答关于该实体的动作问题。"
),
positive_examples=[
{
"question": "穿红色外套的人在视频中总共完成了哪些动作?",
"answer": "B. 先讲解、后示范、最后总结",
"why": "该人物在前段穿外套讲解,中段脱外套示范,后段重新穿上总结,"
"无法跨段追踪的 Agent 会遗漏某段动作。",
},
{
"question": "主持人在节目不同环节中分别做了什么?",
"answer": "A. 开场介绍、采访嘉宾、总结点评",
"why": "主持人在不同场景切换中持续出现,需要跨段聚合。",
},
],
negative_examples=[
{
"question": "视频第一个片段中的人在做什么?",
"why": "仅需单段信息,不考察跨段追踪。",
},
],
distractor_rules=(
"干扰项遗漏某些片段的动作或混入其他实体的动作。构造一个只包含部分片段信息的选项作为强干扰。"
),
)
_EVIDENCE_GAP_CONFABULATION = SubPattern(
name="evidence_gap_confabulation",
weight=0.15,
sampling_level_override=None,
constraint_override=None,
instruction=(
"设计一道动作识别题,视频中存在证据空缺(如遮挡、跳切、画外音)。"
"正确答案承认信息不足或基于间接证据推断,"
"而非凭空编造因果链。Agent 若虚构缺失证据即会出错。"
),
positive_examples=[
{
"question": "画面切走后,演讲者下一步做了什么?",
"answer": "C. 无法从视频中直接确定",
"why": "画面跳切导致该动作无直接视觉证据,"
"倾向于虚构的 Agent 会编造一个看似合理的动作。",
},
],
negative_examples=[
{
"question": "画面中演讲者正在做什么?",
"why": "动作在画面中可见,不存在证据空缺。",
},
],
distractor_rules=(
"干扰项使用看似合理但缺乏视频证据支持的因果推断。"
"至少一个干扰项应构造完整的虚假因果链以诱导 Agent 选择。"
),
)
AR_SUB_PATTERNS: tuple[SubPattern, ...] = (
_PREMATURE_EVIDENCE_ANCHORING,
_TEMPORAL_REASONING_FAILURE,
_SEMANTIC_RIGIDITY,
_FINE_GRAINED_VISUAL_ACTION,
_CROSS_SEGMENT_ENTITY_TRACKING,
_EVIDENCE_GAP_CONFABULATION,
)
# 预计算:名称列表 + 权重列表(避免每次 select 重新构建)
_AR_PATTERN_NAMES: list[str] = [sp.name for sp in AR_SUB_PATTERNS]
_AR_PATTERN_WEIGHTS: list[float] = [sp.weight for sp in AR_SUB_PATTERNS]
_AR_PATTERN_BY_NAME: dict[str, SubPattern] = {sp.name: sp for sp in AR_SUB_PATTERNS}
# ---------------------------------------------------------------------------
# ActionRecognitionStrategy
# ---------------------------------------------------------------------------
_SAMPLING_CONSTRAINT = SamplingConstraint(
min_subtitles=3,
min_l3_nodes=5,
require_frames=True,
cross_l2_span=True,
)
class ActionRecognitionStrategy:
"""Action Recognition 特化出题策略。
自包含实现,不依赖 BaseTaskTypeStrategy 或 QuestionFamilySpec。
靶向 6 种典型失败机制,通过加权随机 SubPattern 引导 VLM 出题。
属性:
task_type: 题型名 — "Action Recognition"。
strategy_name: 策略标识 — "ACTION_RECOGNITION"。
skill_target: 目标失败机制 — "M1_AR"。
sampling_level: 采样层级 — 2(L2,从 L3 升级以获得跨段视野)。
sampling_constraint: 采样约束(min_subtitles=3, min_l3_nodes=5, require_frames, cross_l2_span)。
prompt_template: 出题模板 — "action_recognition.md"。
leak_probe_template: 泄漏探测模板 — "gate_leak_retrieval.md"。
"""
@property
def task_type(self) -> str:
"""返回题型名。"""
return "Action Recognition"
@property
def strategy_name(self) -> str:
"""返回策略标识名。"""
return "ACTION_RECOGNITION"
@property
def skill_target(self) -> str:
"""返回目标失败机制编号。"""
return "M1_AR"
@property
def sampling_level(self) -> int:
"""返回采样层级(L2)。"""
return 2
@property
def sampling_constraint(self) -> SamplingConstraint:
"""返回采样约束。"""
return _SAMPLING_CONSTRAINT
@property
def prompt_template(self) -> str:
"""返回出题 prompt 模板文件名。"""
return "action_recognition.md"
@property
def leak_probe_template(self) -> str:
"""返回泄漏探测模板文件名。"""
return "gate_leak_retrieval.md"
def select_sub_pattern(self, rng: random.Random) -> SubPattern:
"""按权重随机选择一个 SubPattern。
参数:
rng: 随机数生成器(确保可复现)。
返回:
选中的 SubPattern 实例(永不为 None)。
"""
chosen_name = rng.choices(
_AR_PATTERN_NAMES,
weights=_AR_PATTERN_WEIGHTS,
k=1,
)[0]
return _AR_PATTERN_BY_NAME[chosen_name]
def build_prompt_context(self, material: Any, sub_pattern: SubPattern | None) -> dict:
"""构建 prompt 上下文字典。
参数:
material: 采样素材(当前未使用,留给管线扩展)。
sub_pattern: 选中的子模式。
返回:
包含 family_name, prompt_template, sub_pattern 的字典。
"""
return {
"family_name": self.strategy_name,
"prompt_template": self.prompt_template,
"sub_pattern": sub_pattern.name if sub_pattern is not None else None,
}
def extra_gates(self, candidate: Any) -> list:
"""返回额外门控列表(当前为空)。
参数:
candidate: 候选题目。
返回:
空列表。
"""
return []