From b987c79e4c942443cd45cb42bb903ed9334abe56 Mon Sep 17 00:00:00 2001 From: Gaurav Gandhi Date: Tue, 11 Aug 2026 17:44:15 +0530 Subject: [PATCH] fix(evaluation): resolve threshold via criterion in LlmAsJudge, not the deprecated EvalMetric.threshold LlmAsJudge.evaluate_invocations read self._eval_metric.threshold directly, the deprecated field (defaults to None). Configuring the metric only via criterion -- the documented, non-deprecated path -- left this field None and crashed with TypeError: '>=' not supported between instances of 'float' and 'NoneType'. _get_metric_threshold() is the established pattern for this exact resolution, already used by 8 other evaluators since 25101412. This call site was missed. Routes LlmAsJudge through the same helper. --- src/google/adk/evaluation/llm_as_judge.py | 4 +- .../unittests/evaluation/test_llm_as_judge.py | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/google/adk/evaluation/llm_as_judge.py b/src/google/adk/evaluation/llm_as_judge.py index 6b7a7aa9ae7..4c302915009 100644 --- a/src/google/adk/evaluation/llm_as_judge.py +++ b/src/google/adk/evaluation/llm_as_judge.py @@ -34,6 +34,7 @@ from .common import EvalBaseModel from .eval_case import ConversationScenario from .eval_case import Invocation +from .eval_metrics import _get_metric_threshold from .eval_metrics import EvalMetric from .eval_metrics import LlmAsAJudgeCriterion from .eval_metrics import RubricsBasedCriterion @@ -81,6 +82,7 @@ def __init__( expected_invocations_required: bool = False, ): self._eval_metric = eval_metric + self._threshold = _get_metric_threshold(eval_metric) self._expected_invocations_required = expected_invocations_required expected_criterion_type_error = ValueError( @@ -183,7 +185,7 @@ async def evaluate_invocations( expected_invocation=expected, score=auto_rater_score.score, eval_status=get_eval_status( - auto_rater_score.score, self._eval_metric.threshold + auto_rater_score.score, self._threshold ), rubric_scores=auto_rater_score.rubric_scores, ) diff --git a/tests/unittests/evaluation/test_llm_as_judge.py b/tests/unittests/evaluation/test_llm_as_judge.py index 6dfa81fee8d..26d45d583cc 100644 --- a/tests/unittests/evaluation/test_llm_as_judge.py +++ b/tests/unittests/evaluation/test_llm_as_judge.py @@ -83,6 +83,28 @@ def mock_llm_as_judge(): ) +@pytest.fixture +def mock_llm_as_judge_criterion_only(): + """EvalMetric configured via `criterion` only, the documented, non-deprecated + way to set a threshold (`EvalMetric.threshold` is deprecated in favor of + `EvalMetric.criterion.threshold`). + """ + return MockLlmAsJudge( + eval_metric=EvalMetric( + metric_name="test_metric", + criterion=LlmAsAJudgeCriterion( + threshold=0.5, + judge_model_options=JudgeModelOptions( + judge_model="gemini-2.5-flash", + judge_model_config=genai_types.GenerateContentConfig(), + num_samples=3, + ), + ), + ), + criterion_type=LlmAsAJudgeCriterion, + ) + + def test_get_text_from_content(): content = genai_types.Content( parts=[ @@ -237,3 +259,36 @@ async def test_evaluate_invocations_with_mock( assert mock_llm_as_judge.format_auto_rater_prompt.call_count == 2 assert mock_llm_as_judge.convert_auto_rater_response_to_score.call_count == 6 assert mock_llm_as_judge.aggregate_invocation_results.call_count == 1 + + +@pytest.mark.asyncio +async def test_evaluate_invocations_with_criterion_only_threshold( + mock_llm_as_judge_criterion_only, mock_judge_model +): + """`EvalMetric.threshold` is deprecated in favor of `criterion.threshold`. + + Configuring the metric via `criterion` only (the documented, non-deprecated + path) must not crash with + `TypeError: '>=' not supported between instances of 'float' and 'NoneType'`. + """ + mock_llm_as_judge_criterion_only._judge_model = mock_judge_model + + actual_invocations = [ + Invocation( + invocation_id="id1", + user_content=genai_types.Content( + parts=[genai_types.Part(text="user content 1")], + role="user", + ), + final_response=genai_types.Content( + parts=[genai_types.Part(text="final response 1")], + role="model", + ), + ), + ] + + result = await mock_llm_as_judge_criterion_only.evaluate_invocations( + actual_invocations + ) + + assert result.overall_score == 1.0