# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations from google.adk.evaluation.conversation_scenarios import ConversationScenario from google.adk.evaluation.eval_case import EvalCase from google.adk.evaluation.eval_case import get_all_tool_calls from google.adk.evaluation.eval_case import get_all_tool_calls_with_responses from google.adk.evaluation.eval_case import get_all_tool_responses from google.adk.evaluation.eval_case import IntermediateData from google.adk.evaluation.eval_case import InvocationEvent from google.adk.evaluation.eval_case import InvocationEvents from google.adk.evaluation.eval_case import SessionInput from google.genai import types as genai_types import pytest def test_eval_models_preserve_extra_metadata(): session_input = SessionInput( app_name='app', user_id='user', eval_group='retrieval', source='nightly', ) assert session_input.model_extra == { 'eval_group': 'retrieval', 'source': 'nightly', } assert session_input.model_dump()['eval_group'] == 'retrieval' eval_case = EvalCase( eval_id='case_1', conversation=[], session_input=session_input, owner='platform', ) assert eval_case.model_extra == {'owner': 'platform'} dumped = eval_case.model_dump() assert dumped['owner'] == 'platform' assert dumped['session_input']['source'] == 'nightly' def test_get_all_tool_calls_with_none_input(): """Tests that an empty list is returned when intermediate_data is None.""" assert get_all_tool_calls(None) == [] def test_get_all_tool_calls_with_intermediate_data_no_tools(): """Tests IntermediateData with no tool calls.""" intermediate_data = IntermediateData(tool_uses=[]) assert get_all_tool_calls(intermediate_data) == [] def test_get_all_tool_calls_with_intermediate_data(): """Tests that tool calls are correctly extracted from IntermediateData.""" tool_call1 = genai_types.FunctionCall( name='search', args={'query': 'weather'} ) tool_call2 = genai_types.FunctionCall(name='lookup', args={'id': '123'}) intermediate_data = IntermediateData(tool_uses=[tool_call1, tool_call2]) assert get_all_tool_calls(intermediate_data) == [tool_call1, tool_call2] def test_get_all_tool_calls_with_empty_invocation_events(): """Tests InvocationEvents with an empty list of invocation events.""" intermediate_data = InvocationEvents(invocation_events=[]) assert get_all_tool_calls(intermediate_data) == [] def test_get_all_tool_calls_with_invocation_events_no_tools(): """Tests InvocationEvents containing events without any tool calls.""" invocation_event = InvocationEvent( author='agent', content=genai_types.Content( parts=[genai_types.Part(text='Thinking...')], role='model' ), ) intermediate_data = InvocationEvents(invocation_events=[invocation_event]) assert get_all_tool_calls(intermediate_data) == [] def test_get_all_tool_calls_with_invocation_events(): """Tests that tool calls are correctly extracted from a InvocationSteps object.""" tool_call1 = genai_types.FunctionCall( name='search', args={'query': 'weather'} ) tool_call2 = genai_types.FunctionCall(name='lookup', args={'id': '123'}) invocation_event1 = InvocationEvent( author='agent1', content=genai_types.Content( parts=[genai_types.Part(function_call=tool_call1)], role='model', ), ) invocation_event2 = InvocationEvent( author='agent2', content=genai_types.Content( parts=[ genai_types.Part(text='Found something.'), genai_types.Part(function_call=tool_call2), ], role='model', ), ) intermediate_data = InvocationEvents( invocation_events=[invocation_event1, invocation_event2] ) assert get_all_tool_calls(intermediate_data) == [tool_call1, tool_call2] def test_get_all_tool_calls_with_unsupported_type(): """Tests that a ValueError is raised for unsupported intermediate_data types.""" with pytest.raises( ValueError, match='Unsupported type for intermediate_data' ): get_all_tool_calls('this is not a valid type') def test_get_all_tool_responses_with_none_input(): """Tests that an empty list is returned when intermediate_data is None.""" assert get_all_tool_responses(None) == [] def test_get_all_tool_responses_with_empty_invocation_events(): """Tests InvocationEvents with an empty list of events.""" intermediate_data = InvocationEvents(invocation_events=[]) assert get_all_tool_responses(intermediate_data) == [] def test_get_all_tool_responses_with_invocation_events_no_tools(): """Tests InvocationEvents containing events without any tool responses.""" invocation_event = InvocationEvent( author='agent', content=genai_types.Content( parts=[genai_types.Part(text='Thinking...')], role='model' ), ) intermediate_data = InvocationEvents(invocation_events=[invocation_event]) assert get_all_tool_responses(intermediate_data) == [] def test_get_all_tool_responses_with_invocation_events(): """Tests that tool responses are correctly extracted from a InvocationEvents object.""" tool_response1 = genai_types.FunctionResponse( name='search', response={'result': 'weather is good'} ) tool_response2 = genai_types.FunctionResponse( name='lookup', response={'id': '123'} ) invocation_event1 = InvocationEvent( author='agent1', content=genai_types.Content( parts=[genai_types.Part(function_response=tool_response1)], role='model', ), ) invocation_event2 = InvocationEvent( author='agent2', content=genai_types.Content( parts=[ genai_types.Part(text='Found something.'), genai_types.Part(function_response=tool_response2), ], role='model', ), ) intermediate_data = InvocationEvents( invocation_events=[invocation_event1, invocation_event2] ) assert get_all_tool_responses(intermediate_data) == [ tool_response1, tool_response2, ] def test_get_all_tool_responses_with_unsupported_type(): """Tests that a ValueError is raised for unsupported intermediate_data types.""" with pytest.raises( ValueError, match='Unsupported type for intermediate_data' ): get_all_tool_responses('this is not a valid type') def test_get_all_tool_calls_with_responses_with_none_input(): """Tests that an empty list is returned when intermediate_data is None.""" assert get_all_tool_calls_with_responses(None) == [] def test_get_all_tool_calls_with_responses_with_intermediate_data_no_tool_calls(): """Tests get_all_tool_calls_with_responses with IntermediateData with no tool calls.""" # No tool calls intermediate_data = IntermediateData(tool_uses=[], tool_responses=[]) assert get_all_tool_calls_with_responses(intermediate_data) == [] def test_get_all_tool_calls_with_responses_with_intermediate_data_with_tool_calls(): """Tests get_all_tool_calls_with_responses with IntermediateData with tools.""" # With matching and non-matching tool calls tool_call1 = genai_types.FunctionCall( name='search', args={'query': 'weather'}, id='call1' ) tool_response1 = genai_types.FunctionResponse( name='search', response={'result': 'sunny'}, id='call1' ) tool_call2 = genai_types.FunctionCall( name='lookup', args={'id': '123'}, id='call2' ) intermediate_data = IntermediateData( tool_uses=[tool_call1, tool_call2], tool_responses=[tool_response1] ) assert get_all_tool_calls_with_responses(intermediate_data) == [ (tool_call1, tool_response1), (tool_call2, None), ] def test_get_all_tool_calls_with_responses_with_steps_no_tool_calls(): """Tests get_all_tool_calls_with_responses with Steps that don't have tool calls.""" # No tool calls intermediate_data = InvocationEvents(invocation_events=[]) assert get_all_tool_calls_with_responses(intermediate_data) == [] def test_get_all_tool_calls_with_responses_with_invocation_events(): """Tests get_all_tool_calls_with_responses with InvocationEvents.""" # No tools intermediate_data = InvocationEvents(invocation_events=[]) assert get_all_tool_calls_with_responses(intermediate_data) == [] # With matching and non-matching tool calls tool_call1 = genai_types.FunctionCall( name='search', args={'query': 'weather'}, id='call1' ) tool_response1 = genai_types.FunctionResponse( name='search', response={'result': 'sunny'}, id='call1' ) tool_call2 = genai_types.FunctionCall( name='lookup', args={'id': '123'}, id='call2' ) invocation_event1 = InvocationEvent( author='agent', content=genai_types.Content( parts=[ genai_types.Part(function_call=tool_call1), genai_types.Part(function_call=tool_call2), ], role='model', ), ) invocation_event2 = InvocationEvent( author='tool', content=genai_types.Content( parts=[genai_types.Part(function_response=tool_response1)], role='tool', ), ) intermediate_data = InvocationEvents( invocation_events=[invocation_event1, invocation_event2] ) assert get_all_tool_calls_with_responses(intermediate_data) == [ (tool_call1, tool_response1), (tool_call2, None), ] def test_conversation_and_conversation_scenario_mutual_exclusion(): """Tests the ensure_conversation_xor_conversation_scenario validator.""" test_conversation_scenario = ConversationScenario( starting_prompt='', conversation_plan='' ) with pytest.raises( ValueError, match=( 'Exactly one of conversation and conversation_scenario must be' ' provided in an EvalCase.' ), ): EvalCase(eval_id='test_id') with pytest.raises( ValueError, match=( 'Exactly one of conversation and conversation_scenario must be' ' provided in an EvalCase.' ), ): EvalCase( eval_id='test_id', conversation=[], conversation_scenario=test_conversation_scenario, ) # these two should not cause exceptions EvalCase(eval_id='test_id', conversation=[]) EvalCase(eval_id='test_id', conversation_scenario=test_conversation_scenario)