Skip to content

Commit 701b8d4

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: Adding VAPO Prompt Optimizer (PO-data) to the genai SDK.
PiperOrigin-RevId: 770884761
1 parent fe474ae commit 701b8d4

5 files changed

Lines changed: 463 additions & 7 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# pylint: disable=protected-access,bad-continuation
16+
import copy
17+
import importlib
18+
from unittest import mock
19+
20+
from google.cloud import aiplatform
21+
import vertexai
22+
from google.cloud.aiplatform import initializer as aiplatform_initializer
23+
from google.cloud.aiplatform.compat.services import job_service_client
24+
from google.cloud.aiplatform.compat.types import (
25+
custom_job as gca_custom_job_compat,
26+
)
27+
from google.cloud.aiplatform.compat.types import io as gca_io_compat
28+
from google.cloud.aiplatform.compat.types import (
29+
job_state as gca_job_state_compat,
30+
)
31+
from google.cloud.aiplatform.utils import gcs_utils
32+
from google.genai import client
33+
import pytest
34+
35+
36+
_TEST_PROJECT = "test-project"
37+
_TEST_LOCATION = "us-central1"
38+
pytestmark = pytest.mark.usefixtures("google_auth_mock")
39+
_TEST_PROJECT_NUMBER = "12345678"
40+
_TEST_PARENT = f"projects/{_TEST_PROJECT}/locations/{_TEST_LOCATION}"
41+
_TEST_DISPLAY_NAME = f"{_TEST_PARENT}/customJobs/12345"
42+
_TEST_BASE_OUTPUT_DIR = "gs://test_bucket/test_base_output_dir"
43+
44+
_TEST_CUSTOM_JOB_PROTO = gca_custom_job_compat.CustomJob(
45+
display_name=_TEST_DISPLAY_NAME,
46+
job_spec={
47+
"base_output_directory": gca_io_compat.GcsDestination(
48+
output_uri_prefix=_TEST_BASE_OUTPUT_DIR
49+
),
50+
},
51+
labels={"trained_by_vertex_ai": "true"},
52+
)
53+
54+
55+
@pytest.fixture
56+
def mock_create_custom_job():
57+
with mock.patch.object(
58+
job_service_client.JobServiceClient, "create_custom_job"
59+
) as create_custom_job_mock:
60+
custom_job_proto = copy.deepcopy(_TEST_CUSTOM_JOB_PROTO)
61+
custom_job_proto.name = _TEST_DISPLAY_NAME
62+
custom_job_proto.state = gca_job_state_compat.JobState.JOB_STATE_PENDING
63+
create_custom_job_mock.return_value = custom_job_proto
64+
yield create_custom_job_mock
65+
66+
67+
class TestPromptOptimizer:
68+
"""Unit tests for the Prompt Optimizer client."""
69+
70+
def setup_method(self):
71+
importlib.reload(aiplatform_initializer)
72+
importlib.reload(aiplatform)
73+
importlib.reload(vertexai)
74+
vertexai.init(
75+
project=_TEST_PROJECT,
76+
location=_TEST_LOCATION,
77+
)
78+
79+
@pytest.mark.usefixtures("google_auth_mock")
80+
def test_prompt_optimizer_client(self):
81+
test_client = vertexai.Client(project=_TEST_PROJECT, location=_TEST_LOCATION)
82+
assert test_client is not None
83+
assert test_client._api_client.vertexai
84+
assert test_client._api_client.project == _TEST_PROJECT
85+
assert test_client._api_client.location == _TEST_LOCATION
86+
87+
@mock.patch.object(client.Client, "_get_api_client")
88+
@mock.patch.object(
89+
gcs_utils.resource_manager_utils, "get_project_number", return_value=12345
90+
)
91+
def test_prompt_optimizer_optimize(
92+
self, mock_get_project_number, mock_client, mock_create_custom_job
93+
):
94+
"""Test that prompt_optimizer.optimize method creates a custom job."""
95+
test_client = vertexai.Client(project=_TEST_PROJECT, location=_TEST_LOCATION)
96+
test_client.prompt_optimizer.optimize(
97+
method="vapo",
98+
config={
99+
"config_path": "gs://ssusie-vapo-sdk-test/config.json",
100+
"wait_for_completion": False,
101+
},
102+
)
103+
mock_create_custom_job.assert_called_once()
104+
mock_get_project_number.assert_called_once()

vertexai/_genai/client.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
#
1515

1616
import importlib
17-
1817
from typing import Optional, Union
1918

2019
import google.auth
@@ -24,6 +23,7 @@
2423

2524

2625
class AsyncClient:
26+
2727
"""Async Client for the GenAI SDK."""
2828

2929
def __init__(self, api_client: client.Client):
@@ -50,6 +50,8 @@ def evals(self):
5050
) from e
5151
return self._evals.AsyncEvals(self._api_client)
5252

53+
# TODO(b/424176979): add async prompt optimizer here.
54+
5355

5456
class Client:
5557
"""Client for the GenAI SDK.
@@ -101,6 +103,7 @@ def __init__(
101103
http_options=http_options,
102104
)
103105
self._evals = None
106+
self._prompt_optimizer = None
104107

105108
@property
106109
@_common.experimental_warning(
@@ -120,3 +123,14 @@ def evals(self):
120123
"google-cloud-aiplatform[evaluation]"
121124
) from e
122125
return self._evals.Evals(self._api_client)
126+
127+
@property
128+
@_common.experimental_warning(
129+
"The Vertex SDK GenAI prompt optimizer module is experimental, "
130+
"and may change in future versions."
131+
)
132+
def prompt_optimizer(self):
133+
self._prompt_optimizer = importlib.import_module(
134+
".prompt_optimizer", __package__
135+
)
136+
return self._prompt_optimizer.PromptOptimizer(self._api_client)

vertexai/_genai/evals.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
import logging
1919
from typing import Any, Callable, Optional, Union
2020
from urllib.parse import urlencode
21+
2122
from google.genai import _api_module
2223
from google.genai import _common
2324
from google.genai import types as genai_types
2425
from google.genai._api_client import BaseApiClient
2526
from google.genai._common import get_value_by_path as getv
2627
from google.genai._common import set_value_by_path as setv
2728
import pandas as pd
29+
2830
from . import _evals_common
2931
from . import types
3032

@@ -1238,9 +1240,11 @@ def evaluate(
12381240
config = types.EvaluateMethodConfig.model_validate(config)
12391241
if isinstance(dataset, list):
12401242
dataset = [
1241-
types.EvaluationDataset.model_validate(ds_item)
1242-
if isinstance(ds_item, dict)
1243-
else ds_item
1243+
(
1244+
types.EvaluationDataset.model_validate(ds_item)
1245+
if isinstance(ds_item, dict)
1246+
else ds_item
1247+
)
12441248
for ds_item in dataset
12451249
]
12461250
else:

0 commit comments

Comments
 (0)