refactor(question_gen): adapt generator/gates/store signatures for strategy
- generator_v2: _load_prompt_template takes template_name str instead of QuestionFamilySpec; _build_v2_prompt takes prompt_template + strategy_name + sub_pattern_instruction; generate_one_v2 takes discrete params (prompt_template, strategy_name, skill_target, sub_pattern_instruction) - gates: _gate_leak_test and run_gates take leak_probe_template str instead of QuestionFamilySpec - run_store: add sub_pattern column to DDL + idempotent migration; record_item accepts optional sub_pattern param - Remove QuestionFamilySpec imports from generator_v2 and gates modules - Update test call sites accordingly Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""v2 生成器 — 基于家族特化 prompt 模板的单题 VLM 出题模块。
|
||||
"""v2 生成器 — 基于策略特化 prompt 模板的单题 VLM 出题模块。
|
||||
|
||||
使用 VLMProvider 接口调用视觉语言模型,结合 per-family prompt 模板
|
||||
使用 VLMProvider 接口调用视觉语言模型,结合 per-strategy prompt 模板
|
||||
和 MaterialContext 素材上下文,生成一道四选一候选题。
|
||||
|
||||
典型调用路径::
|
||||
@@ -9,10 +9,12 @@
|
||||
vlm=vlm_client,
|
||||
tree=tree_index,
|
||||
material=material_ctx,
|
||||
family_spec=RETRIEVAL_FAMILY,
|
||||
task_type="Action Reasoning",
|
||||
seq=1,
|
||||
video_id="vid_001",
|
||||
prompt_template="retrieval.md",
|
||||
strategy_name="RETRIEVAL",
|
||||
skill_target="M1",
|
||||
session_id="sess_001",
|
||||
)
|
||||
"""
|
||||
@@ -28,7 +30,6 @@ from json_repair import repair_json
|
||||
from loguru import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.question_gen.families import QuestionFamilySpec
|
||||
from app.question_gen.sampler_v2 import MaterialContext
|
||||
from app.tree.index import TreeIndex
|
||||
from core.protocols import VLMProvider
|
||||
@@ -85,11 +86,11 @@ class CandidateQuestion:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _load_prompt_template(family_spec: QuestionFamilySpec) -> str:
|
||||
"""加载家族对应的 prompt 模板文件。
|
||||
def _load_prompt_template(template_name: str) -> str:
|
||||
"""加载 prompt 模板文件。
|
||||
|
||||
参数:
|
||||
family_spec: 问题家族规格(含 prompt_template 文件名)。
|
||||
template_name: store/prompts/question_gen/ 下的模板文件名。
|
||||
|
||||
返回:
|
||||
模板内容字符串。
|
||||
@@ -97,9 +98,9 @@ def _load_prompt_template(family_spec: QuestionFamilySpec) -> str:
|
||||
异常:
|
||||
FileNotFoundError: 模板文件不存在。
|
||||
"""
|
||||
path = _PROMPTS_DIR / family_spec.prompt_template
|
||||
path = _PROMPTS_DIR / template_name
|
||||
if not path.exists():
|
||||
msg = f"家族 prompt 模板文件不存在: {path}"
|
||||
msg = f"Prompt 模板文件不存在: {path}"
|
||||
raise FileNotFoundError(msg)
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
@@ -110,36 +111,40 @@ def _load_prompt_template(family_spec: QuestionFamilySpec) -> str:
|
||||
|
||||
|
||||
def _build_v2_prompt(
|
||||
family_spec: QuestionFamilySpec,
|
||||
prompt_template: str,
|
||||
strategy_name: str,
|
||||
material: MaterialContext,
|
||||
task_type: str,
|
||||
seq: int,
|
||||
*,
|
||||
reject_reason: str | None = None,
|
||||
sub_pattern_instruction: str | None = None,
|
||||
) -> tuple[list[dict[str, str]], list[str]]:
|
||||
"""构建 VLM 出题调用的 messages 和帧路径列表。
|
||||
|
||||
参数:
|
||||
family_spec: 问题家族规格。
|
||||
prompt_template: prompt 模板文件名(store/prompts/question_gen/ 下)。
|
||||
strategy_name: 策略名称(如 "RETRIEVAL")。
|
||||
material: 采样素材上下文。
|
||||
task_type: 任务类型字符串。
|
||||
seq: 当前序号。
|
||||
reject_reason: 上一次被门控拒绝的原因(用于引导 VLM 避免相同错误)。
|
||||
sub_pattern_instruction: 子模式特殊指令(如有)。
|
||||
|
||||
返回:
|
||||
二元组:
|
||||
- messages: 适配 VLMProvider 的 message 列表(system + user)。
|
||||
- frame_paths: 需发送给 VLM 的帧路径列表。
|
||||
"""
|
||||
# Phase 1: 加载家族模板作为 system prompt
|
||||
template_content = _load_prompt_template(family_spec)
|
||||
# Phase 1: 加载策略模板作为 system prompt
|
||||
template_content = _load_prompt_template(prompt_template)
|
||||
system_message = template_content
|
||||
|
||||
# Phase 2: 构建 user prompt — 聚合素材信息
|
||||
user_parts: list[str] = []
|
||||
|
||||
user_parts.append(f"## Task Type: {task_type}")
|
||||
user_parts.append(f"## Question Family: {family_spec.name}")
|
||||
user_parts.append(f"## Question Family: {strategy_name}")
|
||||
user_parts.append(f"## Sequence: #{seq}")
|
||||
|
||||
# 字幕素材
|
||||
@@ -167,6 +172,10 @@ def _build_v2_prompt(
|
||||
f"Please generate a NEW question that avoids this issue."
|
||||
)
|
||||
|
||||
# 子模式特殊指令注入
|
||||
if sub_pattern_instruction is not None:
|
||||
user_parts.append(f"\n## Special Focus:\n{sub_pattern_instruction}")
|
||||
|
||||
# 输出格式指令
|
||||
user_parts.append(
|
||||
"\n## Output Format:\n"
|
||||
@@ -352,18 +361,21 @@ async def generate_one_v2(
|
||||
vlm: VLMProvider,
|
||||
tree: TreeIndex,
|
||||
material: MaterialContext,
|
||||
family_spec: QuestionFamilySpec,
|
||||
task_type: str,
|
||||
seq: int,
|
||||
*,
|
||||
video_id: str,
|
||||
prompt_template: str,
|
||||
strategy_name: str,
|
||||
skill_target: str,
|
||||
reject_reason: str | None = None,
|
||||
sub_pattern_instruction: str | None = None,
|
||||
session_id: str,
|
||||
) -> CandidateQuestion:
|
||||
"""调用 VLM 生成一道候选题目。
|
||||
|
||||
流程:
|
||||
1. 构建 per-family prompt + 帧路径。
|
||||
1. 构建 per-strategy prompt + 帧路径。
|
||||
2. 调用 VLMProvider.chat_with_images。
|
||||
3. 解析响应为 CandidateQuestion。
|
||||
4. 附加素材验证信息(subtitle_sentences、frame_paths)。
|
||||
@@ -372,11 +384,14 @@ async def generate_one_v2(
|
||||
vlm: VLM 调用端口。
|
||||
tree: 视频树索引(当前未直接使用,预留后续扩展)。
|
||||
material: 采样素材上下文。
|
||||
family_spec: 问题家族规格。
|
||||
task_type: 任务类型字符串。
|
||||
seq: 当前序号。
|
||||
video_id: 视频标识。
|
||||
prompt_template: prompt 模板文件名。
|
||||
strategy_name: 策略名称(如 "RETRIEVAL")。
|
||||
skill_target: 目标失败机制编号(M1-M5)。
|
||||
reject_reason: 上一次被门控拒绝的原因。
|
||||
sub_pattern_instruction: 子模式特殊指令(如有)。
|
||||
session_id: 会话 ID(遥测关联)。
|
||||
|
||||
返回:
|
||||
@@ -384,21 +399,23 @@ async def generate_one_v2(
|
||||
|
||||
异常:
|
||||
ValueError: VLM 响应解析失败。
|
||||
FileNotFoundError: 家族 prompt 模板不存在。
|
||||
FileNotFoundError: prompt 模板不存在。
|
||||
"""
|
||||
# Phase 1: 构建 prompt
|
||||
messages, frame_paths = _build_v2_prompt(
|
||||
family_spec=family_spec,
|
||||
prompt_template=prompt_template,
|
||||
strategy_name=strategy_name,
|
||||
material=material,
|
||||
task_type=task_type,
|
||||
seq=seq,
|
||||
reject_reason=reject_reason,
|
||||
sub_pattern_instruction=sub_pattern_instruction,
|
||||
)
|
||||
|
||||
# Phase 2: 调用 VLM
|
||||
logger.debug(
|
||||
"generate_one_v2: family={}, task_type={}, seq={}, frames={}",
|
||||
family_spec.name,
|
||||
"generate_one_v2: strategy={}, task_type={}, seq={}, frames={}",
|
||||
strategy_name,
|
||||
task_type,
|
||||
seq,
|
||||
len(frame_paths),
|
||||
@@ -415,7 +432,7 @@ async def generate_one_v2(
|
||||
raw=response.content,
|
||||
video_id=video_id,
|
||||
task_type=task_type,
|
||||
skill_target=family_spec.skill_target,
|
||||
skill_target=skill_target,
|
||||
seq=seq,
|
||||
source_nodes=material.source_nodes,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user