Skip to content

Commit b393615

Browse files
sararobcopybara-github
authored andcommitted
feat: Add GenAI client (experimental)
PiperOrigin-RevId: 766750716
1 parent c81eb72 commit b393615

File tree

9 files changed

+174
-84
lines changed

9 files changed

+174
-84
lines changed

noxfile.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
"pytest-asyncio",
6868
# Preventing: py.test: error: unrecognized arguments: -n=auto --dist=loadscope
6969
"pytest-xdist",
70+
# "pandas",
71+
# "tqdm",
7072
]
7173
UNIT_TEST_EXTERNAL_DEPENDENCIES = []
7274
UNIT_TEST_LOCAL_DEPENDENCIES = []

tests/unit/vertexai/genai/test_evals.py

Lines changed: 46 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
from vertexai import _genai
2626
from vertexai._genai import types as vertexai_genai_types
2727
from google.genai import types as genai_types
28-
import google.genai.errors as genai_errors
28+
from google.genai import client
29+
from vertexai._genai import evals
2930
import pandas as pd
3031
import pytest
3132
import warnings
@@ -48,47 +49,35 @@ def setup_method(self):
4849
project=_TEST_PROJECT,
4950
location=_TEST_LOCATION,
5051
)
52+
self.client = vertexai.Client(project=_TEST_PROJECT, location=_TEST_LOCATION)
5153

5254
@pytest.mark.usefixtures("google_auth_mock")
53-
def test_evaluate_instances(self):
54-
test_client = _genai.client.Client(
55-
project=_TEST_PROJECT, location=_TEST_LOCATION
56-
)
57-
with warnings.catch_warnings(record=True) as captured_warnings:
58-
warnings.simplefilter("always")
59-
with mock.patch.object(
60-
test_client.evals, "_evaluate_instances"
61-
) as mock_evaluate:
62-
test_client.evals._evaluate_instances(
63-
bleu_input=_genai.types.BleuInput()
64-
)
65-
mock_evaluate.assert_called_once_with(
66-
bleu_input=_genai.types.BleuInput()
67-
)
68-
assert captured_warnings[0].category == genai_errors.ExperimentalWarning
55+
@mock.patch.object(client.Client, "_get_api_client")
56+
@mock.patch.object(evals.Evals, "_evaluate_instances")
57+
def test_evaluate_instances(self, mock_evaluate, mock_get_api_client):
58+
59+
self.client.evals._evaluate_instances(bleu_input=vertexai_genai_types.BleuInput())
60+
mock_evaluate.assert_called_once_with(bleu_input=vertexai_genai_types.BleuInput())
6961

7062
@pytest.mark.usefixtures("google_auth_mock")
7163
def test_eval_run(self):
72-
test_client = _genai.client.Client(
73-
project=_TEST_PROJECT, location=_TEST_LOCATION
74-
)
64+
test_client = vertexai.Client(project=_TEST_PROJECT, location=_TEST_LOCATION)
7565
with pytest.raises(NotImplementedError):
7666
test_client.evals.run()
7767

7868
@pytest.mark.usefixtures("google_auth_mock")
79-
def test_eval_batch_eval(self):
80-
test_client = _genai.client.Client(
81-
project=_TEST_PROJECT, location=_TEST_LOCATION
69+
@mock.patch.object(client.Client, "_get_api_client")
70+
@mock.patch.object(evals.Evals, "batch_eval")
71+
def test_eval_batch_eval(self, mock_evaluate, mock_get_api_client):
72+
test_client = vertexai.Client(project=_TEST_PROJECT, location=_TEST_LOCATION)
73+
test_client.evals.batch_eval(
74+
dataset=vertexai_genai_types.EvaluationDataset(),
75+
metrics=[vertexai_genai_types.Metric()],
76+
output_config=vertexai_genai_types.OutputConfig(),
77+
autorater_config=vertexai_genai_types.AutoraterConfig(),
78+
config=vertexai_genai_types.EvaluateDatasetConfig(),
8279
)
83-
with mock.patch.object(test_client.evals, "batch_eval") as mock_batch_eval:
84-
test_client.evals.batch_eval(
85-
dataset=_genai.types.EvaluationDataset(),
86-
metrics=[_genai.types.Metric()],
87-
output_config=_genai.types.OutputConfig(),
88-
autorater_config=_genai.types.AutoraterConfig(),
89-
config=_genai.types.EvaluateDatasetConfig(),
90-
)
91-
mock_batch_eval.assert_called_once()
80+
mock_evaluate.assert_called_once()
9281

9382

9483
class TestEvalsClientInference:
@@ -99,20 +88,16 @@ def setup_method(self):
9988
importlib.reload(aiplatform)
10089
importlib.reload(vertexai)
10190
importlib.reload(_genai.client)
102-
importlib.reload(_genai.types)
91+
importlib.reload(vertexai_genai_types)
10392
importlib.reload(_genai.evals)
104-
importlib.reload(_genai._evals_utils)
105-
importlib.reload(_genai._evals_common)
10693
vertexai.init(
10794
project=_TEST_PROJECT,
10895
location=_TEST_LOCATION,
10996
)
110-
self.client = _genai.client.Client(
111-
project=_TEST_PROJECT, location=_TEST_LOCATION
112-
)
97+
self.client = vertexai.Client(project=_TEST_PROJECT, location=_TEST_LOCATION)
11398

114-
@mock.patch(f"{_genai._evals_common.__name__}.Models")
115-
@mock.patch(f"{_genai._evals_utils.__name__}.EvalDatasetLoader")
99+
@mock.patch("google.cloud.aiplatform.vertexai._genai._evals_common.Models")
100+
@mock.patch("google.cloud.aiplatform.vertexai._genai._evals_utils.EvalDatasetLoader")
116101
def test_inference_with_string_model_success(
117102
self, mock_eval_dataset_loader, mock_models
118103
):
@@ -153,7 +138,7 @@ def test_inference_with_string_model_success(
153138
),
154139
)
155140

156-
@mock.patch(f"{_genai._evals_utils.__name__}.EvalDatasetLoader")
141+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_utils.EvalDatasetLoader")
157142
def test_inference_with_callable_model_success(self, mock_eval_dataset_loader):
158143
mock_df = pd.DataFrame({"prompt": ["test prompt"]})
159144
mock_eval_dataset_loader.return_value.load.return_value = mock_df.to_dict(
@@ -178,8 +163,8 @@ def mock_model_fn(contents):
178163
),
179164
)
180165

181-
@mock.patch(f"{_genai._evals_common.__name__}.Models")
182-
@mock.patch(f"{_genai._evals_utils.__name__}.EvalDatasetLoader")
166+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_common.Models")
167+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_utils.EvalDatasetLoader")
183168
def test_inference_with_prompt_template(
184169
self, mock_eval_dataset_loader, mock_models
185170
):
@@ -202,7 +187,7 @@ def test_inference_with_prompt_template(
202187
mock_generate_content_response
203188
)
204189

205-
config = _genai.types.EvalRunInferenceConfig(
190+
config = vertexai_genai_types.EvalRunInferenceConfig(
206191
prompt_template="Hello {text_input}"
207192
)
208193
result_df = self.client.evals.run_inference(
@@ -223,9 +208,9 @@ def test_inference_with_prompt_template(
223208
),
224209
)
225210

226-
@mock.patch(f"{_genai._evals_common.__name__}.Models")
227-
@mock.patch(f"{_genai._evals_utils.__name__}.EvalDatasetLoader")
228-
@mock.patch(f"{_genai._evals_utils.__name__}.GcsUtils")
211+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_common.Models")
212+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_utils.EvalDatasetLoader")
213+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_utils.GcsUtils")
229214
def test_inference_with_gcs_destination(
230215
self, mock_gcs_utils, mock_eval_dataset_loader, mock_models
231216
):
@@ -249,7 +234,7 @@ def test_inference_with_gcs_destination(
249234
)
250235

251236
gcs_dest_path = "gs://bucket/output.jsonl"
252-
config = _genai.types.EvalRunInferenceConfig(dest=gcs_dest_path)
237+
config = vertexai_genai_types.EvalRunInferenceConfig(dest=gcs_dest_path)
253238

254239
result_df = self.client.evals.run_inference(
255240
model="gemini-pro", src=mock_df, config=config
@@ -270,8 +255,8 @@ def test_inference_with_gcs_destination(
270255
)
271256
pd.testing.assert_frame_equal(result_df, expected_df_to_save)
272257

273-
@mock.patch(f"{_genai._evals_common.__name__}.Models")
274-
@mock.patch(f"{_genai._evals_utils.__name__}.EvalDatasetLoader")
258+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_common.Models")
259+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_utils.EvalDatasetLoader")
275260
@mock.patch("pandas.DataFrame.to_json")
276261
@mock.patch("os.makedirs")
277262
def test_inference_with_local_destination(
@@ -301,7 +286,7 @@ def test_inference_with_local_destination(
301286
)
302287

303288
local_dest_path = "/tmp/test/output_dir/results.jsonl"
304-
config = _genai.types.EvalRunInferenceConfig(dest=local_dest_path)
289+
config = vertexai_genai_types.EvalRunInferenceConfig(dest=local_dest_path)
305290

306291
result_df = self.client.evals.run_inference(
307292
model="gemini-pro", src=mock_df, config=config
@@ -319,8 +304,8 @@ def test_inference_with_local_destination(
319304
)
320305
pd.testing.assert_frame_equal(result_df, expected_df)
321306

322-
@mock.patch(f"{_genai._evals_common.__name__}.Models")
323-
@mock.patch(f"{_genai._evals_utils.__name__}.EvalDatasetLoader")
307+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_common.Models")
308+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_utils.EvalDatasetLoader")
324309
def test_inference_from_request_column_save_locally(
325310
self, mock_eval_dataset_loader, mock_models
326311
):
@@ -359,7 +344,7 @@ def test_inference_from_request_column_save_locally(
359344
)
360345

361346
local_dest_path = "/tmp/output.jsonl"
362-
config = _genai.types.EvalRunInferenceConfig(dest=local_dest_path)
347+
config = vertexai_genai_types.EvalRunInferenceConfig(dest=local_dest_path)
363348

364349
result_df = self.client.evals.run_inference(
365350
model="gemini-pro", src=mock_df, config=config
@@ -396,7 +381,7 @@ def test_inference_from_request_column_save_locally(
396381
assert saved_records == expected_records
397382
os.remove(local_dest_path)
398383

399-
@mock.patch(f"{_genai._evals_common.__name__}.Models")
384+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_common.Models")
400385
def test_inference_from_local_jsonl_file(self, mock_models):
401386
# Create a temporary JSONL file
402387
local_src_path = "/tmp/input.jsonl"
@@ -450,7 +435,7 @@ def test_inference_from_local_jsonl_file(self, mock_models):
450435
pd.testing.assert_frame_equal(result_df, expected_df)
451436
os.remove(local_src_path)
452437

453-
@mock.patch(f"{_genai._evals_common.__name__}.Models")
438+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_common.Models")
454439
def test_inference_from_local_csv_file(self, mock_models):
455440
# Create a temporary CSV file
456441
local_src_path = "/tmp/input.csv"
@@ -501,8 +486,8 @@ def test_inference_from_local_csv_file(self, mock_models):
501486
pd.testing.assert_frame_equal(result_df, expected_df)
502487
os.remove(local_src_path)
503488

504-
@mock.patch(f"{_genai._evals_common.__name__}.Models")
505-
@mock.patch(f"{_genai._evals_utils.__name__}.EvalDatasetLoader")
489+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_common.Models")
490+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_utils.EvalDatasetLoader")
506491
def test_inference_with_row_level_config_overrides(
507492
self, mock_eval_dataset_loader, mock_models
508493
):
@@ -584,8 +569,8 @@ def test_inference_with_row_level_config_overrides(
584569
)
585570
pd.testing.assert_frame_equal(result_df, expected_df)
586571

587-
@mock.patch(f"{_genai._evals_common.__name__}.Models")
588-
@mock.patch(f"{_genai._evals_utils.__name__}.EvalDatasetLoader")
572+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_common.Models")
573+
@mock.patch(f"google.cloud.aiplatform.vertexai._genai._evals_utils.EvalDatasetLoader")
589574
def test_inference_with_multimodal_content(
590575
self, mock_eval_dataset_loader, mock_models
591576
):
@@ -623,7 +608,7 @@ def test_inference_with_multimodal_content(
623608
mock_generate_content_response
624609
)
625610

626-
config = _genai.types.EvalRunInferenceConfig(
611+
config = vertexai_genai_types.EvalRunInferenceConfig(
627612
prompt_template="multimodal prompt: {media_content}{text_input}"
628613
)
629614
result_df = self.client.evals.run_inference(

tests/unit/vertexai/genai/test_genai_client.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ def setup_method(self):
4444

4545
@pytest.mark.usefixtures("google_auth_mock")
4646
def test_genai_client(self):
47-
test_client = _genai.client.Client(
48-
project=_TEST_PROJECT, location=_TEST_LOCATION
49-
)
47+
test_client = vertexai.Client(project=_TEST_PROJECT, location=_TEST_LOCATION)
5048
assert test_client is not None
5149
assert test_client._api_client.vertexai
5250
assert test_client._api_client.project == _TEST_PROJECT

vertexai/__init__.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222

2323
from google.cloud.aiplatform import init
2424

25-
__all__ = [
26-
"init",
27-
"preview",
28-
]
25+
# from vertexai._genai.client import Client
26+
# from vertexai._genai import types
27+
28+
_genai_client = None
2929

3030

3131
def __getattr__(name):
@@ -39,4 +39,18 @@ def __getattr__(name):
3939
# `import google.cloud.aiplatform.vertexai.preview as vertexai_preview`
4040
return importlib.import_module(".preview", __name__)
4141

42+
if name in ("Client", "types"):
43+
global _genai_client
44+
if _genai_client is None:
45+
_genai_client = importlib.import_module("._genai.client", __name__)
46+
return getattr(_genai_client, name)
47+
4248
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
49+
50+
51+
__all__ = [
52+
"init",
53+
"preview",
54+
"Client",
55+
"types",
56+
]

vertexai/_genai/__init__.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,29 @@
1414
#
1515
"""The vertexai module."""
1616

17-
from . import evals
17+
import importlib
18+
1819
from .client import Client
1920

21+
_evals = None
22+
_evals_common = None
23+
24+
25+
def __getattr__(name):
26+
if name == "evals":
27+
global _evals
28+
if _evals is None:
29+
try:
30+
_evals = importlib.import_module(".evals", __package__)
31+
except ImportError as e:
32+
raise ImportError(
33+
f"The 'evals' module requires 'pandas' and 'tqdm'. "
34+
f"Please install them using pip install "
35+
f"google-cloud-aiplatform[evaluation]"
36+
) from e
37+
return _evals
38+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
39+
2040
__all__ = [
2141
"Client",
2242
"evals",

vertexai/_genai/_evals_common.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,26 @@
2424
from google.genai import types as genai_types
2525
from google.genai._api_client import BaseApiClient
2626
from google.genai.models import Models
27-
import pandas as pd
28-
from tqdm import tqdm
2927

3028
from . import _evals_utils
3129
from . import types
3230

31+
try:
32+
import pandas as pd
33+
except ImportError:
34+
raise ImportError(
35+
'Pandas is not installed. Please install the SDK using "pip install'
36+
' google-cloud-aiplatform[evaluation]"'
37+
)
38+
39+
try:
40+
from tqdm import tqdm
41+
except ImportError:
42+
raise ImportError(
43+
'tqdm is not installed. Please install the SDK using "pip install'
44+
' google-cloud-aiplatform[evaluation]"'
45+
)
46+
3347
logger = logging.getLogger(__name__)
3448

3549
_MAX_WORKERS = 100

vertexai/_genai/_evals_utils.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@
2222
from google.cloud import bigquery
2323
from google.cloud import storage
2424
from google.genai._api_client import BaseApiClient
25-
import pandas as pd
25+
try:
26+
import pandas as pd
27+
except ImportError:
28+
raise ImportError(
29+
'Pandas is not installed. Please install the SDK using "pip install'
30+
' google-cloud-aiplatform[evaluation]"'
31+
)
2632

2733
logger = logging.getLogger(__name__)
2834

0 commit comments

Comments
 (0)