docs: add Scoring Policy page (score vs raise vs unscored)#4518
Open
MattFisher wants to merge 9 commits into
Open
docs: add Scoring Policy page (score vs raise vs unscored)#4518MattFisher wants to merge 9 commits into
MattFisher wants to merge 9 commits into
Conversation
match/includes/exact score a non-match INCORRECT; pattern/answer score NOANSWER when the pattern does not match at all. Note both map to 0.0 and cross-reference Scoring Policy for the wrong-answer vs no-answer distinction. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The model_graded_qa example returned INCORRECT when the grader's verdict could not be parsed, charging an instrument failure to the model. Return Score.unscored() instead, and link Scoring Policy for when to score, raise, or unscore. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pairs the run-level error options with the scorer authoring decision. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…siblings Lead with what scorers do and why before the prescriptive "if you're writing a scorer" guidance, and cross-reference custom-scorers, model-graded, and metrics rather than restating them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5 tasks
MattFisher
marked this pull request as ready for review
July 17, 2026 11:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This adds a new Scoring Policy page to the Scoring section of the docs, covering a question that keeps coming up for eval authors: when something goes wrong on a single sample, should the scorer return a
Score,raise, or returnScore.unscored()? The three outcomes route to different framework machinery and affect the metric denominator differently, and until now the guidance for choosing between them lived in scattered PR threads rather than the docs.Alongside the new page:
custom-scorers.qmd: themodel_graded_qaexample's grade-parse-failure branch now returnsScore.unscored()instead ofINCORRECT, matching what the real scorer has done since fix(scorer): mark model_graded grade-parse failure as unscored (not INCORRECT) #4048 (shipped in 0.3.246). The old example was teaching the pattern that fix(scorer): mark model_graded grade-parse failure as unscored (not INCORRECT) #4048 removed._builtin-scorers.md: documents what the text-matching scorers return when the output doesn't match:match()/includes()/exact()scoreINCORRECT, whilepattern()/answer()scoreNOANSWERwhen the pattern doesn't match at all (see the question below).model-graded.qmd,handling-errors.qmd, andmetrics.qmdanchors so the run-level options and the authoring decision point at each other.How this came about
Over the past couple of months a cluster of inspect_evals PRs all hit the same ambiguity from different angles: contributors converting scorer
raises intoScore(0.0)to stop errors from eating samples (inspect_evals#1681, #1679, #1671, #1845), and model-graded scorers silently charging grader parse failures to the model under test (#4026, fixed by #4048). Each individual change was well-intentioned, but collectively they were making evals behave inconsistently: scoring an infrastructure failure0.0deflates accuracy and silently opts the sample out ofretry_on_error, while raising on a genuine model failure inflates accuracy by shrinking the denominator.We wrote the contributor-facing version of this guidance for inspect_evals in inspect_evals#1931, but the underlying semantics are Inspect's, not inspect_evals', so the canonical explanation belongs here. The new page is deliberately concept-first (what scoring measures and why the distinction exists) before it gets prescriptive.
Questions for maintainers
I'd genuinely value your opinion on three things before this merges, since the page currently takes positions on all of them.
1. The "only non-deterministic instruments get
unscored" bright line. The page draws the raise-vs-unscored line as: deterministic machinery (sandbox exec, file I/O, your own runner, the grader API call) either works or is broken, so its failures should alwaysraise;Score.unscored()is reserved for instruments that are inherently stochastic (a grader model emitting an unparseable verdict after bounded retries, a content filter on a fixed prompt). The rationale is that unscoring a deterministic failure hides a bug, whereas raising keeps it retryable and visible againstfail_on_error. This line has worked well in practice across the inspect_evals cluster, but it's a policy statement in framework docs, so it should have your endorsement (or amendment) rather than just mine.2.
pattern()returningNOANSWERon no-match, and whether to revisit #3628. In April, #3628 changedpattern()(and by extensionanswer()) to returnNOANSWERinstead ofINCORRECTwhen the regex doesn't match. The new docs describe this behaviour neutrally, but it's worth deciding deliberately, because a failed extraction conflates two cases: the model produced no answer (whereNOANSWERfits), and the model produced an answer in the wrong format (an instruction-following failure, which reads asINCORRECT, and is the common case in practice). It's also now inconsistent withmatch()/includes()/exact(), which score the equivalent missINCORRECT. Options as I see them:pattern()toINCORRECT(my lean, argued in more detail on #4091, withmetadata["reason"]preserving the "didn't parse" signal for anyone who wants to partition on it);NOANSWERand accept the inconsistency;NOANSWER's labelling value is already undermined by the defaultmeanepoch reducer collapsing it to0.0before metrics run, and a new sentinel would inherit the same problem.Whichever way this lands, the docs change is one bullet in
_builtin-scorers.md.3. Upstreaming
ScoreReason. The docs recommend tagging every abnormal score with a machine-readablemetadata["reason"](e.g.invalid_response_format,refusal,grader_parse_failure,grader_refusal,grader_no_tool_call) so failures are filterable in the viewer and in dataframes, with agrader_prefix separating instrument failures from model failures. inspect_evals is already standardising on this: inspect_evals#1933 introduces aScoreReasonLiteral plus constants ininspect_evals/utils, and #4048 already ships"grade_parse_failure"(under the keyunscored_reason, which we'd align). I think the type wants to live ininspect_ai.scorer, analogous toStopReason(proposed shape in this #4091 comment). I can send that as a small follow-up PR if you're amenable; the docs deliberately describe it as a convention rather than API until then.Related in-flight work
For the fuller picture, the pieces of this effort currently open:
score_on_errorscores actually contribute to metrics, per the existing docs (fixes #4412). Note the worked example on the new page describesscore_on_error's documented behaviour, so this page and Include errored-but-scored samples in metrics for score_on_error #4415 should land in the same release.Scorecrashes the eval at sample-logging time (found while stress-testing the NaN handling this page documents).execpre-truncates over-limit output rather than raisingOutputLimitExceededError.math()/NOANSWERdiscussion where theScoreReasonshape is being worked out.Checklist
_quarto.yml(Scoring section) and thescoring.qmdlanding table.quarto render(1.9.38 via thedocextra).🤖 Generated with Claude Code