Skip to content

Commit e689f0e

Browse files
jsondaicopybara-github
authored andcommitted
feat: GenAI Client(evals) - async auto-create EvaluationExperiment parity
PiperOrigin-RevId: 955977255
1 parent f343270 commit e689f0e

3 files changed

Lines changed: 145 additions & 23 deletions

File tree

agentplatform/_genai/evals.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5504,6 +5504,7 @@ async def create_evaluation_run(
55045504
metrics: list[types.EvaluationRunMetricOrDict],
55055505
name: Optional[str] = None,
55065506
display_name: Optional[str] = None,
5507+
evaluation_experiment: Optional[str] = None,
55075508
agent_info: Optional[evals_types.AgentInfo] = None,
55085509
agent: Optional[str] = None,
55095510
user_simulator_config: Optional[evals_types.UserSimulatorConfigOrDict] = None,
@@ -5524,6 +5525,11 @@ async def create_evaluation_run(
55245525
metrics: The list of metrics to evaluate.
55255526
name: The name of the evaluation run.
55265527
display_name: The display name of the evaluation run.
5528+
evaluation_experiment: The resource name of an existing
5529+
EvaluationExperiment to group this run under. If omitted, a new
5530+
EvaluationExperiment is created automatically so the run is visible in
5531+
the Agent Platform UI. Pass an existing experiment name to group
5532+
multiple runs together.
55275533
agent_info: The agent info to evaluate. Mutually exclusive with
55285534
`inference_configs`.
55295535
agent: The agent resource name in str type. Accepts either an Agent
@@ -5682,11 +5688,23 @@ async def create_evaluation_run(
56825688
self._api_client, resolved_dataset, inference_configs, parsed_agent_info
56835689
)
56845690
resolved_labels = _evals_common._add_evaluation_run_labels(labels, agent)
5685-
resolved_name = name or f"evaluation_run_{uuid.uuid4()}"
5691+
resolved_display_name = display_name or name or f"evaluation_run_{uuid.uuid4()}"
5692+
resolved_experiment = evaluation_experiment
5693+
if resolved_experiment is None:
5694+
experiment_display_name = (
5695+
display_name or f"SDK Experiment {_evals_common._local_timestamp()}"
5696+
)
5697+
created_experiment = await self.create_evaluation_experiment(
5698+
display_name=experiment_display_name
5699+
)
5700+
resolved_experiment = created_experiment.name
56865701

5702+
# The backend rejects a caller-supplied `name` for runs that belong to an
5703+
# EvaluationExperiment and assigns the resource ID itself, so `name` is
5704+
# only used as a display_name fallback.
56875705
result = await self._create_evaluation_run(
5688-
name=resolved_name,
5689-
display_name=display_name or resolved_name,
5706+
display_name=resolved_display_name,
5707+
evaluation_experiment=resolved_experiment,
56905708
data_source=resolved_dataset,
56915709
evaluation_config=evaluation_config,
56925710
inference_configs=resolved_inference_configs,

tests/unit/agentplatform/genai/test_evals.py

Lines changed: 108 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10635,25 +10635,33 @@ async def test_create_evaluation_run_async_passes_allow_cross_region_model(self)
1063510635
return_value=self.mock_response
1063610636
)
1063710637
async_evals_module = evals.AsyncEvals(api_client_=self.mock_api_client)
10638-
10639-
await async_evals_module.create_evaluation_run(
10640-
dataset=agentplatform_genai_types.EvaluationRunDataSource(
10641-
evaluation_set="projects/123/locations/us-central1/evaluationSets/789"
10642-
),
10643-
metrics=[
10644-
agentplatform_genai_types.EvaluationRunMetric(
10645-
metric="general_quality_v1",
10646-
metric_config=agentplatform_genai_types.UnifiedMetric(
10647-
predefined_metric_spec=genai_types.PredefinedMetricSpec(
10648-
metric_spec_name="general_quality_v1",
10649-
)
10650-
),
10651-
)
10652-
],
10653-
dest="gs://test-bucket/output",
10654-
config={"allow_cross_region_model": True},
10638+
experiment = agentplatform_genai_types.EvaluationExperiment(
10639+
name="projects/123/locations/us-central1/evaluationExperiments/e1"
1065510640
)
1065610641

10642+
with mock.patch.object(
10643+
async_evals_module,
10644+
"create_evaluation_experiment",
10645+
new=mock.AsyncMock(return_value=experiment),
10646+
):
10647+
await async_evals_module.create_evaluation_run(
10648+
dataset=agentplatform_genai_types.EvaluationRunDataSource(
10649+
evaluation_set="projects/123/locations/us-central1/evaluationSets/789"
10650+
),
10651+
metrics=[
10652+
agentplatform_genai_types.EvaluationRunMetric(
10653+
metric="general_quality_v1",
10654+
metric_config=agentplatform_genai_types.UnifiedMetric(
10655+
predefined_metric_spec=genai_types.PredefinedMetricSpec(
10656+
metric_spec_name="general_quality_v1",
10657+
)
10658+
),
10659+
)
10660+
],
10661+
dest="gs://test-bucket/output",
10662+
config={"allow_cross_region_model": True},
10663+
)
10664+
1065710665
self.mock_api_client.async_request.assert_called_once()
1065810666
call_args = self.mock_api_client.async_request.call_args
1065910667
request_body = call_args[0][2] # Third positional arg is the request dict
@@ -12414,3 +12422,86 @@ def test_uses_provided_experiment_without_creating(self):
1241412422
request_body.get("evaluationExperiment")
1241512423
== "projects/123/locations/us-central1/evaluationExperiments/existing"
1241612424
)
12425+
12426+
12427+
class TestAsyncCreateEvaluationRunAutoExperiment:
12428+
12429+
def setup_method(self, method):
12430+
self.mock_api_client = mock.MagicMock()
12431+
self.mock_api_client.vertexai = True
12432+
self.mock_response = mock.MagicMock()
12433+
self.mock_response.body = json.dumps(
12434+
{
12435+
"name": "projects/123/locations/us-central1/evaluationRuns/456",
12436+
"displayName": "test_run",
12437+
"state": "PENDING",
12438+
"evaluationExperiment": (
12439+
"projects/123/locations/us-central1/evaluationExperiments/e1"
12440+
),
12441+
}
12442+
)
12443+
self.mock_api_client.async_request = mock.AsyncMock(
12444+
return_value=self.mock_response
12445+
)
12446+
self.dataset = agentplatform_genai_types.EvaluationRunDataSource(
12447+
evaluation_set="projects/123/locations/us-central1/evaluationSets/789"
12448+
)
12449+
self.metrics = [
12450+
agentplatform_genai_types.EvaluationRunMetric(
12451+
metric="general_quality_v1",
12452+
metric_config=agentplatform_genai_types.UnifiedMetric(
12453+
predefined_metric_spec=genai_types.PredefinedMetricSpec(
12454+
metric_spec_name="general_quality_v1",
12455+
)
12456+
),
12457+
)
12458+
]
12459+
12460+
@pytest.mark.asyncio
12461+
async def test_async_auto_creates_experiment_when_not_provided(self):
12462+
async_evals_module = evals.AsyncEvals(api_client_=self.mock_api_client)
12463+
experiment = agentplatform_genai_types.EvaluationExperiment(
12464+
name="projects/123/locations/us-central1/evaluationExperiments/e1"
12465+
)
12466+
with mock.patch.object(
12467+
async_evals_module,
12468+
"create_evaluation_experiment",
12469+
new=mock.AsyncMock(return_value=experiment),
12470+
) as mock_create_exp:
12471+
await async_evals_module.create_evaluation_run(
12472+
dataset=self.dataset,
12473+
metrics=self.metrics,
12474+
dest="gs://test-bucket/output",
12475+
display_name="my_run",
12476+
)
12477+
12478+
mock_create_exp.assert_awaited_once()
12479+
request_body = self.mock_api_client.async_request.call_args[0][2]
12480+
assert (
12481+
request_body.get("evaluationExperiment")
12482+
== "projects/123/locations/us-central1/evaluationExperiments/e1"
12483+
)
12484+
12485+
@pytest.mark.asyncio
12486+
async def test_async_uses_provided_experiment_without_creating(self):
12487+
async_evals_module = evals.AsyncEvals(api_client_=self.mock_api_client)
12488+
with mock.patch.object(
12489+
async_evals_module,
12490+
"create_evaluation_experiment",
12491+
new=mock.AsyncMock(),
12492+
) as mock_create_exp:
12493+
await async_evals_module.create_evaluation_run(
12494+
dataset=self.dataset,
12495+
metrics=self.metrics,
12496+
dest="gs://test-bucket/output",
12497+
evaluation_experiment=(
12498+
"projects/123/locations/us-central1/evaluationExperiments/existing"
12499+
),
12500+
)
12501+
12502+
mock_create_exp.assert_not_awaited()
12503+
request_body = self.mock_api_client.async_request.call_args[0][2]
12504+
assert (
12505+
request_body.get("evaluationExperiment")
12506+
== "projects/123/locations/us-central1/evaluationExperiments/existing"
12507+
)

vertexai/_genai/evals.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4949,6 +4949,7 @@ async def create_evaluation_run(
49494949
metrics: list[types.EvaluationRunMetricOrDict],
49504950
name: Optional[str] = None,
49514951
display_name: Optional[str] = None,
4952+
evaluation_experiment: Optional[str] = None,
49524953
agent_info: Optional[evals_types.AgentInfo] = None,
49534954
agent: Optional[str] = None,
49544955
user_simulator_config: Optional[evals_types.UserSimulatorConfigOrDict] = None,
@@ -5095,11 +5096,23 @@ async def create_evaluation_run(
50955096
self._api_client, resolved_dataset, inference_configs, parsed_agent_info
50965097
)
50975098
resolved_labels = _evals_common._add_evaluation_run_labels(labels, agent)
5098-
resolved_name = name or f"evaluation_run_{uuid.uuid4()}"
5099+
resolved_display_name = display_name or name or f"evaluation_run_{uuid.uuid4()}"
5100+
resolved_experiment = evaluation_experiment
5101+
if resolved_experiment is None:
5102+
experiment_display_name = (
5103+
display_name or f"SDK Experiment {_evals_common._local_timestamp()}"
5104+
)
5105+
created_experiment = await self.create_evaluation_experiment(
5106+
display_name=experiment_display_name
5107+
)
5108+
resolved_experiment = created_experiment.name
50995109

5110+
# The backend rejects a caller-supplied `name` for runs that belong to an
5111+
# EvaluationExperiment and assigns the resource ID itself, so `name` is
5112+
# only used as a display_name fallback.
51005113
result = await self._create_evaluation_run(
5101-
name=resolved_name,
5102-
display_name=display_name or resolved_name,
5114+
display_name=resolved_display_name,
5115+
evaluation_experiment=resolved_experiment,
51035116
data_source=resolved_dataset,
51045117
evaluation_config=evaluation_config,
51055118
inference_configs=resolved_inference_configs,

0 commit comments

Comments
 (0)