Skip to content

Commit f99b105

Browse files
sararobcopybara-github
authored andcommitted
chore: update replay tests
PiperOrigin-RevId: 781173091
1 parent 795ee17 commit f99b105

6 files changed

Lines changed: 98 additions & 21 deletions

File tree

tests/unit/vertexai/genai/replays/conftest.py

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222
from vertexai._genai import (
2323
client as vertexai_genai_client_module,
2424
)
25+
from google.cloud import storage, bigquery
2526
from google.genai import _replay_api_client
2627
from google.genai import client as google_genai_client_module
28+
from vertexai._genai import _evals_utils
29+
from vertexai._genai import prompt_optimizer
2730
import pytest
2831

2932
IS_KOKORO = os.getenv("KOKORO_BUILD_NUMBER") is not None
@@ -82,11 +85,39 @@ def _get_replay_id(use_vertex: bool, replays_prefix: str) -> str:
8285
return "/".join([replays_prefix, test_name])
8386

8487

88+
EVAL_CONFIG_GCS_URI = (
89+
"gs://vertex-ai-generative-ai-eval-sdk-resources/metrics/text_quality/v1.0.0.yaml"
90+
)
91+
92+
93+
def _mock_read_file_contents_side_effect(uri: str):
94+
"""
95+
Side effect to mock GcsUtils.read_file_contents for eval test test_batch_evaluate.
96+
"""
97+
if uri == EVAL_CONFIG_GCS_URI:
98+
# Construct the absolute path to the local mock file.
99+
current_dir = os.path.dirname(__file__)
100+
local_yaml_path = os.path.join(
101+
current_dir, "test_resources/mock_eval_config.yaml"
102+
)
103+
try:
104+
with open(local_yaml_path, "r") as f:
105+
return f.read()
106+
except FileNotFoundError:
107+
raise FileNotFoundError(
108+
"The mock data file 'mock_eval_config.yaml' was not found."
109+
)
110+
111+
raise ValueError(
112+
f"Unexpected GCS URI '{uri}' in replay test. Only "
113+
f"'{EVAL_CONFIG_GCS_URI}' is mocked."
114+
)
115+
116+
85117
@pytest.fixture
86118
def client(use_vertex, replays_prefix, http_options, request):
87119

88120
mode = request.config.getoption("--mode")
89-
replays_directory_prefix = request.config.getoption("--replays-directory-prefix")
90121
if mode not in ["auto", "record", "replay", "api", "tap"]:
91122
raise ValueError("Invalid mode: " + mode)
92123
test_function_name = request.function.__name__
@@ -114,13 +145,14 @@ def client(use_vertex, replays_prefix, http_options, request):
114145
os.environ["GOOGLE_CLOUD_LOCATION"] = "location"
115146
os.environ["VAPO_CONFIG_PATH"] = "gs://dummy-test/dummy-config.json"
116147
os.environ["VAPO_SERVICE_ACCOUNT_PROJECT_NUMBER"] = "1234567890"
148+
os.environ["GCS_BUCKET"] = "test-bucket"
117149

118150
# Set the replay directory to the root directory of the replays.
119151
# This is needed to ensure that the replay files are found.
120152
replays_root_directory = os.path.abspath(
121153
os.path.join(
122154
os.path.dirname(__file__),
123-
"../../../../../../../../../google/cloud/aiplatform/sdk/genai/replays",
155+
"../../../../../../../../../../google/cloud/aiplatform/sdk/genai/replays",
124156
)
125157
)
126158
os.environ["GOOGLE_GENAI_REPLAYS_DIRECTORY"] = replays_root_directory
@@ -131,18 +163,46 @@ def client(use_vertex, replays_prefix, http_options, request):
131163
http_options=http_options,
132164
)
133165

134-
replay_client.replays_directory = (
135-
f"{replays_directory_prefix}/google/cloud/aiplatform/sdk/replays/"
136-
)
137-
138166
with mock.patch.object(
139167
google_genai_client_module.Client, "_get_api_client"
140168
) as patch_method:
141169
patch_method.return_value = replay_client
142170
google_genai_client = vertexai_genai_client_module.Client()
143171

144-
# Yield the client so that cleanup can be completed at the end of the test.
145-
yield google_genai_client
172+
if mode != "replay":
173+
yield google_genai_client
174+
else:
175+
# Eval tests make a call to GCS and BigQuery
176+
# Need to mock this so it doesn't call the service in replay mode
177+
with mock.patch.object(storage, "Client") as mock_storage_client:
178+
mock_client_instance = mock.MagicMock()
179+
180+
mock_blob = mock.MagicMock()
181+
182+
mock_blob.name = "v1.0.0.yaml"
183+
184+
mock_client_instance.list_blobs.return_value = [mock_blob]
185+
186+
mock_storage_client.return_value = mock_client_instance
187+
188+
with mock.patch.object(bigquery, "Client") as mock_bigquery_client:
189+
mock_bigquery_client.return_value = mock.MagicMock()
190+
191+
with mock.patch.object(
192+
_evals_utils.GcsUtils, "read_file_contents"
193+
) as mock_read_file_contents:
194+
mock_read_file_contents.side_effect = (
195+
_mock_read_file_contents_side_effect
196+
)
197+
198+
with mock.patch.object(
199+
prompt_optimizer.time, "sleep"
200+
) as mock_job_wait:
201+
mock_job_wait.return_value = None
202+
203+
google_genai_client = vertexai_genai_client_module.Client()
204+
205+
# Yield the client so that cleanup can be completed at the end of the test.
206+
yield google_genai_client
146207

147-
# Save the replay after the test if we're in recording mode.
148208
replay_client.close()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "service_account"
3+
}

tests/unit/vertexai/genai/replays/test_prompt_optimizer_optimize_job_state.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from vertexai._genai import types
2121

2222

23+
# If you re-record this test, you will need to update the replay file to
24+
# include the placeholder values for config path and service account
2325
def test_optimize(client):
2426
"""Tests the optimize request parameters method."""
2527

@@ -36,6 +38,7 @@ def test_optimize(client):
3638
service_account_project_number=os.environ.get(
3739
"VAPO_SERVICE_ACCOUNT_PROJECT_NUMBER"
3840
),
41+
optimizer_job_display_name="optimizer_job_test",
3942
)
4043
job = client.prompt_optimizer.optimize(
4144
method="vapo",

vertexai/_genai/evals.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -964,11 +964,9 @@ def evaluate(
964964
config = types.EvaluateMethodConfig.model_validate(config)
965965
if isinstance(dataset, list):
966966
dataset = [
967-
(
968-
types.EvaluationDataset.model_validate(ds_item)
969-
if isinstance(ds_item, dict)
970-
else ds_item
971-
)
967+
types.EvaluationDataset.model_validate(ds_item)
968+
if isinstance(ds_item, dict)
969+
else ds_item
972970
for ds_item in dataset
973971
]
974972
else:

vertexai/_genai/prompt_optimizer.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -599,8 +599,11 @@ def optimize(
599599
if isinstance(config, dict):
600600
config = types.PromptOptimizerVAPOConfig(**config)
601601

602-
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
603-
display_name = f"vapo-optimizer-{timestamp}"
602+
if config.optimizer_job_display_name:
603+
display_name = config.optimizer_job_display_name
604+
else:
605+
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
606+
display_name = f"vapo-optimizer-{timestamp}"
604607
wait_for_completion = config.wait_for_completion
605608
if not config.config_path:
606609
raise ValueError("Config path is required.")
@@ -857,8 +860,11 @@ async def optimize(
857860
if isinstance(config, dict):
858861
config = types.PromptOptimizerVAPOConfig(**config)
859862

860-
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
861-
display_name = f"vapo-optimizer-{timestamp}"
863+
if config.optimizer_job_display_name:
864+
display_name = config.optimizer_job_display_name
865+
else:
866+
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
867+
display_name = f"vapo-optimizer-{timestamp}"
862868

863869
if not config.config_path:
864870
raise ValueError("Config path is required.")

vertexai/_genai/types.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5223,6 +5223,10 @@ class PromptOptimizerVAPOConfig(_common.BaseModel):
52235223
default=True,
52245224
description="""Whether to wait for the job tocomplete. Ignored for async jobs.""",
52255225
)
5226+
optimizer_job_display_name: Optional[str] = Field(
5227+
default=None,
5228+
description="""The display name of the optimization job. If not provided, a display name in the format of "vapo-optimizer-{timestamp}" will be used.""",
5229+
)
52265230

52275231

52285232
class PromptOptimizerVAPOConfigDict(TypedDict, total=False):
@@ -5240,6 +5244,9 @@ class PromptOptimizerVAPOConfigDict(TypedDict, total=False):
52405244
wait_for_completion: Optional[bool]
52415245
"""Whether to wait for the job tocomplete. Ignored for async jobs."""
52425246

5247+
optimizer_job_display_name: Optional[str]
5248+
"""The display name of the optimization job. If not provided, a display name in the format of "vapo-optimizer-{timestamp}" will be used."""
5249+
52435250

52445251
PromptOptimizerVAPOConfigOrDict = Union[
52455252
PromptOptimizerVAPOConfig, PromptOptimizerVAPOConfigDict
@@ -5769,9 +5776,9 @@ def to_yaml_file(self, file_path: str, version: Optional[str] = None) -> None:
57695776
exclude_unset=True,
57705777
exclude_none=True,
57715778
mode="json",
5772-
exclude=(
5773-
fields_to_exclude_callables if fields_to_exclude_callables else None
5774-
),
5779+
exclude=fields_to_exclude_callables
5780+
if fields_to_exclude_callables
5781+
else None,
57755782
)
57765783

57775784
if version:

0 commit comments

Comments
 (0)