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:
+73
-26
@@ -4,7 +4,7 @@
|
||||
1. key_verify: 验证答案在来源素材中有证据支撑。
|
||||
2. blind_answer: 无上下文时 LLM 能否答对(若答对 → 题目泄漏)。
|
||||
3. multi_true: 检测是否有多个选项可被视为正确。
|
||||
4. leak_test: 按家族特定模板探测答题捷径。
|
||||
4. leak_test: 按策略特定模板探测答题捷径。
|
||||
|
||||
设计要点:
|
||||
- run_gates 先做 verbatim_ratio 前置短路(> 0.5 直接 FAIL key_verify)。
|
||||
@@ -26,10 +26,9 @@ from typing import TYPE_CHECKING
|
||||
from loguru import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.question_gen.families import QuestionFamilySpec
|
||||
from app.question_gen.postprocess import PostprocessResult
|
||||
from app.tree.index import TreeIndex
|
||||
from core.protocols import LLMProvider
|
||||
from core.protocols import LLMProvider, VLMProvider
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 常量
|
||||
@@ -248,25 +247,61 @@ def _parse_gate_response(raw_content: str) -> tuple[GateVerdict, str]:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def _gate_key_verify(
|
||||
candidate: CandidateQuestion,
|
||||
tree: TreeIndex,
|
||||
llm: LLMProvider,
|
||||
*,
|
||||
session_id: str,
|
||||
) -> GateResult:
|
||||
"""关键验证门 — 检查答案在来源素材中是否有证据支撑。
|
||||
def _resolve_source_frames(candidate: CandidateQuestion, tree: TreeIndex) -> list[str]:
|
||||
"""从树中收集候选题来源节点关联的帧路径。
|
||||
|
||||
参数:
|
||||
candidate: 候选题目。
|
||||
tree: 视频树索引。
|
||||
llm: LLM 调用端口。
|
||||
|
||||
返回:
|
||||
去重后的帧路径列表(最多 10 张,避免 VLM 输入过长)。
|
||||
"""
|
||||
frames: list[str] = []
|
||||
target_ids = set(candidate.source_nodes)
|
||||
|
||||
for l1 in tree.roots:
|
||||
for l2 in l1.children:
|
||||
if l2.id in target_ids:
|
||||
for l3 in l2.children:
|
||||
if l3.frame_path:
|
||||
frames.append(l3.frame_path)
|
||||
for l3 in l2.children:
|
||||
if l3.id in target_ids and l3.frame_path:
|
||||
frames.append(l3.frame_path)
|
||||
|
||||
# 也使用候选题自带的帧路径
|
||||
frames.extend(candidate.frame_paths)
|
||||
|
||||
seen: set[str] = set()
|
||||
unique: list[str] = []
|
||||
for f in frames:
|
||||
if f not in seen:
|
||||
seen.add(f)
|
||||
unique.append(f)
|
||||
return unique[:10]
|
||||
|
||||
|
||||
async def _gate_key_verify(
|
||||
candidate: CandidateQuestion,
|
||||
tree: TreeIndex,
|
||||
vlm: VLMProvider,
|
||||
*,
|
||||
session_id: str,
|
||||
) -> GateResult:
|
||||
"""关键验证门 — 使用 VLM 检查答案在来源素材(文本+帧画面)中是否有证据支撑。
|
||||
|
||||
参数:
|
||||
candidate: 候选题目。
|
||||
tree: 视频树索引。
|
||||
vlm: VLM 图文调用端口(同时看文本和帧画面)。
|
||||
session_id: 会话 ID(遥测关联)。
|
||||
|
||||
返回:
|
||||
GateResult 实例。
|
||||
"""
|
||||
source_text = _resolve_source_text(candidate, tree)
|
||||
frames = _resolve_source_frames(candidate, tree)
|
||||
template = _load_prompt_template("gate_key_verify.md")
|
||||
prompt = template.format(
|
||||
source_text=source_text,
|
||||
@@ -275,10 +310,20 @@ async def _gate_key_verify(
|
||||
answer=candidate.answer,
|
||||
)
|
||||
|
||||
response = await llm.chat(
|
||||
[{"role": "user", "content": prompt}],
|
||||
session_id=session_id,
|
||||
)
|
||||
if frames:
|
||||
response = await vlm.chat_with_images(
|
||||
[{"role": "user", "content": prompt}],
|
||||
images=frames,
|
||||
session_id=session_id,
|
||||
)
|
||||
else:
|
||||
# 无帧时降级为纯文本(不应常见)
|
||||
logger.warning("key_verify 无可用帧,降级纯文本: {}", candidate.question_id)
|
||||
response = await vlm.chat_with_images(
|
||||
[{"role": "user", "content": prompt}],
|
||||
images=[],
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
verdict, reason = _parse_gate_response(response.content)
|
||||
return GateResult(verdict=verdict, reason=reason, raw_response=response.content)
|
||||
@@ -352,24 +397,23 @@ async def _gate_multi_true(
|
||||
|
||||
async def _gate_leak_test(
|
||||
candidate: CandidateQuestion,
|
||||
family_spec: QuestionFamilySpec,
|
||||
leak_probe_template: str,
|
||||
llm: LLMProvider,
|
||||
*,
|
||||
session_id: str,
|
||||
) -> GateResult:
|
||||
"""泄漏测试门 — 按家族特定模板探测答题捷径。
|
||||
"""泄漏测试门 — 按策略特定模板探测答题捷径。
|
||||
|
||||
参数:
|
||||
candidate: 候选题目。
|
||||
family_spec: 问题家族规格(含 leak_profile)。
|
||||
leak_probe_template: 泄漏探测模板文件名(store/prompts/question_gen/ 下)。
|
||||
llm: LLM 调用端口。
|
||||
session_id: 会话 ID(遥测关联)。
|
||||
|
||||
返回:
|
||||
GateResult 实例。
|
||||
"""
|
||||
probe_template_name = family_spec.leak_profile.probe_template
|
||||
template = _load_prompt_template(probe_template_name)
|
||||
template = _load_prompt_template(leak_probe_template)
|
||||
prompt = template.format(
|
||||
question=candidate.question,
|
||||
options=_format_options(candidate.options),
|
||||
@@ -394,9 +438,10 @@ async def run_gates(
|
||||
candidate: CandidateQuestion,
|
||||
tree: TreeIndex,
|
||||
llm: LLMProvider,
|
||||
family_spec: QuestionFamilySpec,
|
||||
leak_probe_template: str,
|
||||
postprocess: PostprocessResult,
|
||||
*,
|
||||
vlm: VLMProvider | None = None,
|
||||
session_id: str,
|
||||
) -> GateReport:
|
||||
"""编排四门并发执行,返回汇总报告。
|
||||
@@ -407,8 +452,9 @@ async def run_gates(
|
||||
candidate: 候选题目。
|
||||
tree: 视频树索引。
|
||||
llm: LLM 调用端口。
|
||||
family_spec: 问题家族规格。
|
||||
leak_probe_template: 泄漏探测模板文件名(store/prompts/question_gen/ 下)。
|
||||
postprocess: 后处理结果(含 verbatim_ratio)。
|
||||
vlm: VLM 图文调用端口(key_verify 使用,None 时降级为 LLM)。
|
||||
session_id: 会话 ID(遥测关联)。
|
||||
|
||||
返回:
|
||||
@@ -437,12 +483,13 @@ async def run_gates(
|
||||
leak_test=skip_result,
|
||||
)
|
||||
|
||||
# Phase 2: 四门并发执行
|
||||
# Phase 2: 四门并发执行(key_verify 使用 VLM 看帧+文本)
|
||||
key_verify_provider = vlm if vlm is not None else llm
|
||||
key_result, blind_result, multi_result, leak_result = await asyncio.gather(
|
||||
_gate_key_verify(candidate, tree, llm, session_id=session_id),
|
||||
_gate_key_verify(candidate, tree, key_verify_provider, session_id=session_id),
|
||||
_gate_blind_answer(candidate, llm, session_id=session_id),
|
||||
_gate_multi_true(candidate, tree, llm, session_id=session_id),
|
||||
_gate_leak_test(candidate, family_spec, llm, session_id=session_id),
|
||||
_gate_leak_test(candidate, leak_probe_template, llm, session_id=session_id),
|
||||
)
|
||||
|
||||
report = GateReport(
|
||||
|
||||
Reference in New Issue
Block a user