google-cloud-agentplatform 2.4.0__tar.gz → 2.6.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.4.0/google_cloud_agentplatform.egg-info → google_cloud_agentplatform-2.6.0}/PKG-INFO +2 -2
  2. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_evals_common.py +120 -81
  3. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_evals_constant.py +14 -0
  4. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_evals_data_converters.py +17 -0
  5. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_evals_metric_handlers.py +8 -15
  6. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_evals_visualization.py +17 -3
  7. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_runtimes_utils.py +18 -0
  8. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/evals.py +6 -6
  9. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/memory_banks.py +268 -0
  10. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/model_garden.py +1027 -61
  11. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/runtimes.py +26 -8
  12. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/types/__init__.py +72 -0
  13. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/types/common.py +807 -5
  14. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/types/evals.py +1 -1
  15. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/frameworks/adk.py +218 -85
  16. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/version.py +1 -1
  17. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0/google_cloud_agentplatform.egg-info}/PKG-INFO +2 -2
  18. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/google_cloud_agentplatform.egg-info/requires.txt +1 -1
  19. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/pyproject.toml +1 -1
  20. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/LICENSE +0 -0
  21. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/README.md +0 -0
  22. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/__init__.py +0 -0
  23. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/__init__.py +0 -0
  24. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_bigquery_utils.py +0 -0
  25. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_datasets_utils.py +0 -0
  26. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_evals_builtin_tools.py +0 -0
  27. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_evals_metric_loaders.py +0 -0
  28. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_evals_utils.py +0 -0
  29. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_gcs_utils.py +0 -0
  30. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_logging_utils.py +0 -0
  31. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_memory_bank_utils.py +0 -0
  32. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_observability_data_converter.py +0 -0
  33. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_operations_utils.py +0 -0
  34. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_prompt_management_utils.py +0 -0
  35. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_prompt_optimizer_utils.py +0 -0
  36. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_skills_utils.py +0 -0
  37. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/_transformers.py +0 -0
  38. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/a2a_task_events.py +0 -0
  39. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/a2a_tasks.py +0 -0
  40. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/client.py +0 -0
  41. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/datasets.py +0 -0
  42. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/endpoints.py +0 -0
  43. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/example_stores.py +0 -0
  44. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/feedback_contexts.py +0 -0
  45. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/feedback_entries.py +0 -0
  46. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/live.py +0 -0
  47. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/live_runtimes.py +0 -0
  48. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/memories.py +0 -0
  49. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/memory_revisions.py +0 -0
  50. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/prompt_optimizer.py +0 -0
  51. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/prompts.py +0 -0
  52. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/rag.py +0 -0
  53. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/runtime_revisions.py +0 -0
  54. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/sandbox_snapshots.py +0 -0
  55. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/sandbox_templates.py +0 -0
  56. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/sandboxes.py +0 -0
  57. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/serving_profiles.py +0 -0
  58. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/session_events.py +0 -0
  59. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/sessions.py +0 -0
  60. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/skill_revisions.py +0 -0
  61. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/skills.py +0 -0
  62. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/types/agent_engines.py +0 -0
  63. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/types/prompt_optimizer.py +0 -0
  64. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/_genai/types/prompts.py +0 -0
  65. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/frameworks/__init__.py +0 -0
  66. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/frameworks/a2a.py +0 -0
  67. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/frameworks/ag2.py +0 -0
  68. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/frameworks/langchain.py +0 -0
  69. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/frameworks/langgraph.py +0 -0
  70. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/agentplatform/frameworks/llama_index.py +0 -0
  71. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/google_cloud_agentplatform.egg-info/SOURCES.txt +0 -0
  72. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/google_cloud_agentplatform.egg-info/dependency_links.txt +0 -0
  73. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/google_cloud_agentplatform.egg-info/top_level.txt +0 -0
  74. {google_cloud_agentplatform-2.4.0 → google_cloud_agentplatform-2.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-cloud-agentplatform
3
- Version: 2.4.0
3
+ Version: 2.6.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
 
@@ -267,9 +296,8 @@ def _build_generate_content_config(
267
296
  for key in [
268
297
  "system_instruction",
269
298
  "tools",
270
- "tools_config",
299
+ "tool_config",
271
300
  "safety_settings",
272
- "labels",
273
301
  ]:
274
302
  if key in request_dict:
275
303
  merged_config_dict[key] = request_dict[key]
@@ -1561,7 +1589,7 @@ def _build_interaction_id_dataset(
1561
1589
  eval_cases = []
1562
1590
  for i, row in enumerate(loaded_data):
1563
1591
  interaction = row.get(_evals_constant.INTERACTION_ID)
1564
- if not interaction:
1592
+ if _is_missing(interaction) or not interaction:
1565
1593
  raise ValueError(f"Missing `interaction_id` value for row {i}.")
1566
1594
  eval_cases.append(
1567
1595
  types.EvalCase(
@@ -1871,12 +1899,12 @@ def _execute_inference_concurrently(
1871
1899
  max_workers = AGENT_MAX_WORKERS if runtime or agent else MAX_WORKERS
1872
1900
  with tqdm(total=len(prompt_dataset), desc=progress_desc) as pbar:
1873
1901
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
1874
- for index, row in prompt_dataset.iterrows():
1902
+ for index, (_, row) in enumerate(prompt_dataset.iterrows()):
1875
1903
  try:
1876
1904
  if (
1877
1905
  has_agent_data
1878
1906
  and AGENT_DATA in row.index
1879
- and row.get(AGENT_DATA) is not None
1907
+ and not _is_missing(row.get(AGENT_DATA))
1880
1908
  ):
1881
1909
  agent_data_obj = row[AGENT_DATA]
1882
1910
  if isinstance(agent_data_obj, dict):
@@ -2101,7 +2129,7 @@ def _run_litellm_inference(
2101
2129
 
2102
2130
  with tqdm(total=len(prompt_dataset), desc=f"LiteLLM Inference ({model})") as pbar:
2103
2131
  with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
2104
- for index, row in prompt_dataset.iterrows():
2132
+ for index, (_, row) in enumerate(prompt_dataset.iterrows()):
2105
2133
  messages = _convert_prompt_row_to_litellm_messages(row)
2106
2134
  future = executor.submit(
2107
2135
  _call_litellm_completion, model=model, messages=messages
@@ -3481,7 +3509,9 @@ def _create_runtime_session(
3481
3509
  raise RuntimeError(
3482
3510
  "Failed to create session: runtime.api_client is None."
3483
3511
  ) from exc
3484
- operation = runtime.api_client.sessions.create(
3512
+ from . import sessions
3513
+
3514
+ operation = sessions.Sessions(runtime.api_client._api_client).create(
3485
3515
  name=runtime.api_resource.name,
3486
3516
  user_id=user_id,
3487
3517
  config=types.CreateRuntimeSessionConfig(
@@ -3545,10 +3575,12 @@ def _execute_agent_run_with_retry(
3545
3575
  return {"error": "runtime.api_resource is None."}
3546
3576
  if runtime.api_client is None:
3547
3577
  return {"error": "runtime.api_client is None."}
3578
+ from . import sessions
3579
+
3548
3580
  session_name = f"{runtime.api_resource.name}/sessions/{session_id}"
3549
3581
  base_ts = datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
3550
3582
  for i, ag_event in enumerate(history_events):
3551
- runtime.api_client.sessions.events.append(
3583
+ sessions.Sessions(runtime.api_client._api_client).events.append(
3552
3584
  name=session_name,
3553
3585
  author=ag_event.author or "user",
3554
3586
  invocation_id="history",
@@ -3570,28 +3602,11 @@ def _execute_agent_run_with_retry(
3570
3602
  if event and CONTENT in event and PARTS in event[CONTENT]:
3571
3603
  responses.append(event)
3572
3604
  return responses
3573
- except api_exceptions.ResourceExhausted as e:
3574
- logger.warning(
3575
- "Resource Exhausted error on attempt %d/%d: %s. Retrying in %s"
3576
- " seconds...",
3577
- attempt + 1,
3578
- max_retries,
3579
- e,
3580
- 2**attempt,
3581
- )
3582
- if attempt == max_retries - 1:
3583
- return {"error": f"Resource exhausted after retries: {e}"}
3584
- time.sleep(2**attempt)
3585
3605
  except Exception as e: # pylint: disable=broad-exception-caught
3586
- logger.error(
3587
- "Unexpected error during agent engine run on attempt %d/%d: %s",
3588
- attempt + 1,
3589
- max_retries,
3590
- e,
3591
- )
3592
- if attempt == max_retries - 1:
3593
- return {"error": f"Failed after retries: {e}"}
3594
- time.sleep(1)
3606
+ backoff = _get_retry_backoff(e, attempt, max_retries, "agent engine run")
3607
+ if backoff is None:
3608
+ return {"error": f"Failed on attempt {attempt + 1}/{max_retries}: {e}"}
3609
+ time.sleep(backoff)
3595
3610
  return {"error": f"Failed to get agent run results after {max_retries} retries"}
3596
3611
 
3597
3612
 
@@ -3623,7 +3638,7 @@ async def _execute_local_agent_run_with_retry_async(
3623
3638
  # Lazy-import ADK dependencies to avoid top-level import failures when
3624
3639
  # google-adk is not installed.
3625
3640
  from google.adk.runners import Runner
3626
- from google.adk.sessions import InMemorySessionService
3641
+ from google.adk.sessions.in_memory_session_service import InMemorySessionService
3627
3642
 
3628
3643
  # Multi-turn agent scraping with user simulation.
3629
3644
  if user_simulator_config or "conversation_plan" in row:
@@ -3697,28 +3712,13 @@ async def _execute_local_agent_run_with_retry_async(
3697
3712
  if event and CONTENT in event and PARTS in event[CONTENT]:
3698
3713
  events.append(event)
3699
3714
  return events
3700
- except api_exceptions.ResourceExhausted as e:
3701
- logger.warning(
3702
- "Resource Exhausted error on attempt %d/%d: %s. Retrying"
3703
- " in %s seconds...",
3704
- attempt + 1,
3705
- max_retries,
3706
- e,
3707
- 2**attempt,
3708
- )
3709
- if attempt == max_retries - 1:
3710
- return {"error": f"Resource exhausted after retries: {e}"}
3711
- await asyncio.sleep(2**attempt)
3712
3715
  except Exception as e: # pylint: disable=broad-exception-caught
3713
- logger.error(
3714
- "Unexpected error during agent run on attempt %d/%d: %s",
3715
- attempt + 1,
3716
- max_retries,
3717
- e,
3718
- )
3719
- if attempt == max_retries - 1:
3720
- return {"error": f"Failed after retries: {e}"}
3721
- await asyncio.sleep(1)
3716
+ backoff = _get_retry_backoff(e, attempt, max_retries, "agent run")
3717
+ if backoff is None:
3718
+ return {
3719
+ "error": f"Failed on attempt {attempt + 1}/{max_retries}: {e}"
3720
+ }
3721
+ await asyncio.sleep(backoff)
3722
3722
  return {"error": f"Failed to get agent run results after {max_retries} retries"}
3723
3723
 
3724
3724
 
@@ -3734,7 +3734,10 @@ def _convert_gcs_to_evaluation_item_result(
3734
3734
  return types.EvaluationItemResult(**eval_item_data)
3735
3735
  except Exception as e:
3736
3736
  logger.error(
3737
- "Failed to load evaluation result from GCS: %s. Error: %s", gcs_uri, e
3737
+ "Failed to load evaluation result from GCS: %s. Error: %s",
3738
+ gcs_uri,
3739
+ e,
3740
+ exc_info=True,
3738
3741
  )
3739
3742
  return types.EvaluationItemResult()
3740
3743
 
@@ -3751,7 +3754,10 @@ def _convert_gcs_to_evaluation_item_request(
3751
3754
  return types.EvaluationItemRequest(**eval_item_data)
3752
3755
  except Exception as e:
3753
3756
  logger.error(
3754
- "Failed to load evaluation request from GCS: %s. Error: %s", gcs_uri, e
3757
+ "Failed to load evaluation request from GCS: %s. Error: %s",
3758
+ gcs_uri,
3759
+ e,
3760
+ exc_info=True,
3755
3761
  )
3756
3762
  return types.EvaluationItemRequest()
3757
3763
 
@@ -3874,7 +3880,7 @@ def _drop_empty_columns(df: "pd.DataFrame") -> "pd.DataFrame":
3874
3880
  def is_empty(x: Any) -> bool:
3875
3881
  if isinstance(x, (list, dict)):
3876
3882
  return not x
3877
- return pd.isna(x) # type: ignore[no-any-return]
3883
+ return _is_missing(x)
3878
3884
 
3879
3885
  cols_to_drop = [col for col in df.columns if df[col].apply(is_empty).all()]
3880
3886
  return df.drop(columns=cols_to_drop)
@@ -3925,16 +3931,16 @@ def _get_eval_cases_eval_dfs_from_eval_items(
3925
3931
  item.
3926
3932
  - eval_dfs: A list of EvaluationDatasets, one for each candidate.
3927
3933
  """
3928
- dataset_rows = []
3934
+ dataset_rows: list[dict[str, Any]] = []
3929
3935
  eval_case_results = []
3930
- for index, eval_item in enumerate(eval_items):
3936
+ for eval_item in eval_items:
3931
3937
  if (
3932
3938
  eval_item
3933
3939
  and eval_item.evaluation_response
3934
3940
  and eval_item.evaluation_response.request
3935
3941
  ):
3936
3942
  eval_case_results.append(
3937
- _get_eval_case_result_from_eval_item(index, eval_item)
3943
+ _get_eval_case_result_from_eval_item(len(dataset_rows), eval_item)
3938
3944
  )
3939
3945
  dataset_rows.append(
3940
3946
  _convert_request_to_dataset_row(eval_item.evaluation_response.request)
@@ -4119,6 +4125,36 @@ def _get_content(row: dict[str, Any], column: str) -> Optional[genai_types.Conte
4119
4125
  )
4120
4126
 
4121
4127
 
4128
+ # Columns mapped to EvaluationItemRequest fields; all others go to extra_context.
4129
+ _EVALUATION_SET_STANDARD_COLUMNS = _evals_constant.COMMON_DATASET_COLUMNS | {
4130
+ _evals_constant.RESPONSE,
4131
+ "interaction",
4132
+ "gemini_agent",
4133
+ }
4134
+
4135
+
4136
+ def _to_extra_context_content(value: Any) -> Optional[genai_types.Content]:
4137
+ if isinstance(value, str):
4138
+ return genai_types.Content(parts=[genai_types.Part(text=value)])
4139
+ if isinstance(value, genai_types.Content):
4140
+ return value
4141
+ parts = []
4142
+ for content in _evals_metric_handlers._value_to_content_list(value):
4143
+ parts.extend(content.parts or [])
4144
+ return genai_types.Content(parts=parts) if parts else None
4145
+
4146
+
4147
+ def _get_extra_context(row: "pd.Series") -> Optional[dict[str, genai_types.Content]]:
4148
+ extra_context = {}
4149
+ for column, value in row.items():
4150
+ if column in _EVALUATION_SET_STANDARD_COLUMNS or _is_missing(value):
4151
+ continue
4152
+ content = _to_extra_context_content(value)
4153
+ if content:
4154
+ extra_context[str(column)] = content
4155
+ return extra_context or None
4156
+
4157
+
4122
4158
  def _create_evaluation_set_from_dataframe(
4123
4159
  api_client: BaseApiClient,
4124
4160
  gcs_dest_prefix: str,
@@ -4173,6 +4209,8 @@ def _create_evaluation_set_from_dataframe(
4173
4209
  if _evals_constant.RESPONSE in row or agent_data_obj or intermediate_events:
4174
4210
  # Resolve the oneof conflict: prioritize agent_data over flat text
4175
4211
  response_text = row.get(_evals_constant.RESPONSE) or None
4212
+ if _is_missing(response_text):
4213
+ response_text = None
4176
4214
 
4177
4215
  if agent_data_obj and response_text:
4178
4216
  logger.info(
@@ -4227,7 +4265,7 @@ def _create_evaluation_set_from_dataframe(
4227
4265
  prompt = types.EvaluationPrompt(
4228
4266
  prompt_template_data=types.PromptTemplateData(values=values)
4229
4267
  )
4230
- elif _evals_constant.PROMPT in row:
4268
+ elif not _is_missing(row.get(_evals_constant.PROMPT)):
4231
4269
  prompt = types.EvaluationPrompt(text=row[_evals_constant.PROMPT])
4232
4270
 
4233
4271
  eval_item_requests.append(
@@ -4235,12 +4273,13 @@ def _create_evaluation_set_from_dataframe(
4235
4273
  prompt=prompt or None,
4236
4274
  golden_response=(
4237
4275
  types.CandidateResponse(text=row[_evals_constant.REFERENCE])
4238
- if _evals_constant.REFERENCE in row
4276
+ if not _is_missing(row.get(_evals_constant.REFERENCE))
4239
4277
  else None
4240
4278
  ),
4241
4279
  candidate_responses=(
4242
4280
  candidate_responses if candidate_responses else None
4243
4281
  ),
4282
+ extra_context=_get_extra_context(row),
4244
4283
  )
4245
4284
  )
4246
4285
  logger.info("Writing evaluation item requests to GCS.")
@@ -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
+ )
@@ -230,6 +230,18 @@ class _FlattenEvalDataConverter(_evals_utils.EvalDataConverter):
230
230
  "Invalid prompt type for case %s: %s" % (i, type(prompt_data))
231
231
  )
232
232
 
233
+ if (
234
+ isinstance(conversation_history_data, str)
235
+ and conversation_history_data.strip()
236
+ ):
237
+ try:
238
+ conversation_history_data = json.loads(conversation_history_data)
239
+ except json.JSONDecodeError:
240
+ logger.warning(
241
+ "Could not decode conversation_history JSON string for case %s.",
242
+ eval_case_id,
243
+ )
244
+
233
245
  conversation_history: Optional[list[types.evals.Message]] = None
234
246
  if isinstance(conversation_history_data, list):
235
247
  conversation_history = []
@@ -242,6 +254,11 @@ class _FlattenEvalDataConverter(_evals_utils.EvalDataConverter):
242
254
  )
243
255
  )
244
256
  elif isinstance(content, dict):
257
+ if isinstance(content.get("content"), str):
258
+ content = {
259
+ "role": content.get("role"),
260
+ "parts": [{"text": content["content"]}],
261
+ }
245
262
  try:
246
263
  validated_content = genai_types.Content.model_validate(
247
264
  content
@@ -39,19 +39,6 @@ from . import types
39
39
 
40
40
  logger = logging.getLogger(__name__)
41
41
  _MAX_RETRIES = 5
42
- # HTTP status codes that are safe to retry with backoff.
43
- _RETRYABLE_STATUS_CODES = frozenset(
44
- {
45
- 408, # RequestTimeout (DEADLINE_EXCEEDED)
46
- 409, # Conflict / Aborted (ABORTED)
47
- 429, # TooManyRequests / ResourceExhausted (RESOURCE_EXHAUSTED)
48
- 499, # Client Closed Request (CANCELLED)
49
- 500, # InternalServerError (INTERNAL)
50
- 502, # BadGateway
51
- 503, # ServiceUnavailable (UNAVAILABLE)
52
- 504, # GatewayTimeout (DEADLINE_EXCEEDED)
53
- }
54
- )
55
42
 
56
43
  R = TypeVar("R")
57
44
  T = TypeVar("T", types.Metric, types.MetricSource, types.LLMMetric)
@@ -64,7 +51,7 @@ def _call_with_retry(
64
51
  """Calls ``fn()`` with exponential backoff + jitter on retryable errors.
65
52
 
66
53
  Retries up to ``_MAX_RETRIES`` times on errors whose HTTP status code is
67
- in ``_RETRYABLE_STATUS_CODES`` (Aborted, DeadlineExceeded,
54
+ in ``_evals_constant.RETRYABLE_STATUS_CODES`` (Aborted, DeadlineExceeded,
68
55
  ResourceExhausted, ServiceUnavailable, Cancelled). Non-retryable errors
69
56
  are re-raised immediately. If all retries are exhausted the last
70
57
  exception is re-raised so the caller can decide how to handle it.
@@ -84,7 +71,7 @@ def _call_with_retry(
84
71
  try:
85
72
  return fn()
86
73
  except genai_errors.APIError as e:
87
- if e.code in _RETRYABLE_STATUS_CODES:
74
+ if e.code in _evals_constant.RETRYABLE_STATUS_CODES:
88
75
  backoff = 2**attempt + random.uniform(0, 1)
89
76
  logger.warning(
90
77
  "Retryable error (code=%s) on attempt %d/%d for metric"
@@ -686,6 +673,12 @@ def _build_evaluation_instance(
686
673
  other_data_map["context"] = types.evals.InstanceData(text=eval_case.context)
687
674
  elif isinstance(eval_case.context, genai_types.Content):
688
675
  other_data_map["context"] = _content_to_instance_data(eval_case.context)
676
+ else:
677
+ other_data_map["context"] = types.evals.InstanceData(
678
+ contents=types.evals.InstanceDataContents(
679
+ contents=_value_to_content_list(eval_case.context)
680
+ )
681
+ )
689
682
 
690
683
  # 3. Extract custom variables from LLMMetric templates
691
684
  if prompt_template:
@@ -23,7 +23,7 @@ import textwrap
23
23
  from typing import Any, Optional
24
24
 
25
25
  import pandas as pd
26
- from pydantic import errors
26
+ import pydantic_core
27
27
 
28
28
  from . import _evals_common
29
29
  from . import types
@@ -229,6 +229,20 @@ def _extract_dataset_rows(dataset: types.EvaluationDataset) -> list[dict[str, An
229
229
  }
230
230
  processed_rows.append(processed_row)
231
231
 
232
+ for row in processed_rows:
233
+ if isinstance(row["agent_data"], str) or not isinstance(
234
+ row["intermediate_events"], str
235
+ ):
236
+ continue
237
+ try:
238
+ events = json.loads(row["intermediate_events"])
239
+ except json.JSONDecodeError:
240
+ continue
241
+ if isinstance(events, list) and events:
242
+ row["agent_data"] = json.dumps(
243
+ {"turns": [{"turn_index": 0, "events": events}]}, ensure_ascii=False
244
+ )
245
+
232
246
  return processed_rows
233
247
 
234
248
 
@@ -1155,7 +1169,7 @@ def get_comparison_html(eval_result_json: str) -> str:
1155
1169
  winRateText = m.win_rates.map((rate, i) => `<b>${{candidateNames ? candidateNames[i] : `Candidate #${{i+1}}`}}</b> wins: <b>${{(rate * 100).toFixed(1)}}%</b>`).join('<br>');
1156
1170
  if (m.tie_rate !== undefined) {{ winRateText += `<br>Ties: <b>${{(m.tie_rate * 100).toFixed(1)}}%</b>`; }}
1157
1171
  }}
1158
- table += `<tr><td>${{m.metric_name}}</td><td>${{m.mean_score.toFixed(4)}}</td><td>${{m.stdev_score.toFixed(4)}}</td><td>${{winRateText}}</td></tr>`;
1172
+ table += `<tr><td>${{m.metric_name}}</td><td>${{m.mean_score != null ? m.mean_score.toFixed(4) : 'N/A'}}</td><td>${{m.stdev_score != null ? m.stdev_score.toFixed(4) : 'N/A'}}</td><td>${{winRateText}}</td></tr>`;
1159
1173
  }});
1160
1174
  container.innerHTML = table + '</tbody></table>';
1161
1175
  }}
@@ -1469,7 +1483,7 @@ def display_evaluation_result(
1469
1483
  result_dump = eval_result_obj.model_dump(
1470
1484
  mode="json", exclude_none=True, exclude={"evaluation_dataset"}
1471
1485
  )
1472
- except errors.PydanticSerializationError as e:
1486
+ except pydantic_core.PydanticSerializationError as e:
1473
1487
  logger.error(
1474
1488
  "Serialization Error: %s\nCould not display the evaluation "
1475
1489
  "result due to a data serialization issue. Please check the "
@@ -52,6 +52,7 @@ import httpx
52
52
  import proto
53
53
 
54
54
  from google.api_core import exceptions
55
+ from google.genai import errors as google_genai_errors
55
56
  from google.genai import types as google_genai_types
56
57
  from google.api import httpbody_pb2
57
58
  from google.protobuf import struct_pb2
@@ -564,6 +565,23 @@ def _await_operation(
564
565
  return operation
565
566
 
566
567
 
568
+ def _raise_for_operation_error(
569
+ *,
570
+ operation: Any,
571
+ message: str,
572
+ ) -> None:
573
+ """Raises an APIError for a failed operation with a context note."""
574
+ try:
575
+ google_genai_errors.APIError.raise_error(
576
+ operation.error.get("code"), operation.error, None
577
+ )
578
+ except google_genai_errors.APIError as e:
579
+ # TODO(yifany): Drop guard once Python 3.10 support is removed.
580
+ if sys.version_info >= (3, 11):
581
+ e.add_note(message)
582
+ raise
583
+
584
+
567
585
  def _compare_requirements(
568
586
  *,
569
587
  requirements: Mapping[str, str],
@@ -3070,7 +3070,7 @@ class Evals(_api_module.BaseModule):
3070
3070
  events are replayed as session history for local ADK agents.
3071
3071
  model: Optional type is experimental and may change in future versions.
3072
3072
  The model to use for inference, optional for agent evaluations.
3073
- - For Google Gemini models, provide the model name string (e.g., "gemini-2.5-flash").
3073
+ - For Google Gemini models, provide the model name string (e.g., "gemini-flash-latest").
3074
3074
  - For third-party models via LiteLLM, use the format "provider/model_name"
3075
3075
  (e.g., "openai/gpt-4o"). Ensure the necessary API key (e.g., OPENAI_API_KEY)
3076
3076
  is set as an environment variable.
@@ -3342,7 +3342,7 @@ class Evals(_api_module.BaseModule):
3342
3342
  with the content.
3343
3343
  generator_model_config: Optional. Configuration for the model used
3344
3344
  in custom rubric generation. Only used if `prompt_template` is provided.
3345
- e.g., {"autorater_model": "gemini-2.5-flash"}.
3345
+ e.g., {"autorater_model": "gemini-flash-latest"}.
3346
3346
  rubric_content_type: Optional. The type of rubric content to be
3347
3347
  generated. Only used if `prompt_template` is provided.
3348
3348
  rubric_type_ontology: Optional. A pre-defined list of allowed types
@@ -3619,12 +3619,12 @@ class Evals(_api_module.BaseModule):
3619
3619
  If provided, `agent_info` must be None. If omitted and `agent_info` is provided,
3620
3620
  this will be automatically constructed using `agent_info` and `user_simulator_config`.
3621
3621
  The `model` field of an inference config accepts a short Gemini model
3622
- name (e.g. `gemini-2.5-flash`), which is automatically expanded to a
3622
+ name (e.g. `gemini-flash-latest`), which is automatically expanded to a
3623
3623
  fully-qualified resource name using the client's project and location,
3624
3624
  or an already fully-qualified publisher-model or endpoint resource
3625
3625
  name.
3626
3626
  Example:
3627
- {"candidate-1": types.EvaluationRunInferenceConfig(model="gemini-2.5-flash")}
3627
+ {"candidate-1": types.EvaluationRunInferenceConfig(model="gemini-flash-latest")}
3628
3628
  labels: The labels to apply to the evaluation run.
3629
3629
  loss_analysis_metrics: This field is experimental and may change in future
3630
3630
  versions. Optional list of metrics to run loss analysis on. The
@@ -6261,12 +6261,12 @@ class AsyncEvals(_api_module.BaseModule):
6261
6261
  If provided, `agent_info` must be None. If omitted and `agent_info` is provided,
6262
6262
  this will be automatically constructed using `agent_info` and `user_simulator_config`.
6263
6263
  The `model` field of an inference config accepts a short Gemini model
6264
- name (e.g. `gemini-2.5-flash`), which is automatically expanded to a
6264
+ name (e.g. `gemini-flash-latest`), which is automatically expanded to a
6265
6265
  fully-qualified resource name using the client's project and location,
6266
6266
  or an already fully-qualified publisher-model or endpoint resource
6267
6267
  name.
6268
6268
  Example:
6269
- {"candidate-1": types.EvaluationRunInferenceConfig(model="gemini-2.5-flash")}
6269
+ {"candidate-1": types.EvaluationRunInferenceConfig(model="gemini-flash-latest")}
6270
6270
  red_teaming_config: This field is experimental and may change in future
6271
6271
  versions. Optional configuration for automated Agent Red Teaming
6272
6272
  analysis. Specifies attack categories and vulnerable tools to