"""两个半判据合成一个判定 —— 优先级不能反。 `combine_verdict` 是整套东西的裁决点,它的每一条优先级都对应一个具体的 坏结果: * error 高于一切 —— 供应商抖一下不是「助手答得差」; * 确定性阻断高于评委 —— 评委是模型读模型,不能给一个查得出来的编造引用翻案; * 评委不可用 = error,不是 fail —— 否则评委自己的故障会长在助手的趋势线上; * 硬维度地板由服务端兜 —— 「pass」不能等于「接地性只有 1 分但它说 pass」。 """ from __future__ import annotations from dataclasses import dataclass, field from app.assistant.evaluation import checks from app.assistant.evaluation.constants import ( CHECK_TURN_FAILED, CHECK_UNGROUNDED_CITATION, DIM_ACCURATE, DIM_ACTIONABLE, DIM_COMPLETE, DIM_ESCALATION, DIM_GROUNDED, DIM_TONE, FAIL_JUDGE_ERROR, FAIL_KB_DOC, FAIL_TOOL_FIELD_OR_DATA, VERDICT_ERROR, VERDICT_FAIL, VERDICT_PASS, ) from app.assistant.evaluation.runner import combine_verdict @dataclass class _Judge: available: bool = True scores: dict = field(default_factory=dict) verdict: str | None = VERDICT_PASS failure_type: str | None = None def _good_scores(**overrides) -> dict: base = { DIM_GROUNDED: 5, DIM_ACCURATE: 5, DIM_COMPLETE: 4, DIM_ACTIONABLE: 4, DIM_ESCALATION: 5, DIM_TONE: 4, } base.update(overrides) return base def _finding(code: str) -> checks.Finding: severity = ( checks.SEVERITY_ERROR if code in {CHECK_TURN_FAILED} else checks.SEVERITY_BLOCKING ) return checks.Finding(code=code, severity=severity, detail="") def test_a_clean_attempt_with_a_happy_judge_passes(): """🔴 锚。这条一旦不再通过,下面每一条阴性用例都失去意义。""" verdict, failure = combine_verdict( findings=[], judge=_Judge(scores=_good_scores()), ) assert (verdict, failure) == (VERDICT_PASS, None) def test_an_error_finding_beats_everything(): """模型没答上话,这一轮什么质量都没观察到。""" verdict, failure = combine_verdict( findings=[_finding(CHECK_TURN_FAILED)], judge=_Judge(scores=_good_scores(), verdict=VERDICT_PASS), ) assert verdict == VERDICT_ERROR assert failure is None def test_a_blocking_finding_cannot_be_overruled_by_the_judge(): """🔴 这是「确定性检查先跑」的全部意义。 评委给了满分并判 pass —— 它当然会,一个编造出来的链接读起来和真的一样。 但「这个链接在不在本轮任何工具结果里」是查得出来的,查出来的事实不接受 投票翻案。 """ verdict, failure = combine_verdict( findings=[_finding(CHECK_UNGROUNDED_CITATION)], judge=_Judge(scores=_good_scores(), verdict=VERDICT_PASS), ) assert verdict == VERDICT_FAIL assert failure == FAIL_TOOL_FIELD_OR_DATA def test_the_judges_own_classification_wins_when_it_has_one(): """确定性闸负责「拦下来」,分类仍优先采信评委 —— 它读过全文。""" verdict, failure = combine_verdict( findings=[_finding(CHECK_UNGROUNDED_CITATION)], judge=_Judge( scores=_good_scores(), verdict=VERDICT_FAIL, failure_type=FAIL_KB_DOC, ), ) assert (verdict, failure) == (VERDICT_FAIL, FAIL_KB_DOC) def test_an_unavailable_judge_is_an_error_not_a_failure(): """🔴 评委自己的故障不能画进助手的趋势线。""" verdict, failure = combine_verdict(findings=[], judge=_Judge(available=False)) assert (verdict, failure) == (VERDICT_ERROR, FAIL_JUDGE_ERROR) def test_a_judge_that_returned_no_scores_is_an_error(): verdict, failure = combine_verdict( findings=[], judge=_Judge(scores={}, verdict=VERDICT_PASS), ) assert (verdict, failure) == (VERDICT_ERROR, FAIL_JUDGE_ERROR) def test_a_missing_judge_object_is_an_error(): verdict, failure = combine_verdict(findings=[], judge=None) assert (verdict, failure) == (VERDICT_ERROR, FAIL_JUDGE_ERROR) def test_a_floored_hard_dimension_fails_even_when_the_judge_said_pass(): """🔴 服务端兜底:「pass」不能等于「接地性 1 分但它说 pass」。""" verdict, _failure = combine_verdict( findings=[], judge=_Judge( scores=_good_scores(**{DIM_GROUNDED: 1}), verdict=VERDICT_PASS, ), ) assert verdict == VERDICT_FAIL def test_a_floored_soft_dimension_does_not_fail_on_its_own(): """护栏:语气差不是不能给员工看。硬地板只有接地性和准确性两条。""" verdict, _failure = combine_verdict( findings=[], judge=_Judge(scores=_good_scores(**{DIM_TONE: 1}), verdict=VERDICT_PASS), ) assert verdict == VERDICT_PASS def test_a_partially_scored_reply_is_not_failed_for_the_dimensions_it_omitted(): """评委只填了一部分维度时,没填的那些不该被当成 0 分。""" verdict, _failure = combine_verdict( findings=[], judge=_Judge( scores={DIM_GROUNDED: 5, DIM_ACCURATE: 4}, verdict=VERDICT_PASS, ), ) assert verdict == VERDICT_PASS def test_a_judge_with_scores_but_no_verdict_field_still_yields_one(): """维度分齐了却漏了 verdict —— 这是一次可用的测量,不该丢掉。""" verdict, _failure = combine_verdict( findings=[], judge=_Judge(scores=_good_scores(), verdict=None), ) assert verdict == VERDICT_PASS