feat: add could-not-evaluate status so non-gradable cases are excluded from scores - #357
Open
pdebjyot wants to merge 1 commit into
Open
feat: add could-not-evaluate status so non-gradable cases are excluded from scores#357pdebjyot wants to merge 1 commit into
pdebjyot wants to merge 1 commit into
Conversation
…d from scores
Adds an EvaluationOutput.status field ("graded" | "could_not_evaluate" |
"informational", default "graded") so an evaluator can signal that it could
not produce a real verdict for a case. Non-graded outputs are excluded from
every score/pass aggregate instead of being recorded as a score-0 quality
failure.
- types/evaluation.py: add GRADED/COULD_NOT_EVALUATE/INFORMATIONAL constants
and the status field (plain str + Literal, default graded for back-compat).
- evaluators/evaluator.py: _default_aggregator averages graded outputs only;
all-skipped returns a non-failure sentinel.
- experiment.py: roll per-output statuses up per evaluator, tag the two
error-isolation paths (RetryError, generic Exception) could_not_evaluate,
and compute overall_score from graded rows only.
- types/evaluation_report.py: flatten() excludes non-graded case rows from
overall_score; rows without a status default to graded.
Raw per-case scores/test_passes lists are left untouched; only aggregates
change. Adds tests at the aggregator, report, and experiment layers.
Closes strands-agents#346
pdebjyot
requested a deployment
to
manual-approval
August 10, 2026 13:56 — with
GitHub Actions
Waiting
pdebjyot
requested a deployment
to
manual-approval
August 10, 2026 13:57 — with
GitHub Actions
Waiting
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.
What
Adds an
EvaluationOutput.statusfield so an evaluator can signal that it could not produce a real verdict for a case, and excludes those non-gradable outputs from every score/pass aggregate. Implements the design in #346.statusis one of:"graded"(default) —score/test_passare real verdicts and count toward aggregates."could_not_evaluate"— the evaluator tried but couldn't grade the case (preconditions not met, missing data, harness error). Excluded from aggregates."informational"— surfaces content for human review; never counts toward a numeric aggregate.The default is
"graded", so existing evaluators, consumers, and previously-saved reports are unaffected.Why
Today, when an evaluator fails — a judge context-window overflow, missing data, unmet preconditions — the case is recorded as
score=0, test_pass=Falseand that0is thrown intooverall_score. A correct agent gets silently punished because the judge couldn't score the case, not because the agent was wrong. A capability failure of the harness is reported as a quality failure of the agent, with no way to tell the two apart.How
types/evaluation.py— addGRADED/COULD_NOT_EVALUATE/INFORMATIONALconstants (plainstr, not anEnum, so new values can be added without a breaking change) and thestatusfield onEvaluationOutput(Literal[...], defaultgraded).evaluators/evaluator.py—_default_aggregatoraverages graded outputs only. When every output is non-graded there is nothing to score, so it returns a non-failure sentinel with the evaluator's own reason string.experiment.py— roll per-output statuses up to an evaluator-level status; tag both error-isolation paths (RetryErrorand genericException) ascould_not_evaluate; computeoverall_scorefrom graded rows only.types/evaluation_report.py—flatten()excludes non-graded case rows fromoverall_score; rows with nostatuskey default togradedso legacy reports load identically.Raw per-case
scores/test_passeslists are left untouched — only the aggregate changes, so anything reading individual rows is unaffected.Behavior change (illustration)
A correct agent, one evaluator grading it
1.0, and one evaluator that raises (simulating a judge context-window overflow):gradedcould_not_evaluateoverall_score = 0.500(naive average punishes the correct agent).overall_score = 1.000(reflects only what was gradable).Tests
18 tests across the three layers:
_default_aggregator): graded-only average,could_not_evaluate/informationalexclusion, all-skipped non-failure, empty-list unchanged.flatten()): non-graded row excluded, missing-status defaults to graded, all-non-graded → 0.EvaluationOutput.statusdefault + settable.could_not_evaluate; mixed-status run excludes the non-graded rows fromoverall_score.Full suite: 1753 passed (baseline + net-new tests, no regressions).
ruff check/ruff format --checkclean.Closes #346