Repository navigation
fix(eval): cross-judge exits 2 on a result entry of the wrong shape, before any judge call - #233
Conversation
…before any judge call In the intel#189 review, wrong-shaped contents inside a `--pairwise` file were left for a follow-up: `{"verdicts": {"skill": {"q1": {}}}}` ended in `KeyError: 'score'`. The same holds for a verdict that is not an object, a non-numeric score, an arm that is not an object of verdicts, and in a `--checklist` file for a question with no `question_id`, an `arms` value or an arm that is not an object, or a coverage that is not a number. The measures were also built only after the `--judges` loop, so the traceback came after the paid judge calls, although the comment above the file reads promised that a bad file costs none. `pairwise_measure` and `coverage_measure` now raise ValueError naming the first bad entry (`verdicts.skill.q1`, `questions[1]`), and cross-judge builds both measures when it reads the files, before the first judge call, printing `Error: <file>: <entry> ...` with exit 2. A numeric string score is still accepted. `cmd_eval_cross_judge` drops from complexity 32 to 30, and its growth entry follows. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #233 +/- ##
==========================================
+ Coverage 92.25% 92.95% +0.69%
==========================================
Files 239 241 +2
Lines 29937 30672 +735
Branches 4918 5060 +142
==========================================
+ Hits 27618 28510 +892
+ Misses 1632 1460 -172
- Partials 687 702 +15
🚀 New features to boost your workflow:
|
|
@exai-bot /review |
exai-bot
left a comment
There was a problem hiding this comment.
Note
AI-generated review of 0494bac8a7a2, requested by @antonwolfy, using the default review skill. It can be wrong: verify a finding before acting on it. React with a thumbs up or down on a finding to tell the bot whether it helped.
Model us.anthropic.claude-sonnet-5-5, effort default, 2 of 80 turns, 0.2 of 40 minutes, 34k in / 1k out tokens.
The change moves construction of the --pairwise and --checklist measures ahead of the judge loop. pairwise_measure and coverage_measure now raise ValueError naming the first bad entry, and _result_measure turns that into Error: <file>: <entry> ... with exit 2. I found no defects. The exception catches cover the wrong shapes listed in the description, and the report keeps its measure order. The test runs 11 wrong-shaped files with a recording judge and asserts both exit 2 and that no judge call was made.
No issues found.
napetrov
left a comment
There was a problem hiding this comment.
Reviewed at 0494bac. No fixes pushed.
Evidence on the PR head: pytest tests/test_record_readers.py tests/test_judge_reliability.py 222 passed; ruff check . passes; scripts/check_growth.py passes. I also probed pairwise_measure / coverage_measure directly with extra shapes beyond the 11 test cases.
Open items (low severity, not blocking; both pre-existing behaviour, not introduced by this PR):
{"score": "nan"}is accepted and stored asnan(float("nan")succeeds), and a checklistcoverageofNaNis accepted too. A NaN score will propagate into the agreement statistics rather than exit 2. If "numeric" is meant as finite, amath.isfinitecheck would close it; otherwise fine to leave.coverage: truepasses the new type check (boolis anint) and is stored asTrue;score: truebecomes1.0. Probably harmless, mentioned only because the new check reads as a strict number check.
Confirmed working: a non-object verdicts / questions of the wrong kind is still stopped earlier by _load_json_object, and arms that is not an object now gives questions[i] ... with exit 2.
napetrov
left a comment
There was a problem hiding this comment.
Follow-up to the earlier review at 0494bac (no new code findings).
Land-time note: dokimi/scripts/growth_baseline.json is also changed by #235, #236 and #239. Whichever of these lands later will conflict on it or carry a stale count, so the baseline should be regenerated on the final base at merge time rather than hand-merged.
vchamarthi
left a comment
There was a problem hiding this comment.
Approved. 1 non-blocking comment.
No blockers. The malformed-entry check now runs before the first judge call and the measure order is unchanged. One test-id nit.
-
Nit:
test_record_readers.py:153uses the idarm-not-an-objecttwice; rename the checklist one (e.g.arm-entry-not-an-object) so the ids stay distinct.idsat lines 153-155 contains "arm-not-an-object" at positions 5 and 9, for different flags and shapes. Pytest suffixes duplicates, so it runs, but-kselection by id is ambiguous. (dokimi/tests/test_record_readers.py:154)
CI at send time: 0 failing, 0 pending.
…hecklist question needs both keys From the reviews of intel#233: - vlad-perevezentsev: `q.get("arms") or {}` skipped a question with no `arms`, a null or a list one, and read `question_id` only inside the arm loop, so `{"question_id": "q1"}` and `{"arms": {}}` passed and two null ids overwrote each other, while the changelog says such a question exits 2. A question now needs a `question_id` that is not null or a bool, and an `arms` object, before its arms are read. - vlad-perevezentsev and napetrov: "numeric" meant two things. A pairwise score went through `float()`, which takes "1", NaN and infinity and raises OverflowError, uncaught, on an integer past a float's range; a coverage of `true` or NaN passed the type check. Both now go through `_number`: a finite JSON number, not a bool or a string. `eval pairwise` writes an int score and `eval checklist` a float or null coverage, so no file they wrote changes reading. - vchamarthi: the test id `arm-not-an-object` was used twice; the checklist one is `arm-entry-not-an-object`. A checklist arm with a null coverage, which `eval checklist` writes for an arm with no scored point, has a test of its own. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
@vchamarthi the duplicate test id is now |
Summary
Follow-up to the review of #189, where wrong-shaped contents inside a result file were left for a follow-up (review, reply). Given as
--pairwise,{"verdicts": {"skill": {"q1": {}}}}ends inKeyError: 'score'.Other shapes of
--pairwiseand--checklistfiles end the same way. The traceback also comes after--judgeshas made its paid calls. The measures were built only after the judge loop, although the comment above the file reads says a bad file costs no paid calls.On
main, througheval cross-judge --no-stored:{"verdicts": {"skill": {"q1": {}}}}KeyError: 'score'{"verdicts": {"skill": {"q1": 5}}}TypeError{"verdicts": {"skill": {"q1": {"score": "x"}}}}ValueError{"verdicts": {"skill": 5}}AttributeError{"questions": [5]}AttributeError{"questions": [{"arms": {"skill": {"coverage": 1}}}]}KeyError: 'question_id'"coverage": "x"TypeErroronce another measure is compared with itChanges
cross_judge.pairwise_measureandcoverage_measureraiseValueErrornaming the first bad entry, for exampleverdicts.skill.q1 has no score that is a finite numberorquestions[1] is not a checklist question: .... A score and a coverage go through_number: a finite JSON number, not a bool or a string, and an integer past a float's range is caught too. A checklist question needs aquestion_idthat is not null or a bool, and anarmsobject, before its arms are read.cmd_eval_cross_judgebuilds both measures when it reads the files, before the first judge call, through_result_measure. A bad entry printsError: <file>: <entry> ...and exits 2, the code an unreadable file gets since fix(eval): cross-judge exits 2 on an unreadable input file #189. The measures keep their place in the report.cmd_eval_cross_judgegoes from complexity 32 to 30, and its growth entry follows.Risk
Files these two readers took before and now refuse: a checklist
coveragethat is not a finite number or null (before, it passed only while nothing was compared with it, and crashed otherwise), a pairwisescorethat is a numeric string, a bool, NaN or infinite, and a checklist question withoutarmsor with a nullquestion_id.eval pairwisewrites an int score andeval checklista question id, anarmsobject and a float or null coverage, so no file they wrote reads differently. Valid files give the same report, and the existing cross-judge tests pass unchanged.Testing
On a Linux review host, at
cb23dd5(onmain54ce188):make ci-local: 29 passed, 0 failed, 7 skipped (the Docker catalog smoke,test-latest, thecli-surfacepackaging step, conda-forge-deps without micromamba, docsdeploy, the pip-audit report, semgrep). 8,224 tests passed, 2 skipped; changed-line coverage 100%.0494bac(a numeric string, bool, NaN, infinite or too large score; a null, bool or missingquestion_idorarms; a bool, NaN or too large coverage) fail at0494bac(12 failed) and pass here; a checklist arm with a null coverage still reads. 11 mutants of that fix each fail a test:_numbertaking a bool, a string or a non-finite value,OverflowErroruncaught for a score or a coverage, a null or boolquestion_idtaken, a missingarmsskipped, a null coverage refused, the coverage unchecked, andAttributeErroruncaught.main59de48a: clean, and the growth check passes.test_cross_judge_names_a_result_entry_of_the_wrong_shape_before_any_judge_call(11 cases, intests/test_record_readers.py:tests/test_judge_reliability.pyis at its growth cap) runs with--judgesand a recording judge. Each case exits 2 with the entry in the message, and no judge call is made. Withmain's code all 11 fail. They also all fail with only the new measure functions, because the judges were still called first.KeyErrororValueErrordropped from the verdict catch, the coverage type check off,AttributeErrororKeyErrordropped from the question catch,_result_measurenot catching, and the file measures not added to the report.main54ce188 (0a5717c): the full suite (8,232 passed, 2 skipped), the growth check,mypyandruffpass.Checklist
pytestpassesruff,mypy, and schema checks pass.github/.licenserc.yaml)