Skip to content

fix(evaluation): NOT_EVALUATED metric no longer masked by a passing one - #6682

Open
gaurav-gandhi-2411 wants to merge 1 commit into
google:mainfrom
gaurav-gandhi-2411:fix/local-eval-service-not-evaluated-verdict
Open

fix(evaluation): NOT_EVALUATED metric no longer masked by a passing one#6682
gaurav-gandhi-2411 wants to merge 1 commit into
google:mainfrom
gaurav-gandhi-2411:fix/local-eval-service-not-evaluated-verdict

Conversation

@gaurav-gandhi-2411

Copy link
Copy Markdown

🔴 Required Information

Describe the Bug:
LocalEvalService._generate_final_eval_status computes an eval case's overall status from its per-metric results. It sets PASSED whenever a PASSED result is seen, and uses a bare continue on NOT_EVALUATED that leaves an already-set PASSED untouched. _evaluate_metric_for_eval_case catches any exception during a single metric's evaluation (a judge-model API failure, a rate limit, etc.) and records that metric as NOT_EVALUATED rather than letting the exception propagate — by design, so one metric's failure doesn't take down the others. But nothing downstream then treats that NOT_EVALUATED as reducing confidence in the final verdict.

Steps to Reproduce:
Run an eval case with two requested metrics where one crashes (→ NOT_EVALUATED) and the other passes.

Expected Behavior: An eval case where a requested metric never produced a verdict should not be reported as a clean PASSED.

Observed Behavior: [NOT_EVALUATED, PASSED] and [PASSED, NOT_EVALUATED] (same set of outcomes, opposite order) both currently return PASSED — the crashed metric is silently absorbed. FAILED was already handled correctly in both orderings (it breaks the loop immediately), which is what pins this down as specifically a PASSED-vs-NOT_EVALUATED bug rather than intended behavior: there's no principled reason two orderings of the same outcomes should disagree.

Why this fix

Track whether any metric was NOT_EVALUATED; if so, and the loop would otherwise have concluded PASSED, report NOT_EVALUATED instead. FAILED still takes precedence over NOT_EVALUATED in all orderings, since a genuine failure is real evidence, not a missing verdict. This uses only the three existing EvalStatus values (PASSED / FAILED / NOT_EVALUATED) — no new status is introduced.

Testing Plan

Added two tests exercising both orderings of [NOT_EVALUATED, PASSED], named to make the order-dependence explicit, plus a same-shape test confirming FAILED still dominates NOT_EVALUATED regardless of order. Confirmed the two new PASSED/NOT_EVALUATED-ordering tests fail on main with the exact bug described above, and pass after the fix.

tests/unittests/evaluation/test_local_eval_service.py -k generate_final_eval_status: 4 passed
tests/unittests/evaluation/: 786 passed

_generate_final_eval_status looped over an eval case's per-metric
results and set the final status to PASSED whenever a PASSED result was
seen, using a bare `continue` on NOT_EVALUATED that left an
already-set PASSED untouched. A metric that crashed mid-evaluation
(caught in _evaluate_metric_for_eval_case and recorded as
NOT_EVALUATED) therefore had no effect on the final verdict as long as
some other metric in the same eval case passed: [NOT_EVALUATED, PASSED]
and [PASSED, NOT_EVALUATED] both reported PASSED, silently dropping the
fact that one of the requested metrics never actually produced a
verdict.

This is order-dependent in a way that has no principled justification --
the two orderings represent the same set of per-metric outcomes and
must produce the same final status. FAILED already dominated
NOT_EVALUATED correctly in both orderings (it breaks the loop
immediately), which is what confirms this was specifically a
PASSED-vs-NOT_EVALUATED bug rather than intended behavior.

Fix uses the existing EvalStatus values only: if any metric was
NOT_EVALUATED and no metric FAILED, the final status is NOT_EVALUATED
rather than PASSED. A genuine FAILED still takes precedence over
NOT_EVALUATED, since it's real evidence rather than a missing verdict.

Added test_generate_final_eval_status_not_evaluated_then_passed_is_not_evaluated
and the reverse-order counterpart to make the order-dependence visible,
plus a same-precedence test confirming FAILED still wins over
NOT_EVALUATED regardless of order. All four generate_final_eval_status
tests, including the pre-existing doesn_t_throw_on one, and the full
evaluation/ suite (786 tests) pass after the fix.
@adk-bot adk-bot added the eval [Component] This issue is related to evaluation label Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

eval [Component] This issue is related to evaluation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants