google-cloud-agentplatform 2.1.3__tar.gz → 2.4.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.1.3/google_cloud_agentplatform.egg-info → google_cloud_agentplatform-2.4.0}/PKG-INFO +1 -1
  2. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/__init__.py +11 -2
  3. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_common.py +48 -20
  4. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_data_converters.py +306 -1
  5. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_metric_handlers.py +21 -5
  6. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_metric_loaders.py +9 -4
  7. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_visualization.py +3 -0
  8. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_transformers.py +5 -1
  9. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/client.py +26 -0
  10. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/evals.py +28 -0
  11. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/memory_banks.py +7 -0
  12. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/runtimes.py +15 -2
  13. google_cloud_agentplatform-2.4.0/agentplatform/_genai/serving_profiles.py +1251 -0
  14. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/__init__.py +92 -0
  15. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/common.py +765 -31
  16. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/adk.py +35 -18
  17. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/version.py +1 -1
  18. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0/google_cloud_agentplatform.egg-info}/PKG-INFO +1 -1
  19. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/google_cloud_agentplatform.egg-info/SOURCES.txt +1 -0
  20. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/LICENSE +0 -0
  21. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/README.md +0 -0
  22. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/__init__.py +0 -0
  23. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_bigquery_utils.py +0 -0
  24. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_datasets_utils.py +0 -0
  25. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_builtin_tools.py +0 -0
  26. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_constant.py +0 -0
  27. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_utils.py +0 -0
  28. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_gcs_utils.py +0 -0
  29. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_logging_utils.py +0 -0
  30. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_memory_bank_utils.py +0 -0
  31. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_observability_data_converter.py +0 -0
  32. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_operations_utils.py +0 -0
  33. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_prompt_management_utils.py +0 -0
  34. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_prompt_optimizer_utils.py +0 -0
  35. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_runtimes_utils.py +0 -0
  36. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_skills_utils.py +0 -0
  37. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/a2a_task_events.py +0 -0
  38. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/a2a_tasks.py +0 -0
  39. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/datasets.py +0 -0
  40. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/endpoints.py +0 -0
  41. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/example_stores.py +0 -0
  42. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/feedback_contexts.py +0 -0
  43. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/feedback_entries.py +0 -0
  44. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/live.py +0 -0
  45. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/live_runtimes.py +0 -0
  46. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/memories.py +0 -0
  47. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/memory_revisions.py +0 -0
  48. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/model_garden.py +0 -0
  49. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/prompt_optimizer.py +0 -0
  50. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/prompts.py +0 -0
  51. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/rag.py +0 -0
  52. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/runtime_revisions.py +0 -0
  53. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/sandbox_snapshots.py +0 -0
  54. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/sandbox_templates.py +0 -0
  55. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/sandboxes.py +0 -0
  56. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/session_events.py +0 -0
  57. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/sessions.py +0 -0
  58. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/skill_revisions.py +0 -0
  59. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/skills.py +0 -0
  60. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/agent_engines.py +0 -0
  61. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/evals.py +0 -0
  62. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/prompt_optimizer.py +0 -0
  63. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/prompts.py +0 -0
  64. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/__init__.py +0 -0
  65. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/a2a.py +0 -0
  66. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/ag2.py +0 -0
  67. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/langchain.py +0 -0
  68. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/langgraph.py +0 -0
  69. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/llama_index.py +0 -0
  70. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/google_cloud_agentplatform.egg-info/dependency_links.txt +0 -0
  71. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/google_cloud_agentplatform.egg-info/requires.txt +0 -0
  72. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/google_cloud_agentplatform.egg-info/top_level.txt +0 -0
  73. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/pyproject.toml +0 -0
  74. {google_cloud_agentplatform-2.1.3 → google_cloud_agentplatform-2.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-cloud-agentplatform
3
- Version: 2.1.3
3
+ Version: 2.4.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
@@ -54,8 +54,17 @@ def __getattr__(name): # type: ignore[no-untyped-def]
54
54
  global _genai_types
55
55
  if _genai_types is None:
56
56
  _genai_types = importlib.import_module("._genai.types", __name__)
57
- if "vertexai.types" not in sys.modules:
58
- sys.modules["vertexai.types"] = _genai_types
57
+ # `types` is an alias for `._genai.types` rather than a real submodule,
58
+ # so register it to keep
59
+ # `from agentplatform.types import TypeName`
60
+ # working without a prior attribute access. Spell it the google3 way:
61
+ # Copybara rewrites that prefix in both directions, and the external
62
+ # spelling fails its reversibility check. This key was `vertexai.types`,
63
+ # which both misnamed this package's own alias and shadowed the real
64
+ # `vertexai.types` module.
65
+ types_module_name = f"{__name__}.types"
66
+ if types_module_name not in sys.modules:
67
+ sys.modules[types_module_name] = _genai_types
59
68
  return _genai_types
60
69
 
61
70
  raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
@@ -37,6 +37,11 @@ from google.genai._gaos.types.interactions import functioncallstep
37
37
  from google.genai._gaos.types.interactions import functionresultstep
38
38
  from google.genai._gaos.types.interactions import modeloutputstep
39
39
  from google.genai._gaos.types.interactions import userinputstep
40
+
41
+ try:
42
+ from google.genai._gaos.utils.serializers import ALLOW_UNKNOWN_UNION_VARIANTS
43
+ except (ImportError, AttributeError):
44
+ ALLOW_UNKNOWN_UNION_VARIANTS = "speakeasy_allow_unknown_union_variants"
40
45
  from google.genai.models import Models
41
46
  import pandas as pd
42
47
  from tqdm import tqdm
@@ -775,7 +780,16 @@ def _interaction_dict_to_agent_data(
775
780
  Returns:
776
781
  An AgentData object with one or more ConversationTurns.
777
782
  """
778
- typed_interaction = interaction_types.Interaction.model_validate(interaction)
783
+ # A server response, so unknown step types must degrade rather than raise.
784
+ # `Step` is an open discriminated union: parse_open_union only falls back to
785
+ # the Unknown variant when the validation context carries
786
+ # ALLOW_UNKNOWN_UNION_VARIANTS, and raises without it so that a
787
+ # user-constructed request payload surfaces its mistakes locally. This
788
+ # payload comes off the Interactions API, so it is on the tolerant side of
789
+ # that line. _interaction_steps_to_events already drops steps it cannot map.
790
+ typed_interaction = interaction_types.Interaction.model_validate(
791
+ interaction, context={ALLOW_UNKNOWN_UNION_VARIANTS: True}
792
+ )
779
793
  all_events = _interaction_steps_to_events(typed_interaction.steps or [])
780
794
 
781
795
  # Group events into turns. Each UserInputStep starts a new turn.
@@ -1689,8 +1703,12 @@ def _resolve_interactions_to_eval_cases(
1689
1703
  break
1690
1704
  interaction_dict = json.loads(response.body)
1691
1705
  try:
1706
+ # Also a server response -- see _interaction_dict_to_agent_data.
1707
+ # Without the context a single step type this client does not
1708
+ # know about raised, and the except below turned that into a
1709
+ # `break`, silently truncating the interaction history.
1692
1710
  typed_interaction = interaction_types.Interaction.model_validate(
1693
- interaction_dict
1711
+ interaction_dict, context={ALLOW_UNKNOWN_UNION_VARIANTS: True}
1694
1712
  )
1695
1713
  except Exception as e:
1696
1714
  logger.warning("Failed to validate interaction model: %s", e)
@@ -2636,7 +2654,7 @@ def _execute_inference(
2636
2654
  "Gemini Agent inference completed in %.2f seconds.",
2637
2655
  end_time - start_time,
2638
2656
  )
2639
- return types.EvaluationDataset(
2657
+ evaluation_dataset = types.EvaluationDataset(
2640
2658
  eval_dataset_df=results_df,
2641
2659
  candidate_name=gemini_agent.split("/")[-1],
2642
2660
  )
@@ -2741,7 +2759,12 @@ def _execute_inference(
2741
2759
  results_df.to_json(full_dest_path, orient="records", lines=True)
2742
2760
  logger.info("Results saved locally to: %s", full_dest_path)
2743
2761
  except Exception as e: # pylint: disable=broad-exception-caught
2744
- logger.error("Failed to save results to %s. Error: %s", full_dest_path, e)
2762
+ logger.error(
2763
+ "Failed to save results to %s. Error: %s",
2764
+ full_dest_path,
2765
+ e,
2766
+ exc_info=True,
2767
+ )
2745
2768
 
2746
2769
  return evaluation_dataset
2747
2770
 
@@ -2868,6 +2891,21 @@ def _resolve_dataset_inputs(
2868
2891
  return processed_eval_dataset, num_response_candidates
2869
2892
 
2870
2893
 
2894
+ def _prebuilt_evaluation_run_metric(
2895
+ resolved_metric: types.Metric,
2896
+ ) -> types.EvaluationRunMetric:
2897
+ """Builds the evaluation run metric for a resolved RubricMetric."""
2898
+ if resolved_metric.name in _evals_constant.SUPPORTED_PREDEFINED_METRICS:
2899
+ metric_config = t.t_metrics([resolved_metric])[0]
2900
+ else:
2901
+ metric_config = {
2902
+ "predefined_metric_spec": {"metric_spec_name": resolved_metric.name}
2903
+ }
2904
+ return types.EvaluationRunMetric(
2905
+ metric=resolved_metric.name, metric_config=metric_config
2906
+ )
2907
+
2908
+
2871
2909
  def _resolve_evaluation_run_metrics(
2872
2910
  metrics: Union[list[types.EvaluationRunMetric], list[types.Metric]], api_client: Any
2873
2911
  ) -> list[types.EvaluationRunMetric]:
@@ -2885,14 +2923,7 @@ def _resolve_evaluation_run_metrics(
2885
2923
  resolved_metric = metric_instance.resolve(api_client=api_client)
2886
2924
  if resolved_metric.name:
2887
2925
  resolved_metrics_list.append(
2888
- types.EvaluationRunMetric(
2889
- metric=resolved_metric.name,
2890
- metric_config=types.UnifiedMetric(
2891
- predefined_metric_spec=genai_types.PredefinedMetricSpec(
2892
- metric_spec_name=resolved_metric.name,
2893
- )
2894
- ),
2895
- )
2926
+ _prebuilt_evaluation_run_metric(resolved_metric)
2896
2927
  )
2897
2928
  except Exception as e:
2898
2929
  logger.error(
@@ -2926,14 +2957,7 @@ def _resolve_evaluation_run_metrics(
2926
2957
  )
2927
2958
  if resolved_metric.name:
2928
2959
  resolved_metrics_list.append(
2929
- types.EvaluationRunMetric(
2930
- metric=resolved_metric.name,
2931
- metric_config=types.UnifiedMetric(
2932
- predefined_metric_spec=genai_types.PredefinedMetricSpec(
2933
- metric_spec_name=resolved_metric.name,
2934
- )
2935
- ),
2936
- )
2960
+ _prebuilt_evaluation_run_metric(resolved_metric)
2937
2961
  )
2938
2962
  else:
2939
2963
  raise TypeError(
@@ -3002,6 +3026,7 @@ def _execute_evaluation( # type: ignore[no-untyped-def]
3002
3026
  dest: Optional[str] = None,
3003
3027
  location: Optional[str] = None,
3004
3028
  evaluation_service_qps: Optional[float] = None,
3029
+ allow_cross_region_model: Optional[bool] = None,
3005
3030
  **kwargs,
3006
3031
  ) -> types.EvaluationResult:
3007
3032
  """Evaluates a dataset using the provided metrics.
@@ -3017,6 +3042,8 @@ def _execute_evaluation( # type: ignore[no-untyped-def]
3017
3042
  evaluation_service_qps: The rate limit (queries per second) for calls
3018
3043
  to the evaluation service. Defaults to 10. Increase this value if
3019
3044
  your project has a higher EvaluateInstances API quota.
3045
+ allow_cross_region_model: Opt-in flag to authorize cross-region
3046
+ routing for judge models.
3020
3047
  **kwargs: Extra arguments to pass to evaluation, such as `agent_info`.
3021
3048
 
3022
3049
  Returns:
@@ -3099,6 +3126,7 @@ def _execute_evaluation( # type: ignore[no-untyped-def]
3099
3126
  evaluation_result = _evals_metric_handlers.compute_metrics_and_aggregate(
3100
3127
  evaluation_run_config,
3101
3128
  evaluation_service_qps=evaluation_service_qps,
3129
+ allow_cross_region_model=allow_cross_region_model,
3102
3130
  )
3103
3131
  t2 = time.perf_counter()
3104
3132
  logger.info("Evaluation took: %f seconds", t2 - t1)
@@ -15,15 +15,18 @@
15
15
  """Dataset converters for evals."""
16
16
 
17
17
  import copy
18
+ import datetime
18
19
  import json
19
20
  import logging
20
- from typing import Any, Optional, Union
21
+ from typing import Any, Optional, TypeVar, Union
21
22
 
22
23
  from google.genai import _common
23
24
  from google.genai import types as genai_types
25
+ from pydantic import alias_generators
24
26
  from pydantic import ValidationError
25
27
  from typing_extensions import override
26
28
 
29
+ from . import _evals_constant
27
30
  from . import _evals_utils
28
31
  from . import _observability_data_converter
29
32
  from . import types
@@ -605,6 +608,308 @@ class _OpenAIDataConverter(_evals_utils.EvalDataConverter):
605
608
  return types.EvaluationDataset(eval_cases=eval_cases)
606
609
 
607
610
 
611
+ _ADK_EVAL_CASE_FIELDS = frozenset(
612
+ {
613
+ "eval_id",
614
+ "conversation",
615
+ "conversation_scenario",
616
+ "session_input",
617
+ "creation_timestamp",
618
+ "rubrics",
619
+ "final_session_state",
620
+ }
621
+ )
622
+ _ADK_EVAL_CASE_KEYS = _ADK_EVAL_CASE_FIELDS | {
623
+ alias_generators.to_camel(field) for field in _ADK_EVAL_CASE_FIELDS
624
+ }
625
+ _ADK_SESSION_INPUT_FIELDS = ("app_name", "user_id", "state")
626
+ _ADK_DEFAULT_RUBRIC_GROUP = "adk_rubrics"
627
+ _REFERENCE_TRAJECTORY = "reference_trajectory"
628
+
629
+ _GenaiModel = TypeVar("_GenaiModel", bound=_common.BaseModel)
630
+
631
+
632
+ def _get_adk_field(data: dict[str, Any], name: str) -> Any:
633
+ """Returns an ADK eval set field, which ADK accepts in snake or camel case."""
634
+ if not isinstance(data, dict):
635
+ raise TypeError(f"Expected a JSON object for '{name}', got {type(data)}.")
636
+ if name in data:
637
+ return data[name]
638
+ return data.get(alias_generators.to_camel(name))
639
+
640
+
641
+ def _validate_adk_genai(model: type[_GenaiModel], data: Any) -> _GenaiModel:
642
+ """Validates ADK eval set JSON as a genai type.
643
+
644
+ Validating in JSON mode decodes base64 bytes fields, such as inline data and
645
+ thought signatures, the same way ADK does when it reads an eval set file.
646
+
647
+ Args:
648
+ model: The genai type to validate as.
649
+ data: The parsed JSON value from the eval set.
650
+
651
+ Returns:
652
+ The validated genai object.
653
+ """
654
+ return model.model_validate_json(json.dumps(data))
655
+
656
+
657
+ def _adk_content(data: dict[str, Any], default_role: str) -> genai_types.Content:
658
+ """Validates an ADK Content, filling in the role when the file omits it."""
659
+ content = _validate_adk_genai(genai_types.Content, data)
660
+ if not content.role:
661
+ content.role = default_role
662
+ return content
663
+
664
+
665
+ def _adk_user_content(invocation: dict[str, Any]) -> genai_types.Content:
666
+ """Returns the user content of an ADK invocation."""
667
+ user_content = _get_adk_field(invocation, "user_content")
668
+ if user_content is None:
669
+ raise ValueError("Every invocation must have user_content.")
670
+ return _adk_content(user_content, "user")
671
+
672
+
673
+ def _adk_intermediate_events(
674
+ intermediate_data: Optional[dict[str, Any]],
675
+ ) -> list[tuple[Optional[str], genai_types.Content]]:
676
+ """Returns (author, content) pairs from ADK IntermediateData or InvocationEvents."""
677
+ if not intermediate_data:
678
+ return []
679
+ invocation_events = _get_adk_field(intermediate_data, "invocation_events")
680
+ if invocation_events is not None:
681
+ return [
682
+ (
683
+ _get_adk_field(event, "author"),
684
+ _validate_adk_genai(
685
+ genai_types.Content, _get_adk_field(event, "content")
686
+ ),
687
+ )
688
+ for event in invocation_events
689
+ if _get_adk_field(event, "content")
690
+ ]
691
+
692
+ events: list[tuple[Optional[str], genai_types.Content]] = []
693
+ tool_uses = _get_adk_field(intermediate_data, "tool_uses")
694
+ if tool_uses:
695
+ events.append(
696
+ (
697
+ None,
698
+ genai_types.Content(
699
+ role="model",
700
+ parts=[
701
+ genai_types.Part(
702
+ function_call=_validate_adk_genai(
703
+ genai_types.FunctionCall, tool_use
704
+ )
705
+ )
706
+ for tool_use in tool_uses
707
+ ],
708
+ ),
709
+ )
710
+ )
711
+ tool_responses = _get_adk_field(intermediate_data, "tool_responses")
712
+ if tool_responses:
713
+ events.append(
714
+ (
715
+ None,
716
+ genai_types.Content(
717
+ role="user",
718
+ parts=[
719
+ genai_types.Part(
720
+ function_response=_validate_adk_genai(
721
+ genai_types.FunctionResponse, tool_response
722
+ )
723
+ )
724
+ for tool_response in tool_responses
725
+ ],
726
+ ),
727
+ )
728
+ )
729
+ for author, parts in (
730
+ _get_adk_field(intermediate_data, "intermediate_responses") or []
731
+ ):
732
+ events.append(
733
+ (
734
+ author,
735
+ genai_types.Content(
736
+ role="model",
737
+ parts=[
738
+ _validate_adk_genai(genai_types.Part, part) for part in parts
739
+ ],
740
+ ),
741
+ )
742
+ )
743
+ return events
744
+
745
+
746
+ class AdkEvalSetConverter(_evals_utils.EvalDataConverter):
747
+ """Converter for ADK eval sets (the `.evalset.json` format).
748
+
749
+ Reads the eval set JSON directly, so google-adk does not need to be
750
+ installed.
751
+ """
752
+
753
+ def _invocation_to_messages(
754
+ self, invocation: dict[str, Any], turn_index: int
755
+ ) -> list[types.evals.Message]:
756
+ """Converts an earlier ADK invocation into conversation history messages."""
757
+ turn_id = _get_adk_field(invocation, "invocation_id") or str(turn_index)
758
+ timestamp = _get_adk_field(invocation, "creation_timestamp")
759
+ messages = [
760
+ types.evals.Message(
761
+ turn_id=turn_id,
762
+ author="user",
763
+ content=_adk_user_content(invocation),
764
+ creation_timestamp=(
765
+ datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
766
+ if timestamp
767
+ else None
768
+ ),
769
+ )
770
+ ]
771
+ messages.extend(
772
+ types.evals.Message(turn_id=turn_id, author=author, content=content)
773
+ for author, content in _adk_intermediate_events(
774
+ _get_adk_field(invocation, "intermediate_data")
775
+ )
776
+ )
777
+ final_response = _get_adk_field(invocation, "final_response")
778
+ if final_response:
779
+ messages.append(
780
+ types.evals.Message(
781
+ turn_id=turn_id, content=_adk_content(final_response, "model")
782
+ )
783
+ )
784
+ return messages
785
+
786
+ def _convert_rubrics(
787
+ self, rubrics: list[dict[str, Any]]
788
+ ) -> dict[str, types.RubricGroup]:
789
+ """Groups ADK rubrics by their type, which ADK uses to select rubrics."""
790
+ groups: dict[str, list[types.evals.Rubric]] = {}
791
+ for rubric in rubrics:
792
+ rubric_type = _get_adk_field(rubric, "type")
793
+ rubric_content = _get_adk_field(rubric, "rubric_content") or {}
794
+ groups.setdefault(rubric_type or _ADK_DEFAULT_RUBRIC_GROUP, []).append(
795
+ types.evals.Rubric(
796
+ rubric_id=_get_adk_field(rubric, "rubric_id"),
797
+ type=rubric_type,
798
+ content=types.evals.RubricContent(
799
+ property=types.evals.RubricContentProperty(
800
+ description=_get_adk_field(rubric_content, "text_property")
801
+ )
802
+ ),
803
+ )
804
+ )
805
+ return {
806
+ name: types.RubricGroup(rubrics=group) for name, group in groups.items()
807
+ }
808
+
809
+ def _convert_eval_case(self, case: dict[str, Any]) -> types.EvalCase:
810
+ """Converts one ADK EvalCase into an EvalCase."""
811
+ if not isinstance(case, dict):
812
+ raise TypeError(f"Expected an ADK EvalCase object, got {type(case)}.")
813
+ conversation = _get_adk_field(case, "conversation")
814
+ scenario = _get_adk_field(case, "conversation_scenario")
815
+ if (conversation is None) == (scenario is None):
816
+ raise ValueError(
817
+ "Exactly one of conversation and conversation_scenario must be set."
818
+ )
819
+
820
+ fields: dict[str, Any] = {"eval_case_id": _get_adk_field(case, "eval_id")}
821
+ rubrics = list(_get_adk_field(case, "rubrics") or [])
822
+ if scenario is not None:
823
+ fields["user_scenario"] = types.evals.UserScenario(
824
+ starting_prompt=_get_adk_field(scenario, "starting_prompt"),
825
+ conversation_plan=_get_adk_field(scenario, "conversation_plan"),
826
+ )
827
+ else:
828
+ if not conversation:
829
+ raise ValueError("conversation must have at least one invocation.")
830
+ *earlier_invocations, last_invocation = conversation
831
+ fields["prompt"] = _adk_user_content(last_invocation)
832
+ history = []
833
+ for turn_index, invocation in enumerate(earlier_invocations):
834
+ history.extend(self._invocation_to_messages(invocation, turn_index))
835
+ if history:
836
+ fields["conversation_history"] = history
837
+ final_response = _get_adk_field(last_invocation, "final_response")
838
+ if final_response:
839
+ fields["reference"] = types.ResponseCandidate(
840
+ response=_adk_content(final_response, "model")
841
+ )
842
+ reference_trajectory: list[genai_types.Content] = []
843
+ for _, content in _adk_intermediate_events(
844
+ _get_adk_field(last_invocation, "intermediate_data")
845
+ ):
846
+ reference_trajectory.extend(
847
+ genai_types.Content(
848
+ role="model",
849
+ parts=[genai_types.Part(function_call=part.function_call)],
850
+ )
851
+ for part in content.parts or []
852
+ if part.function_call
853
+ )
854
+ if reference_trajectory:
855
+ fields[_REFERENCE_TRAJECTORY] = reference_trajectory
856
+ rubrics.extend(_get_adk_field(last_invocation, "rubrics") or [])
857
+
858
+ if rubrics:
859
+ fields["rubric_groups"] = self._convert_rubrics(rubrics)
860
+ session_input = _get_adk_field(case, "session_input")
861
+ if session_input:
862
+ fields[_evals_constant.SESSION_INPUT] = {
863
+ name: value
864
+ for name in _ADK_SESSION_INPUT_FIELDS
865
+ if (value := _get_adk_field(session_input, name)) is not None
866
+ }
867
+
868
+ reserved_keys = set(fields)
869
+ for field_name in types.EvalCase.model_fields:
870
+ reserved_keys.update((field_name, alias_generators.to_camel(field_name)))
871
+ for key, value in case.items():
872
+ if key in _ADK_EVAL_CASE_KEYS:
873
+ continue
874
+ if key in reserved_keys:
875
+ logger.warning(
876
+ "Skipping ADK eval case field '%s' because it conflicts with"
877
+ " an EvalCase field.",
878
+ key,
879
+ )
880
+ continue
881
+ fields[key] = value
882
+ return types.EvalCase(**fields)
883
+
884
+ @override
885
+ def convert(self, raw_data: dict[str, Any]) -> types.EvaluationDataset:
886
+ """Converts a parsed ADK EvalSet into an EvaluationDataset."""
887
+ if not isinstance(raw_data, dict):
888
+ raise ValueError(
889
+ "Expected an ADK EvalSet JSON object, got"
890
+ f" {type(raw_data)}. For the legacy list format, load the"
891
+ " file with google.adk.evaluation.local_eval_sets_manager"
892
+ ".load_eval_set_from_file() and pass"
893
+ " eval_set.model_dump(mode='json') instead."
894
+ )
895
+ adk_eval_cases = _get_adk_field(raw_data, "eval_cases")
896
+ if not isinstance(adk_eval_cases, list):
897
+ raise ValueError("Expected an ADK EvalSet with an 'eval_cases' list.")
898
+
899
+ eval_cases = []
900
+ for i, case in enumerate(adk_eval_cases):
901
+ try:
902
+ eval_cases.append(self._convert_eval_case(case))
903
+ except (TypeError, ValueError) as e:
904
+ case_id = (
905
+ _get_adk_field(case, "eval_id") if isinstance(case, dict) else None
906
+ )
907
+ raise ValueError(
908
+ f"Failed to convert ADK eval case '{case_id or i}': {e}"
909
+ ) from e
910
+ return types.EvaluationDataset(eval_cases=eval_cases)
911
+
912
+
608
913
  def auto_detect_dataset_schema(
609
914
  raw_dataset: list[dict[str, Any]],
610
915
  ) -> Union[EvalDatasetSchema, str]:
@@ -288,6 +288,7 @@ class MetricHandler(abc.ABC, Generic[T]):
288
288
  def __init__(self, module: "evals.Evals", metric: T):
289
289
  self.module = module
290
290
  self.metric: T = metric
291
+ self.allow_cross_region_model: Optional[bool] = None
291
292
 
292
293
  @property
293
294
  @abc.abstractmethod
@@ -761,6 +762,7 @@ class LLMMetricHandler(MetricHandler[types.LLMMetric]):
761
762
  lambda: self.module._evaluate_instances(
762
763
  metrics=[self.metric],
763
764
  instance=instance,
765
+ allow_cross_region_model=self.allow_cross_region_model,
764
766
  ),
765
767
  self.metric_name,
766
768
  )
@@ -981,7 +983,7 @@ class PredefinedMetricHandler(MetricHandler[types.Metric]):
981
983
  raise ValueError(
982
984
  f"Metric '{self.metric.name}' is not a supported predefined metric."
983
985
  )
984
- if (
986
+ if self.metric.name.startswith("multi_turn") and (
985
987
  self.metric.judge_model
986
988
  or self.metric.judge_model_generation_config
987
989
  or self.metric.judge_model_sampling_count
@@ -989,7 +991,8 @@ class PredefinedMetricHandler(MetricHandler[types.Metric]):
989
991
  logger.warning(
990
992
  "Autorater config settings (judge_model, "
991
993
  "judge_model_generation_config, judge_model_sampling_count) "
992
- "are ignored for predefined metric '%s'.",
994
+ "are ignored for multi-turn metric '%s'. Use "
995
+ "judge_model_step_configs to set its judges.",
993
996
  self.metric.name,
994
997
  )
995
998
 
@@ -1071,6 +1074,7 @@ class PredefinedMetricHandler(MetricHandler[types.Metric]):
1071
1074
  metrics=[self.metric],
1072
1075
  instance=payload.get("instance"),
1073
1076
  autorater_config=payload.get("autorater_config"),
1077
+ allow_cross_region_model=self.allow_cross_region_model,
1074
1078
  ),
1075
1079
  metric_name,
1076
1080
  )
@@ -1318,6 +1322,7 @@ class RegisteredMetricHandler(MetricHandler[types.Metric]):
1318
1322
  metric_sources=[metric_source],
1319
1323
  instance=payload.get("instance"),
1320
1324
  autorater_config=payload.get("autorater_config"),
1325
+ allow_cross_region_model=self.allow_cross_region_model,
1321
1326
  ),
1322
1327
  metric_name,
1323
1328
  )
@@ -1404,12 +1409,16 @@ MetricHandlerType = TypeVar(
1404
1409
 
1405
1410
 
1406
1411
  def get_handler_for_metric(
1407
- module: "evals.Evals", metric: types.Metric
1412
+ module: "evals.Evals",
1413
+ metric: types.Metric,
1414
+ allow_cross_region_model: Optional[bool] = None,
1408
1415
  ) -> Union[MetricHandlerType, Any]:
1409
1416
  """Returns a metric handler for the given metric."""
1410
1417
  for condition, handler_class in _METRIC_HANDLER_MAPPING:
1411
1418
  if condition(metric): # type: ignore[no-untyped-call]
1412
- return handler_class(module=module, metric=metric)
1419
+ handler = handler_class(module=module, metric=metric)
1420
+ handler.allow_cross_region_model = allow_cross_region_model
1421
+ return handler
1413
1422
  raise ValueError(f"Unsupported metric: {metric.name}")
1414
1423
 
1415
1424
 
@@ -1548,6 +1557,7 @@ def _rate_limited_get_metric_result(
1548
1557
  def compute_metrics_and_aggregate(
1549
1558
  evaluation_run_config: EvaluationRunConfig,
1550
1559
  evaluation_service_qps: Optional[float] = None,
1560
+ allow_cross_region_model: Optional[bool] = None,
1551
1561
  ) -> types.EvaluationResult:
1552
1562
  """Computes metrics and aggregates them for a given evaluation run config.
1553
1563
 
@@ -1556,6 +1566,8 @@ def compute_metrics_and_aggregate(
1556
1566
  evaluation_service_qps: Optional QPS limit for the evaluation service.
1557
1567
  Defaults to _DEFAULT_EVAL_SERVICE_QPS (10). Users with higher
1558
1568
  quotas can increase this value.
1569
+ allow_cross_region_model: Opt-in flag to authorize cross-region
1570
+ routing for judge models.
1559
1571
  """
1560
1572
  metric_handlers = []
1561
1573
  all_futures = []
@@ -1574,7 +1586,11 @@ def compute_metrics_and_aggregate(
1574
1586
 
1575
1587
  for eval_metric in evaluation_run_config.metrics:
1576
1588
  metric_handlers.append(
1577
- get_handler_for_metric(evaluation_run_config.evals_module, eval_metric)
1589
+ get_handler_for_metric(
1590
+ evaluation_run_config.evals_module,
1591
+ eval_metric,
1592
+ allow_cross_region_model=allow_cross_region_model,
1593
+ )
1578
1594
  )
1579
1595
 
1580
1596
  eval_case_count = len(evaluation_run_config.dataset.eval_cases)
@@ -215,8 +215,11 @@ class LazyLoadedPrebuiltMetric:
215
215
  if self._resolved_metric:
216
216
  return self._resolved_metric
217
217
 
218
+ # The shared cache is keyed by name and version only, so a metric with
219
+ # overrides (such as judge_model_step_configs) must bypass it.
220
+ use_cache = not self.metric_kwargs
218
221
  cache_key = f"{self.name}@{self.version or 'default'}"
219
- if cache_key in LazyLoadedPrebuiltMetric._cache:
222
+ if use_cache and cache_key in LazyLoadedPrebuiltMetric._cache:
220
223
  self._resolved_metric = LazyLoadedPrebuiltMetric._cache[cache_key]
221
224
  logger.debug("Metric '%s' found in cache.", cache_key)
222
225
  return self._resolved_metric
@@ -225,7 +228,8 @@ class LazyLoadedPrebuiltMetric:
225
228
  api_metric = self._resolve_api_predefined()
226
229
  if api_metric:
227
230
  self._resolved_metric = api_metric
228
- LazyLoadedPrebuiltMetric._cache[cache_key] = self._resolved_metric
231
+ if use_cache:
232
+ LazyLoadedPrebuiltMetric._cache[cache_key] = self._resolved_metric
229
233
  return self._resolved_metric
230
234
 
231
235
  # Fallback to GCS loading for custom LLM-based Prebuilt Metrics
@@ -234,8 +238,9 @@ class LazyLoadedPrebuiltMetric:
234
238
  )
235
239
  try:
236
240
  gcs_metric = self._fetch_and_parse(api_client)
237
- final_cache_key = f"{self.name}@{self.version}"
238
- LazyLoadedPrebuiltMetric._cache[final_cache_key] = gcs_metric
241
+ if use_cache:
242
+ final_cache_key = f"{self.name}@{self.version}"
243
+ LazyLoadedPrebuiltMetric._cache[final_cache_key] = gcs_metric
239
244
  self._resolved_metric = gcs_metric
240
245
  return self._resolved_metric
241
246
  except Exception as e:
@@ -232,6 +232,9 @@ def _extract_dataset_rows(dataset: types.EvaluationDataset) -> list[dict[str, An
232
232
  return processed_rows
233
233
 
234
234
 
235
+ extract_dataset_rows = _extract_dataset_rows
236
+
237
+
235
238
  def get_evaluation_html(eval_result_json: str) -> str:
236
239
  """Returns a self-contained HTML for single evaluation visualization."""
237
240
  payload_b64 = _encode_to_base64(eval_result_json)
@@ -64,10 +64,14 @@ def t_metrics(
64
64
  elif (
65
65
  metric_name and metric_name in _evals_constant.SUPPORTED_PREDEFINED_METRICS
66
66
  ):
67
- metric_payload_item["predefined_metric_spec"] = {
67
+ predefined_spec: dict[str, Any] = {
68
68
  "metric_spec_name": metric_name,
69
69
  "metric_spec_parameters": metric.metric_spec_parameters,
70
70
  }
71
+ step_configs = getv(metric, ["judge_model_step_configs"])
72
+ if step_configs:
73
+ predefined_spec["step_autorater_configs"] = step_configs
74
+ metric_payload_item["predefined_metric_spec"] = predefined_spec
71
75
  # Custom Code Execution Metric
72
76
  elif (
73
77
  hasattr(metric, "remote_custom_function") and metric.remote_custom_function