google-cloud-agentplatform 2.3.0__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.3.0/google_cloud_agentplatform.egg-info → google_cloud_agentplatform-2.4.0}/PKG-INFO +1 -1
  2. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_common.py +28 -18
  3. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_data_converters.py +306 -1
  4. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_metric_handlers.py +21 -5
  5. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_metric_loaders.py +9 -4
  6. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_visualization.py +3 -0
  7. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_transformers.py +5 -1
  8. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/evals.py +14 -0
  9. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/runtimes.py +8 -2
  10. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/__init__.py +6 -0
  11. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/common.py +140 -6
  12. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/adk.py +35 -18
  13. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/version.py +1 -1
  14. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0/google_cloud_agentplatform.egg-info}/PKG-INFO +1 -1
  15. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/LICENSE +0 -0
  16. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/README.md +0 -0
  17. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/__init__.py +0 -0
  18. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/__init__.py +0 -0
  19. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_bigquery_utils.py +0 -0
  20. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_datasets_utils.py +0 -0
  21. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_builtin_tools.py +0 -0
  22. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_constant.py +0 -0
  23. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_evals_utils.py +0 -0
  24. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_gcs_utils.py +0 -0
  25. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_logging_utils.py +0 -0
  26. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_memory_bank_utils.py +0 -0
  27. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_observability_data_converter.py +0 -0
  28. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_operations_utils.py +0 -0
  29. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_prompt_management_utils.py +0 -0
  30. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_prompt_optimizer_utils.py +0 -0
  31. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_runtimes_utils.py +0 -0
  32. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/_skills_utils.py +0 -0
  33. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/a2a_task_events.py +0 -0
  34. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/a2a_tasks.py +0 -0
  35. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/client.py +0 -0
  36. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/datasets.py +0 -0
  37. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/endpoints.py +0 -0
  38. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/example_stores.py +0 -0
  39. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/feedback_contexts.py +0 -0
  40. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/feedback_entries.py +0 -0
  41. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/live.py +0 -0
  42. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/live_runtimes.py +0 -0
  43. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/memories.py +0 -0
  44. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/memory_banks.py +0 -0
  45. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/memory_revisions.py +0 -0
  46. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/model_garden.py +0 -0
  47. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/prompt_optimizer.py +0 -0
  48. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/prompts.py +0 -0
  49. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/rag.py +0 -0
  50. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/runtime_revisions.py +0 -0
  51. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/sandbox_snapshots.py +0 -0
  52. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/sandbox_templates.py +0 -0
  53. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/sandboxes.py +0 -0
  54. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/serving_profiles.py +0 -0
  55. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/session_events.py +0 -0
  56. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/sessions.py +0 -0
  57. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/skill_revisions.py +0 -0
  58. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/skills.py +0 -0
  59. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/agent_engines.py +0 -0
  60. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/evals.py +0 -0
  61. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/prompt_optimizer.py +0 -0
  62. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/_genai/types/prompts.py +0 -0
  63. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/__init__.py +0 -0
  64. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/a2a.py +0 -0
  65. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/ag2.py +0 -0
  66. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/langchain.py +0 -0
  67. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/langgraph.py +0 -0
  68. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/agentplatform/frameworks/llama_index.py +0 -0
  69. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/google_cloud_agentplatform.egg-info/SOURCES.txt +0 -0
  70. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/google_cloud_agentplatform.egg-info/dependency_links.txt +0 -0
  71. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/google_cloud_agentplatform.egg-info/requires.txt +0 -0
  72. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/google_cloud_agentplatform.egg-info/top_level.txt +0 -0
  73. {google_cloud_agentplatform-2.3.0 → google_cloud_agentplatform-2.4.0}/pyproject.toml +0 -0
  74. {google_cloud_agentplatform-2.3.0 → 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.3.0
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
@@ -2654,7 +2654,7 @@ def _execute_inference(
2654
2654
  "Gemini Agent inference completed in %.2f seconds.",
2655
2655
  end_time - start_time,
2656
2656
  )
2657
- return types.EvaluationDataset(
2657
+ evaluation_dataset = types.EvaluationDataset(
2658
2658
  eval_dataset_df=results_df,
2659
2659
  candidate_name=gemini_agent.split("/")[-1],
2660
2660
  )
@@ -2759,7 +2759,12 @@ def _execute_inference(
2759
2759
  results_df.to_json(full_dest_path, orient="records", lines=True)
2760
2760
  logger.info("Results saved locally to: %s", full_dest_path)
2761
2761
  except Exception as e: # pylint: disable=broad-exception-caught
2762
- 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
+ )
2763
2768
 
2764
2769
  return evaluation_dataset
2765
2770
 
@@ -2886,6 +2891,21 @@ def _resolve_dataset_inputs(
2886
2891
  return processed_eval_dataset, num_response_candidates
2887
2892
 
2888
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
+
2889
2909
  def _resolve_evaluation_run_metrics(
2890
2910
  metrics: Union[list[types.EvaluationRunMetric], list[types.Metric]], api_client: Any
2891
2911
  ) -> list[types.EvaluationRunMetric]:
@@ -2903,14 +2923,7 @@ def _resolve_evaluation_run_metrics(
2903
2923
  resolved_metric = metric_instance.resolve(api_client=api_client)
2904
2924
  if resolved_metric.name:
2905
2925
  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
- )
2926
+ _prebuilt_evaluation_run_metric(resolved_metric)
2914
2927
  )
2915
2928
  except Exception as e:
2916
2929
  logger.error(
@@ -2944,14 +2957,7 @@ def _resolve_evaluation_run_metrics(
2944
2957
  )
2945
2958
  if resolved_metric.name:
2946
2959
  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
- )
2960
+ _prebuilt_evaluation_run_metric(resolved_metric)
2955
2961
  )
2956
2962
  else:
2957
2963
  raise TypeError(
@@ -3020,6 +3026,7 @@ def _execute_evaluation( # type: ignore[no-untyped-def]
3020
3026
  dest: Optional[str] = None,
3021
3027
  location: Optional[str] = None,
3022
3028
  evaluation_service_qps: Optional[float] = None,
3029
+ allow_cross_region_model: Optional[bool] = None,
3023
3030
  **kwargs,
3024
3031
  ) -> types.EvaluationResult:
3025
3032
  """Evaluates a dataset using the provided metrics.
@@ -3035,6 +3042,8 @@ def _execute_evaluation( # type: ignore[no-untyped-def]
3035
3042
  evaluation_service_qps: The rate limit (queries per second) for calls
3036
3043
  to the evaluation service. Defaults to 10. Increase this value if
3037
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.
3038
3047
  **kwargs: Extra arguments to pass to evaluation, such as `agent_info`.
3039
3048
 
3040
3049
  Returns:
@@ -3117,6 +3126,7 @@ def _execute_evaluation( # type: ignore[no-untyped-def]
3117
3126
  evaluation_result = _evals_metric_handlers.compute_metrics_and_aggregate(
3118
3127
  evaluation_run_config,
3119
3128
  evaluation_service_qps=evaluation_service_qps,
3129
+ allow_cross_region_model=allow_cross_region_model,
3120
3130
  )
3121
3131
  t2 = time.perf_counter()
3122
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
@@ -392,6 +392,13 @@ def _EvaluateInstancesRequestParameters_to_vertex(
392
392
  ],
393
393
  )
394
394
 
395
+ if getv(from_object, ["allow_cross_region_model"]) is not None:
396
+ setv(
397
+ to_object,
398
+ ["allowCrossRegionModel"],
399
+ getv(from_object, ["allow_cross_region_model"]),
400
+ )
401
+
395
402
  if getv(from_object, ["config"]) is not None:
396
403
  setv(to_object, ["config"], getv(from_object, ["config"]))
397
404
 
@@ -1935,6 +1942,7 @@ class Evals(_api_module.BaseModule):
1935
1942
  metrics: Optional[list[types.MetricOrDict]] = None,
1936
1943
  instance: Optional[types.EvaluationInstanceOrDict] = None,
1937
1944
  metric_sources: Optional[list[types.MetricSourceOrDict]] = None,
1945
+ allow_cross_region_model: Optional[bool] = None,
1938
1946
  config: Optional[types.EvaluateInstancesConfigOrDict] = None,
1939
1947
  ) -> types.EvaluateInstancesResponse:
1940
1948
  """
@@ -1956,6 +1964,7 @@ class Evals(_api_module.BaseModule):
1956
1964
  metrics=metrics,
1957
1965
  instance=instance,
1958
1966
  metric_sources=metric_sources,
1967
+ allow_cross_region_model=allow_cross_region_model,
1959
1968
  config=config,
1960
1969
  )
1961
1970
 
@@ -3171,6 +3180,8 @@ class Evals(_api_module.BaseModule):
3171
3180
  - evaluation_service_qps: The rate limit (queries per second) for
3172
3181
  calls to the evaluation service. Defaults to 10. Increase this
3173
3182
  value if your project has a higher EvaluateInstances API quota.
3183
+ - allow_cross_region_model: Opt-in flag to authorize cross-region
3184
+ routing for judge models.
3174
3185
  **kwargs: Extra arguments to pass to evaluation, such as `agent_info`.
3175
3186
 
3176
3187
  Returns:
@@ -3214,6 +3225,7 @@ class Evals(_api_module.BaseModule):
3214
3225
  dest=config.dest,
3215
3226
  location=location,
3216
3227
  evaluation_service_qps=getattr(config, "evaluation_service_qps", None),
3228
+ allow_cross_region_model=getattr(config, "allow_cross_region_model", None),
3217
3229
  **kwargs,
3218
3230
  )
3219
3231
 
@@ -4944,6 +4956,7 @@ class AsyncEvals(_api_module.BaseModule):
4944
4956
  metrics: Optional[list[types.MetricOrDict]] = None,
4945
4957
  instance: Optional[types.EvaluationInstanceOrDict] = None,
4946
4958
  metric_sources: Optional[list[types.MetricSourceOrDict]] = None,
4959
+ allow_cross_region_model: Optional[bool] = None,
4947
4960
  config: Optional[types.EvaluateInstancesConfigOrDict] = None,
4948
4961
  ) -> types.EvaluateInstancesResponse:
4949
4962
  """
@@ -4965,6 +4978,7 @@ class AsyncEvals(_api_module.BaseModule):
4965
4978
  metrics=metrics,
4966
4979
  instance=instance,
4967
4980
  metric_sources=metric_sources,
4981
+ allow_cross_region_model=allow_cross_region_model,
4968
4982
  config=config,
4969
4983
  )
4970
4984