AgentEvalTool/tests/unit/test_judgement.py
sinohqb 5db0ede4f4
Some checks failed
CI / test (push) Failing after 50s
refactor(judgement): converge case-pass decision into one deep module
「用例是否通过」此前散落 8 处且互相矛盾:engine 权威判定焊死在持久化里
不可单测;report 聚合/compare/markdown 各自从规则结果反推,规则还不一致
(markdown 用 all([]) 把故障用例误渲染成 )。

- 新增纯函数 evaluation/judgement.combine_case_outcome(RuleOutcome/
  CaseOutcome),判定组合脱离通道与 DB 可单测(判定矩阵 14 例)
- engine 调用它一次,逐用例权威结果写入 summary.case_outcomes(JSON,
  零迁移);report/compare/markdown 只读权威值,老 run fallback 反推
- 故障用例判 False(ADR-0002):修正 markdown 的  bug 与 compare 的
  None;顺带修 engine 连通用例无回复也算通过的 bug
- pass_rate 口径改为用例级(CONTEXT.md 词条),规则级保留在
  passed_rules/total_rules;CLI 对比标签同步更正
- 修 RunRepository.update 漏拷 scenario_version/triggered_by 的字段漂移
2026-07-29 19:45:02 +08:00

130 lines
4.6 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.

"""判定组合器judgement单元测试 — 纯函数,无通道无 DB。
语义来源CONTEXT.md连通用例/通过率、ADR-0002故障=不通过)、
spec v0.5(期望叠加:隐式规则是 rule_logic 之外的硬约束)。
"""
from agenteval.evaluation.judgement import CaseOutcome, RuleOutcome, combine_case_outcome
from agenteval.models import RuleLogic
def _r(passed: bool, score: float | None = None, weight: float = 1.0) -> RuleOutcome:
return RuleOutcome(passed=passed, score=score, weight=weight)
# ── 连通用例(无任何判定标准) ──────────────────────────────────────
def test_connectivity_case_all_replied_passes():
outcome = combine_case_outcome(all_replied=True)
assert outcome == CaseOutcome(passed=True, connectivity=True)
def test_connectivity_case_missing_reply_fails():
# 无回复=故障=不通过ADR-0002 服务视角),且不再标注为连通
outcome = combine_case_outcome(all_replied=False)
assert outcome == CaseOutcome(passed=False, connectivity=False)
# ── 显式规则组合rule_logic ─────────────────────────────────────
def test_all_logic_every_rule_passes():
outcome = combine_case_outcome(all_replied=True, explicit=[_r(True), _r(True)])
assert outcome == CaseOutcome(passed=True, connectivity=False)
def test_all_logic_single_failure_fails():
outcome = combine_case_outcome(all_replied=True, explicit=[_r(True), _r(False)])
assert outcome.passed is False
def test_any_logic_single_pass_suffices():
outcome = combine_case_outcome(
all_replied=True, explicit=[_r(False), _r(True)], rule_logic=RuleLogic.ANY
)
assert outcome.passed is True
def test_any_logic_no_pass_fails():
outcome = combine_case_outcome(
all_replied=True, explicit=[_r(False), _r(False)], rule_logic=RuleLogic.ANY
)
assert outcome.passed is False
def test_weighted_logic_above_threshold_passes():
outcome = combine_case_outcome(
all_replied=True,
explicit=[_r(True, score=0.9, weight=2.0), _r(False, score=0.3, weight=1.0)],
rule_logic=RuleLogic.WEIGHTED,
threshold=0.6,
)
# (0.9*2 + 0.3*1) / 3 = 0.7 >= 0.6
assert outcome.passed is True
def test_weighted_logic_below_threshold_fails():
outcome = combine_case_outcome(
all_replied=True,
explicit=[_r(True, score=0.9, weight=1.0), _r(False, score=0.1, weight=2.0)],
rule_logic=RuleLogic.WEIGHTED,
threshold=0.6,
)
# (0.9 + 0.1*2) / 3 ≈ 0.367 < 0.6
assert outcome.passed is False
def test_weighted_logic_scoreless_rule_uses_binary_score():
# 无 score 的规则按通过=1.0 / 失败=0.0 计(与引擎既有口径一致)
outcome = combine_case_outcome(
all_replied=True,
explicit=[_r(True, score=None, weight=1.0)],
rule_logic=RuleLogic.WEIGHTED,
threshold=0.6,
)
assert outcome.passed is True
# ── 期望叠加(隐式规则是硬约束) ───────────────────────────────────
def test_implicit_failure_vetoes_explicit_pass():
outcome = combine_case_outcome(
all_replied=True, explicit=[_r(True)], implicit=[_r(False)]
)
assert outcome.passed is False
def test_implicit_failure_vetoes_even_any_logic():
# 隐式规则不参与 rule_logic 组合any 满足也救不回隐式失败
outcome = combine_case_outcome(
all_replied=True,
explicit=[_r(True), _r(False)],
implicit=[_r(False)],
rule_logic=RuleLogic.ANY,
)
assert outcome.passed is False
def test_implicit_only_case_passes_when_implicit_pass():
# 仅期望无显式规则:显式组合空真
outcome = combine_case_outcome(all_replied=True, implicit=[_r(True)])
assert outcome == CaseOutcome(passed=True, connectivity=False)
def test_explicit_failure_not_saved_by_implicit_pass():
outcome = combine_case_outcome(
all_replied=True, explicit=[_r(False)], implicit=[_r(True)]
)
assert outcome.passed is False
# ── 有规则用例的回复缺失:判定权在规则 ─────────────────────────────
def test_ruled_case_judged_by_rules_even_with_missing_reply():
# 有判定标准时由规则说了算(规则自身会因无回复而失败),不因缺回复直接判死
outcome = combine_case_outcome(all_replied=False, explicit=[_r(True)])
assert outcome == CaseOutcome(passed=True, connectivity=False)