google-cloud-agentplatform 1.165.1.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. agentplatform/__init__.py +72 -0
  2. agentplatform/_genai/__init__.py +43 -0
  3. agentplatform/_genai/_agent_engines_utils.py +2341 -0
  4. agentplatform/_genai/_bigquery_utils.py +49 -0
  5. agentplatform/_genai/_datasets_utils.py +344 -0
  6. agentplatform/_genai/_evals_builtin_tools.py +209 -0
  7. agentplatform/_genai/_evals_common.py +4268 -0
  8. agentplatform/_genai/_evals_constant.py +122 -0
  9. agentplatform/_genai/_evals_data_converters.py +926 -0
  10. agentplatform/_genai/_evals_metric_handlers.py +1783 -0
  11. agentplatform/_genai/_evals_metric_loaders.py +401 -0
  12. agentplatform/_genai/_evals_utils.py +1043 -0
  13. agentplatform/_genai/_evals_visualization.py +2070 -0
  14. agentplatform/_genai/_gcs_utils.py +262 -0
  15. agentplatform/_genai/_logging_utils.py +47 -0
  16. agentplatform/_genai/_memory_bank_utils.py +206 -0
  17. agentplatform/_genai/_observability_data_converter.py +186 -0
  18. agentplatform/_genai/_operations_utils.py +94 -0
  19. agentplatform/_genai/_prompt_management_utils.py +147 -0
  20. agentplatform/_genai/_prompt_optimizer_utils.py +215 -0
  21. agentplatform/_genai/_skills_utils.py +69 -0
  22. agentplatform/_genai/_transformers.py +628 -0
  23. agentplatform/_genai/a2a_task_events.py +509 -0
  24. agentplatform/_genai/a2a_tasks.py +861 -0
  25. agentplatform/_genai/agent_engines.py +3931 -0
  26. agentplatform/_genai/client.py +519 -0
  27. agentplatform/_genai/datasets.py +3045 -0
  28. agentplatform/_genai/endpoints.py +1149 -0
  29. agentplatform/_genai/evals.py +6883 -0
  30. agentplatform/_genai/example_stores.py +1445 -0
  31. agentplatform/_genai/feedback_contexts.py +700 -0
  32. agentplatform/_genai/feedback_entries.py +1644 -0
  33. agentplatform/_genai/live.py +64 -0
  34. agentplatform/_genai/live_agent_engines.py +179 -0
  35. agentplatform/_genai/memories.py +2962 -0
  36. agentplatform/_genai/memory_banks.py +1927 -0
  37. agentplatform/_genai/memory_revisions.py +465 -0
  38. agentplatform/_genai/model_garden.py +2638 -0
  39. agentplatform/_genai/prompt_optimizer.py +995 -0
  40. agentplatform/_genai/prompts.py +4515 -0
  41. agentplatform/_genai/rag.py +4961 -0
  42. agentplatform/_genai/runtime_revisions.py +1257 -0
  43. agentplatform/_genai/runtimes.py +78 -0
  44. agentplatform/_genai/sandbox_snapshots.py +1015 -0
  45. agentplatform/_genai/sandbox_templates.py +1088 -0
  46. agentplatform/_genai/sandboxes.py +1604 -0
  47. agentplatform/_genai/session_events.py +543 -0
  48. agentplatform/_genai/sessions.py +1449 -0
  49. agentplatform/_genai/skill_revisions.py +377 -0
  50. agentplatform/_genai/skills.py +1708 -0
  51. agentplatform/_genai/types/__init__.py +4695 -0
  52. agentplatform/_genai/types/agent_engines.py +16 -0
  53. agentplatform/_genai/types/common.py +32784 -0
  54. agentplatform/_genai/types/evals.py +1031 -0
  55. agentplatform/_genai/types/prompt_optimizer.py +107 -0
  56. agentplatform/_genai/types/prompts.py +107 -0
  57. agentplatform/version.py +17 -0
  58. google_cloud_agentplatform-1.165.1.dev0.dist-info/METADATA +79 -0
  59. google_cloud_agentplatform-1.165.1.dev0.dist-info/RECORD +62 -0
  60. google_cloud_agentplatform-1.165.1.dev0.dist-info/WHEEL +5 -0
  61. google_cloud_agentplatform-1.165.1.dev0.dist-info/licenses/LICENSE +202 -0
  62. google_cloud_agentplatform-1.165.1.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2638 @@
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
+
16
+ # Code generated by the Google Gen AI SDK generator DO NOT EDIT.
17
+
18
+ import json
19
+ import logging
20
+ from typing import Any, Optional, Union
21
+ from urllib.parse import urlencode
22
+
23
+ from google.genai import _api_module
24
+ from google.genai import _common
25
+ from google.genai import types as genai_types
26
+ from google.genai._common import get_value_by_path as getv
27
+ from google.genai._common import set_value_by_path as setv
28
+
29
+ from . import _operations_utils
30
+ from . import types
31
+
32
+ logger = logging.getLogger("agentplatform_genai.modelgarden")
33
+
34
+
35
+ def _DeployRequestParameters_to_vertex(
36
+ from_object: Union[dict[str, Any], object],
37
+ parent_object: Optional[dict[str, Any]] = None,
38
+ ) -> dict[str, Any]:
39
+ to_object: dict[str, Any] = {}
40
+ if getv(from_object, ["destination"]) is not None:
41
+ setv(to_object, ["_url", "destination"], getv(from_object, ["destination"]))
42
+
43
+ if getv(from_object, ["publisher_model_name"]) is not None:
44
+ setv(
45
+ to_object,
46
+ ["publisherModelName"],
47
+ getv(from_object, ["publisher_model_name"]),
48
+ )
49
+
50
+ if getv(from_object, ["hugging_face_model_id"]) is not None:
51
+ setv(
52
+ to_object,
53
+ ["huggingFaceModelId"],
54
+ getv(from_object, ["hugging_face_model_id"]),
55
+ )
56
+
57
+ if getv(from_object, ["custom_model"]) is not None:
58
+ setv(to_object, ["customModel"], getv(from_object, ["custom_model"]))
59
+
60
+ if getv(from_object, ["model_config_val"]) is not None:
61
+ setv(to_object, ["modelConfig"], getv(from_object, ["model_config_val"]))
62
+
63
+ if getv(from_object, ["endpoint_config"]) is not None:
64
+ setv(to_object, ["endpointConfig"], getv(from_object, ["endpoint_config"]))
65
+
66
+ if getv(from_object, ["deploy_config"]) is not None:
67
+ setv(to_object, ["deployConfig"], getv(from_object, ["deploy_config"]))
68
+
69
+ return to_object
70
+
71
+
72
+ def _ExportPublisherModelConfig_to_vertex(
73
+ from_object: Union[dict[str, Any], object],
74
+ parent_object: Optional[dict[str, Any]] = None,
75
+ ) -> dict[str, Any]:
76
+ to_object: dict[str, Any] = {}
77
+
78
+ if getv(from_object, ["destination"]) is not None:
79
+ setv(parent_object, ["destination"], getv(from_object, ["destination"]))
80
+
81
+ return to_object
82
+
83
+
84
+ def _ExportPublisherModelRequestParameters_to_vertex(
85
+ from_object: Union[dict[str, Any], object],
86
+ parent_object: Optional[dict[str, Any]] = None,
87
+ ) -> dict[str, Any]:
88
+ to_object: dict[str, Any] = {}
89
+ if getv(from_object, ["parent"]) is not None:
90
+ setv(to_object, ["_url", "parent"], getv(from_object, ["parent"]))
91
+
92
+ if getv(from_object, ["name"]) is not None:
93
+ setv(to_object, ["_url", "name"], getv(from_object, ["name"]))
94
+
95
+ if getv(from_object, ["config"]) is not None:
96
+ _ExportPublisherModelConfig_to_vertex(getv(from_object, ["config"]), to_object)
97
+
98
+ return to_object
99
+
100
+
101
+ def _GetDeployOperationParameters_to_vertex(
102
+ from_object: Union[dict[str, Any], object],
103
+ parent_object: Optional[dict[str, Any]] = None,
104
+ ) -> dict[str, Any]:
105
+ to_object: dict[str, Any] = {}
106
+ if getv(from_object, ["operation_name"]) is not None:
107
+ setv(
108
+ to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
109
+ )
110
+
111
+ return to_object
112
+
113
+
114
+ def _GetExportPublisherModelOperationParameters_to_vertex(
115
+ from_object: Union[dict[str, Any], object],
116
+ parent_object: Optional[dict[str, Any]] = None,
117
+ ) -> dict[str, Any]:
118
+ to_object: dict[str, Any] = {}
119
+ if getv(from_object, ["operation_name"]) is not None:
120
+ setv(
121
+ to_object, ["_url", "operationName"], getv(from_object, ["operation_name"])
122
+ )
123
+
124
+ return to_object
125
+
126
+
127
+ def _GetPublisherModelConfig_to_vertex(
128
+ from_object: Union[dict[str, Any], object],
129
+ parent_object: Optional[dict[str, Any]] = None,
130
+ ) -> dict[str, Any]:
131
+ to_object: dict[str, Any] = {}
132
+
133
+ if getv(from_object, ["hugging_face_token"]) is not None:
134
+ setv(
135
+ parent_object,
136
+ ["_query", "huggingFaceToken"],
137
+ getv(from_object, ["hugging_face_token"]),
138
+ )
139
+
140
+ if (
141
+ getv(from_object, ["include_equivalent_model_garden_model_deployment_configs"])
142
+ is not None
143
+ ):
144
+ setv(
145
+ parent_object,
146
+ ["_query", "includeEquivalentModelGardenModelDeploymentConfigs"],
147
+ getv(
148
+ from_object,
149
+ ["include_equivalent_model_garden_model_deployment_configs"],
150
+ ),
151
+ )
152
+
153
+ if getv(from_object, ["is_hugging_face_model"]) is not None:
154
+ setv(
155
+ parent_object,
156
+ ["_query", "isHuggingFaceModel"],
157
+ getv(from_object, ["is_hugging_face_model"]),
158
+ )
159
+
160
+ return to_object
161
+
162
+
163
+ def _GetPublisherModelRequestParameters_to_vertex(
164
+ from_object: Union[dict[str, Any], object],
165
+ parent_object: Optional[dict[str, Any]] = None,
166
+ ) -> dict[str, Any]:
167
+ to_object: dict[str, Any] = {}
168
+ if getv(from_object, ["name"]) is not None:
169
+ setv(to_object, ["_url", "name"], getv(from_object, ["name"]))
170
+
171
+ if getv(from_object, ["config"]) is not None:
172
+ _GetPublisherModelConfig_to_vertex(getv(from_object, ["config"]), to_object)
173
+
174
+ return to_object
175
+
176
+
177
+ def _ListPublisherModelsConfig_to_vertex(
178
+ from_object: Union[dict[str, Any], object],
179
+ parent_object: Optional[dict[str, Any]] = None,
180
+ ) -> dict[str, Any]:
181
+ to_object: dict[str, Any] = {}
182
+
183
+ if getv(from_object, ["page_size"]) is not None:
184
+ setv(parent_object, ["_query", "pageSize"], getv(from_object, ["page_size"]))
185
+
186
+ if getv(from_object, ["page_token"]) is not None:
187
+ setv(parent_object, ["_query", "pageToken"], getv(from_object, ["page_token"]))
188
+
189
+ if getv(from_object, ["filter"]) is not None:
190
+ setv(parent_object, ["_query", "filter"], getv(from_object, ["filter"]))
191
+
192
+ if getv(from_object, ["list_all_versions"]) is not None:
193
+ setv(
194
+ parent_object,
195
+ ["_query", "listAllVersions"],
196
+ getv(from_object, ["list_all_versions"]),
197
+ )
198
+
199
+ return to_object
200
+
201
+
202
+ def _ListPublisherModelsRequestParameters_to_vertex(
203
+ from_object: Union[dict[str, Any], object],
204
+ parent_object: Optional[dict[str, Any]] = None,
205
+ ) -> dict[str, Any]:
206
+ to_object: dict[str, Any] = {}
207
+ if getv(from_object, ["parent"]) is not None:
208
+ setv(to_object, ["_url", "parent"], getv(from_object, ["parent"]))
209
+
210
+ if getv(from_object, ["config"]) is not None:
211
+ _ListPublisherModelsConfig_to_vertex(getv(from_object, ["config"]), to_object)
212
+
213
+ return to_object
214
+
215
+
216
+ def _RecommendSpecConfig_to_vertex(
217
+ from_object: Union[dict[str, Any], object],
218
+ parent_object: Optional[dict[str, Any]] = None,
219
+ ) -> dict[str, Any]:
220
+ to_object: dict[str, Any] = {}
221
+
222
+ if getv(from_object, ["check_machine_availability"]) is not None:
223
+ setv(
224
+ parent_object,
225
+ ["checkMachineAvailability"],
226
+ getv(from_object, ["check_machine_availability"]),
227
+ )
228
+
229
+ if getv(from_object, ["check_user_quota"]) is not None:
230
+ setv(parent_object, ["checkUserQuota"], getv(from_object, ["check_user_quota"]))
231
+
232
+ return to_object
233
+
234
+
235
+ def _RecommendSpecRequestParameters_to_vertex(
236
+ from_object: Union[dict[str, Any], object],
237
+ parent_object: Optional[dict[str, Any]] = None,
238
+ ) -> dict[str, Any]:
239
+ to_object: dict[str, Any] = {}
240
+ if getv(from_object, ["parent"]) is not None:
241
+ setv(to_object, ["_url", "parent"], getv(from_object, ["parent"]))
242
+
243
+ if getv(from_object, ["gcs_uri"]) is not None:
244
+ setv(to_object, ["gcsUri"], getv(from_object, ["gcs_uri"]))
245
+
246
+ if getv(from_object, ["config"]) is not None:
247
+ _RecommendSpecConfig_to_vertex(getv(from_object, ["config"]), to_object)
248
+
249
+ return to_object
250
+
251
+
252
+ class ModelGarden(_api_module.BaseModule):
253
+ """Model Garden module."""
254
+
255
+ def _list_publisher_models(
256
+ self,
257
+ *,
258
+ parent: Optional[str] = None,
259
+ config: Optional[types.ListPublisherModelsConfigOrDict] = None,
260
+ ) -> types.ListPublisherModelsResponse:
261
+ """
262
+ Lists publisher models (internal).
263
+ """
264
+
265
+ parameter_model = types._ListPublisherModelsRequestParameters(
266
+ parent=parent,
267
+ config=config,
268
+ )
269
+
270
+ request_url_dict: Optional[dict[str, str]]
271
+ if not self._api_client.vertexai:
272
+ raise ValueError(
273
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
274
+ )
275
+ else:
276
+ request_dict = _ListPublisherModelsRequestParameters_to_vertex(
277
+ parameter_model
278
+ )
279
+ request_url_dict = request_dict.get("_url")
280
+ if request_url_dict:
281
+ path = "{parent}/models".format_map(request_url_dict)
282
+ else:
283
+ path = "{parent}/models"
284
+
285
+ query_params = request_dict.get("_query")
286
+ if query_params:
287
+ path = f"{path}?{urlencode(query_params)}"
288
+ # TODO: remove the hack that pops config.
289
+ request_dict.pop("config", None)
290
+
291
+ http_options: Optional[types.HttpOptions] = None
292
+ if (
293
+ parameter_model.config is not None
294
+ and parameter_model.config.http_options is not None
295
+ ):
296
+ http_options = parameter_model.config.http_options
297
+
298
+ request_dict = _common.convert_to_dict(request_dict)
299
+ request_dict = _common.encode_unserializable_types(request_dict)
300
+
301
+ response = self._api_client.request("get", path, request_dict, http_options)
302
+
303
+ response_dict = {} if not response.body else json.loads(response.body)
304
+
305
+ return_value = types.ListPublisherModelsResponse._from_response(
306
+ response=response_dict,
307
+ kwargs=(
308
+ {
309
+ "config": {
310
+ "response_schema": getattr(
311
+ parameter_model.config, "response_schema", None
312
+ ),
313
+ "response_json_schema": getattr(
314
+ parameter_model.config, "response_json_schema", None
315
+ ),
316
+ "include_all_fields": getattr(
317
+ parameter_model.config, "include_all_fields", None
318
+ ),
319
+ }
320
+ }
321
+ if getattr(parameter_model, "config", None)
322
+ else {}
323
+ ),
324
+ )
325
+
326
+ self._api_client._verify_response(return_value)
327
+ return return_value
328
+
329
+ def _get_publisher_model(
330
+ self, *, name: str, config: Optional[types.GetPublisherModelConfigOrDict] = None
331
+ ) -> types.PublisherModel:
332
+ """
333
+ Gets a publisher model (internal).
334
+ """
335
+
336
+ parameter_model = types._GetPublisherModelRequestParameters(
337
+ name=name,
338
+ config=config,
339
+ )
340
+
341
+ request_url_dict: Optional[dict[str, str]]
342
+ if not self._api_client.vertexai:
343
+ raise ValueError(
344
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
345
+ )
346
+ else:
347
+ request_dict = _GetPublisherModelRequestParameters_to_vertex(
348
+ parameter_model
349
+ )
350
+ request_url_dict = request_dict.get("_url")
351
+ if request_url_dict:
352
+ path = "{name}".format_map(request_url_dict)
353
+ else:
354
+ path = "{name}"
355
+
356
+ query_params = request_dict.get("_query")
357
+ if query_params:
358
+ path = f"{path}?{urlencode(query_params)}"
359
+ # TODO: remove the hack that pops config.
360
+ request_dict.pop("config", None)
361
+
362
+ http_options: Optional[types.HttpOptions] = None
363
+ if (
364
+ parameter_model.config is not None
365
+ and parameter_model.config.http_options is not None
366
+ ):
367
+ http_options = parameter_model.config.http_options
368
+
369
+ request_dict = _common.convert_to_dict(request_dict)
370
+ request_dict = _common.encode_unserializable_types(request_dict)
371
+
372
+ response = self._api_client.request("get", path, request_dict, http_options)
373
+
374
+ response_dict = {} if not response.body else json.loads(response.body)
375
+
376
+ return_value = types.PublisherModel._from_response(
377
+ response=response_dict,
378
+ kwargs=(
379
+ {
380
+ "config": {
381
+ "response_schema": getattr(
382
+ parameter_model.config, "response_schema", None
383
+ ),
384
+ "response_json_schema": getattr(
385
+ parameter_model.config, "response_json_schema", None
386
+ ),
387
+ "include_all_fields": getattr(
388
+ parameter_model.config, "include_all_fields", None
389
+ ),
390
+ }
391
+ }
392
+ if getattr(parameter_model, "config", None)
393
+ else {}
394
+ ),
395
+ )
396
+
397
+ self._api_client._verify_response(return_value)
398
+ return return_value
399
+
400
+ def _recommend_spec(
401
+ self,
402
+ *,
403
+ parent: str,
404
+ gcs_uri: str,
405
+ config: Optional[types.RecommendSpecConfigOrDict] = None,
406
+ ) -> types.RecommendSpecResponse:
407
+ """
408
+ Recommends spec for a custom model (internal).
409
+ """
410
+
411
+ parameter_model = types._RecommendSpecRequestParameters(
412
+ parent=parent,
413
+ gcs_uri=gcs_uri,
414
+ config=config,
415
+ )
416
+
417
+ request_url_dict: Optional[dict[str, str]]
418
+ if not self._api_client.vertexai:
419
+ raise ValueError(
420
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
421
+ )
422
+ else:
423
+ request_dict = _RecommendSpecRequestParameters_to_vertex(parameter_model)
424
+ request_url_dict = request_dict.get("_url")
425
+ if request_url_dict:
426
+ path = "{parent}:recommendSpec".format_map(request_url_dict)
427
+ else:
428
+ path = "{parent}:recommendSpec"
429
+
430
+ query_params = request_dict.get("_query")
431
+ if query_params:
432
+ path = f"{path}?{urlencode(query_params)}"
433
+ # TODO: remove the hack that pops config.
434
+ request_dict.pop("config", None)
435
+
436
+ http_options: Optional[types.HttpOptions] = None
437
+ if (
438
+ parameter_model.config is not None
439
+ and parameter_model.config.http_options is not None
440
+ ):
441
+ http_options = parameter_model.config.http_options
442
+
443
+ request_dict = _common.convert_to_dict(request_dict)
444
+ request_dict = _common.encode_unserializable_types(request_dict)
445
+
446
+ response = self._api_client.request("post", path, request_dict, http_options)
447
+
448
+ response_dict = {} if not response.body else json.loads(response.body)
449
+
450
+ return_value = types.RecommendSpecResponse._from_response(
451
+ response=response_dict,
452
+ kwargs=(
453
+ {
454
+ "config": {
455
+ "response_schema": getattr(
456
+ parameter_model.config, "response_schema", None
457
+ ),
458
+ "response_json_schema": getattr(
459
+ parameter_model.config, "response_json_schema", None
460
+ ),
461
+ "include_all_fields": getattr(
462
+ parameter_model.config, "include_all_fields", None
463
+ ),
464
+ }
465
+ }
466
+ if getattr(parameter_model, "config", None)
467
+ else {}
468
+ ),
469
+ )
470
+
471
+ self._api_client._verify_response(return_value)
472
+ return return_value
473
+
474
+ def _export_publisher_model(
475
+ self,
476
+ *,
477
+ parent: str,
478
+ name: str,
479
+ config: Optional[types.ExportPublisherModelConfigOrDict] = None,
480
+ ) -> types.ExportModelOperation:
481
+ """
482
+ Exports a publisher model (internal).
483
+ """
484
+
485
+ parameter_model = types._ExportPublisherModelRequestParameters(
486
+ parent=parent,
487
+ name=name,
488
+ config=config,
489
+ )
490
+
491
+ request_url_dict: Optional[dict[str, str]]
492
+ if not self._api_client.vertexai:
493
+ raise ValueError(
494
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
495
+ )
496
+ else:
497
+ request_dict = _ExportPublisherModelRequestParameters_to_vertex(
498
+ parameter_model
499
+ )
500
+ request_url_dict = request_dict.get("_url")
501
+ if request_url_dict:
502
+ path = "{parent}/{name}:export".format_map(request_url_dict)
503
+ else:
504
+ path = "{parent}/{name}:export"
505
+
506
+ query_params = request_dict.get("_query")
507
+ if query_params:
508
+ path = f"{path}?{urlencode(query_params)}"
509
+ # TODO: remove the hack that pops config.
510
+ request_dict.pop("config", None)
511
+
512
+ http_options: Optional[types.HttpOptions] = None
513
+ if (
514
+ parameter_model.config is not None
515
+ and parameter_model.config.http_options is not None
516
+ ):
517
+ http_options = parameter_model.config.http_options
518
+
519
+ request_dict = _common.convert_to_dict(request_dict)
520
+ request_dict = _common.encode_unserializable_types(request_dict)
521
+
522
+ response = self._api_client.request("post", path, request_dict, http_options)
523
+
524
+ response_dict = {} if not response.body else json.loads(response.body)
525
+
526
+ return_value = types.ExportModelOperation._from_response(
527
+ response=response_dict,
528
+ kwargs=(
529
+ {
530
+ "config": {
531
+ "response_schema": getattr(
532
+ parameter_model.config, "response_schema", None
533
+ ),
534
+ "response_json_schema": getattr(
535
+ parameter_model.config, "response_json_schema", None
536
+ ),
537
+ "include_all_fields": getattr(
538
+ parameter_model.config, "include_all_fields", None
539
+ ),
540
+ }
541
+ }
542
+ if getattr(parameter_model, "config", None)
543
+ else {}
544
+ ),
545
+ )
546
+
547
+ self._api_client._verify_response(return_value)
548
+ return return_value
549
+
550
+ def get_export_publisher_model_operation(
551
+ self,
552
+ *,
553
+ operation_name: str,
554
+ config: Optional[types.GetExportPublisherModelOperationConfigOrDict] = None,
555
+ ) -> types.ExportModelOperation:
556
+ """
557
+ Fetches the status of an in-flight export_open_model LRO.
558
+ """
559
+
560
+ parameter_model = types._GetExportPublisherModelOperationParameters(
561
+ operation_name=operation_name,
562
+ config=config,
563
+ )
564
+
565
+ request_url_dict: Optional[dict[str, str]]
566
+ if not self._api_client.vertexai:
567
+ raise ValueError(
568
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
569
+ )
570
+ else:
571
+ request_dict = _GetExportPublisherModelOperationParameters_to_vertex(
572
+ parameter_model
573
+ )
574
+ request_url_dict = request_dict.get("_url")
575
+ if request_url_dict:
576
+ path = "{operationName}".format_map(request_url_dict)
577
+ else:
578
+ path = "{operationName}"
579
+
580
+ query_params = request_dict.get("_query")
581
+ if query_params:
582
+ path = f"{path}?{urlencode(query_params)}"
583
+ # TODO: remove the hack that pops config.
584
+ request_dict.pop("config", None)
585
+
586
+ http_options: Optional[types.HttpOptions] = None
587
+ if (
588
+ parameter_model.config is not None
589
+ and parameter_model.config.http_options is not None
590
+ ):
591
+ http_options = parameter_model.config.http_options
592
+
593
+ request_dict = _common.convert_to_dict(request_dict)
594
+ request_dict = _common.encode_unserializable_types(request_dict)
595
+
596
+ response = self._api_client.request("get", path, request_dict, http_options)
597
+
598
+ response_dict = {} if not response.body else json.loads(response.body)
599
+
600
+ return_value = types.ExportModelOperation._from_response(
601
+ response=response_dict,
602
+ kwargs=(
603
+ {
604
+ "config": {
605
+ "response_schema": getattr(
606
+ parameter_model.config, "response_schema", None
607
+ ),
608
+ "response_json_schema": getattr(
609
+ parameter_model.config, "response_json_schema", None
610
+ ),
611
+ "include_all_fields": getattr(
612
+ parameter_model.config, "include_all_fields", None
613
+ ),
614
+ }
615
+ }
616
+ if getattr(parameter_model, "config", None)
617
+ else {}
618
+ ),
619
+ )
620
+
621
+ self._api_client._verify_response(return_value)
622
+ return return_value
623
+
624
+ def _deploy(
625
+ self,
626
+ *,
627
+ destination: str,
628
+ publisher_model_name: Optional[str] = None,
629
+ hugging_face_model_id: Optional[str] = None,
630
+ custom_model: Optional[types.DeployRequestCustomModelOrDict] = None,
631
+ model_config_val: Optional[types.DeployRequestModelConfigOrDict] = None,
632
+ endpoint_config: Optional[types.DeployRequestEndpointConfigOrDict] = None,
633
+ deploy_config: Optional[types.DeployRequestDeployConfigOrDict] = None,
634
+ config: Optional[types.DeployConfigOrDict] = None,
635
+ ) -> types.DeployModelOperation:
636
+ """
637
+ Deploys a model (internal).
638
+ """
639
+
640
+ parameter_model = types._DeployRequestParameters(
641
+ destination=destination,
642
+ publisher_model_name=publisher_model_name,
643
+ hugging_face_model_id=hugging_face_model_id,
644
+ custom_model=custom_model,
645
+ model_config_val=model_config_val,
646
+ endpoint_config=endpoint_config,
647
+ deploy_config=deploy_config,
648
+ config=config,
649
+ )
650
+
651
+ request_url_dict: Optional[dict[str, str]]
652
+ if not self._api_client.vertexai:
653
+ raise ValueError(
654
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
655
+ )
656
+ else:
657
+ request_dict = _DeployRequestParameters_to_vertex(parameter_model)
658
+ request_url_dict = request_dict.get("_url")
659
+ if request_url_dict:
660
+ path = "{destination}:deploy".format_map(request_url_dict)
661
+ else:
662
+ path = "{destination}:deploy"
663
+
664
+ query_params = request_dict.get("_query")
665
+ if query_params:
666
+ path = f"{path}?{urlencode(query_params)}"
667
+ # TODO: remove the hack that pops config.
668
+ request_dict.pop("config", None)
669
+
670
+ http_options: Optional[types.HttpOptions] = None
671
+ if (
672
+ parameter_model.config is not None
673
+ and parameter_model.config.http_options is not None
674
+ ):
675
+ http_options = parameter_model.config.http_options
676
+
677
+ request_dict = _common.convert_to_dict(request_dict)
678
+ request_dict = _common.encode_unserializable_types(request_dict)
679
+
680
+ response = self._api_client.request("post", path, request_dict, http_options)
681
+
682
+ response_dict = {} if not response.body else json.loads(response.body)
683
+
684
+ return_value = types.DeployModelOperation._from_response(
685
+ response=response_dict,
686
+ kwargs=(
687
+ {
688
+ "config": {
689
+ "response_schema": getattr(
690
+ parameter_model.config, "response_schema", None
691
+ ),
692
+ "response_json_schema": getattr(
693
+ parameter_model.config, "response_json_schema", None
694
+ ),
695
+ "include_all_fields": getattr(
696
+ parameter_model.config, "include_all_fields", None
697
+ ),
698
+ }
699
+ }
700
+ if getattr(parameter_model, "config", None)
701
+ else {}
702
+ ),
703
+ )
704
+
705
+ self._api_client._verify_response(return_value)
706
+ return return_value
707
+
708
+ def get_deploy_publisher_model_operation(
709
+ self,
710
+ *,
711
+ operation_name: str,
712
+ config: Optional[types.GetDeployOperationConfigOrDict] = None,
713
+ ) -> types.DeployModelOperation:
714
+ """
715
+ Fetches the status of an in-flight ``deploy_publisher_model`` LRO.
716
+ """
717
+
718
+ parameter_model = types._GetDeployOperationParameters(
719
+ operation_name=operation_name,
720
+ config=config,
721
+ )
722
+
723
+ request_url_dict: Optional[dict[str, str]]
724
+ if not self._api_client.vertexai:
725
+ raise ValueError(
726
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
727
+ )
728
+ else:
729
+ request_dict = _GetDeployOperationParameters_to_vertex(parameter_model)
730
+ request_url_dict = request_dict.get("_url")
731
+ if request_url_dict:
732
+ path = "{operationName}".format_map(request_url_dict)
733
+ else:
734
+ path = "{operationName}"
735
+
736
+ query_params = request_dict.get("_query")
737
+ if query_params:
738
+ path = f"{path}?{urlencode(query_params)}"
739
+ # TODO: remove the hack that pops config.
740
+ request_dict.pop("config", None)
741
+
742
+ http_options: Optional[types.HttpOptions] = None
743
+ if (
744
+ parameter_model.config is not None
745
+ and parameter_model.config.http_options is not None
746
+ ):
747
+ http_options = parameter_model.config.http_options
748
+
749
+ request_dict = _common.convert_to_dict(request_dict)
750
+ request_dict = _common.encode_unserializable_types(request_dict)
751
+
752
+ response = self._api_client.request("get", path, request_dict, http_options)
753
+
754
+ response_dict = {} if not response.body else json.loads(response.body)
755
+
756
+ return_value = types.DeployModelOperation._from_response(
757
+ response=response_dict,
758
+ kwargs=(
759
+ {
760
+ "config": {
761
+ "response_schema": getattr(
762
+ parameter_model.config, "response_schema", None
763
+ ),
764
+ "response_json_schema": getattr(
765
+ parameter_model.config, "response_json_schema", None
766
+ ),
767
+ "include_all_fields": getattr(
768
+ parameter_model.config, "include_all_fields", None
769
+ ),
770
+ }
771
+ }
772
+ if getattr(parameter_model, "config", None)
773
+ else {}
774
+ ),
775
+ )
776
+
777
+ self._api_client._verify_response(return_value)
778
+ return return_value
779
+
780
+ # Fallbacks for ``ExportOpenModelConfig`` when the caller does not
781
+ # override them. 2h matches the legacy SDK's blocking ``.export()`` and is
782
+ # generous enough for large open weights (e.g. Gemma 3 27B).
783
+ _DEFAULT_EXPORT_TIMEOUT_SECONDS = 2 * 60 * 60
784
+ _DEFAULT_EXPORT_POLL_INTERVAL_SECONDS = 30
785
+
786
+ # Deploy LRO polling defaults. 2h matches legacy vertexai.model_garden and
787
+ # the Vertex AI Console's one-click deployment timeout.
788
+ _DEFAULT_DEPLOY_TIMEOUT_SECONDS = 2 * 60 * 60
789
+ _DEFAULT_DEPLOY_POLL_INTERVAL_SECONDS = 30
790
+
791
+ @staticmethod
792
+ def _build_filter_str(
793
+ model_filter: Optional[str],
794
+ include_hugging_face_models: bool,
795
+ deployable_only: bool,
796
+ ) -> str:
797
+ """Builds the filter string for the ListPublisherModels API.
798
+
799
+ Args:
800
+ model_filter: Optional substring to match against model IDs and display
801
+ names (case-insensitive).
802
+ include_hugging_face_models: Whether to include HuggingFace models. If
803
+ True, uses ``is_hf_wildcard(true)``; otherwise ``is_hf_wildcard(false)``.
804
+ deployable_only: Whether to restrict to models with verified deployment
805
+ configurations via the ``VERIFIED_DEPLOYMENT_SUCCEED`` label.
806
+
807
+ Returns:
808
+ A filter string suitable for the ``filter`` parameter of the
809
+ ListPublisherModels API.
810
+ """
811
+ import re
812
+
813
+ if include_hugging_face_models:
814
+ filter_str = "is_hf_wildcard(true)"
815
+ if deployable_only:
816
+ filter_str += (
817
+ " AND labels.VERIFIED_DEPLOYMENT_CONFIG=VERIFIED_DEPLOYMENT_SUCCEED"
818
+ )
819
+ else:
820
+ filter_str = "is_hf_wildcard(false)"
821
+
822
+ if model_filter:
823
+ escaped = re.escape(model_filter)
824
+ filter_str = (
825
+ f'{filter_str} AND (model_user_id=~"(?i).*{escaped}.*"'
826
+ f' OR display_name=~"(?i).*{escaped}.*")'
827
+ )
828
+
829
+ return filter_str
830
+
831
+ @staticmethod
832
+ def _format_model_name(
833
+ model: types.PublisherModel,
834
+ include_hugging_face_models: bool,
835
+ ) -> str:
836
+ """Formats a PublisherModel into a human-readable model name string.
837
+
838
+ Args:
839
+ model: The PublisherModel to format.
840
+ include_hugging_face_models: Whether HuggingFace models are included in
841
+ the listing. Controls whether the ``@version`` suffix is appended.
842
+
843
+ Returns:
844
+ A formatted model name string in one of the following formats:
845
+
846
+ - ``'{publisher}/{model}@{version}'`` when
847
+ ``include_hugging_face_models`` is False.
848
+ - ``'{publisher}/{model}'`` when ``include_hugging_face_models`` is True.
849
+ """
850
+ import re
851
+
852
+ name = model.name or ""
853
+ formatted = re.sub(r"publishers/(hf-|)|models/", "", name)
854
+ if include_hugging_face_models:
855
+ return formatted
856
+ return formatted + "@" + (model.version_id or "")
857
+
858
+ @staticmethod
859
+ def _has_deploy_config(model: types.PublisherModel) -> bool:
860
+ """Checks whether a model has verified deployment configurations.
861
+
862
+ Args:
863
+ model: The PublisherModel to check.
864
+
865
+ Returns:
866
+ True if the model has at least one entry in
867
+ ``supported_actions.multi_deploy_vertex``.
868
+ """
869
+ return bool(
870
+ model.supported_actions
871
+ and model.supported_actions.multi_deploy_vertex
872
+ and model.supported_actions.multi_deploy_vertex.multi_deploy_vertex
873
+ )
874
+
875
+ @staticmethod
876
+ def _reconcile_model_name(model_name: str) -> str:
877
+ """Normalizes a model name into a publisher model resource name.
878
+
879
+ Args:
880
+ model_name: A Model Garden model resource name in the format
881
+ ``'publishers/{publisher}/models/{model}@{version}'``, a simplified name
882
+ in the format ``'{publisher}/{model}@{version}'`` (or without
883
+ ``@{version}``), or a Hugging Face model ID ``'{organization}/{model}'``.
884
+
885
+ Returns:
886
+ The resource name in the format
887
+ ``'publishers/{publisher}/models/{model}@{version}'``.
888
+
889
+ Raises:
890
+ ValueError: If ``model_name`` is not a valid publisher model name.
891
+ """
892
+ import re
893
+
894
+ model_name = model_name.lower() # Hugging Face IDs are lower-case.
895
+ # A full resource name must carry an @version, matching the legacy SDK; a
896
+ # versionless full name is not accepted.
897
+ full_match = re.match(
898
+ r"^publishers/(?P<publisher>[^/]+)/models/(?P<model>[^@]+)@(?P<version>[^@]+)$",
899
+ model_name,
900
+ )
901
+ if full_match:
902
+ return (
903
+ f"publishers/{full_match.group('publisher')}/models/"
904
+ f"{full_match.group('model')}@{full_match.group('version')}"
905
+ )
906
+ # Reject Model Registry names; they would otherwise match the simplified
907
+ # branch and be silently mangled.
908
+ if re.match(r"^projects/.+/locations/.+/models/.+$", model_name):
909
+ raise ValueError(f"`{model_name}` is not a valid publisher model name")
910
+ simplified_match = re.match(
911
+ r"^(?P<publisher>[^/]+)/(?P<model>[^@]+)(?:@(?P<version>.+))?$",
912
+ model_name,
913
+ )
914
+ if simplified_match:
915
+ model = simplified_match.group("model")
916
+ if simplified_match.group("version"):
917
+ model = f"{model}@{simplified_match.group('version')}"
918
+ return f"publishers/{simplified_match.group('publisher')}/models/{model}"
919
+ raise ValueError(f"`{model_name}` is not a valid publisher model name")
920
+
921
+ @staticmethod
922
+ def _is_hugging_face_model(model_name: str) -> bool:
923
+ """Returns whether a model name looks like a Hugging Face model ID.
924
+
925
+ Matches the bare ``'{organization}/{model}'`` shape (a single slash and no
926
+ ``@version``), e.g. ``'meta-llama/Llama-3.3-70B-Instruct'``.
927
+
928
+ Args:
929
+ model_name: The model name to inspect.
930
+
931
+ Returns:
932
+ True if ``model_name`` matches the Hugging Face ID shape.
933
+ """
934
+ import re
935
+
936
+ return bool(re.match(r"^(?P<publisher>[^/]+)/(?P<model>[^/@]+)$", model_name))
937
+
938
+ @staticmethod
939
+ def _matches_filter(
940
+ value: Optional[str],
941
+ model_filter: Optional[Union[str, list[str]]],
942
+ ) -> bool:
943
+ """Returns whether ``value`` matches the (optional) keyword filter.
944
+
945
+ Mirrors the legacy SDK: the filter may be a single keyword or a list of
946
+ keywords, and matching is a case-insensitive substring test where the value
947
+ matches if it contains *any* of the keywords.
948
+
949
+ Args:
950
+ value: The field value to test (e.g. a machine type), or None.
951
+ model_filter: A keyword, a list of keywords, or None (no filtering).
952
+
953
+ Returns:
954
+ True if there is no filter, or if ``value`` contains any of the keywords.
955
+ """
956
+ if not model_filter:
957
+ return True
958
+ if value is None:
959
+ return False
960
+ keywords = [model_filter] if isinstance(model_filter, str) else model_filter
961
+ value_lower = value.lower()
962
+ return any(keyword.lower() in value_lower for keyword in keywords)
963
+
964
+ @staticmethod
965
+ def _extract_and_filter_deploy_options(
966
+ publisher_model: types.PublisherModel,
967
+ machine_type_filter: Optional[Union[str, list[str]]] = None,
968
+ accelerator_type_filter: Optional[Union[str, list[str]]] = None,
969
+ serving_container_image_uri_filter: Optional[Union[str, list[str]]] = None,
970
+ ) -> list[types.DeployOption]:
971
+ """Extracts and filters deploy options from a publisher model.
972
+
973
+ Args:
974
+ publisher_model: The publisher model to extract deploy options from.
975
+ machine_type_filter: Optional case-insensitive keyword (or list of
976
+ keywords) matched against the machine type; an option is kept if its
977
+ machine type contains any of them (e.g. ``'g2'`` or ``['n1', 'g2']``).
978
+ accelerator_type_filter: Optional case-insensitive keyword (or list of
979
+ keywords) matched against the accelerator type (e.g. ``'L4'`` or
980
+ ``['T4', 'L4']``).
981
+ serving_container_image_uri_filter: Optional case-insensitive keyword (or
982
+ list of keywords) matched against the serving container image URI
983
+ (e.g. ``'vllm'`` or ``['vllm', 'tgi']``).
984
+
985
+ Returns:
986
+ A list of ``DeployOption`` objects matching the provided filters.
987
+
988
+ Raises:
989
+ ValueError: If the model does not support deployment, or if no deploy
990
+ options remain after applying the filters.
991
+ """
992
+ if not (
993
+ publisher_model.supported_actions
994
+ and publisher_model.supported_actions.multi_deploy_vertex
995
+ and publisher_model.supported_actions.multi_deploy_vertex.multi_deploy_vertex
996
+ ):
997
+ raise ValueError(
998
+ "Model does not support deployment. "
999
+ "Use `list_deployable_models()` to find supported models."
1000
+ )
1001
+
1002
+ options = (
1003
+ publisher_model.supported_actions.multi_deploy_vertex.multi_deploy_vertex
1004
+ )
1005
+ result = []
1006
+ for opt in options:
1007
+ container = opt.container_spec.image_uri if opt.container_spec else None
1008
+ machine = (
1009
+ opt.dedicated_resources.machine_spec
1010
+ if opt.dedicated_resources
1011
+ else None
1012
+ )
1013
+ machine_type = machine.machine_type if machine else None
1014
+
1015
+ # Restore the proto3 defaults the JSON transport drops, so structured
1016
+ # output matches the gRPC SDK on CPU/TPU machines.
1017
+ accelerator_enum = machine.accelerator_type if machine else None
1018
+ accelerator_value = accelerator_enum.value if accelerator_enum else None
1019
+ has_accelerator = (
1020
+ accelerator_value is not None
1021
+ and accelerator_value != "ACCELERATOR_TYPE_UNSPECIFIED"
1022
+ )
1023
+ if machine:
1024
+ accelerator_type = (
1025
+ accelerator_value
1026
+ if accelerator_value is not None
1027
+ else "ACCELERATOR_TYPE_UNSPECIFIED"
1028
+ )
1029
+ accelerator_count = (
1030
+ machine.accelerator_count
1031
+ if machine.accelerator_count is not None
1032
+ else 0
1033
+ )
1034
+ else:
1035
+ accelerator_type = None
1036
+ accelerator_count = None
1037
+
1038
+ if not ModelGarden._matches_filter(machine_type, machine_type_filter):
1039
+ continue
1040
+ # ACCELERATOR_TYPE_UNSPECIFIED means "no accelerator" and never matches.
1041
+ if accelerator_type_filter and not has_accelerator:
1042
+ continue
1043
+ if not ModelGarden._matches_filter(
1044
+ accelerator_type, accelerator_type_filter
1045
+ ):
1046
+ continue
1047
+ if not ModelGarden._matches_filter(
1048
+ container, serving_container_image_uri_filter
1049
+ ):
1050
+ continue
1051
+
1052
+ result.append(
1053
+ types.DeployOption(
1054
+ option_name=opt.deploy_task_name,
1055
+ serving_container_image_uri=container,
1056
+ machine_type=machine_type,
1057
+ accelerator_type=accelerator_type,
1058
+ accelerator_count=accelerator_count,
1059
+ )
1060
+ )
1061
+
1062
+ if not result:
1063
+ raise ValueError("No deploy options found.")
1064
+
1065
+ return result
1066
+
1067
+ @staticmethod
1068
+ def _format_concise_deploy_options(
1069
+ options: list[types.DeployOption],
1070
+ ) -> str:
1071
+ """Formats deploy options into a human-readable string.
1072
+
1073
+ Mirrors the legacy ``vertexai.model_garden`` SDK output: each option is
1074
+ rendered as a ``[Option N: <option_name>]`` block followed by its non-null
1075
+ fields (container image, machine type, accelerator type/count).
1076
+
1077
+ Args:
1078
+ options: The deploy options to format.
1079
+
1080
+ Returns:
1081
+ A human-readable, multi-line string describing the deploy options.
1082
+ """
1083
+ fields = [
1084
+ "serving_container_image_uri",
1085
+ "machine_type",
1086
+ "accelerator_type",
1087
+ "accelerator_count",
1088
+ ]
1089
+ blocks = []
1090
+ for i, option in enumerate(options):
1091
+ if option.option_name:
1092
+ header = f"[Option {i + 1}: {option.option_name}]\n"
1093
+ else:
1094
+ header = f"[Option {i + 1}]\n"
1095
+ lines = []
1096
+ for field in fields:
1097
+ value = getattr(option, field)
1098
+ if value is None:
1099
+ continue
1100
+ if field == "accelerator_count":
1101
+ lines.append(f" {field}={value},")
1102
+ else:
1103
+ lines.append(f' {field}="{value}",')
1104
+ blocks.append(header + "\n".join(lines))
1105
+ return "\n\n".join(blocks)
1106
+
1107
+ @staticmethod
1108
+ def _resolve_deploy_model_name(model: str) -> tuple[Optional[str], Optional[str]]:
1109
+ """Returns the ``(publisher_model_name, hugging_face_model_id)`` pair for a deploy call.
1110
+
1111
+ Exactly one element of the returned pair is set: HF model IDs (matched
1112
+ by ``_is_hugging_face_model``) are sent as ``huggingFaceModelId`` and
1113
+ lowercased for legacy parity; everything else is reconciled to the full
1114
+ ``publishers/{pub}/models/{model}@{version}`` form.
1115
+ """
1116
+ if ModelGarden._is_hugging_face_model(model):
1117
+ return None, model.lower()
1118
+ return ModelGarden._reconcile_model_name(model), None
1119
+
1120
+ @staticmethod
1121
+ def _validate_deploy_config(
1122
+ config: types.DeployPublisherModelConfig,
1123
+ ) -> None:
1124
+ """Rejects deploy configs whose options would be silently dropped.
1125
+
1126
+ Container overrides are only sent when ``serving_container_image_uri``
1127
+ is set (the backend has no way to patch a model's default container
1128
+ piecemeal), so a config that sets only the command/args/env would
1129
+ otherwise deploy successfully with none of the overrides applied.
1130
+
1131
+ Raises:
1132
+ ValueError: If any container override is set without
1133
+ ``serving_container_image_uri``.
1134
+ """
1135
+ if config.serving_container_image_uri:
1136
+ return
1137
+ dropped = [
1138
+ name
1139
+ for name in ("container_command", "container_args", "container_variables")
1140
+ if getattr(config, name)
1141
+ ]
1142
+ if dropped:
1143
+ raise ValueError(
1144
+ f"{', '.join(dropped)} require serving_container_image_uri to "
1145
+ "also be set; container overrides are only applied on top of an "
1146
+ "explicit serving container image."
1147
+ )
1148
+
1149
+ @staticmethod
1150
+ def _deploy_response_or_raise(
1151
+ operation: types.DeployModelOperation,
1152
+ ) -> types.DeployResponse:
1153
+ """Unwraps a completed deploy LRO into its ``DeployResponse``.
1154
+
1155
+ Shared by the sync and async surfaces so the two cannot drift as the
1156
+ LRO shape evolves.
1157
+
1158
+ Raises:
1159
+ RuntimeError: If the LRO carries an error, or completed without an
1160
+ endpoint resource name.
1161
+ """
1162
+ if operation.error:
1163
+ raise RuntimeError(f"Deploy failed: {operation.error}")
1164
+ if not operation.response or not operation.response.endpoint:
1165
+ raise RuntimeError(
1166
+ f"Deploy completed but response has no endpoint: {operation!r}"
1167
+ )
1168
+ return operation.response
1169
+
1170
+ @staticmethod
1171
+ def _build_container_spec(
1172
+ config: types.DeployPublisherModelConfig,
1173
+ ) -> Optional[types.ModelContainerSpec]:
1174
+ """Returns a ``ModelContainerSpec`` when the user overrides the container, else None."""
1175
+ if not config.serving_container_image_uri:
1176
+ return None
1177
+ env = None
1178
+ variables = config.container_variables
1179
+ if variables:
1180
+ env = [types.EnvVar(name=k, value=v) for k, v in variables.items()]
1181
+ return types.ModelContainerSpec(
1182
+ image_uri=config.serving_container_image_uri,
1183
+ command=config.container_command,
1184
+ args=config.container_args,
1185
+ env=env,
1186
+ )
1187
+
1188
+ @staticmethod
1189
+ def _prepare_deploy_request(
1190
+ config: types.DeployPublisherModelConfig,
1191
+ ) -> tuple[
1192
+ types.DeployRequestModelConfig,
1193
+ types.DeployRequestEndpointConfig,
1194
+ types.DeployRequestDeployConfig,
1195
+ ]:
1196
+ """Translates ``DeployPublisherModelConfig`` to the three ``DeployRequest`` sub-messages."""
1197
+ model_config = types.DeployRequestModelConfig(
1198
+ accept_eula=config.accept_eula,
1199
+ model_display_name=config.model_display_name,
1200
+ hugging_face_access_token=config.hugging_face_access_token,
1201
+ container_spec=ModelGarden._build_container_spec(config),
1202
+ )
1203
+
1204
+ endpoint_config = types.DeployRequestEndpointConfig(
1205
+ endpoint_display_name=config.endpoint_display_name,
1206
+ )
1207
+ disabled = config.dedicated_endpoint_disabled
1208
+ if disabled is not None:
1209
+ endpoint_config.dedicated_endpoint_enabled = not disabled
1210
+ if config.enable_private_service_connect:
1211
+ endpoint_config.private_service_connect_config = (
1212
+ types.PrivateServiceConnectConfig(
1213
+ enable_private_service_connect=True,
1214
+ project_allowlist=config.psc_project_allow_list,
1215
+ )
1216
+ )
1217
+
1218
+ deploy_config = types.DeployRequestDeployConfig(
1219
+ fast_tryout_enabled=config.fast_tryout_enabled,
1220
+ )
1221
+ if config.machine_type or config.accelerator_type or config.accelerator_count:
1222
+ deploy_config.dedicated_resources = types.DedicatedResources(
1223
+ machine_spec=types.MachineSpec(
1224
+ machine_type=config.machine_type,
1225
+ accelerator_type=config.accelerator_type,
1226
+ accelerator_count=config.accelerator_count,
1227
+ ),
1228
+ min_replica_count=config.min_replica_count,
1229
+ max_replica_count=config.max_replica_count,
1230
+ spot=config.spot,
1231
+ )
1232
+
1233
+ return model_config, endpoint_config, deploy_config
1234
+
1235
+ def _list_all_publisher_models(
1236
+ self,
1237
+ api_config: types.ListPublisherModelsConfig,
1238
+ ) -> list[types.PublisherModel]:
1239
+ """Fetches all pages of publisher models from the API.
1240
+
1241
+ Args:
1242
+ api_config: The configuration for the ListPublisherModels API call,
1243
+ including filter and version settings.
1244
+
1245
+ Returns:
1246
+ A list of all ``PublisherModel`` objects across all pages.
1247
+ """
1248
+ all_models = []
1249
+ page_token = None
1250
+ while True:
1251
+ if page_token:
1252
+ api_config = types.ListPublisherModelsConfig(
1253
+ filter=api_config.filter,
1254
+ list_all_versions=api_config.list_all_versions,
1255
+ page_token=page_token,
1256
+ )
1257
+ response = self._list_publisher_models(
1258
+ parent="publishers/*",
1259
+ config=api_config,
1260
+ )
1261
+ all_models.extend(response.publisher_models or [])
1262
+ page_token = response.next_page_token
1263
+ if not page_token:
1264
+ break
1265
+ return all_models
1266
+
1267
+ def _list(
1268
+ self,
1269
+ model_filter: Optional[str],
1270
+ include_hugging_face_models: Optional[bool],
1271
+ deployable_only: bool,
1272
+ ) -> list[str]:
1273
+ """Shared implementation for listing models.
1274
+
1275
+ Args:
1276
+ model_filter: Optional substring to filter models by.
1277
+ include_hugging_face_models: Whether to include HuggingFace models.
1278
+ deployable_only: If True, only return models with deployment configs.
1279
+
1280
+ Returns:
1281
+ A list of formatted model name strings.
1282
+ """
1283
+ include_hf = include_hugging_face_models is True
1284
+
1285
+ filter_str = self._build_filter_str(
1286
+ model_filter, include_hf, deployable_only=deployable_only
1287
+ )
1288
+
1289
+ api_config = types.ListPublisherModelsConfig(
1290
+ filter=filter_str,
1291
+ list_all_versions=True,
1292
+ )
1293
+
1294
+ models = self._list_all_publisher_models(api_config)
1295
+
1296
+ if deployable_only:
1297
+ # The VERIFIED_DEPLOYMENT_SUCCEED server filter only applies to HF
1298
+ # models; filter native models client-side via multi_deploy_vertex.
1299
+ models = [m for m in models if self._has_deploy_config(m)]
1300
+
1301
+ return [self._format_model_name(m, include_hf) for m in models]
1302
+
1303
+ def list_deployable_models(
1304
+ self,
1305
+ config: Optional[types.ListDeployableModelsConfigOrDict] = None,
1306
+ ) -> list[str]:
1307
+ """Lists models in Model Garden that support deployment.
1308
+
1309
+ Returns models that have at least one verified deployment configuration.
1310
+ When ``include_hugging_face_models`` is False (the default),
1311
+ HuggingFace models are excluded from the results.
1312
+
1313
+ Args:
1314
+ config: Optional configuration for filtering results. Accepts a
1315
+ ``ListDeployableModelsConfig`` instance or an equivalent dict.
1316
+
1317
+ Returns:
1318
+ A list of model name strings in the format
1319
+ ``'{publisher}/{model}@{version}'`` (e.g. ``'google/gemma2@gemma-2-2b-it'``)
1320
+ or ``'{publisher}/{model}'`` when ``include_hugging_face_models`` is True
1321
+ (e.g. ``'meta-llama/Llama-3.3-70B-Instruct'``).
1322
+ """
1323
+ if config is None:
1324
+ config = types.ListDeployableModelsConfig()
1325
+ if isinstance(config, dict):
1326
+ config = types.ListDeployableModelsConfig.model_validate(config)
1327
+
1328
+ return self._list(
1329
+ config.model_filter,
1330
+ config.include_hugging_face_models,
1331
+ deployable_only=True,
1332
+ )
1333
+
1334
+ def list_models(
1335
+ self,
1336
+ config: Optional[types.ListModelGardenModelsConfigOrDict] = None,
1337
+ ) -> list[str]:
1338
+ """Lists all models available in Model Garden.
1339
+
1340
+ Returns all models regardless of deployment support. When
1341
+ ``include_hugging_face_models`` is False (the default), HuggingFace
1342
+ models are excluded from the results.
1343
+
1344
+ Args:
1345
+ config: Optional configuration for filtering results. Accepts a
1346
+ ``ListModelGardenModelsConfig`` instance or an equivalent dict.
1347
+
1348
+ Returns:
1349
+ A list of model name strings in the format
1350
+ ``'{publisher}/{model}@{version}'`` (e.g. ``'google/gemma2@gemma-2-2b-it'``)
1351
+ or ``'{publisher}/{model}'`` when ``include_hugging_face_models`` is True
1352
+ (e.g. ``'meta-llama/Llama-3.3-70B-Instruct'``).
1353
+ """
1354
+ if config is None:
1355
+ config = types.ListModelGardenModelsConfig()
1356
+ if isinstance(config, dict):
1357
+ config = types.ListModelGardenModelsConfig.model_validate(config)
1358
+
1359
+ return self._list(
1360
+ config.model_filter,
1361
+ config.include_hugging_face_models,
1362
+ deployable_only=False,
1363
+ )
1364
+
1365
+ def list_publisher_model_deploy_options(
1366
+ self,
1367
+ model: str,
1368
+ config: Optional[types.ListPublisherModelDeployOptionsConfigOrDict] = None,
1369
+ ) -> Union[str, list[types.DeployOption]]:
1370
+ """Lists the verified deploy options for a Model Garden publisher model.
1371
+
1372
+ Supports Google open models (e.g. ``'google/gemma3@gemma-3-12b-it'``),
1373
+ partner publisher models (e.g.
1374
+ ``'mistralai/mistral-7b@mistral-7b-instruct-v0.2'``), and Hugging Face
1375
+ model IDs (e.g. ``'meta-llama/Llama-3.3-70B-Instruct'``).
1376
+
1377
+ Args:
1378
+ model: The publisher model to list deploy options for. Accepts the full
1379
+ resource name ``'publishers/{publisher}/models/{model}@{version}'``, a
1380
+ simplified ``'{publisher}/{model}@{version}'`` (or without the
1381
+ ``@{version}``), or a Hugging Face model ID ``'{organization}/{model}'``.
1382
+ config: Optional configuration for filtering the deploy options. Accepts a
1383
+ ``ListPublisherModelDeployOptionsConfig`` instance or an equivalent
1384
+ dict.
1385
+
1386
+ Returns:
1387
+ A list of ``DeployOption`` objects, one per verified deployment
1388
+ configuration (container image URI, machine type, accelerator type and
1389
+ count). If ``config.concise`` is True, returns a human-readable string
1390
+ describing those deploy options instead.
1391
+
1392
+ Raises:
1393
+ ValueError: If ``model`` is not a valid publisher model name, if the
1394
+ model does not support deployment, or if no deploy options match the
1395
+ provided filters.
1396
+ """
1397
+ if config is None:
1398
+ config = types.ListPublisherModelDeployOptionsConfig()
1399
+ if isinstance(config, dict):
1400
+ config = types.ListPublisherModelDeployOptionsConfig.model_validate(config)
1401
+
1402
+ get_publisher_model_config = types.GetPublisherModelConfig(
1403
+ is_hugging_face_model=self._is_hugging_face_model(model),
1404
+ include_equivalent_model_garden_model_deployment_configs=True,
1405
+ )
1406
+ publisher_model = self._get_publisher_model(
1407
+ name=self._reconcile_model_name(model),
1408
+ config=get_publisher_model_config,
1409
+ )
1410
+
1411
+ options = self._extract_and_filter_deploy_options(
1412
+ publisher_model,
1413
+ machine_type_filter=config.machine_type_filter,
1414
+ accelerator_type_filter=config.accelerator_type_filter,
1415
+ serving_container_image_uri_filter=config.serving_container_image_uri_filter,
1416
+ )
1417
+
1418
+ if config.concise is True:
1419
+ return self._format_concise_deploy_options(options)
1420
+
1421
+ return options
1422
+
1423
+ def deploy_publisher_model(
1424
+ self,
1425
+ *,
1426
+ model: str,
1427
+ config: Optional[types.DeployPublisherModelConfigOrDict] = None,
1428
+ ) -> Union[types.DeployResponse, types.DeployModelOperation]:
1429
+ """Deploys a Model Garden publisher model to a Vertex AI endpoint.
1430
+
1431
+ Supports Google open models (e.g. ``'google/gemma3@gemma-3-12b-it'``),
1432
+ partner publisher models (e.g. ``'ai21/jamba-large-1.6@001'``), and
1433
+ Hugging Face model IDs (e.g. ``'meta-llama/Llama-3.3-70B-Instruct'``).
1434
+
1435
+ Args:
1436
+ model: The publisher model to deploy. Accepts the full resource name
1437
+ ``'publishers/{publisher}/models/{model}@{version}'``, a simplified
1438
+ ``'{publisher}/{model}@{version}'`` (or without the ``@{version}``),
1439
+ or a Hugging Face model ID ``'{organization}/{model}'``. Hugging
1440
+ Face model IDs are lowercased before being sent, matching
1441
+ ``vertexai.model_garden.OpenModel.deploy`` behavior.
1442
+ config: Optional deployment configuration (machine shape, container
1443
+ overrides, blocking behavior, poll timing). See
1444
+ ``DeployPublisherModelConfig``.
1445
+
1446
+ Returns:
1447
+ When ``config.wait_for_completion`` is ``True`` (default), a
1448
+ ``DeployResponse`` carrying the deployed ``endpoint`` and ``model``
1449
+ resource names (both ``str``).
1450
+ When ``config.wait_for_completion`` is ``False``, the
1451
+ ``DeployModelOperation`` for the caller to poll (via
1452
+ ``get_deploy_publisher_model_operation`` or their own strategy).
1453
+
1454
+ Raises:
1455
+ ValueError: If ``model`` is not a valid publisher model name, or if
1456
+ container overrides are set without ``serving_container_image_uri``.
1457
+ TimeoutError: If ``wait_for_completion=True`` and the LRO does not
1458
+ complete within ``config.timeout_seconds`` (default 2 hours).
1459
+ RuntimeError: If the LRO completes with an error, or completes
1460
+ successfully but without an endpoint resource name.
1461
+ """
1462
+ if config is None:
1463
+ config = types.DeployPublisherModelConfig()
1464
+ elif isinstance(config, dict):
1465
+ config = types.DeployPublisherModelConfig.model_validate(config)
1466
+ ModelGarden._validate_deploy_config(config)
1467
+
1468
+ publisher_model_name, hugging_face_model_id = (
1469
+ ModelGarden._resolve_deploy_model_name(model)
1470
+ )
1471
+ model_config, endpoint_config, deploy_config = (
1472
+ ModelGarden._prepare_deploy_request(config)
1473
+ )
1474
+ destination = (
1475
+ f"projects/{self._api_client.project}/locations/"
1476
+ f"{self._api_client.location}"
1477
+ )
1478
+
1479
+ operation = self._deploy(
1480
+ destination=destination,
1481
+ publisher_model_name=publisher_model_name,
1482
+ hugging_face_model_id=hugging_face_model_id,
1483
+ model_config_val=model_config,
1484
+ endpoint_config=endpoint_config,
1485
+ deploy_config=deploy_config,
1486
+ )
1487
+ if not config.wait_for_completion:
1488
+ return operation
1489
+
1490
+ operation = _operations_utils.await_operation(
1491
+ operation_name=operation.name,
1492
+ get_operation_fn=self.get_deploy_publisher_model_operation,
1493
+ poll_interval=(
1494
+ config.poll_interval_seconds
1495
+ or ModelGarden._DEFAULT_DEPLOY_POLL_INTERVAL_SECONDS
1496
+ ),
1497
+ timeout_seconds=(
1498
+ config.timeout_seconds or ModelGarden._DEFAULT_DEPLOY_TIMEOUT_SECONDS
1499
+ ),
1500
+ )
1501
+ return ModelGarden._deploy_response_or_raise(operation)
1502
+
1503
+ @staticmethod
1504
+ def _extract_recommend_spec(spec) -> dict[str, Any]:
1505
+ """Extracts machine spec fields from a single recommend-spec entry.
1506
+
1507
+ Args:
1508
+ spec: A ``RecommendSpecResponseMachineAndModelContainerSpec`` describing a
1509
+ recommended machine and container configuration.
1510
+
1511
+ Returns:
1512
+ A dict with the ``machine_type``, ``accelerator_type`` and
1513
+ ``accelerator_count`` of the spec (values may be ``None``).
1514
+ """
1515
+ machine_spec = spec.machine_spec
1516
+ machine_type = None
1517
+ accelerator_type = None
1518
+ accelerator_count = None
1519
+
1520
+ if machine_spec:
1521
+ machine_type = getattr(machine_spec, "machine_type", None)
1522
+ accelerator_enum = getattr(machine_spec, "accelerator_type", None)
1523
+ if accelerator_enum:
1524
+ accelerator_type = getattr(accelerator_enum, "name", None)
1525
+ accelerator_count = getattr(machine_spec, "accelerator_count", None)
1526
+
1527
+ return {
1528
+ "machine_type": machine_type,
1529
+ "accelerator_type": accelerator_type,
1530
+ "accelerator_count": accelerator_count,
1531
+ }
1532
+
1533
+ @staticmethod
1534
+ def _extract_recommendation(recommendation) -> dict[str, Any]:
1535
+ """Extracts the spec, region and user quota state from a recommendation.
1536
+
1537
+ Args:
1538
+ recommendation: A ``RecommendSpecResponseRecommendation`` returned when
1539
+ machine availability is requested.
1540
+
1541
+ Returns:
1542
+ A dict with the machine spec fields plus ``region`` and, when known, the
1543
+ ``user_quota_state``.
1544
+ """
1545
+ extracted_spec = ModelGarden._extract_recommend_spec(recommendation.spec)
1546
+ extracted_spec["region"] = getattr(recommendation, "region", None)
1547
+ if (
1548
+ recommendation.user_quota_state
1549
+ and recommendation.user_quota_state
1550
+ != types.QuotaState.QUOTA_STATE_UNSPECIFIED
1551
+ ):
1552
+ extracted_spec["user_quota_state"] = recommendation.user_quota_state.name
1553
+ return extracted_spec
1554
+
1555
+ @staticmethod
1556
+ def _format_custom_deploy_options(options: list[dict[str, Any]]) -> str:
1557
+ """Formats custom model deploy options into a human-readable string.
1558
+
1559
+ Mirrors the legacy ``vertexai.model_garden`` ``CustomModel`` SDK output:
1560
+ each option is rendered as an ``[Option N]`` block followed by its non-null
1561
+ fields; ``accelerator_count`` is rendered unquoted.
1562
+
1563
+ Args:
1564
+ options: The extracted deploy option dicts to format.
1565
+
1566
+ Returns:
1567
+ A human-readable, multi-line string describing the deploy options.
1568
+ """
1569
+ return "\n\n".join(
1570
+ f"[Option {i + 1}]\n"
1571
+ + ",\n".join(
1572
+ f' {k}="{v}"' if k != "accelerator_count" else f" {k}={v}"
1573
+ for k, v in option.items()
1574
+ if v is not None
1575
+ )
1576
+ for i, option in enumerate(options)
1577
+ )
1578
+
1579
+ def list_custom_model_deploy_options(
1580
+ self,
1581
+ src: str,
1582
+ config: Optional[types.ListCustomModelDeployOptionsConfigOrDict] = None,
1583
+ ) -> str:
1584
+ """Lists the recommended deploy options for a Model Garden custom model.
1585
+
1586
+ Args:
1587
+ src: The Google Cloud Storage URI of the custom model, storing the model
1588
+ weights and config files (e.g. ``'gs://my-bucket/weights/'``).
1589
+ config: Optional configuration. Accepts a
1590
+ ``ListCustomModelDeployOptionsConfig`` instance or an equivalent dict.
1591
+
1592
+ Returns:
1593
+ A human-readable string describing the recommended deploy options
1594
+ (machine type, accelerator type/count, region and, when available, the
1595
+ user quota state).
1596
+
1597
+ Raises:
1598
+ ValueError: If ``src`` is not specified, or if no deploy options are
1599
+ returned by the API (either because the backend produced none or
1600
+ because the ``filter_by_user_quota`` filter dropped them all).
1601
+ """
1602
+ if not src:
1603
+ raise ValueError("src must be specified.")
1604
+ if config is None:
1605
+ config = types.ListCustomModelDeployOptionsConfig()
1606
+ if isinstance(config, dict):
1607
+ config = types.ListCustomModelDeployOptionsConfig.model_validate(config)
1608
+
1609
+ parent = (
1610
+ f"projects/{self._api_client.project}/locations/"
1611
+ f"{self._api_client.location}"
1612
+ )
1613
+
1614
+ api_config = types.RecommendSpecConfig(
1615
+ check_machine_availability=config.check_machine_availability,
1616
+ check_user_quota=config.filter_by_user_quota,
1617
+ )
1618
+
1619
+ response = self._recommend_spec(
1620
+ parent=parent,
1621
+ gcs_uri=src,
1622
+ config=api_config,
1623
+ )
1624
+
1625
+ options = []
1626
+ if response.recommendations:
1627
+ options = [
1628
+ self._extract_recommendation(recommendation)
1629
+ for recommendation in response.recommendations
1630
+ if recommendation.spec
1631
+ ]
1632
+ if config.filter_by_user_quota:
1633
+ options = [
1634
+ option
1635
+ for option in options
1636
+ if option.get("user_quota_state")
1637
+ == types.QuotaState.QUOTA_STATE_USER_HAS_QUOTA.name
1638
+ ]
1639
+ elif response.specs:
1640
+ options = [
1641
+ self._extract_recommend_spec(spec) for spec in response.specs if spec
1642
+ ]
1643
+
1644
+ if not options:
1645
+ raise ValueError("No deploy options found.")
1646
+
1647
+ return self._format_custom_deploy_options(options)
1648
+
1649
+ def export_open_model(
1650
+ self,
1651
+ *,
1652
+ model: str,
1653
+ output_gcs_uri: str,
1654
+ config: Optional[types.ExportOpenModelConfigOrDict] = None,
1655
+ ) -> Union[str, types.ExportModelOperation]:
1656
+ """Exports Google open model weights to a Cloud Storage bucket.
1657
+
1658
+ Args:
1659
+ model: The publisher model to export. Accepts a full resource name
1660
+ ``'publishers/{publisher}/models/{model}@{version}'`` or the
1661
+ simplified form ``'{publisher}/{model}@{version}'``. Hugging Face
1662
+ model IDs are not supported.
1663
+ output_gcs_uri: Cloud Storage URI prefix to write the model weights
1664
+ to (e.g. ``'gs://my-bucket/gemma-weights/'``).
1665
+ config: Optional export configuration (blocking behavior, poll
1666
+ timing). See ``ExportOpenModelConfig``.
1667
+
1668
+ Returns:
1669
+ When ``config.wait_for_completion`` is ``True`` (default), the
1670
+ Cloud Storage URI (``str``) where the weights were written.
1671
+ When ``config.wait_for_completion`` is ``False``, the
1672
+ ``ExportModelOperation`` for the caller to poll (via
1673
+ ``get_export_publisher_model_operation`` or their own strategy).
1674
+
1675
+ Raises:
1676
+ ValueError: If ``output_gcs_uri`` is empty, if ``model`` is not a
1677
+ valid Google publisher model resource name, or if ``model`` looks
1678
+ like a Hugging Face model ID.
1679
+ TimeoutError: If ``wait_for_completion=True`` and the LRO does not
1680
+ complete within ``config.timeout_seconds`` (default 2 hours).
1681
+ RuntimeError: If the LRO completes with an error, or completes
1682
+ successfully but without a ``destination_uri``.
1683
+ """
1684
+ if not output_gcs_uri:
1685
+ raise ValueError("output_gcs_uri must be a non-empty Cloud Storage URI.")
1686
+ if ModelGarden._is_hugging_face_model(model):
1687
+ raise ValueError(
1688
+ f"export_open_model does not support Hugging Face model IDs "
1689
+ f"(got {model!r}); only Google publisher open models are "
1690
+ f"exportable via this API."
1691
+ )
1692
+ if config is None:
1693
+ config = types.ExportOpenModelConfig()
1694
+ elif isinstance(config, dict):
1695
+ config = types.ExportOpenModelConfig.model_validate(config)
1696
+
1697
+ publisher_model_name = ModelGarden._reconcile_model_name(model)
1698
+ parent = (
1699
+ f"projects/{self._api_client.project}/locations/"
1700
+ f"{self._api_client.location}"
1701
+ )
1702
+ operation = self._export_publisher_model(
1703
+ parent=parent,
1704
+ name=publisher_model_name,
1705
+ config=types.ExportPublisherModelConfig(
1706
+ destination=genai_types.GcsDestination(
1707
+ output_uri_prefix=output_gcs_uri,
1708
+ ),
1709
+ ),
1710
+ )
1711
+ if not config.wait_for_completion:
1712
+ return operation
1713
+
1714
+ operation = _operations_utils.await_operation(
1715
+ operation_name=operation.name,
1716
+ get_operation_fn=self.get_export_publisher_model_operation,
1717
+ poll_interval=(
1718
+ config.poll_interval_seconds
1719
+ or ModelGarden._DEFAULT_EXPORT_POLL_INTERVAL_SECONDS
1720
+ ),
1721
+ timeout_seconds=(
1722
+ config.timeout_seconds or ModelGarden._DEFAULT_EXPORT_TIMEOUT_SECONDS
1723
+ ),
1724
+ )
1725
+ if operation.error:
1726
+ raise RuntimeError(f"Export failed: {operation.error}")
1727
+ if not operation.response or not operation.response.destination_uri:
1728
+ raise RuntimeError(
1729
+ f"Export completed but response has no destination_uri: "
1730
+ f"{operation!r}"
1731
+ )
1732
+ return operation.response.destination_uri
1733
+
1734
+
1735
+ class AsyncModelGarden(_api_module.BaseModule):
1736
+ """Model Garden module."""
1737
+
1738
+ async def _list_publisher_models(
1739
+ self,
1740
+ *,
1741
+ parent: Optional[str] = None,
1742
+ config: Optional[types.ListPublisherModelsConfigOrDict] = None,
1743
+ ) -> types.ListPublisherModelsResponse:
1744
+ """
1745
+ Lists publisher models (internal).
1746
+ """
1747
+
1748
+ parameter_model = types._ListPublisherModelsRequestParameters(
1749
+ parent=parent,
1750
+ config=config,
1751
+ )
1752
+
1753
+ request_url_dict: Optional[dict[str, str]]
1754
+ if not self._api_client.vertexai:
1755
+ raise ValueError(
1756
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
1757
+ )
1758
+ else:
1759
+ request_dict = _ListPublisherModelsRequestParameters_to_vertex(
1760
+ parameter_model
1761
+ )
1762
+ request_url_dict = request_dict.get("_url")
1763
+ if request_url_dict:
1764
+ path = "{parent}/models".format_map(request_url_dict)
1765
+ else:
1766
+ path = "{parent}/models"
1767
+
1768
+ query_params = request_dict.get("_query")
1769
+ if query_params:
1770
+ path = f"{path}?{urlencode(query_params)}"
1771
+ # TODO: remove the hack that pops config.
1772
+ request_dict.pop("config", None)
1773
+
1774
+ http_options: Optional[types.HttpOptions] = None
1775
+ if (
1776
+ parameter_model.config is not None
1777
+ and parameter_model.config.http_options is not None
1778
+ ):
1779
+ http_options = parameter_model.config.http_options
1780
+
1781
+ request_dict = _common.convert_to_dict(request_dict)
1782
+ request_dict = _common.encode_unserializable_types(request_dict)
1783
+
1784
+ response = await self._api_client.async_request(
1785
+ "get", path, request_dict, http_options
1786
+ )
1787
+
1788
+ response_dict = {} if not response.body else json.loads(response.body)
1789
+
1790
+ return_value = types.ListPublisherModelsResponse._from_response(
1791
+ response=response_dict,
1792
+ kwargs=(
1793
+ {
1794
+ "config": {
1795
+ "response_schema": getattr(
1796
+ parameter_model.config, "response_schema", None
1797
+ ),
1798
+ "response_json_schema": getattr(
1799
+ parameter_model.config, "response_json_schema", None
1800
+ ),
1801
+ "include_all_fields": getattr(
1802
+ parameter_model.config, "include_all_fields", None
1803
+ ),
1804
+ }
1805
+ }
1806
+ if getattr(parameter_model, "config", None)
1807
+ else {}
1808
+ ),
1809
+ )
1810
+
1811
+ self._api_client._verify_response(return_value)
1812
+ return return_value
1813
+
1814
+ async def _get_publisher_model(
1815
+ self, *, name: str, config: Optional[types.GetPublisherModelConfigOrDict] = None
1816
+ ) -> types.PublisherModel:
1817
+ """
1818
+ Gets a publisher model (internal).
1819
+ """
1820
+
1821
+ parameter_model = types._GetPublisherModelRequestParameters(
1822
+ name=name,
1823
+ config=config,
1824
+ )
1825
+
1826
+ request_url_dict: Optional[dict[str, str]]
1827
+ if not self._api_client.vertexai:
1828
+ raise ValueError(
1829
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
1830
+ )
1831
+ else:
1832
+ request_dict = _GetPublisherModelRequestParameters_to_vertex(
1833
+ parameter_model
1834
+ )
1835
+ request_url_dict = request_dict.get("_url")
1836
+ if request_url_dict:
1837
+ path = "{name}".format_map(request_url_dict)
1838
+ else:
1839
+ path = "{name}"
1840
+
1841
+ query_params = request_dict.get("_query")
1842
+ if query_params:
1843
+ path = f"{path}?{urlencode(query_params)}"
1844
+ # TODO: remove the hack that pops config.
1845
+ request_dict.pop("config", None)
1846
+
1847
+ http_options: Optional[types.HttpOptions] = None
1848
+ if (
1849
+ parameter_model.config is not None
1850
+ and parameter_model.config.http_options is not None
1851
+ ):
1852
+ http_options = parameter_model.config.http_options
1853
+
1854
+ request_dict = _common.convert_to_dict(request_dict)
1855
+ request_dict = _common.encode_unserializable_types(request_dict)
1856
+
1857
+ response = await self._api_client.async_request(
1858
+ "get", path, request_dict, http_options
1859
+ )
1860
+
1861
+ response_dict = {} if not response.body else json.loads(response.body)
1862
+
1863
+ return_value = types.PublisherModel._from_response(
1864
+ response=response_dict,
1865
+ kwargs=(
1866
+ {
1867
+ "config": {
1868
+ "response_schema": getattr(
1869
+ parameter_model.config, "response_schema", None
1870
+ ),
1871
+ "response_json_schema": getattr(
1872
+ parameter_model.config, "response_json_schema", None
1873
+ ),
1874
+ "include_all_fields": getattr(
1875
+ parameter_model.config, "include_all_fields", None
1876
+ ),
1877
+ }
1878
+ }
1879
+ if getattr(parameter_model, "config", None)
1880
+ else {}
1881
+ ),
1882
+ )
1883
+
1884
+ self._api_client._verify_response(return_value)
1885
+ return return_value
1886
+
1887
+ async def _recommend_spec(
1888
+ self,
1889
+ *,
1890
+ parent: str,
1891
+ gcs_uri: str,
1892
+ config: Optional[types.RecommendSpecConfigOrDict] = None,
1893
+ ) -> types.RecommendSpecResponse:
1894
+ """
1895
+ Recommends spec for a custom model (internal).
1896
+ """
1897
+
1898
+ parameter_model = types._RecommendSpecRequestParameters(
1899
+ parent=parent,
1900
+ gcs_uri=gcs_uri,
1901
+ config=config,
1902
+ )
1903
+
1904
+ request_url_dict: Optional[dict[str, str]]
1905
+ if not self._api_client.vertexai:
1906
+ raise ValueError(
1907
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
1908
+ )
1909
+ else:
1910
+ request_dict = _RecommendSpecRequestParameters_to_vertex(parameter_model)
1911
+ request_url_dict = request_dict.get("_url")
1912
+ if request_url_dict:
1913
+ path = "{parent}:recommendSpec".format_map(request_url_dict)
1914
+ else:
1915
+ path = "{parent}:recommendSpec"
1916
+
1917
+ query_params = request_dict.get("_query")
1918
+ if query_params:
1919
+ path = f"{path}?{urlencode(query_params)}"
1920
+ # TODO: remove the hack that pops config.
1921
+ request_dict.pop("config", None)
1922
+
1923
+ http_options: Optional[types.HttpOptions] = None
1924
+ if (
1925
+ parameter_model.config is not None
1926
+ and parameter_model.config.http_options is not None
1927
+ ):
1928
+ http_options = parameter_model.config.http_options
1929
+
1930
+ request_dict = _common.convert_to_dict(request_dict)
1931
+ request_dict = _common.encode_unserializable_types(request_dict)
1932
+
1933
+ response = await self._api_client.async_request(
1934
+ "post", path, request_dict, http_options
1935
+ )
1936
+
1937
+ response_dict = {} if not response.body else json.loads(response.body)
1938
+
1939
+ return_value = types.RecommendSpecResponse._from_response(
1940
+ response=response_dict,
1941
+ kwargs=(
1942
+ {
1943
+ "config": {
1944
+ "response_schema": getattr(
1945
+ parameter_model.config, "response_schema", None
1946
+ ),
1947
+ "response_json_schema": getattr(
1948
+ parameter_model.config, "response_json_schema", None
1949
+ ),
1950
+ "include_all_fields": getattr(
1951
+ parameter_model.config, "include_all_fields", None
1952
+ ),
1953
+ }
1954
+ }
1955
+ if getattr(parameter_model, "config", None)
1956
+ else {}
1957
+ ),
1958
+ )
1959
+
1960
+ self._api_client._verify_response(return_value)
1961
+ return return_value
1962
+
1963
+ async def _export_publisher_model(
1964
+ self,
1965
+ *,
1966
+ parent: str,
1967
+ name: str,
1968
+ config: Optional[types.ExportPublisherModelConfigOrDict] = None,
1969
+ ) -> types.ExportModelOperation:
1970
+ """
1971
+ Exports a publisher model (internal).
1972
+ """
1973
+
1974
+ parameter_model = types._ExportPublisherModelRequestParameters(
1975
+ parent=parent,
1976
+ name=name,
1977
+ config=config,
1978
+ )
1979
+
1980
+ request_url_dict: Optional[dict[str, str]]
1981
+ if not self._api_client.vertexai:
1982
+ raise ValueError(
1983
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
1984
+ )
1985
+ else:
1986
+ request_dict = _ExportPublisherModelRequestParameters_to_vertex(
1987
+ parameter_model
1988
+ )
1989
+ request_url_dict = request_dict.get("_url")
1990
+ if request_url_dict:
1991
+ path = "{parent}/{name}:export".format_map(request_url_dict)
1992
+ else:
1993
+ path = "{parent}/{name}:export"
1994
+
1995
+ query_params = request_dict.get("_query")
1996
+ if query_params:
1997
+ path = f"{path}?{urlencode(query_params)}"
1998
+ # TODO: remove the hack that pops config.
1999
+ request_dict.pop("config", None)
2000
+
2001
+ http_options: Optional[types.HttpOptions] = None
2002
+ if (
2003
+ parameter_model.config is not None
2004
+ and parameter_model.config.http_options is not None
2005
+ ):
2006
+ http_options = parameter_model.config.http_options
2007
+
2008
+ request_dict = _common.convert_to_dict(request_dict)
2009
+ request_dict = _common.encode_unserializable_types(request_dict)
2010
+
2011
+ response = await self._api_client.async_request(
2012
+ "post", path, request_dict, http_options
2013
+ )
2014
+
2015
+ response_dict = {} if not response.body else json.loads(response.body)
2016
+
2017
+ return_value = types.ExportModelOperation._from_response(
2018
+ response=response_dict,
2019
+ kwargs=(
2020
+ {
2021
+ "config": {
2022
+ "response_schema": getattr(
2023
+ parameter_model.config, "response_schema", None
2024
+ ),
2025
+ "response_json_schema": getattr(
2026
+ parameter_model.config, "response_json_schema", None
2027
+ ),
2028
+ "include_all_fields": getattr(
2029
+ parameter_model.config, "include_all_fields", None
2030
+ ),
2031
+ }
2032
+ }
2033
+ if getattr(parameter_model, "config", None)
2034
+ else {}
2035
+ ),
2036
+ )
2037
+
2038
+ self._api_client._verify_response(return_value)
2039
+ return return_value
2040
+
2041
+ async def get_export_publisher_model_operation(
2042
+ self,
2043
+ *,
2044
+ operation_name: str,
2045
+ config: Optional[types.GetExportPublisherModelOperationConfigOrDict] = None,
2046
+ ) -> types.ExportModelOperation:
2047
+ """
2048
+ Fetches the status of an in-flight export_open_model LRO.
2049
+ """
2050
+
2051
+ parameter_model = types._GetExportPublisherModelOperationParameters(
2052
+ operation_name=operation_name,
2053
+ config=config,
2054
+ )
2055
+
2056
+ request_url_dict: Optional[dict[str, str]]
2057
+ if not self._api_client.vertexai:
2058
+ raise ValueError(
2059
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
2060
+ )
2061
+ else:
2062
+ request_dict = _GetExportPublisherModelOperationParameters_to_vertex(
2063
+ parameter_model
2064
+ )
2065
+ request_url_dict = request_dict.get("_url")
2066
+ if request_url_dict:
2067
+ path = "{operationName}".format_map(request_url_dict)
2068
+ else:
2069
+ path = "{operationName}"
2070
+
2071
+ query_params = request_dict.get("_query")
2072
+ if query_params:
2073
+ path = f"{path}?{urlencode(query_params)}"
2074
+ # TODO: remove the hack that pops config.
2075
+ request_dict.pop("config", None)
2076
+
2077
+ http_options: Optional[types.HttpOptions] = None
2078
+ if (
2079
+ parameter_model.config is not None
2080
+ and parameter_model.config.http_options is not None
2081
+ ):
2082
+ http_options = parameter_model.config.http_options
2083
+
2084
+ request_dict = _common.convert_to_dict(request_dict)
2085
+ request_dict = _common.encode_unserializable_types(request_dict)
2086
+
2087
+ response = await self._api_client.async_request(
2088
+ "get", path, request_dict, http_options
2089
+ )
2090
+
2091
+ response_dict = {} if not response.body else json.loads(response.body)
2092
+
2093
+ return_value = types.ExportModelOperation._from_response(
2094
+ response=response_dict,
2095
+ kwargs=(
2096
+ {
2097
+ "config": {
2098
+ "response_schema": getattr(
2099
+ parameter_model.config, "response_schema", None
2100
+ ),
2101
+ "response_json_schema": getattr(
2102
+ parameter_model.config, "response_json_schema", None
2103
+ ),
2104
+ "include_all_fields": getattr(
2105
+ parameter_model.config, "include_all_fields", None
2106
+ ),
2107
+ }
2108
+ }
2109
+ if getattr(parameter_model, "config", None)
2110
+ else {}
2111
+ ),
2112
+ )
2113
+
2114
+ self._api_client._verify_response(return_value)
2115
+ return return_value
2116
+
2117
+ async def _deploy(
2118
+ self,
2119
+ *,
2120
+ destination: str,
2121
+ publisher_model_name: Optional[str] = None,
2122
+ hugging_face_model_id: Optional[str] = None,
2123
+ custom_model: Optional[types.DeployRequestCustomModelOrDict] = None,
2124
+ model_config_val: Optional[types.DeployRequestModelConfigOrDict] = None,
2125
+ endpoint_config: Optional[types.DeployRequestEndpointConfigOrDict] = None,
2126
+ deploy_config: Optional[types.DeployRequestDeployConfigOrDict] = None,
2127
+ config: Optional[types.DeployConfigOrDict] = None,
2128
+ ) -> types.DeployModelOperation:
2129
+ """
2130
+ Deploys a model (internal).
2131
+ """
2132
+
2133
+ parameter_model = types._DeployRequestParameters(
2134
+ destination=destination,
2135
+ publisher_model_name=publisher_model_name,
2136
+ hugging_face_model_id=hugging_face_model_id,
2137
+ custom_model=custom_model,
2138
+ model_config_val=model_config_val,
2139
+ endpoint_config=endpoint_config,
2140
+ deploy_config=deploy_config,
2141
+ config=config,
2142
+ )
2143
+
2144
+ request_url_dict: Optional[dict[str, str]]
2145
+ if not self._api_client.vertexai:
2146
+ raise ValueError(
2147
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
2148
+ )
2149
+ else:
2150
+ request_dict = _DeployRequestParameters_to_vertex(parameter_model)
2151
+ request_url_dict = request_dict.get("_url")
2152
+ if request_url_dict:
2153
+ path = "{destination}:deploy".format_map(request_url_dict)
2154
+ else:
2155
+ path = "{destination}:deploy"
2156
+
2157
+ query_params = request_dict.get("_query")
2158
+ if query_params:
2159
+ path = f"{path}?{urlencode(query_params)}"
2160
+ # TODO: remove the hack that pops config.
2161
+ request_dict.pop("config", None)
2162
+
2163
+ http_options: Optional[types.HttpOptions] = None
2164
+ if (
2165
+ parameter_model.config is not None
2166
+ and parameter_model.config.http_options is not None
2167
+ ):
2168
+ http_options = parameter_model.config.http_options
2169
+
2170
+ request_dict = _common.convert_to_dict(request_dict)
2171
+ request_dict = _common.encode_unserializable_types(request_dict)
2172
+
2173
+ response = await self._api_client.async_request(
2174
+ "post", path, request_dict, http_options
2175
+ )
2176
+
2177
+ response_dict = {} if not response.body else json.loads(response.body)
2178
+
2179
+ return_value = types.DeployModelOperation._from_response(
2180
+ response=response_dict,
2181
+ kwargs=(
2182
+ {
2183
+ "config": {
2184
+ "response_schema": getattr(
2185
+ parameter_model.config, "response_schema", None
2186
+ ),
2187
+ "response_json_schema": getattr(
2188
+ parameter_model.config, "response_json_schema", None
2189
+ ),
2190
+ "include_all_fields": getattr(
2191
+ parameter_model.config, "include_all_fields", None
2192
+ ),
2193
+ }
2194
+ }
2195
+ if getattr(parameter_model, "config", None)
2196
+ else {}
2197
+ ),
2198
+ )
2199
+
2200
+ self._api_client._verify_response(return_value)
2201
+ return return_value
2202
+
2203
+ async def get_deploy_publisher_model_operation(
2204
+ self,
2205
+ *,
2206
+ operation_name: str,
2207
+ config: Optional[types.GetDeployOperationConfigOrDict] = None,
2208
+ ) -> types.DeployModelOperation:
2209
+ """
2210
+ Fetches the status of an in-flight ``deploy_publisher_model`` LRO.
2211
+ """
2212
+
2213
+ parameter_model = types._GetDeployOperationParameters(
2214
+ operation_name=operation_name,
2215
+ config=config,
2216
+ )
2217
+
2218
+ request_url_dict: Optional[dict[str, str]]
2219
+ if not self._api_client.vertexai:
2220
+ raise ValueError(
2221
+ "This method is only supported in Gemini Enterprise Agent Platform mode, not in Gemini Developer API mode."
2222
+ )
2223
+ else:
2224
+ request_dict = _GetDeployOperationParameters_to_vertex(parameter_model)
2225
+ request_url_dict = request_dict.get("_url")
2226
+ if request_url_dict:
2227
+ path = "{operationName}".format_map(request_url_dict)
2228
+ else:
2229
+ path = "{operationName}"
2230
+
2231
+ query_params = request_dict.get("_query")
2232
+ if query_params:
2233
+ path = f"{path}?{urlencode(query_params)}"
2234
+ # TODO: remove the hack that pops config.
2235
+ request_dict.pop("config", None)
2236
+
2237
+ http_options: Optional[types.HttpOptions] = None
2238
+ if (
2239
+ parameter_model.config is not None
2240
+ and parameter_model.config.http_options is not None
2241
+ ):
2242
+ http_options = parameter_model.config.http_options
2243
+
2244
+ request_dict = _common.convert_to_dict(request_dict)
2245
+ request_dict = _common.encode_unserializable_types(request_dict)
2246
+
2247
+ response = await self._api_client.async_request(
2248
+ "get", path, request_dict, http_options
2249
+ )
2250
+
2251
+ response_dict = {} if not response.body else json.loads(response.body)
2252
+
2253
+ return_value = types.DeployModelOperation._from_response(
2254
+ response=response_dict,
2255
+ kwargs=(
2256
+ {
2257
+ "config": {
2258
+ "response_schema": getattr(
2259
+ parameter_model.config, "response_schema", None
2260
+ ),
2261
+ "response_json_schema": getattr(
2262
+ parameter_model.config, "response_json_schema", None
2263
+ ),
2264
+ "include_all_fields": getattr(
2265
+ parameter_model.config, "include_all_fields", None
2266
+ ),
2267
+ }
2268
+ }
2269
+ if getattr(parameter_model, "config", None)
2270
+ else {}
2271
+ ),
2272
+ )
2273
+
2274
+ self._api_client._verify_response(return_value)
2275
+ return return_value
2276
+
2277
+ async def _list_all_publisher_models(
2278
+ self,
2279
+ api_config: types.ListPublisherModelsConfig,
2280
+ ) -> list[types.PublisherModel]:
2281
+ """Fetches all pages of publisher models from the API.
2282
+
2283
+ Args:
2284
+ api_config: The configuration for the ListPublisherModels API call,
2285
+ including filter and version settings.
2286
+
2287
+ Returns:
2288
+ A list of all ``PublisherModel`` objects across all pages.
2289
+ """
2290
+ all_models = []
2291
+ page_token = None
2292
+ while True:
2293
+ if page_token:
2294
+ api_config = types.ListPublisherModelsConfig(
2295
+ filter=api_config.filter,
2296
+ list_all_versions=api_config.list_all_versions,
2297
+ page_token=page_token,
2298
+ )
2299
+ response = await self._list_publisher_models(
2300
+ parent="publishers/*",
2301
+ config=api_config,
2302
+ )
2303
+ all_models.extend(response.publisher_models or [])
2304
+ page_token = response.next_page_token
2305
+ if not page_token:
2306
+ break
2307
+ return all_models
2308
+
2309
+ async def _list(
2310
+ self,
2311
+ model_filter: Optional[str],
2312
+ include_hugging_face_models: Optional[bool],
2313
+ deployable_only: bool,
2314
+ ) -> list[str]:
2315
+ """Shared implementation for listing models.
2316
+
2317
+ Args:
2318
+ model_filter: Optional substring to filter models by.
2319
+ include_hugging_face_models: Whether to include HuggingFace models.
2320
+ deployable_only: If True, only return models with deployment configs.
2321
+
2322
+ Returns:
2323
+ A list of formatted model name strings.
2324
+ """
2325
+ include_hf = include_hugging_face_models is True
2326
+
2327
+ filter_str = ModelGarden._build_filter_str(
2328
+ model_filter, include_hf, deployable_only=deployable_only
2329
+ )
2330
+
2331
+ api_config = types.ListPublisherModelsConfig(
2332
+ filter=filter_str,
2333
+ list_all_versions=True,
2334
+ )
2335
+
2336
+ models = await self._list_all_publisher_models(api_config)
2337
+
2338
+ if deployable_only:
2339
+ models = [m for m in models if ModelGarden._has_deploy_config(m)]
2340
+
2341
+ return [ModelGarden._format_model_name(m, include_hf) for m in models]
2342
+
2343
+ async def list_deployable_models(
2344
+ self,
2345
+ config: Optional[types.ListDeployableModelsConfigOrDict] = None,
2346
+ ) -> list[str]:
2347
+ """Lists models in Model Garden that support deployment.
2348
+
2349
+ Returns models that have at least one verified deployment configuration.
2350
+ When ``include_hugging_face_models`` is False (the default),
2351
+ HuggingFace models are excluded from the results.
2352
+
2353
+ Args:
2354
+ config: Optional configuration for filtering results. Accepts a
2355
+ ``ListDeployableModelsConfig`` instance or an equivalent dict.
2356
+
2357
+ Returns:
2358
+ A list of model name strings in the format
2359
+ ``'{publisher}/{model}@{version}'`` (e.g. ``'google/gemma2@gemma-2-2b-it'``)
2360
+ or ``'{publisher}/{model}'`` when ``include_hugging_face_models`` is True
2361
+ (e.g. ``'meta-llama/Llama-3.3-70B-Instruct'``).
2362
+ """
2363
+ if config is None:
2364
+ config = types.ListDeployableModelsConfig()
2365
+ if isinstance(config, dict):
2366
+ config = types.ListDeployableModelsConfig.model_validate(config)
2367
+
2368
+ return await self._list(
2369
+ config.model_filter,
2370
+ config.include_hugging_face_models,
2371
+ deployable_only=True,
2372
+ )
2373
+
2374
+ async def list_models(
2375
+ self,
2376
+ config: Optional[types.ListModelGardenModelsConfigOrDict] = None,
2377
+ ) -> list[str]:
2378
+ """Lists all models available in Model Garden.
2379
+
2380
+ Returns all models regardless of deployment support. When
2381
+ ``include_hugging_face_models`` is False (the default), HuggingFace
2382
+ models are excluded from the results.
2383
+
2384
+ Args:
2385
+ config: Optional configuration for filtering results. Accepts a
2386
+ ``ListModelGardenModelsConfig`` instance or an equivalent dict.
2387
+
2388
+ Returns:
2389
+ A list of model name strings in the format
2390
+ ``'{publisher}/{model}@{version}'`` (e.g. ``'google/gemma2@gemma-2-2b-it'``)
2391
+ or ``'{publisher}/{model}'`` when ``include_hugging_face_models`` is True
2392
+ (e.g. ``'meta-llama/Llama-3.3-70B-Instruct'``).
2393
+ """
2394
+ if config is None:
2395
+ config = types.ListModelGardenModelsConfig()
2396
+ if isinstance(config, dict):
2397
+ config = types.ListModelGardenModelsConfig.model_validate(config)
2398
+
2399
+ return await self._list(
2400
+ config.model_filter,
2401
+ config.include_hugging_face_models,
2402
+ deployable_only=False,
2403
+ )
2404
+
2405
+ async def list_publisher_model_deploy_options(
2406
+ self,
2407
+ model: str,
2408
+ config: Optional[types.ListPublisherModelDeployOptionsConfigOrDict] = None,
2409
+ ) -> Union[str, list[types.DeployOption]]:
2410
+ """Lists the verified deploy options for a Model Garden publisher model.
2411
+
2412
+ Supports Google open models (e.g. ``'google/gemma3@gemma-3-12b-it'``),
2413
+ partner publisher models (e.g.
2414
+ ``'mistralai/mistral-7b@mistral-7b-instruct-v0.2'``), and Hugging Face
2415
+ model IDs (e.g. ``'meta-llama/Llama-3.3-70B-Instruct'``).
2416
+
2417
+ Args:
2418
+ model: The publisher model to list deploy options for. Accepts the full
2419
+ resource name ``'publishers/{publisher}/models/{model}@{version}'``, a
2420
+ simplified ``'{publisher}/{model}@{version}'`` (or without the
2421
+ ``@{version}``), or a Hugging Face model ID ``'{organization}/{model}'``.
2422
+ config: Optional configuration for filtering the deploy options. Accepts a
2423
+ ``ListPublisherModelDeployOptionsConfig`` instance or an equivalent
2424
+ dict.
2425
+
2426
+ Returns:
2427
+ A list of ``DeployOption`` objects, one per verified deployment
2428
+ configuration (container image URI, machine type, accelerator type and
2429
+ count). If ``config.concise`` is True, returns a human-readable string
2430
+ describing those deploy options instead.
2431
+
2432
+ Raises:
2433
+ ValueError: If ``model`` is not a valid publisher model name, if the
2434
+ model does not support deployment, or if no deploy options match the
2435
+ provided filters.
2436
+ """
2437
+ if config is None:
2438
+ config = types.ListPublisherModelDeployOptionsConfig()
2439
+ if isinstance(config, dict):
2440
+ config = types.ListPublisherModelDeployOptionsConfig.model_validate(config)
2441
+
2442
+ api_config = types.GetPublisherModelConfig(
2443
+ is_hugging_face_model=ModelGarden._is_hugging_face_model(model),
2444
+ include_equivalent_model_garden_model_deployment_configs=True,
2445
+ )
2446
+ publisher_model = await self._get_publisher_model(
2447
+ name=ModelGarden._reconcile_model_name(model), config=api_config
2448
+ )
2449
+
2450
+ options = ModelGarden._extract_and_filter_deploy_options(
2451
+ publisher_model,
2452
+ machine_type_filter=config.machine_type_filter,
2453
+ accelerator_type_filter=config.accelerator_type_filter,
2454
+ serving_container_image_uri_filter=config.serving_container_image_uri_filter,
2455
+ )
2456
+
2457
+ if config.concise is True:
2458
+ return ModelGarden._format_concise_deploy_options(options)
2459
+
2460
+ return options
2461
+
2462
+ async def deploy_publisher_model(
2463
+ self,
2464
+ *,
2465
+ model: str,
2466
+ config: Optional[types.DeployPublisherModelConfigOrDict] = None,
2467
+ ) -> Union[types.DeployResponse, types.DeployModelOperation]:
2468
+ """Async variant of ``ModelGarden.deploy_publisher_model``."""
2469
+ if config is None:
2470
+ config = types.DeployPublisherModelConfig()
2471
+ elif isinstance(config, dict):
2472
+ config = types.DeployPublisherModelConfig.model_validate(config)
2473
+ ModelGarden._validate_deploy_config(config)
2474
+
2475
+ publisher_model_name, hugging_face_model_id = (
2476
+ ModelGarden._resolve_deploy_model_name(model)
2477
+ )
2478
+ model_config, endpoint_config, deploy_config = (
2479
+ ModelGarden._prepare_deploy_request(config)
2480
+ )
2481
+ destination = (
2482
+ f"projects/{self._api_client.project}/locations/"
2483
+ f"{self._api_client.location}"
2484
+ )
2485
+
2486
+ operation = await self._deploy(
2487
+ destination=destination,
2488
+ publisher_model_name=publisher_model_name,
2489
+ hugging_face_model_id=hugging_face_model_id,
2490
+ model_config_val=model_config,
2491
+ endpoint_config=endpoint_config,
2492
+ deploy_config=deploy_config,
2493
+ )
2494
+ if not config.wait_for_completion:
2495
+ return operation
2496
+
2497
+ operation = await _operations_utils.await_operation_async(
2498
+ operation_name=operation.name,
2499
+ get_operation_fn=self.get_deploy_publisher_model_operation,
2500
+ poll_interval=(
2501
+ config.poll_interval_seconds
2502
+ or ModelGarden._DEFAULT_DEPLOY_POLL_INTERVAL_SECONDS
2503
+ ),
2504
+ timeout_seconds=(
2505
+ config.timeout_seconds or ModelGarden._DEFAULT_DEPLOY_TIMEOUT_SECONDS
2506
+ ),
2507
+ )
2508
+ return ModelGarden._deploy_response_or_raise(operation)
2509
+
2510
+ async def list_custom_model_deploy_options(
2511
+ self,
2512
+ src: str,
2513
+ config: Optional[types.ListCustomModelDeployOptionsConfigOrDict] = None,
2514
+ ) -> str:
2515
+ """Lists the recommended deploy options for a Model Garden custom model.
2516
+
2517
+ Args:
2518
+ src: The Google Cloud Storage URI of the custom model, storing the model
2519
+ weights and config files (e.g. ``'gs://my-bucket/weights/'``).
2520
+ config: Optional configuration. Accepts a
2521
+ ``ListCustomModelDeployOptionsConfig`` instance or an equivalent dict.
2522
+
2523
+ Returns:
2524
+ A human-readable string describing the recommended deploy options
2525
+ (machine type, accelerator type/count, region and, when available, the
2526
+ user quota state).
2527
+
2528
+ Raises:
2529
+ ValueError: If ``src`` is not specified, or if no deploy options are
2530
+ returned by the API (either because the backend produced none or
2531
+ because the ``filter_by_user_quota`` filter dropped them all).
2532
+ """
2533
+ if not src:
2534
+ raise ValueError("src must be specified.")
2535
+ if config is None:
2536
+ config = types.ListCustomModelDeployOptionsConfig()
2537
+ if isinstance(config, dict):
2538
+ config = types.ListCustomModelDeployOptionsConfig.model_validate(config)
2539
+
2540
+ parent = (
2541
+ f"projects/{self._api_client.project}/locations/"
2542
+ f"{self._api_client.location}"
2543
+ )
2544
+
2545
+ api_config = types.RecommendSpecConfig(
2546
+ check_machine_availability=config.check_machine_availability,
2547
+ check_user_quota=config.filter_by_user_quota,
2548
+ )
2549
+
2550
+ response = await self._recommend_spec(
2551
+ parent=parent,
2552
+ gcs_uri=src,
2553
+ config=api_config,
2554
+ )
2555
+
2556
+ options = []
2557
+ if response.recommendations:
2558
+ options = [
2559
+ ModelGarden._extract_recommendation(recommendation)
2560
+ for recommendation in response.recommendations
2561
+ if recommendation.spec
2562
+ ]
2563
+ if config.filter_by_user_quota:
2564
+ options = [
2565
+ option
2566
+ for option in options
2567
+ if option.get("user_quota_state")
2568
+ == types.QuotaState.QUOTA_STATE_USER_HAS_QUOTA.name
2569
+ ]
2570
+ elif response.specs:
2571
+ options = [
2572
+ ModelGarden._extract_recommend_spec(spec)
2573
+ for spec in response.specs
2574
+ if spec
2575
+ ]
2576
+
2577
+ if not options:
2578
+ raise ValueError("No deploy options found.")
2579
+
2580
+ return ModelGarden._format_custom_deploy_options(options)
2581
+
2582
+ async def export_open_model(
2583
+ self,
2584
+ *,
2585
+ model: str,
2586
+ output_gcs_uri: str,
2587
+ config: Optional[types.ExportOpenModelConfigOrDict] = None,
2588
+ ) -> Union[str, types.ExportModelOperation]:
2589
+ """Async variant of ``ModelGarden.export_open_model``."""
2590
+ if not output_gcs_uri:
2591
+ raise ValueError("output_gcs_uri must be a non-empty Cloud Storage URI.")
2592
+ if ModelGarden._is_hugging_face_model(model):
2593
+ raise ValueError(
2594
+ f"export_open_model does not support Hugging Face model IDs "
2595
+ f"(got {model!r}); only Google publisher open models are "
2596
+ f"exportable via this API."
2597
+ )
2598
+ if config is None:
2599
+ config = types.ExportOpenModelConfig()
2600
+ elif isinstance(config, dict):
2601
+ config = types.ExportOpenModelConfig.model_validate(config)
2602
+
2603
+ publisher_model_name = ModelGarden._reconcile_model_name(model)
2604
+ parent = (
2605
+ f"projects/{self._api_client.project}/locations/"
2606
+ f"{self._api_client.location}"
2607
+ )
2608
+ operation = await self._export_publisher_model(
2609
+ parent=parent,
2610
+ name=publisher_model_name,
2611
+ config=types.ExportPublisherModelConfig(
2612
+ destination=genai_types.GcsDestination(
2613
+ output_uri_prefix=output_gcs_uri,
2614
+ ),
2615
+ ),
2616
+ )
2617
+ if not config.wait_for_completion:
2618
+ return operation
2619
+
2620
+ operation = await _operations_utils.await_operation_async(
2621
+ operation_name=operation.name,
2622
+ get_operation_fn=self.get_export_publisher_model_operation,
2623
+ poll_interval=(
2624
+ config.poll_interval_seconds
2625
+ or ModelGarden._DEFAULT_EXPORT_POLL_INTERVAL_SECONDS
2626
+ ),
2627
+ timeout_seconds=(
2628
+ config.timeout_seconds or ModelGarden._DEFAULT_EXPORT_TIMEOUT_SECONDS
2629
+ ),
2630
+ )
2631
+ if operation.error:
2632
+ raise RuntimeError(f"Export failed: {operation.error}")
2633
+ if not operation.response or not operation.response.destination_uri:
2634
+ raise RuntimeError(
2635
+ f"Export completed but response has no destination_uri: "
2636
+ f"{operation!r}"
2637
+ )
2638
+ return operation.response.destination_uri