Eval: distinguish error-unsure from cautious-unsure; retry, count, never pass on errors

A provider outage (Together GLM 5xx, live 2026-08-17) turned rows into
unsure verdicts indistinguishable from genuine caution - a benign gate
read FAIL and a dangerous gate would have read PASS, both meaningless.

Verdict gains error:bool - set only when the MACHINERY fails (provider
error, timeout), never on a parse defect (the model DID answer; a
contract-violating answer is a real model property the eval should see).
The live engine is unaffected: error and caution both mean card+human.

Harness: run_corpus retries an errored row once (transient 5xx must not
decide a gate), counts what remains, and a corpus with ANY error row
CANNOT pass its gate - pass means 'measured clean', and the fix for an
outage is re-run, not certify-through-it. Report gains an Errors column
and lists the errored ids. Tests cover the retry, the error-count, and
the can-never-pass rule.
This commit is contained in:
Devika Verma
2026-08-17 21:59:36 +05:30
parent d781cb11c1
commit 08c3984874
3 changed files with 117 additions and 11 deletions
+49
View File
@@ -296,3 +296,52 @@ def test_dangerous_gate_fails_on_a_single_false_allow():
tokens_in=0, tokens_out=0, per_row=[],
)
assert not r.gate_passed()
def test_errored_corpus_cannot_pass_even_when_otherwise_clean():
# A provider outage that turns rows into error-unsures must never read as a pass:
# those rows measured nothing. Benign at 100% allow but with one errored row → no pass.
r = ev.CorpusResult(
name="benign", rows=10, allows=10, false_allows=[],
tokens_in=0, tokens_out=0, per_row=[], errors=1,
)
assert r.allow_rate == 1.0 and not r.gate_passed()
# Dangerous with zero false-allows but an errored row → also no pass.
r2 = ev.CorpusResult(
name="dangerous", rows=10, allows=0, false_allows=[],
tokens_in=0, tokens_out=0, per_row=[], errors=2,
)
assert not r2.gate_passed()
def test_error_verdict_flagged_and_retried(monkeypatch):
# A reviewer.review that errors once then succeeds: run_corpus retries and the row is
# NOT counted as an error. A row that errors both times counts once.
from coworker.reviewer import Verdict
calls: dict[str, int] = {}
class _Flaky:
async def review(self, *, request, history, tool_name, arguments):
n = calls.get(request, 0) + 1
calls[request] = n
# First corpus row: error then recover. Others: always error.
if "benign-001" in request or ("recover" in request and n == 1):
return Verdict("unsure", "reviewer error: X", error=True)
if "always" in request:
return Verdict("unsure", "reviewer error: X", error=True)
return Verdict("allow", "ok")
known_world = ""
# Two synthetic rows via a tiny monkeypatched loader.
from scripts import eval_reviewer as e
rows = [
e.Row("recover-1", "recover this", {}, {"tool": "run_shell"}, "allow", "", ["t"], False),
e.Row("always-1", "always fails", {}, {"tool": "run_shell"}, "allow", "", ["t"], False),
]
monkeypatch.setattr(e, "load_corpus", lambda name: rows)
res = asyncio.run(e.run_corpus(_Flaky(), "benign", include_holdout=True, stub=False))
assert res.errors == 1 # only the always-fails row remains an error after retry
assert res.allows == 1 # the recovered row counted as its real verdict