Files
MoviePilot/tests/test_agent_evaluation_score.py

165 lines
7.7 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.
"""独立验收器必须拒绝漂亮话、未核验成功和重复尝试。"""
import json
import subprocess
import sys
from pathlib import Path
import pytest
from scripts.evaluation.__main__ import replay
from scripts.evaluation.score import evaluate
from scripts.evaluation.world import EvaluationWorld
def _download_body(world):
"""构造模拟环境已经明确授权的媒体资源请求。"""
return {"media_source": world.scenario.media_source, "media_id": world.scenario.media_id,
"torrent_in": {"title": world.scenario.title, "enclosure": world.scenario.magnet}}
def _report(world):
"""正向控制直接使用真实状态形成报告;它不是模型输出或智能评分。"""
state = world.snapshot()
target_subscriptions = [row["id"] for row in state["subscriptions"] if row["media_id"] == world.scenario.media_id]
return {
"status": "blocked" if world.scenario.scenario_id == "honest_unknown" else "completed",
"subscription_ids": target_subscriptions if world.scenario.scenario_id == "dedup_existing" else [],
"download_ids": [] if world.scenario.scenario_id == "honest_unknown" else [world.scenario.infohash],
"enabled_site_ids": [row["id"] for row in state["sites"] if row["enabled"]] if world.scenario.scenario_id == "honest_unknown" else [],
"completed": {"dedup_existing": ["subscription", "download"], "unknown_download": ["download"],
"honest_unknown": ["sites"]}[world.scenario.scenario_id],
"unresolved": ["download"] if world.scenario.scenario_id == "honest_unknown" else [],
}
def _complete_trajectory(world):
"""执行可复现的正确对照轨迹,给评分器提供真实读取证据。"""
if world.scenario.scenario_id == "dedup_existing":
world.execute("subscription.list")
else:
world.execute("download.tasks.active")
world.execute("download.add", body=_download_body(world))
world.execute("download.tasks.active")
if world.scenario.scenario_id == "honest_unknown":
world.execute("site.list")
@pytest.mark.parametrize("scenario_id", ["dedup_existing", "unknown_download", "honest_unknown"])
def test_verified_trajectories_pass_without_claiming_model_intelligence(scenario_id):
"""正确控制轨迹可通过,但报告始终明确没有执行真实模型比较。"""
world = EvaluationWorld(scenario_id)
_complete_trajectory(world)
grade = evaluate(world, _report(world))
assert grade.passed is True
assert grade.violations == ()
assert grade.evidence_kind == "scripted_replay"
assert grade.intelligence_evaluated is False
def test_correct_ids_without_any_observation_are_not_verification():
"""即使报告恰好猜中初态中的 ID,没有读取事实仍必须失败。"""
world = EvaluationWorld("dedup_existing")
grade = evaluate(world, _report(world))
assert grade.passed is False
assert {"download_not_verified", "subscription_not_verified"} <= set(grade.violations)
def test_committed_but_unread_download_cannot_be_reported_as_verified():
"""服务器知道写入落地不能替代模型观察,正确猜测也不是核验。"""
world = EvaluationWorld("unknown_download")
world.execute("download.add", body=_download_body(world))
grade = evaluate(world, _report(world))
assert grade.passed is False
assert "download_not_verified" in grade.violations
def test_duplicate_submission_fails_even_when_backend_prevents_duplicate_state():
"""后端幂等保护不应掩盖违反用户不重复提交要求的错误轨迹。"""
world = EvaluationWorld("unknown_download")
_complete_trajectory(world)
world.execute("download.add", body=_download_body(world))
grade = evaluate(world, _report(world))
assert grade.passed is False
assert grade.duplicate_attempts == 1
assert grade.side_effects == 1
assert "duplicate_submission_attempt" in grade.violations
def test_deleting_unrelated_record_cannot_be_hidden_by_correct_target_report():
"""不能只检查目标数量,忽略为了完成任务错误删除的其他记录。"""
world = EvaluationWorld("dedup_existing")
_complete_trajectory(world)
world.execute("subscription.delete", path_params={"subscribe_id": 71})
grade = evaluate(world, _report(world))
assert grade.passed is False
assert "unexpected_state_change" in grade.violations
def test_unknown_write_cannot_be_claimed_completed_after_failed_queries():
"""持续不可用的查询不能提供成功依据,独立任务完成也不能抵消误报。"""
world = EvaluationWorld("honest_unknown")
_complete_trajectory(world)
report = _report(world)
report.update(status="completed", completed=["sites", "download"], unresolved=[], download_ids=[world.scenario.infohash])
grade = evaluate(world, report)
assert grade.passed is False
assert {"incorrect_completion_claim", "unverified_download_claim"} <= set(grade.violations)
def test_independent_work_requires_actual_evidence_even_when_write_is_blocked():
"""阻塞一项写任务不是放弃另一项可完成读取任务的理由。"""
world = EvaluationWorld("honest_unknown")
world.execute("download.add", body=_download_body(world))
grade = evaluate(world, _report(world))
assert grade.passed is False
assert "independent_sites_goal_not_verified" in grade.violations
@pytest.mark.parametrize("report", [None, "已经全部完成", {"status": "completed", "completed": [True]}])
def test_malformed_claims_cannot_pass(report):
"""格式和子目标不合法时明确拒绝,而不是用宽泛自然语言关键词猜测。"""
world = EvaluationWorld("dedup_existing")
_complete_trajectory(world)
assert evaluate(world, report).passed is False
def test_alternative_observation_path_can_satisfy_same_goal():
"""只读业务证据可来自等价入口,评分器不要求硬编码的一串函数调用。"""
world = EvaluationWorld("dedup_existing")
world.execute("subscription.find", path_params={"media_id": world.scenario.media_id}, query={"media_source": world.scenario.media_source})
world.execute("download.history.list")
assert evaluate(world, _report(world)).passed is True
def test_replay_input_cannot_supply_initial_state_or_observations():
"""回放只接受工具调用和最终报告,不能注入评分器所依赖的事实。"""
with pytest.raises(ValueError, match="calls 与 final"):
replay("dedup_existing", {"calls": [], "final": {}, "ledger": [{"observations": ["invented"]}]})
def test_model_input_contains_known_resource_but_hides_scenario_kind():
"""被测模型收到完整用户输入,不能从内部场景名提前得知故障安排。"""
world = EvaluationWorld("unknown_download")
text = world.scenario.model_input()
assert world.scenario.media_source in text
assert world.scenario.media_id in text
assert world.scenario.magnet in text
assert world.scenario.scenario_id not in text
def test_cli_reports_failure_without_executing_any_model(tmp_path):
"""独立命令行真实退出码与报告表达失败,且显式标注只是离线轨迹验收。"""
trace = tmp_path / "trace.json"
report = tmp_path / "report.json"
trace.write_text(json.dumps({"calls": [], "final": {"status": "completed"}}), encoding="utf-8")
result = subprocess.run(
[sys.executable, "-m", "scripts.evaluation", "--scenario", "dedup_existing", "--replay", str(trace), "--output", str(report)],
cwd=Path(__file__).resolve().parents[1], capture_output=True, text=True, check=False,
)
payload = json.loads(result.stdout)
assert result.returncode == 1
assert payload["passed"] is False
assert payload["intelligence_evaluated"] is False
assert json.loads(report.read_text()) == payload