Skip to content

Commit 9dbc2ae

Browse files
chelsealongcopybara-github
authored andcommitted
fix: raise when evaluate or evaluate_eval_set evaluates zero cases
`AgentEvaluator.evaluate` and `AgentEvaluator.evaluate_eval_set` now raise `ValueError` instead of silently passing when no test cases are evaluated (e.g. empty eval set or no `*.test.json` files found in directory). Both methods also validate that `num_runs` is at least 1 up front. Merge #6952 Fixes #6951 PiperOrigin-RevId: 982715587
1 parent a4a50a4 commit 9dbc2ae

2 files changed

Lines changed: 83 additions & 1 deletion

File tree

src/google/adk/evaluation/agent_evaluator.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,21 @@ async def evaluate_eval_set(
168168
eval_set_results_manager: Optional manager used to persist the eval set
169169
evaluation result as `*.evalset_result.json`.
170170
"""
171+
if num_runs < 1:
172+
raise ValueError(f"`num_runs` must be at least 1, got {num_runs}.")
173+
171174
if eval_set_results_manager is not None and not app_name:
172175
raise ValueError(
173176
"app_name is required when eval_set_results_manager is provided."
174177
)
175178

179+
if not eval_set or not eval_set.eval_cases:
180+
raise ValueError(
181+
"No eval cases were evaluated, so there is nothing to report a"
182+
" pass or a failure for. This happens when `eval_set` has no eval"
183+
" cases."
184+
)
185+
176186
if criteria:
177187
logger.warning(
178188
"`criteria` field is deprecated and will be removed in future"
@@ -331,6 +341,9 @@ async def evaluate(
331341
eval_set_results_manager: Optional manager used to persist the eval set
332342
evaluation result as `*.evalset_result.json`.
333343
"""
344+
if num_runs < 1:
345+
raise ValueError(f"`num_runs` must be at least 1, got {num_runs}.")
346+
334347
if eval_set_results_manager is not None and not app_name:
335348
raise ValueError(
336349
"app_name is required when eval_set_results_manager is provided."
@@ -347,6 +360,12 @@ async def evaluate(
347360
else:
348361
test_files = [eval_dataset_file_path_or_dir]
349362

363+
if not test_files:
364+
raise ValueError(
365+
"No `*.test.json` eval files found in"
366+
f" {eval_dataset_file_path_or_dir}, so there is nothing to evaluate."
367+
)
368+
350369
initial_session = AgentEvaluator._get_initial_session(initial_session_file)
351370

352371
for test_file in test_files:

tests/unittests/evaluation/test_agent_evaluator.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ async def _empty(*args, **kwargs):
136136

137137
await AgentEvaluator.evaluate_eval_set(
138138
agent_module="my.agent.module",
139-
eval_set=EvalSet(eval_set_id="es1", eval_cases=[]),
139+
eval_set=_make_eval_set(),
140140
eval_config=EvalConfig(),
141141
num_runs=1,
142142
artifact_service=my_service,
@@ -320,6 +320,42 @@ async def test_evaluate_eval_set_passes_when_metrics_pass(mocker):
320320
await _mock_evaluate_eval_set(mocker, passing_result)
321321

322322

323+
@pytest.mark.asyncio
324+
async def test_evaluate_eval_set_raises_when_no_eval_cases_were_evaluated(
325+
mocker,
326+
):
327+
"""An empty `eval_set` must fail early before evaluating."""
328+
mock_get_agent = mocker.patch.object(AgentEvaluator, "_get_agent_for_eval")
329+
mock_get_results = mocker.patch.object(
330+
AgentEvaluator, "_get_eval_results_by_eval_id"
331+
)
332+
333+
with pytest.raises(ValueError, match="No eval cases were evaluated"):
334+
await AgentEvaluator.evaluate_eval_set(
335+
agent_module="my.agent.module",
336+
eval_set=EvalSet(eval_set_id="es1", eval_cases=[]),
337+
eval_config=EvalConfig(),
338+
num_runs=1,
339+
)
340+
341+
mock_get_agent.assert_not_called()
342+
mock_get_results.assert_not_called()
343+
344+
345+
@pytest.mark.asyncio
346+
@pytest.mark.parametrize("num_runs", [0, -1])
347+
async def test_evaluate_eval_set_raises_when_num_runs_less_than_one(num_runs):
348+
with pytest.raises(
349+
ValueError, match=f"`num_runs` must be at least 1, got {num_runs}."
350+
):
351+
await AgentEvaluator.evaluate_eval_set(
352+
agent_module="my.agent.module",
353+
eval_set=_make_eval_set(),
354+
eval_config=EvalConfig(),
355+
num_runs=num_runs,
356+
)
357+
358+
323359
class TestGetAgentForEval:
324360
"""Resolution of the wrapping App alongside the agent to evaluate."""
325361

@@ -1006,6 +1042,33 @@ async def test_evaluate_requires_app_name_when_manager_given(mocker):
10061042
)
10071043

10081044

1045+
@pytest.mark.asyncio
1046+
async def test_evaluate_raises_when_no_test_files_found(tmp_path):
1047+
empty_dir = tmp_path / "empty_evals"
1048+
empty_dir.mkdir()
1049+
with pytest.raises(
1050+
ValueError,
1051+
match=r"No `\*\.test\.json` eval files found in",
1052+
):
1053+
await AgentEvaluator.evaluate(
1054+
agent_module="pkg.search_agent",
1055+
eval_dataset_file_path_or_dir=str(empty_dir),
1056+
)
1057+
1058+
1059+
@pytest.mark.asyncio
1060+
@pytest.mark.parametrize("num_runs", [0, -1])
1061+
async def test_evaluate_raises_when_num_runs_less_than_one(num_runs):
1062+
with pytest.raises(
1063+
ValueError, match=f"`num_runs` must be at least 1, got {num_runs}."
1064+
):
1065+
await AgentEvaluator.evaluate(
1066+
agent_module="pkg.search_agent",
1067+
eval_dataset_file_path_or_dir="some.test.json",
1068+
num_runs=num_runs,
1069+
)
1070+
1071+
10091072
@pytest.mark.asyncio
10101073
async def test_evaluate_passes_results_manager_and_app_name(mocker, tmp_path):
10111074
test_dir = tmp_path / "evals"

0 commit comments

Comments
 (0)