google-cloud-agentplatform 2.6.0__tar.gz → 2.8.0__tar.gz

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 (74) hide show
  1. {google_cloud_agentplatform-2.6.0/google_cloud_agentplatform.egg-info → google_cloud_agentplatform-2.8.0}/PKG-INFO +2 -3
  2. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_evals_common.py +31 -3
  3. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_evals_metric_handlers.py +62 -0
  4. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_runtimes_utils.py +0 -5
  5. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_transformers.py +52 -0
  6. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/evals.py +14 -0
  7. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/model_garden.py +42 -3
  8. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/runtimes.py +40 -10
  9. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/sandboxes.py +5 -4
  10. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/types/__init__.py +62 -0
  11. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/types/common.py +416 -2
  12. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/frameworks/adk.py +25 -7
  13. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/version.py +1 -1
  14. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0/google_cloud_agentplatform.egg-info}/PKG-INFO +2 -3
  15. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/google_cloud_agentplatform.egg-info/requires.txt +2 -5
  16. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/pyproject.toml +3 -4
  17. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/LICENSE +0 -0
  18. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/README.md +0 -0
  19. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/__init__.py +0 -0
  20. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/__init__.py +0 -0
  21. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_bigquery_utils.py +0 -0
  22. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_datasets_utils.py +0 -0
  23. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_evals_builtin_tools.py +0 -0
  24. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_evals_constant.py +0 -0
  25. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_evals_data_converters.py +0 -0
  26. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_evals_metric_loaders.py +0 -0
  27. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_evals_utils.py +0 -0
  28. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_evals_visualization.py +0 -0
  29. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_gcs_utils.py +0 -0
  30. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_logging_utils.py +0 -0
  31. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_memory_bank_utils.py +0 -0
  32. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_observability_data_converter.py +0 -0
  33. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_operations_utils.py +0 -0
  34. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_prompt_management_utils.py +0 -0
  35. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_prompt_optimizer_utils.py +0 -0
  36. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/_skills_utils.py +0 -0
  37. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/a2a_task_events.py +0 -0
  38. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/a2a_tasks.py +0 -0
  39. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/client.py +0 -0
  40. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/datasets.py +0 -0
  41. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/endpoints.py +0 -0
  42. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/example_stores.py +0 -0
  43. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/feedback_contexts.py +0 -0
  44. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/feedback_entries.py +0 -0
  45. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/live.py +0 -0
  46. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/live_runtimes.py +0 -0
  47. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/memories.py +0 -0
  48. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/memory_banks.py +0 -0
  49. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/memory_revisions.py +0 -0
  50. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/prompt_optimizer.py +0 -0
  51. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/prompts.py +0 -0
  52. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/rag.py +0 -0
  53. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/runtime_revisions.py +0 -0
  54. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/sandbox_snapshots.py +0 -0
  55. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/sandbox_templates.py +0 -0
  56. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/serving_profiles.py +0 -0
  57. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/session_events.py +0 -0
  58. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/sessions.py +0 -0
  59. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/skill_revisions.py +0 -0
  60. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/skills.py +0 -0
  61. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/types/agent_engines.py +0 -0
  62. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/types/evals.py +0 -0
  63. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/types/prompt_optimizer.py +0 -0
  64. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/_genai/types/prompts.py +0 -0
  65. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/frameworks/__init__.py +0 -0
  66. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/frameworks/a2a.py +0 -0
  67. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/frameworks/ag2.py +0 -0
  68. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/frameworks/langchain.py +0 -0
  69. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/frameworks/langgraph.py +0 -0
  70. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/agentplatform/frameworks/llama_index.py +0 -0
  71. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/google_cloud_agentplatform.egg-info/SOURCES.txt +0 -0
  72. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/google_cloud_agentplatform.egg-info/dependency_links.txt +0 -0
  73. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/google_cloud_agentplatform.egg-info/top_level.txt +0 -0
  74. {google_cloud_agentplatform-2.6.0 → google_cloud_agentplatform-2.8.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-cloud-agentplatform
3
- Version: 2.6.0
3
+ Version: 2.8.0
4
4
  Summary: Google Cloud Agent Platform Client Library
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  License: Apache-2.0
@@ -61,8 +61,7 @@ Provides-Extra: evaluation
61
61
  Requires-Dist: pandas>=1.0.0; extra == "evaluation"
62
62
  Requires-Dist: tqdm>=4.23.0; extra == "evaluation"
63
63
  Requires-Dist: pyyaml; extra == "evaluation"
64
- Requires-Dist: litellm<1.86.0,>=1.83.7; python_version < "3.14" and extra == "evaluation"
65
- Requires-Dist: litellm<1.97.0,>=1.93.0; python_version >= "3.14" and extra == "evaluation"
64
+ Requires-Dist: litellm<1.107.0,>=1.106.5; python_version < "3.15" and extra == "evaluation"
66
65
  Requires-Dist: google-cloud-storage<4.0.0,>=1.32.0; extra == "evaluation"
67
66
  Provides-Extra: bigquery
68
67
  Requires-Dist: google-cloud-bigquery<4.0.0,>=1.15.0; extra == "bigquery"
@@ -3238,6 +3238,31 @@ def _process_multi_turn_agent_response(
3238
3238
  ).model_dump(exclude_unset=True)
3239
3239
 
3240
3240
 
3241
+ def _extract_final_response_text(
3242
+ events: list[dict[str, Any]],
3243
+ ) -> tuple[Optional[int], str]:
3244
+ """Returns the index and text of the last final response in the events.
3245
+
3246
+ As in ADK's `Event.is_final_response()`, events with function calls or
3247
+ responses are not final responses, so they stay in the intermediate events.
3248
+ Thought parts are not part of the response text.
3249
+ """
3250
+ for index in range(len(events) - 1, -1, -1):
3251
+ parts = (events[index].get("content") or {}).get("parts") or []
3252
+ if any(
3253
+ part.get("function_call") or part.get("function_response") for part in parts
3254
+ ):
3255
+ continue
3256
+ texts = [
3257
+ part["text"]
3258
+ for part in parts
3259
+ if part.get("text") and not part.get("thought")
3260
+ ]
3261
+ if texts:
3262
+ return index, "".join(texts)
3263
+ return None, ""
3264
+
3265
+
3241
3266
  def _process_single_turn_agent_response(
3242
3267
  resp_item: Any,
3243
3268
  agent_data_agents: Optional[dict[str, Any]],
@@ -3253,8 +3278,10 @@ def _process_single_turn_agent_response(
3253
3278
 
3254
3279
  if isinstance(resp_item, list):
3255
3280
  try:
3256
- response_row = resp_item[-1]["content"]["parts"][0]["text"]
3257
- for intermediate_event in resp_item[:-1]:
3281
+ response_index, response_row = _extract_final_response_text(resp_item)
3282
+ for index, intermediate_event in enumerate(resp_item):
3283
+ if index == response_index:
3284
+ continue
3258
3285
  intermediate_events_row.append(
3259
3286
  {
3260
3287
  "event_id": intermediate_event.get("id"),
@@ -3263,7 +3290,7 @@ def _process_single_turn_agent_response(
3263
3290
  "author": intermediate_event.get("author"),
3264
3291
  }
3265
3292
  )
3266
- # Construct AgentData natively for single-turn runs
3293
+ # Construct AgentData natively for single-turn runs
3267
3294
  agent_events = []
3268
3295
  for event_dict in resp_item:
3269
3296
  content_dict = event_dict.get("content")
@@ -3288,6 +3315,7 @@ def _process_single_turn_agent_response(
3288
3315
  agents=agent_data_agents,
3289
3316
  ).model_dump(exclude_unset=True)
3290
3317
  except Exception as e: # pylint: disable=broad-exception-caught
3318
+ intermediate_events_row = []
3291
3319
  error_payload = {
3292
3320
  "error": (
3293
3321
  f"Failed to parse agent run response {str(resp_item)} to "
@@ -846,6 +846,66 @@ class LLMMetricHandler(MetricHandler[types.LLMMetric]):
846
846
  return _default_aggregate_scores(self.metric.name, eval_case_metric_results)
847
847
 
848
848
 
849
+ class StepBasedLLMMetricHandler(LLMMetricHandler):
850
+ """Metric handler for step-based LLM metrics."""
851
+
852
+ @override
853
+ def get_metric_result(
854
+ self, eval_case: types.EvalCase, response_index: int
855
+ ) -> types.EvalCaseMetricResult:
856
+ """Evaluates one case with the step-based LLM metric."""
857
+ try:
858
+ response_content = _get_response_from_eval_case(
859
+ eval_case, response_index, self.metric_name
860
+ )
861
+ if not response_content and not getattr(eval_case, "agent_data", None):
862
+ raise ValueError(
863
+ f"Response content missing for candidate {response_index}."
864
+ )
865
+
866
+ # Joined so dataset columns referenced by any step reach other_data.
867
+ prompt_templates = "\n".join(
868
+ step.prompt_template or "" for step in self.metric.steps or []
869
+ )
870
+ instance = _build_evaluation_instance(
871
+ eval_case, response_content, prompt_template=prompt_templates
872
+ )
873
+ api_response = _call_with_retry(
874
+ lambda: self.module._evaluate_instances(
875
+ metrics=[self.metric],
876
+ instance=instance,
877
+ allow_cross_region_model=self.allow_cross_region_model,
878
+ ),
879
+ self.metric_name,
880
+ )
881
+
882
+ if api_response and api_response.metric_results:
883
+ result = api_response.metric_results[0]
884
+ error_msg = None
885
+ if result.error and getattr(result.error, "code"):
886
+ error_msg = f"Error in metric result: {result.error}"
887
+ return types.EvalCaseMetricResult(
888
+ metric_name=self.metric_name,
889
+ score=result.score,
890
+ explanation=result.explanation,
891
+ error_message=error_msg,
892
+ )
893
+ return types.EvalCaseMetricResult(
894
+ metric_name=self.metric_name,
895
+ error_message="Metric results missing in API response.",
896
+ )
897
+ except Exception as e: # pylint: disable=broad-exception-caught
898
+ logger.error(
899
+ "Error processing metric %s for case %s.",
900
+ self.metric_name,
901
+ eval_case.eval_case_id,
902
+ exc_info=True,
903
+ )
904
+ return types.EvalCaseMetricResult(
905
+ metric_name=self.metric_name, error_message=str(e)
906
+ )
907
+
908
+
849
909
  class CustomMetricHandler(MetricHandler[types.Metric]):
850
910
  """Metric handler for custom metrics."""
851
911
 
@@ -1375,6 +1435,7 @@ _METRIC_HANDLER_MAPPING = [
1375
1435
  lambda m: getattr(m, "metric_resource_name", None) is not None,
1376
1436
  RegisteredMetricHandler,
1377
1437
  ),
1438
+ (lambda m: getattr(m, "steps", None), StepBasedLLMMetricHandler),
1378
1439
  (
1379
1440
  lambda m: m.name in ComputationMetricHandler.SUPPORTED_COMPUTATION_METRICS,
1380
1441
  ComputationMetricHandler,
@@ -1395,6 +1456,7 @@ MetricHandlerType = TypeVar(
1395
1456
  ComputationMetricHandler,
1396
1457
  TranslationMetricHandler,
1397
1458
  LLMMetricHandler,
1459
+ StepBasedLLMMetricHandler,
1398
1460
  CustomMetricHandler,
1399
1461
  CustomCodeExecutionMetricHandler,
1400
1462
  PredefinedMetricHandler,
@@ -1294,11 +1294,6 @@ def _upload_runtime(
1294
1294
  "`from a2a import types`."
1295
1295
  )
1296
1296
  raise TypeError(error_msg) from e
1297
- with blob.open("rb") as f:
1298
- try:
1299
- _ = cloudpickle.load(f)
1300
- except Exception as e:
1301
- raise TypeError("agent runtime serialized to an invalid format") from e
1302
1297
  dir_name = f"gs://{gcs_bucket.name}/{gcs_dir_name}"
1303
1298
  logger.info(f"Wrote to {dir_name}/{_BLOB_FILENAME}")
1304
1299
 
@@ -89,6 +89,11 @@ def t_metrics(
89
89
  metric_payload_item["custom_code_execution_spec"] = {
90
90
  "evaluation_function": metric.custom_function
91
91
  }
92
+ # Step-based LLM metrics
93
+ elif getv(metric, ["steps"]):
94
+ metric_payload_item["step_based_llm_metric_spec"] = (
95
+ _step_based_llm_metric_spec(metric)
96
+ )
92
97
  # LLM-based metrics
93
98
  elif hasattr(metric, "prompt_template") and metric.prompt_template:
94
99
  llm_based_spec: dict[str, Any] = {
@@ -145,6 +150,47 @@ def t_metrics(
145
150
  return metrics_payload
146
151
 
147
152
 
153
+ def _dump(value: Any) -> Any:
154
+ if hasattr(value, "model_dump"):
155
+ return value.model_dump(exclude_none=True)
156
+ return value
157
+
158
+
159
+ def _step_based_llm_metric_spec(metric: Any) -> dict[str, Any]:
160
+ """Builds a StepBasedLlmMetricSpec payload.
161
+
162
+ autorater_config fields a step leaves unset fall back to the metric's judge
163
+ model settings.
164
+ """
165
+ default_autorater_config: dict[str, Any] = {}
166
+ if getv(metric, ["judge_model"]):
167
+ default_autorater_config["autorater_model"] = getv(metric, ["judge_model"])
168
+ if getv(metric, ["judge_model_generation_config"]):
169
+ default_autorater_config["generation_config"] = _dump(
170
+ getv(metric, ["judge_model_generation_config"])
171
+ )
172
+ if getv(metric, ["judge_model_sampling_count"]):
173
+ default_autorater_config["sampling_count"] = getv(
174
+ metric, ["judge_model_sampling_count"]
175
+ )
176
+
177
+ steps = []
178
+ for step in getv(metric, ["steps"]):
179
+ step_payload = _dump(step)
180
+ if default_autorater_config:
181
+ step_payload["autorater_config"] = {
182
+ **default_autorater_config,
183
+ **step_payload.get("autorater_config", {}),
184
+ }
185
+ steps.append(step_payload)
186
+
187
+ spec: dict[str, Any] = {"steps": steps}
188
+ preprocessor = getv(metric, ["preprocessor"])
189
+ if preprocessor:
190
+ spec["preprocessor"] = _dump(preprocessor)
191
+ return spec
192
+
193
+
148
194
  def t_metric_sources(metrics: list[Any]) -> list[dict[str, Any]]:
149
195
  """Prepares the MetricSource payload."""
150
196
  sources_payload = []
@@ -217,6 +263,12 @@ def t_metric_for_registry(
217
263
  "evaluation_function": metric.custom_function
218
264
  }
219
265
 
266
+ # Step-based LLM metric
267
+ elif getv(metric, ["steps"]):
268
+ metric_payload_item["step_based_llm_metric_spec"] = _step_based_llm_metric_spec(
269
+ metric
270
+ )
271
+
220
272
  # LLM-based metric
221
273
  elif (hasattr(metric, "prompt_template") and metric.prompt_template) or (
222
274
  hasattr(metric, "rubric_group_name") and metric.rubric_group_name
@@ -1172,6 +1172,13 @@ def _UnifiedMetric_from_vertex(
1172
1172
  getv(from_object, ["computationBasedMetricSpec"]),
1173
1173
  )
1174
1174
 
1175
+ if getv(from_object, ["stepBasedLlmMetricSpec"]) is not None:
1176
+ setv(
1177
+ to_object,
1178
+ ["step_based_llm_metric_spec"],
1179
+ getv(from_object, ["stepBasedLlmMetricSpec"]),
1180
+ )
1181
+
1175
1182
  return to_object
1176
1183
 
1177
1184
 
@@ -1223,6 +1230,13 @@ def _UnifiedMetric_to_vertex(
1223
1230
  getv(from_object, ["computation_based_metric_spec"]),
1224
1231
  )
1225
1232
 
1233
+ if getv(from_object, ["step_based_llm_metric_spec"]) is not None:
1234
+ setv(
1235
+ to_object,
1236
+ ["stepBasedLlmMetricSpec"],
1237
+ getv(from_object, ["step_based_llm_metric_spec"]),
1238
+ )
1239
+
1226
1240
  return to_object
1227
1241
 
1228
1242
 
@@ -1157,6 +1157,33 @@ class ModelGarden(_api_module.BaseModule):
1157
1157
  _DEFAULT_DEPLOY_TIMEOUT_SECONDS = 2 * 60 * 60
1158
1158
  _DEFAULT_DEPLOY_POLL_INTERVAL_SECONDS = 30
1159
1159
 
1160
+ # Model Garden catalog reads (ListPublisherModels, GetPublisherModel) are
1161
+ # served from us-central1, as in vertexai.model_garden. Other regional
1162
+ # endpoints return a partial catalog (e.g. no deploy configs for models that
1163
+ # deploy fine there, since Deploy reads the global model configs).
1164
+ _CATALOG_BASE_URL = "https://us-central1-aiplatform.googleapis.com/"
1165
+
1166
+ @staticmethod
1167
+ def _catalog_http_options(
1168
+ api_client: Any,
1169
+ ) -> Optional[genai_types.HttpOptions]:
1170
+ """Returns ``http_options`` that send catalog reads to us-central1.
1171
+
1172
+ Only the catalog reads are routed this way; deploy, export and batch
1173
+ requests still go to the client's location.
1174
+
1175
+ Args:
1176
+ api_client: The client's API client.
1177
+
1178
+ Returns:
1179
+ ``HttpOptions`` with the us-central1 base URL, or None when the client
1180
+ was created with a custom ``base_url`` (e.g. a proxy or private
1181
+ endpoint), which is then left unchanged.
1182
+ """
1183
+ if api_client.custom_base_url:
1184
+ return None
1185
+ return genai_types.HttpOptions(base_url=ModelGarden._CATALOG_BASE_URL)
1186
+
1160
1187
  @staticmethod
1161
1188
  def _build_filter_str(
1162
1189
  model_filter: Optional[str],
@@ -1573,9 +1600,11 @@ class ModelGarden(_api_module.BaseModule):
1573
1600
  endpoint_config = types.DeployRequestEndpointConfig(
1574
1601
  endpoint_display_name=config.endpoint_display_name,
1575
1602
  )
1576
- disabled = config.dedicated_endpoint_disabled
1577
- if disabled is not None:
1578
- endpoint_config.dedicated_endpoint_enabled = not disabled
1603
+ # Send the current field, as vertexai.model_garden does. The deprecated
1604
+ # dedicated_endpoint_enabled cannot express "off": False is its proto3
1605
+ # default, so the backend would keep its default (dedicated endpoint on).
1606
+ if config.dedicated_endpoint_disabled:
1607
+ endpoint_config.dedicated_endpoint_disabled = True
1579
1608
  if config.enable_private_service_connect:
1580
1609
  endpoint_config.private_service_connect_config = (
1581
1610
  types.PrivateServiceConnectConfig(
@@ -1598,6 +1627,10 @@ class ModelGarden(_api_module.BaseModule):
1598
1627
  max_replica_count=config.max_replica_count,
1599
1628
  spot=config.spot,
1600
1629
  )
1630
+ elif config.spot:
1631
+ # Without a machine shape the backend deploys the model's default
1632
+ # verified config and applies spot to it, as with vertexai.model_garden.
1633
+ deploy_config.dedicated_resources = types.DedicatedResources(spot=True)
1601
1634
 
1602
1635
  return model_config, endpoint_config, deploy_config
1603
1636
 
@@ -1710,6 +1743,7 @@ class ModelGarden(_api_module.BaseModule):
1710
1743
  filter=api_config.filter,
1711
1744
  list_all_versions=api_config.list_all_versions,
1712
1745
  page_token=page_token,
1746
+ http_options=api_config.http_options,
1713
1747
  )
1714
1748
  response = self._list_publisher_models(
1715
1749
  parent="publishers/*",
@@ -1746,6 +1780,7 @@ class ModelGarden(_api_module.BaseModule):
1746
1780
  api_config = types.ListPublisherModelsConfig(
1747
1781
  filter=filter_str,
1748
1782
  list_all_versions=True,
1783
+ http_options=ModelGarden._catalog_http_options(self._api_client),
1749
1784
  )
1750
1785
 
1751
1786
  models = self._list_all_publisher_models(api_config)
@@ -1859,6 +1894,7 @@ class ModelGarden(_api_module.BaseModule):
1859
1894
  get_publisher_model_config = types.GetPublisherModelConfig(
1860
1895
  is_hugging_face_model=self._is_hugging_face_model(model),
1861
1896
  include_equivalent_model_garden_model_deployment_configs=True,
1897
+ http_options=ModelGarden._catalog_http_options(self._api_client),
1862
1898
  )
1863
1899
  publisher_model = self._get_publisher_model(
1864
1900
  name=self._reconcile_model_name(model),
@@ -3189,6 +3225,7 @@ class AsyncModelGarden(_api_module.BaseModule):
3189
3225
  filter=api_config.filter,
3190
3226
  list_all_versions=api_config.list_all_versions,
3191
3227
  page_token=page_token,
3228
+ http_options=api_config.http_options,
3192
3229
  )
3193
3230
  response = await self._list_publisher_models(
3194
3231
  parent="publishers/*",
@@ -3225,6 +3262,7 @@ class AsyncModelGarden(_api_module.BaseModule):
3225
3262
  api_config = types.ListPublisherModelsConfig(
3226
3263
  filter=filter_str,
3227
3264
  list_all_versions=True,
3265
+ http_options=ModelGarden._catalog_http_options(self._api_client),
3228
3266
  )
3229
3267
 
3230
3268
  models = await self._list_all_publisher_models(api_config)
@@ -3336,6 +3374,7 @@ class AsyncModelGarden(_api_module.BaseModule):
3336
3374
  api_config = types.GetPublisherModelConfig(
3337
3375
  is_hugging_face_model=ModelGarden._is_hugging_face_model(model),
3338
3376
  include_equivalent_model_garden_model_deployment_configs=True,
3377
+ http_options=ModelGarden._catalog_http_options(self._api_client),
3339
3378
  )
3340
3379
  publisher_model = await self._get_publisher_model(
3341
3380
  name=ModelGarden._reconcile_model_name(model), config=api_config
@@ -1607,6 +1607,7 @@ class Runtimes(_api_module.BaseModule):
1607
1607
  agent
1608
1608
  or config.source_packages
1609
1609
  or config.developer_connect_source
1610
+ or config.git_source
1610
1611
  or config.agent_config_source
1611
1612
  or config.container_spec
1612
1613
  ):
@@ -1860,6 +1861,9 @@ class Runtimes(_api_module.BaseModule):
1860
1861
  developer_connect_source = json.loads(
1861
1862
  developer_connect_source.model_dump_json()
1862
1863
  )
1864
+ git_source = config.git_source
1865
+ if git_source is not None:
1866
+ git_source = json.loads(git_source.model_dump_json(exclude_none=True))
1863
1867
  agent_config_source = config.agent_config_source
1864
1868
  if agent_config_source is not None:
1865
1869
  agent_config_source = json.loads(agent_config_source.model_dump_json())
@@ -1900,6 +1904,7 @@ class Runtimes(_api_module.BaseModule):
1900
1904
  class_methods=config.class_methods,
1901
1905
  source_packages=config.source_packages,
1902
1906
  developer_connect_source=developer_connect_source,
1907
+ git_source=git_source,
1903
1908
  entrypoint_module=config.entrypoint_module,
1904
1909
  entrypoint_object=config.entrypoint_object,
1905
1910
  requirements_file=config.requirements_file,
@@ -1969,6 +1974,9 @@ class Runtimes(_api_module.BaseModule):
1969
1974
  developer_connect_source: Optional[
1970
1975
  types.ReasoningEngineSpecSourceCodeSpecDeveloperConnectConfigDict
1971
1976
  ] = None,
1977
+ git_source: Optional[
1978
+ types.ReasoningEngineSpecSourceCodeSpecGitSourceDict
1979
+ ] = None,
1972
1980
  class_methods: Optional[Sequence[dict[str, Any]]] = None,
1973
1981
  entrypoint_module: Optional[str] = None,
1974
1982
  entrypoint_object: Optional[str] = None,
@@ -2000,10 +2008,13 @@ class Runtimes(_api_module.BaseModule):
2000
2008
  source_code_spec["developer_connect_source"] = {
2001
2009
  "config": developer_connect_source
2002
2010
  }
2011
+ elif git_source:
2012
+ update_masks.append("spec.source_code_spec.git_source")
2013
+ source_code_spec["git_source"] = git_source
2003
2014
  elif not agent_config_source:
2004
2015
  raise ValueError(
2005
2016
  "Please specify one of `source_packages`, `developer_connect_source`, "
2006
- "or `agent_config_source`."
2017
+ "`git_source`, or `agent_config_source`."
2007
2018
  )
2008
2019
  if class_methods is not None:
2009
2020
  update_masks.append("spec.class_methods")
@@ -2019,8 +2030,8 @@ class Runtimes(_api_module.BaseModule):
2019
2030
  elif image_spec is None:
2020
2031
  raise ValueError(
2021
2032
  "`class_methods` must be specified if `source_packages`, "
2022
- "`developer_connect_source`, or `agent_config_source` is "
2023
- "specified without a Dockerfile or `image_spec`."
2033
+ "`developer_connect_source`, `git_source`, or `agent_config_source` "
2034
+ "is specified without a Dockerfile or `image_spec`."
2024
2035
  )
2025
2036
  if image_spec is not None:
2026
2037
  if entrypoint_module or entrypoint_object or requirements_file:
@@ -2077,13 +2088,13 @@ class Runtimes(_api_module.BaseModule):
2077
2088
 
2078
2089
  if not entrypoint_module:
2079
2090
  raise ValueError(
2080
- "`entrypoint_module` must be specified if `source_packages` or `developer_connect_source` is specified."
2091
+ "`entrypoint_module` must be specified if `source_packages`, `developer_connect_source`, or `git_source` is specified."
2081
2092
  )
2082
2093
  update_masks.append("spec.source_code_spec.python_spec.entrypoint_module")
2083
2094
  python_spec["entrypoint_module"] = entrypoint_module
2084
2095
  if not entrypoint_object:
2085
2096
  raise ValueError(
2086
- "`entrypoint_object` must be specified if `source_packages` or `developer_connect_source` is specified."
2097
+ "`entrypoint_object` must be specified if `source_packages`, `developer_connect_source`, or `git_source` is specified."
2087
2098
  )
2088
2099
  update_masks.append("spec.source_code_spec.python_spec.entrypoint_object")
2089
2100
  python_spec["entrypoint_object"] = entrypoint_object
@@ -2215,6 +2226,9 @@ class Runtimes(_api_module.BaseModule):
2215
2226
  developer_connect_source: Optional[
2216
2227
  types.ReasoningEngineSpecSourceCodeSpecDeveloperConnectConfigDict
2217
2228
  ] = None,
2229
+ git_source: Optional[
2230
+ types.ReasoningEngineSpecSourceCodeSpecGitSourceDict
2231
+ ] = None,
2218
2232
  entrypoint_module: Optional[str] = None,
2219
2233
  entrypoint_object: Optional[str] = None,
2220
2234
  requirements_file: Optional[str] = None,
@@ -2284,9 +2298,18 @@ class Runtimes(_api_module.BaseModule):
2284
2298
  "do not specify `agent` in `runtimes.create()` or "
2285
2299
  "`runtimes.update()`."
2286
2300
  )
2287
- elif source_packages and developer_connect_source:
2301
+ if git_source:
2302
+ raise ValueError(
2303
+ "If you have provided `git_source` in `config`, please "
2304
+ "do not specify `agent` in `runtimes.create()` or "
2305
+ "`runtimes.update()`."
2306
+ )
2307
+ elif (
2308
+ sum(map(bool, (source_packages, developer_connect_source, git_source))) > 1
2309
+ ):
2288
2310
  raise ValueError(
2289
- "Please specify only one of `source_packages` or `developer_connect_source` in `config`."
2311
+ "Please specify only one of `source_packages`, "
2312
+ "`developer_connect_source`, or `git_source` in `config`."
2290
2313
  )
2291
2314
 
2292
2315
  if container_spec:
@@ -2296,10 +2319,11 @@ class Runtimes(_api_module.BaseModule):
2296
2319
  "do not specify `agent` in `runtimes.create()` or "
2297
2320
  "`runtimes.update()`."
2298
2321
  )
2299
- if source_packages or developer_connect_source:
2322
+ if source_packages or developer_connect_source or git_source:
2300
2323
  raise ValueError(
2301
2324
  "If you have provided `container_spec` in `config`, please "
2302
- "do not specify `source_packages` or `developer_connect_source` in `config`."
2325
+ "do not specify `source_packages`, `developer_connect_source`, or "
2326
+ "`git_source` in `config`."
2303
2327
  )
2304
2328
 
2305
2329
  runtime_spec: Any = None
@@ -2323,6 +2347,7 @@ class Runtimes(_api_module.BaseModule):
2323
2347
  elif (
2324
2348
  source_packages
2325
2349
  or developer_connect_source
2350
+ or git_source
2326
2351
  or image_spec
2327
2352
  or agent_config_source
2328
2353
  ):
@@ -2332,6 +2357,7 @@ class Runtimes(_api_module.BaseModule):
2332
2357
  update_masks=update_masks,
2333
2358
  source_packages=source_packages,
2334
2359
  developer_connect_source=developer_connect_source,
2360
+ git_source=git_source,
2335
2361
  class_methods=class_methods,
2336
2362
  entrypoint_module=entrypoint_module,
2337
2363
  entrypoint_object=entrypoint_object,
@@ -2373,7 +2399,7 @@ class Runtimes(_api_module.BaseModule):
2373
2399
  "`max_instances`, `resource_limits`, `container_concurrency`, or "
2374
2400
  "`keep_alive_probe`, you must also provide the `agent` variable or "
2375
2401
  "the source code options (`source_packages`, "
2376
- "`developer_connect_source` or `agent_config_source`)."
2402
+ "`developer_connect_source`, `git_source` or `agent_config_source`)."
2377
2403
  )
2378
2404
 
2379
2405
  if runtime_spec is not None:
@@ -2672,6 +2698,9 @@ class Runtimes(_api_module.BaseModule):
2672
2698
  developer_connect_source = json.loads(
2673
2699
  developer_connect_source.model_dump_json()
2674
2700
  )
2701
+ git_source = config.git_source
2702
+ if git_source is not None:
2703
+ git_source = json.loads(git_source.model_dump_json(exclude_none=True))
2675
2704
  agent_config_source = config.agent_config_source
2676
2705
  if agent_config_source is not None:
2677
2706
  agent_config_source = json.loads(agent_config_source.model_dump_json())
@@ -2721,6 +2750,7 @@ class Runtimes(_api_module.BaseModule):
2721
2750
  class_methods=config.class_methods,
2722
2751
  source_packages=config.source_packages,
2723
2752
  developer_connect_source=developer_connect_source,
2753
+ git_source=git_source,
2724
2754
  entrypoint_module=config.entrypoint_module,
2725
2755
  entrypoint_object=config.entrypoint_object,
2726
2756
  requirements_file=config.requirements_file,
@@ -1403,9 +1403,9 @@ class Sandboxes(_api_module.BaseModule):
1403
1403
  if not connection_info:
1404
1404
  raise ValueError("Connection info is not available.")
1405
1405
  if psc_endpoint:
1406
- # VPC-SC path: caller has provisioned a PSC endpoint against
1407
- # connection_info.service_attachment and passes its host here.
1408
- endpoint = "https://" + psc_endpoint
1406
+ # VPC-SC path: PSC endpoint routes to the router pod, which serves
1407
+ # plain HTTP on `port`.
1408
+ endpoint = f"http://{psc_endpoint}:{port}"
1409
1409
  elif connection_info.load_balancer_hostname:
1410
1410
  endpoint = "https://" + connection_info.load_balancer_hostname
1411
1411
  elif connection_info.load_balancer_ip:
@@ -1489,7 +1489,8 @@ class Sandboxes(_api_module.BaseModule):
1489
1489
 
1490
1490
  connection_info = sandbox_environment.connection_info
1491
1491
  if psc_endpoint:
1492
- ws_base_url = "wss://" + psc_endpoint
1492
+ # VPC-SC path: see send_command for rationale.
1493
+ ws_base_url = f"ws://{psc_endpoint}:{port}"
1493
1494
  elif connection_info.load_balancer_hostname:
1494
1495
  ws_base_url = "wss://" + connection_info.load_balancer_hostname
1495
1496
  elif connection_info.load_balancer_ip: