google-cloud-agentplatform 2.3.0__tar.gz → 2.5.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.3.0/google_cloud_agentplatform.egg-info → google_cloud_agentplatform-2.5.0}/PKG-INFO +2 -2
  2. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_evals_common.py +105 -92
  3. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_evals_constant.py +14 -0
  4. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_evals_data_converters.py +306 -1
  5. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_evals_metric_handlers.py +29 -20
  6. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_evals_metric_loaders.py +9 -4
  7. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_evals_visualization.py +18 -1
  8. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_runtimes_utils.py +18 -0
  9. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_transformers.py +5 -1
  10. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/evals.py +20 -6
  11. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/runtimes.py +30 -6
  12. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/types/__init__.py +6 -0
  13. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/types/common.py +162 -9
  14. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/frameworks/adk.py +35 -18
  15. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/version.py +1 -1
  16. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0/google_cloud_agentplatform.egg-info}/PKG-INFO +2 -2
  17. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/google_cloud_agentplatform.egg-info/requires.txt +1 -1
  18. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/pyproject.toml +1 -1
  19. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/LICENSE +0 -0
  20. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/README.md +0 -0
  21. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/__init__.py +0 -0
  22. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/__init__.py +0 -0
  23. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_bigquery_utils.py +0 -0
  24. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_datasets_utils.py +0 -0
  25. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_evals_builtin_tools.py +0 -0
  26. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_evals_utils.py +0 -0
  27. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_gcs_utils.py +0 -0
  28. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_logging_utils.py +0 -0
  29. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_memory_bank_utils.py +0 -0
  30. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_observability_data_converter.py +0 -0
  31. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_operations_utils.py +0 -0
  32. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_prompt_management_utils.py +0 -0
  33. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_prompt_optimizer_utils.py +0 -0
  34. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/_skills_utils.py +0 -0
  35. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/a2a_task_events.py +0 -0
  36. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/a2a_tasks.py +0 -0
  37. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/client.py +0 -0
  38. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/datasets.py +0 -0
  39. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/endpoints.py +0 -0
  40. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/example_stores.py +0 -0
  41. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/feedback_contexts.py +0 -0
  42. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/feedback_entries.py +0 -0
  43. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/live.py +0 -0
  44. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/live_runtimes.py +0 -0
  45. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/memories.py +0 -0
  46. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/memory_banks.py +0 -0
  47. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/memory_revisions.py +0 -0
  48. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/model_garden.py +0 -0
  49. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/prompt_optimizer.py +0 -0
  50. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/prompts.py +0 -0
  51. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/rag.py +0 -0
  52. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/runtime_revisions.py +0 -0
  53. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/sandbox_snapshots.py +0 -0
  54. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/sandbox_templates.py +0 -0
  55. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/sandboxes.py +0 -0
  56. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/serving_profiles.py +0 -0
  57. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/session_events.py +0 -0
  58. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/sessions.py +0 -0
  59. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/skill_revisions.py +0 -0
  60. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/skills.py +0 -0
  61. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/types/agent_engines.py +0 -0
  62. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/types/evals.py +0 -0
  63. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/types/prompt_optimizer.py +0 -0
  64. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/_genai/types/prompts.py +0 -0
  65. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/frameworks/__init__.py +0 -0
  66. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/frameworks/a2a.py +0 -0
  67. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/frameworks/ag2.py +0 -0
  68. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/frameworks/langchain.py +0 -0
  69. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/frameworks/langgraph.py +0 -0
  70. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/agentplatform/frameworks/llama_index.py +0 -0
  71. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/google_cloud_agentplatform.egg-info/SOURCES.txt +0 -0
  72. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/google_cloud_agentplatform.egg-info/dependency_links.txt +0 -0
  73. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/google_cloud_agentplatform.egg-info/top_level.txt +0 -0
  74. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-cloud-agentplatform
3
- Version: 2.3.0
3
+ Version: 2.5.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
@@ -31,7 +31,7 @@ Requires-Dist: google-cloud-storage<4.0.0,>=1.32.0; extra == "agent-engines"
31
31
  Requires-Dist: opentelemetry-sdk<2; extra == "agent-engines"
32
32
  Requires-Dist: opentelemetry-exporter-gcp-logging<2.0.0,>=1.11.0a0; extra == "agent-engines"
33
33
  Requires-Dist: opentelemetry-exporter-otlp-proto-http<2; extra == "agent-engines"
34
- Requires-Dist: opentelemetry-instrumentation-google-genai<1.0.0,>=0.3b0; extra == "agent-engines"
34
+ Requires-Dist: opentelemetry-instrumentation-google-genai<2.0.0,>=0.3b0; extra == "agent-engines"
35
35
  Requires-Dist: pydantic<3,>=2.11.1; extra == "agent-engines"
36
36
  Provides-Extra: a2a
37
37
  Requires-Dist: google-cloud-agentplatform[agent_engines]; extra == "a2a"
@@ -22,6 +22,7 @@ import datetime
22
22
  import json
23
23
  import logging
24
24
  import os
25
+ import random
25
26
  import threading
26
27
  import time
27
28
  from typing import Any, Callable, Literal, Optional, Union, cast
@@ -122,6 +123,11 @@ def _temp_logger_level(logger_name: str, level: int) -> None: # type: ignore[mi
122
123
  logger_instance.setLevel(original_level)
123
124
 
124
125
 
126
+ def _is_missing(value: Any) -> bool:
127
+ """Returns True for None or a scalar NaN, such as a missing DataFrame cell."""
128
+ return bool(pd.api.types.is_scalar(value) and pd.isna(value))
129
+
130
+
125
131
  def _get_api_client_with_location(
126
132
  api_client: BaseApiClient, location: Optional[str]
127
133
  ) -> BaseApiClient:
@@ -153,6 +159,8 @@ def _get_runtime_instance(
153
159
  client = agentplatform.Client(
154
160
  project=api_client.project,
155
161
  location=api_client.location,
162
+ credentials=api_client._credentials,
163
+ http_options=api_client._http_options,
156
164
  )
157
165
  _thread_local_data.runtime_instances[agent_name] = client.runtimes.get(
158
166
  name=agent_name
@@ -160,6 +168,45 @@ def _get_runtime_instance(
160
168
  return _thread_local_data.runtime_instances[agent_name]
161
169
 
162
170
 
171
+ def _is_retryable_error(e: Exception) -> bool:
172
+ """Returns True for transient errors that are safe to retry with backoff."""
173
+ if isinstance(e, genai_errors.APIError):
174
+ return e.code in _evals_constant.RETRYABLE_STATUS_CODES
175
+ return isinstance(
176
+ e, (api_exceptions.ResourceExhausted, api_exceptions.ServiceUnavailable)
177
+ )
178
+
179
+
180
+ def _get_retry_backoff(
181
+ e: Exception, attempt: int, max_retries: int, operation: str
182
+ ) -> Optional[float]:
183
+ """Returns the seconds to wait before the next attempt, or None to stop.
184
+
185
+ Retryable errors back off exponentially with jitter. Other errors fail
186
+ fast, and the last attempt never waits.
187
+ """
188
+ if not _is_retryable_error(e) or attempt == max_retries - 1:
189
+ logger.error(
190
+ "Error during %s on attempt %d/%d: %s",
191
+ operation,
192
+ attempt + 1,
193
+ max_retries,
194
+ e,
195
+ )
196
+ return None
197
+ backoff = 2**attempt + random.uniform(0, 1)
198
+ logger.warning(
199
+ "Retryable error during %s on attempt %d/%d: %s. Retrying in %.1f"
200
+ " seconds...",
201
+ operation,
202
+ attempt + 1,
203
+ max_retries,
204
+ e,
205
+ backoff,
206
+ )
207
+ return backoff
208
+
209
+
163
210
  def _generate_content_with_retry(
164
211
  api_client: BaseApiClient,
165
212
  model: str,
@@ -222,29 +269,11 @@ def _generate_content_with_retry(
222
269
  }
223
270
  else:
224
271
  return response
225
- except api_exceptions.ResourceExhausted as e:
226
- logger.warning(
227
- "Resource Exhausted error on attempt %d/%d: %s. Retrying in %s"
228
- " seconds...",
229
- attempt + 1,
230
- max_retries,
231
- e,
232
- 2**attempt,
233
- )
234
- if attempt == max_retries - 1:
235
- return {"error": f"Resource exhausted after retries: {e}"}
236
- time.sleep(2**attempt)
237
272
  except Exception as e: # pylint: disable=broad-exception-caught
238
- logger.error(
239
- "Unexpected error during generate_content on attempt %d/%d: %s",
240
- attempt + 1,
241
- max_retries,
242
- e,
243
- )
244
-
245
- if attempt == max_retries - 1:
246
- return {"error": f"Failed after retries: {e}"}
247
- time.sleep(1)
273
+ backoff = _get_retry_backoff(e, attempt, max_retries, "generate_content")
274
+ if backoff is None:
275
+ return {"error": f"Failed on attempt {attempt + 1}/{max_retries}: {e}"}
276
+ time.sleep(backoff)
248
277
  return {"error": f"Failed to generate content after {max_retries} retries"}
249
278
 
250
279
 
@@ -1561,7 +1590,7 @@ def _build_interaction_id_dataset(
1561
1590
  eval_cases = []
1562
1591
  for i, row in enumerate(loaded_data):
1563
1592
  interaction = row.get(_evals_constant.INTERACTION_ID)
1564
- if not interaction:
1593
+ if _is_missing(interaction) or not interaction:
1565
1594
  raise ValueError(f"Missing `interaction_id` value for row {i}.")
1566
1595
  eval_cases.append(
1567
1596
  types.EvalCase(
@@ -1871,12 +1900,12 @@ def _execute_inference_concurrently(
1871
1900
  max_workers = AGENT_MAX_WORKERS if runtime or agent else MAX_WORKERS
1872
1901
  with tqdm(total=len(prompt_dataset), desc=progress_desc) as pbar:
1873
1902
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
1874
- for index, row in prompt_dataset.iterrows():
1903
+ for index, (_, row) in enumerate(prompt_dataset.iterrows()):
1875
1904
  try:
1876
1905
  if (
1877
1906
  has_agent_data
1878
1907
  and AGENT_DATA in row.index
1879
- and row.get(AGENT_DATA) is not None
1908
+ and not _is_missing(row.get(AGENT_DATA))
1880
1909
  ):
1881
1910
  agent_data_obj = row[AGENT_DATA]
1882
1911
  if isinstance(agent_data_obj, dict):
@@ -2101,7 +2130,7 @@ def _run_litellm_inference(
2101
2130
 
2102
2131
  with tqdm(total=len(prompt_dataset), desc=f"LiteLLM Inference ({model})") as pbar:
2103
2132
  with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
2104
- for index, row in prompt_dataset.iterrows():
2133
+ for index, (_, row) in enumerate(prompt_dataset.iterrows()):
2105
2134
  messages = _convert_prompt_row_to_litellm_messages(row)
2106
2135
  future = executor.submit(
2107
2136
  _call_litellm_completion, model=model, messages=messages
@@ -2654,7 +2683,7 @@ def _execute_inference(
2654
2683
  "Gemini Agent inference completed in %.2f seconds.",
2655
2684
  end_time - start_time,
2656
2685
  )
2657
- return types.EvaluationDataset(
2686
+ evaluation_dataset = types.EvaluationDataset(
2658
2687
  eval_dataset_df=results_df,
2659
2688
  candidate_name=gemini_agent.split("/")[-1],
2660
2689
  )
@@ -2759,7 +2788,12 @@ def _execute_inference(
2759
2788
  results_df.to_json(full_dest_path, orient="records", lines=True)
2760
2789
  logger.info("Results saved locally to: %s", full_dest_path)
2761
2790
  except Exception as e: # pylint: disable=broad-exception-caught
2762
- logger.error("Failed to save results to %s. Error: %s", full_dest_path, e)
2791
+ logger.error(
2792
+ "Failed to save results to %s. Error: %s",
2793
+ full_dest_path,
2794
+ e,
2795
+ exc_info=True,
2796
+ )
2763
2797
 
2764
2798
  return evaluation_dataset
2765
2799
 
@@ -2886,6 +2920,21 @@ def _resolve_dataset_inputs(
2886
2920
  return processed_eval_dataset, num_response_candidates
2887
2921
 
2888
2922
 
2923
+ def _prebuilt_evaluation_run_metric(
2924
+ resolved_metric: types.Metric,
2925
+ ) -> types.EvaluationRunMetric:
2926
+ """Builds the evaluation run metric for a resolved RubricMetric."""
2927
+ if resolved_metric.name in _evals_constant.SUPPORTED_PREDEFINED_METRICS:
2928
+ metric_config = t.t_metrics([resolved_metric])[0]
2929
+ else:
2930
+ metric_config = {
2931
+ "predefined_metric_spec": {"metric_spec_name": resolved_metric.name}
2932
+ }
2933
+ return types.EvaluationRunMetric(
2934
+ metric=resolved_metric.name, metric_config=metric_config
2935
+ )
2936
+
2937
+
2889
2938
  def _resolve_evaluation_run_metrics(
2890
2939
  metrics: Union[list[types.EvaluationRunMetric], list[types.Metric]], api_client: Any
2891
2940
  ) -> list[types.EvaluationRunMetric]:
@@ -2903,14 +2952,7 @@ def _resolve_evaluation_run_metrics(
2903
2952
  resolved_metric = metric_instance.resolve(api_client=api_client)
2904
2953
  if resolved_metric.name:
2905
2954
  resolved_metrics_list.append(
2906
- types.EvaluationRunMetric(
2907
- metric=resolved_metric.name,
2908
- metric_config=types.UnifiedMetric(
2909
- predefined_metric_spec=genai_types.PredefinedMetricSpec(
2910
- metric_spec_name=resolved_metric.name,
2911
- )
2912
- ),
2913
- )
2955
+ _prebuilt_evaluation_run_metric(resolved_metric)
2914
2956
  )
2915
2957
  except Exception as e:
2916
2958
  logger.error(
@@ -2944,14 +2986,7 @@ def _resolve_evaluation_run_metrics(
2944
2986
  )
2945
2987
  if resolved_metric.name:
2946
2988
  resolved_metrics_list.append(
2947
- types.EvaluationRunMetric(
2948
- metric=resolved_metric.name,
2949
- metric_config=types.UnifiedMetric(
2950
- predefined_metric_spec=genai_types.PredefinedMetricSpec(
2951
- metric_spec_name=resolved_metric.name,
2952
- )
2953
- ),
2954
- )
2989
+ _prebuilt_evaluation_run_metric(resolved_metric)
2955
2990
  )
2956
2991
  else:
2957
2992
  raise TypeError(
@@ -3020,6 +3055,7 @@ def _execute_evaluation( # type: ignore[no-untyped-def]
3020
3055
  dest: Optional[str] = None,
3021
3056
  location: Optional[str] = None,
3022
3057
  evaluation_service_qps: Optional[float] = None,
3058
+ allow_cross_region_model: Optional[bool] = None,
3023
3059
  **kwargs,
3024
3060
  ) -> types.EvaluationResult:
3025
3061
  """Evaluates a dataset using the provided metrics.
@@ -3035,6 +3071,8 @@ def _execute_evaluation( # type: ignore[no-untyped-def]
3035
3071
  evaluation_service_qps: The rate limit (queries per second) for calls
3036
3072
  to the evaluation service. Defaults to 10. Increase this value if
3037
3073
  your project has a higher EvaluateInstances API quota.
3074
+ allow_cross_region_model: Opt-in flag to authorize cross-region
3075
+ routing for judge models.
3038
3076
  **kwargs: Extra arguments to pass to evaluation, such as `agent_info`.
3039
3077
 
3040
3078
  Returns:
@@ -3117,6 +3155,7 @@ def _execute_evaluation( # type: ignore[no-untyped-def]
3117
3155
  evaluation_result = _evals_metric_handlers.compute_metrics_and_aggregate(
3118
3156
  evaluation_run_config,
3119
3157
  evaluation_service_qps=evaluation_service_qps,
3158
+ allow_cross_region_model=allow_cross_region_model,
3120
3159
  )
3121
3160
  t2 = time.perf_counter()
3122
3161
  logger.info("Evaluation took: %f seconds", t2 - t1)
@@ -3471,7 +3510,9 @@ def _create_runtime_session(
3471
3510
  raise RuntimeError(
3472
3511
  "Failed to create session: runtime.api_client is None."
3473
3512
  ) from exc
3474
- operation = runtime.api_client.sessions.create(
3513
+ from . import sessions
3514
+
3515
+ operation = sessions.Sessions(runtime.api_client._api_client).create(
3475
3516
  name=runtime.api_resource.name,
3476
3517
  user_id=user_id,
3477
3518
  config=types.CreateRuntimeSessionConfig(
@@ -3535,10 +3576,12 @@ def _execute_agent_run_with_retry(
3535
3576
  return {"error": "runtime.api_resource is None."}
3536
3577
  if runtime.api_client is None:
3537
3578
  return {"error": "runtime.api_client is None."}
3579
+ from . import sessions
3580
+
3538
3581
  session_name = f"{runtime.api_resource.name}/sessions/{session_id}"
3539
3582
  base_ts = datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
3540
3583
  for i, ag_event in enumerate(history_events):
3541
- runtime.api_client.sessions.events.append(
3584
+ sessions.Sessions(runtime.api_client._api_client).events.append(
3542
3585
  name=session_name,
3543
3586
  author=ag_event.author or "user",
3544
3587
  invocation_id="history",
@@ -3560,28 +3603,11 @@ def _execute_agent_run_with_retry(
3560
3603
  if event and CONTENT in event and PARTS in event[CONTENT]:
3561
3604
  responses.append(event)
3562
3605
  return responses
3563
- except api_exceptions.ResourceExhausted as e:
3564
- logger.warning(
3565
- "Resource Exhausted error on attempt %d/%d: %s. Retrying in %s"
3566
- " seconds...",
3567
- attempt + 1,
3568
- max_retries,
3569
- e,
3570
- 2**attempt,
3571
- )
3572
- if attempt == max_retries - 1:
3573
- return {"error": f"Resource exhausted after retries: {e}"}
3574
- time.sleep(2**attempt)
3575
3606
  except Exception as e: # pylint: disable=broad-exception-caught
3576
- logger.error(
3577
- "Unexpected error during agent engine run on attempt %d/%d: %s",
3578
- attempt + 1,
3579
- max_retries,
3580
- e,
3581
- )
3582
- if attempt == max_retries - 1:
3583
- return {"error": f"Failed after retries: {e}"}
3584
- time.sleep(1)
3607
+ backoff = _get_retry_backoff(e, attempt, max_retries, "agent engine run")
3608
+ if backoff is None:
3609
+ return {"error": f"Failed on attempt {attempt + 1}/{max_retries}: {e}"}
3610
+ time.sleep(backoff)
3585
3611
  return {"error": f"Failed to get agent run results after {max_retries} retries"}
3586
3612
 
3587
3613
 
@@ -3613,7 +3639,7 @@ async def _execute_local_agent_run_with_retry_async(
3613
3639
  # Lazy-import ADK dependencies to avoid top-level import failures when
3614
3640
  # google-adk is not installed.
3615
3641
  from google.adk.runners import Runner
3616
- from google.adk.sessions import InMemorySessionService
3642
+ from google.adk.sessions.in_memory_session_service import InMemorySessionService
3617
3643
 
3618
3644
  # Multi-turn agent scraping with user simulation.
3619
3645
  if user_simulator_config or "conversation_plan" in row:
@@ -3687,28 +3713,13 @@ async def _execute_local_agent_run_with_retry_async(
3687
3713
  if event and CONTENT in event and PARTS in event[CONTENT]:
3688
3714
  events.append(event)
3689
3715
  return events
3690
- except api_exceptions.ResourceExhausted as e:
3691
- logger.warning(
3692
- "Resource Exhausted error on attempt %d/%d: %s. Retrying"
3693
- " in %s seconds...",
3694
- attempt + 1,
3695
- max_retries,
3696
- e,
3697
- 2**attempt,
3698
- )
3699
- if attempt == max_retries - 1:
3700
- return {"error": f"Resource exhausted after retries: {e}"}
3701
- await asyncio.sleep(2**attempt)
3702
3716
  except Exception as e: # pylint: disable=broad-exception-caught
3703
- logger.error(
3704
- "Unexpected error during agent run on attempt %d/%d: %s",
3705
- attempt + 1,
3706
- max_retries,
3707
- e,
3708
- )
3709
- if attempt == max_retries - 1:
3710
- return {"error": f"Failed after retries: {e}"}
3711
- await asyncio.sleep(1)
3717
+ backoff = _get_retry_backoff(e, attempt, max_retries, "agent run")
3718
+ if backoff is None:
3719
+ return {
3720
+ "error": f"Failed on attempt {attempt + 1}/{max_retries}: {e}"
3721
+ }
3722
+ await asyncio.sleep(backoff)
3712
3723
  return {"error": f"Failed to get agent run results after {max_retries} retries"}
3713
3724
 
3714
3725
 
@@ -3864,7 +3875,7 @@ def _drop_empty_columns(df: "pd.DataFrame") -> "pd.DataFrame":
3864
3875
  def is_empty(x: Any) -> bool:
3865
3876
  if isinstance(x, (list, dict)):
3866
3877
  return not x
3867
- return pd.isna(x) # type: ignore[no-any-return]
3878
+ return _is_missing(x)
3868
3879
 
3869
3880
  cols_to_drop = [col for col in df.columns if df[col].apply(is_empty).all()]
3870
3881
  return df.drop(columns=cols_to_drop)
@@ -4163,6 +4174,8 @@ def _create_evaluation_set_from_dataframe(
4163
4174
  if _evals_constant.RESPONSE in row or agent_data_obj or intermediate_events:
4164
4175
  # Resolve the oneof conflict: prioritize agent_data over flat text
4165
4176
  response_text = row.get(_evals_constant.RESPONSE) or None
4177
+ if _is_missing(response_text):
4178
+ response_text = None
4166
4179
 
4167
4180
  if agent_data_obj and response_text:
4168
4181
  logger.info(
@@ -4217,7 +4230,7 @@ def _create_evaluation_set_from_dataframe(
4217
4230
  prompt = types.EvaluationPrompt(
4218
4231
  prompt_template_data=types.PromptTemplateData(values=values)
4219
4232
  )
4220
- elif _evals_constant.PROMPT in row:
4233
+ elif not _is_missing(row.get(_evals_constant.PROMPT)):
4221
4234
  prompt = types.EvaluationPrompt(text=row[_evals_constant.PROMPT])
4222
4235
 
4223
4236
  eval_item_requests.append(
@@ -4225,7 +4238,7 @@ def _create_evaluation_set_from_dataframe(
4225
4238
  prompt=prompt or None,
4226
4239
  golden_response=(
4227
4240
  types.CandidateResponse(text=row[_evals_constant.REFERENCE])
4228
- if _evals_constant.REFERENCE in row
4241
+ if not _is_missing(row.get(_evals_constant.REFERENCE))
4229
4242
  else None
4230
4243
  ),
4231
4244
  candidate_responses=(
@@ -120,3 +120,17 @@ COMMON_DATASET_COLUMNS = frozenset(
120
120
  INTERACTION_ID,
121
121
  }
122
122
  )
123
+
124
+ # HTTP status codes that are safe to retry with backoff.
125
+ RETRYABLE_STATUS_CODES = frozenset(
126
+ {
127
+ 408, # RequestTimeout (DEADLINE_EXCEEDED)
128
+ 409, # Conflict / Aborted (ABORTED)
129
+ 429, # TooManyRequests / ResourceExhausted (RESOURCE_EXHAUSTED)
130
+ 499, # Client Closed Request (CANCELLED)
131
+ 500, # InternalServerError (INTERNAL)
132
+ 502, # BadGateway
133
+ 503, # ServiceUnavailable (UNAVAILABLE)
134
+ 504, # GatewayTimeout (DEADLINE_EXCEEDED)
135
+ }
136
+ )