test: assert stage/verdict/pair_id and Q-refusal flip_skipped path
This commit is contained in:
@@ -131,10 +131,20 @@ async def test_flip_gate_answer_flips_passed(tmp_path):
|
||||
"SELECT verdict FROM adversarial_verdicts WHERE question_id='hard' AND stage='cheat'"
|
||||
).fetchone()[0]
|
||||
assert cheat == "passed"
|
||||
mrow = store._conn.execute(
|
||||
"SELECT verdict, pair_id FROM adversarial_verdicts WHERE stage='flip_mirror'"
|
||||
).fetchone()
|
||||
assert mrow[0] == "passed" and mrow[1] # 镜像独立行 + pair_id 非空
|
||||
# flip_original + flip_mirror 两条独立 stage 行,共用同一 pair_id(非空),verdict=passed
|
||||
by_stage = {
|
||||
r[0]: (r[1], r[2])
|
||||
for r in store._conn.execute(
|
||||
"SELECT stage, verdict, pair_id FROM adversarial_verdicts "
|
||||
"WHERE question_id='hard' AND stage IN ('flip_original', 'flip_mirror')"
|
||||
)
|
||||
}
|
||||
assert set(by_stage) == {"flip_original", "flip_mirror"}
|
||||
assert by_stage["flip_original"][0] == "passed"
|
||||
assert by_stage["flip_mirror"][0] == "passed"
|
||||
assert by_stage["flip_original"][1] # pair_id 非空
|
||||
assert by_stage["flip_original"][1] == by_stage["flip_mirror"][1] # 共用同一 pair_id
|
||||
assert "hard" in store.final_passed_question_ids({"hard": question_hash(q)}, _fp())
|
||||
store.close()
|
||||
|
||||
|
||||
@@ -172,6 +182,23 @@ async def test_flip_gate_same_answer_filtered(tmp_path):
|
||||
"SELECT verdict FROM adversarial_verdicts WHERE question_id='stick' AND stage='cheat'"
|
||||
).fetchone()[0]
|
||||
assert cheat == "filtered_no_flip" # cheat 行被改写 → final 不含它
|
||||
# flip_original + flip_mirror 两条独立 stage 行,共用同一 pair_id(非空),verdict 一致
|
||||
by_stage = {
|
||||
r[0]: (r[1], r[2])
|
||||
for r in store._conn.execute(
|
||||
"SELECT stage, verdict, pair_id FROM adversarial_verdicts "
|
||||
"WHERE question_id='stick' AND stage IN ('flip_original', 'flip_mirror')"
|
||||
)
|
||||
}
|
||||
assert set(by_stage) == {"flip_original", "flip_mirror"}
|
||||
assert by_stage["flip_original"][0] == "filtered_no_flip"
|
||||
assert by_stage["flip_mirror"][0] == "filtered_no_flip"
|
||||
assert by_stage["flip_original"][1] # pair_id 非空
|
||||
assert by_stage["flip_original"][1] == by_stage["flip_mirror"][1] # 共用同一 pair_id
|
||||
# 改写后的 cheat 行不再计入终判 passed(双保险:cheat 改判 + filtered_no_flip 排除)
|
||||
assert "stick" not in store.final_passed_question_ids(
|
||||
{"stick": question_hash(q)}, _fp()
|
||||
)
|
||||
store.close()
|
||||
|
||||
|
||||
@@ -202,6 +229,61 @@ async def test_flip_gate_invalid_mirror_skipped_but_kept(tmp_path):
|
||||
store.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_flip_gate_mirror_ok_but_agent_refuses_skipped(tmp_path):
|
||||
"""镜像生成成功但 agent 对镜像 Q 拒答(None) → flip_skipped,survivor 保留不误杀。
|
||||
|
||||
与 invalid_mirror 用例互补:此处镜像题**造得出来**(走到 agent),只因 agent 无
|
||||
有效预测→canonical(Q)=None→保守 flip_skipped,绝不据此误杀原题。
|
||||
"""
|
||||
store = QuestionGenStore(str(tmp_path / "q.db"))
|
||||
q = _q("refuse")
|
||||
_preset_cheat(store, q, pred="A") # P canonical="蒸"
|
||||
agent = _FakeAgent({}) # 镜像题无预测 → predict 返回 None(拒答)
|
||||
vlm = _FakeVLM(
|
||||
json.dumps(
|
||||
{
|
||||
"mirror": {
|
||||
"question": "X 之后?",
|
||||
"options": ["A. 炒", "B. 蒸", "C. 煮", "D. 炸"],
|
||||
"answer": "A",
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
)
|
||||
kept = await run_flip_gate(
|
||||
[q],
|
||||
agent=agent,
|
||||
vlm=vlm,
|
||||
store=store,
|
||||
trees={"v1": object()},
|
||||
config=AdversarialFilterConfig(),
|
||||
round_no=0,
|
||||
run_id="r0",
|
||||
session_id="s",
|
||||
)
|
||||
assert {x.question_id for x in kept} == {"refuse"} # 保守保留,不误杀
|
||||
assert agent.calls == ["refuse_mirror"] # 镜像被跑一次,C2:原题 P 未被重跑
|
||||
by_stage = {
|
||||
r[0]: r[1]
|
||||
for r in store._conn.execute(
|
||||
"SELECT stage, verdict FROM adversarial_verdicts "
|
||||
"WHERE question_id='refuse' AND stage IN ('flip_original', 'flip_mirror')"
|
||||
)
|
||||
}
|
||||
assert by_stage == {"flip_original": "flip_skipped", "flip_mirror": "flip_skipped"}
|
||||
cheat = store._conn.execute(
|
||||
"SELECT verdict FROM adversarial_verdicts WHERE question_id='refuse' AND stage='cheat'"
|
||||
).fetchone()[0]
|
||||
assert cheat == "passed" # cheat 行保持 passed(保留只经作弊门)
|
||||
# flip_skipped 不算 filtered_no_flip → 仍计入终判 passed(保留题最终可入库)
|
||||
assert "refuse" in store.final_passed_question_ids(
|
||||
{"refuse": question_hash(q)}, _fp()
|
||||
)
|
||||
store.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_flip_gate_mirror_excluded_and_unsupported_passes(tmp_path):
|
||||
store = QuestionGenStore(str(tmp_path / "q.db"))
|
||||
|
||||
Reference in New Issue
Block a user