Skip to content

Commit b901ae7

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: Resolve Interactions API data for agent trace display in eval results.
PiperOrigin-RevId: 944658614
1 parent 135224c commit b901ae7

3 files changed

Lines changed: 440 additions & 0 deletions

File tree

agentplatform/_genai/_evals_common.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,99 @@ def _build_interaction_id_dataset(
10071007
return types.EvaluationDataset(eval_cases=eval_cases)
10081008

10091009

1010+
def _resolve_interactions_for_display(
1011+
api_client: BaseApiClient,
1012+
dataset_list: list[types.EvaluationDataset],
1013+
) -> list[types.EvaluationDataset]:
1014+
"""Resolves interactions_data_source on EvalCases for display.
1015+
1016+
For each EvalCase that has ``interactions_data_source`` set but no
1017+
``agent_data``, fetches the Interaction and Agent config from the
1018+
API, converts to AgentData, and populates it on the EvalCase so
1019+
that ``show()`` can render the System Topology and Conversation
1020+
Trace sections.
1021+
1022+
Args:
1023+
api_client: The API client used to fetch interactions and agents.
1024+
dataset_list: The original evaluation datasets.
1025+
1026+
Returns:
1027+
A list of EvaluationDatasets with agent_data populated from
1028+
resolved interactions.
1029+
"""
1030+
resolved_datasets = []
1031+
for dataset in dataset_list:
1032+
if not dataset.eval_cases:
1033+
resolved_datasets.append(dataset)
1034+
continue
1035+
1036+
resolved_cases = []
1037+
any_resolved = False
1038+
for case in dataset.eval_cases:
1039+
ids = case.interactions_data_source
1040+
if ids and not case.agent_data:
1041+
interaction_name = ids.interaction
1042+
gemini_cfg = ids.gemini_agent_config
1043+
agent_name = gemini_cfg.gemini_agent if gemini_cfg else None
1044+
1045+
if interaction_name:
1046+
try:
1047+
# Extract the interaction short ID from the resource
1048+
# name; handles both full resource names and bare IDs.
1049+
interaction_id = interaction_name.split("/")[-1]
1050+
1051+
logger.info(
1052+
"Fetching interaction chain for %s for display.",
1053+
interaction_name,
1054+
)
1055+
path = f"interactions/{interaction_id}"
1056+
response = api_client.request("get", path, {}, None)
1057+
if not response.body:
1058+
logger.warning(
1059+
"Empty response fetching interaction %s.",
1060+
interaction_name,
1061+
)
1062+
resolved_cases.append(case)
1063+
continue
1064+
interaction_dict = json.loads(response.body)
1065+
1066+
agent_data = _interaction_dict_to_agent_data(interaction_dict)
1067+
1068+
# Best-effort: fetch agent config (instruction,
1069+
# tools, description) from the Agent API.
1070+
agent_config = _fetch_agent_config_dict(
1071+
api_client, agent_name or ""
1072+
)
1073+
agent_data.agents = {agent_config.agent_id: agent_config}
1074+
1075+
_merge_text_parts_in_agent_data(agent_data)
1076+
1077+
# Preserve all original EvalCase fields; only
1078+
# update agent_data.
1079+
resolved_cases.append(
1080+
case.model_copy(update={"agent_data": agent_data})
1081+
)
1082+
any_resolved = True
1083+
continue
1084+
except Exception as e: # pylint: disable=broad-exception-caught
1085+
logger.warning(
1086+
"Failed to resolve interaction %s for display: %s",
1087+
interaction_name,
1088+
e,
1089+
)
1090+
1091+
resolved_cases.append(case)
1092+
1093+
if any_resolved:
1094+
resolved_datasets.append(
1095+
dataset.model_copy(update={"eval_cases": resolved_cases})
1096+
)
1097+
else:
1098+
resolved_datasets.append(dataset)
1099+
1100+
return resolved_datasets
1101+
1102+
10101103
def _add_evaluation_run_labels(
10111104
labels: Optional[dict[str, str]] = None,
10121105
agent: Optional[str] = None,
@@ -2349,6 +2442,11 @@ def _execute_evaluation( # type: ignore[no-untyped-def]
23492442
t2 = time.perf_counter()
23502443
logger.info("Evaluation took: %f seconds", t2 - t1)
23512444

2445+
# Resolve interactions_data_source to agent_data for display.
2446+
# This fetches Interaction trace data client-side so that show() can
2447+
# render the System Topology and Conversation Trace sections.
2448+
dataset_list = _resolve_interactions_for_display(api_client, dataset_list)
2449+
23522450
evaluation_result.evaluation_dataset = dataset_list
23532451
evaluation_result.agent_info = validated_agent_info
23542452

tests/unit/agentplatform/genai/replays/test_evaluate.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,57 @@ def test_evaluation_with_interaction_id(client):
609609
assert len(evaluation_result.eval_case_results) == 1
610610

611611

612+
def test_evaluation_with_interaction_resolution(client):
613+
"""Tests evaluate() resolves interaction trace + agent config for display."""
614+
interaction_id = "ChAxMDE5YTgzOGU3Mjg4NDE4EAgaATAqBG1haW4"
615+
agent = (
616+
"projects/model-evaluation-dev/locations/global/agents/"
617+
"async-scrape-test-fe111cc6"
618+
)
619+
eval_dataset = types.EvaluationDataset(
620+
eval_cases=[
621+
types.EvalCase(
622+
interactions_data_source=types.InteractionsDataSource(
623+
interaction=(
624+
"projects/model-evaluation-dev/locations/global"
625+
f"/interactions/{interaction_id}"
626+
),
627+
gemini_agent_config=types.GeminiAgentConfig(
628+
gemini_agent=agent,
629+
),
630+
),
631+
)
632+
]
633+
)
634+
635+
evaluation_result = client.evals.evaluate(
636+
dataset=eval_dataset,
637+
metrics=[types.RubricMetric.MULTI_TURN_TASK_SUCCESS],
638+
)
639+
640+
assert isinstance(evaluation_result, types.EvaluationResult)
641+
assert evaluation_result.summary_metrics is not None
642+
assert len(evaluation_result.summary_metrics) > 0
643+
644+
assert evaluation_result.eval_case_results is not None
645+
assert len(evaluation_result.eval_case_results) == 1
646+
647+
# Verify the interaction was resolved for display: agent_data should
648+
# be populated with conversation trace and agent topology.
649+
eval_dataset_list = evaluation_result.evaluation_dataset
650+
assert eval_dataset_list is not None
651+
assert len(eval_dataset_list) > 0
652+
resolved_case = eval_dataset_list[0].eval_cases[0]
653+
assert resolved_case.agent_data is not None
654+
# Conversation trace: at least one turn with events.
655+
assert resolved_case.agent_data.turns is not None
656+
assert len(resolved_case.agent_data.turns) > 0
657+
assert len(resolved_case.agent_data.turns[0].events) > 0
658+
# Agent topology: agents map populated with agent config.
659+
assert resolved_case.agent_data.agents is not None
660+
assert len(resolved_case.agent_data.agents) > 0
661+
662+
612663
pytestmark = pytest_helper.setup(
613664
file=__file__,
614665
globals_for_file=globals(),

0 commit comments

Comments
 (0)