"""Tests for Google structured output + tool combination (Gemini 3). Tests the three restriction lifts for Gemini 3+: 1. NativeOutput + function tools (response_schema + function_declarations) 2. Function tools + native tools (function_declarations + native_tools) 3. Output tools + native tools (ToolOutput function_declarations + native_tools) Also verifies that older models still raise appropriate errors. """ from __future__ import annotations as _annotations import re from collections.abc import Callable from typing import TYPE_CHECKING import pytest from pydantic import BaseModel from pydantic_ai import Agent from pydantic_ai.capabilities import NativeTool from pydantic_ai.exceptions import UserError from pydantic_ai.messages import ( ModelRequest, ModelResponse, NativeToolCallPart, NativeToolReturnPart, TextPart, ToolCallPart, ToolReturnPart, UserPromptPart, ) from pydantic_ai.native_tools import WebSearchTool from pydantic_ai.output import NativeOutput, ToolOutput from pydantic_ai.usage import RequestUsage from ..._inline_snapshot import snapshot from ...conftest import IsDatetime, IsStr, try_import with try_import() as imports_successful: from pydantic_ai.models.google import GoogleModel if TYPE_CHECKING: GoogleModelFactory = Callable[..., GoogleModel] pytestmark = [ pytest.mark.skipif(not imports_successful(), reason='google-genai not installed'), pytest.mark.anyio, pytest.mark.vcr, ] class CityLocation(BaseModel): city: str country: str # ============================================================================= # Error tests — older models still block unsupported combinations # ============================================================================= async def test_native_output_with_function_tools_unsupported( allow_model_requests: None, google_model: GoogleModelFactory ): m = google_model('gemini-2.5-flash') agent = Agent(m, output_type=NativeOutput(CityLocation)) @agent.tool_plain async def get_user_country() -> str: return 'Mexico' # pragma: no cover with pytest.raises( UserError, match=re.escape( 'This model does not support `NativeOutput` and function tools at the same time. ' 'Use `output_type=ToolOutput(...)` instead.' ), ): await agent.run('What is the largest city in the user country?') async def test_function_tools_with_builtin_tools_unsupported( allow_model_requests: None, google_model: GoogleModelFactory ): m = google_model('gemini-2.5-flash') agent = Agent(m, capabilities=[NativeTool(WebSearchTool())]) @agent.tool_plain async def get_user_country() -> str: return 'Mexico' # pragma: no cover with pytest.raises( UserError, match=re.escape('This model does not support function tools and built-in tools at the same time.'), ): await agent.run('What is the largest city in the user country?') async def test_tool_output_with_builtin_tools_unsupported(allow_model_requests: None, google_model: GoogleModelFactory): m = google_model('gemini-2.5-flash') agent = Agent(m, output_type=ToolOutput(CityLocation), capabilities=[NativeTool(WebSearchTool())]) with pytest.raises( UserError, match=re.escape( 'This model does not support output tools and built-in tools at the same time. ' 'Use `output_type=PromptedOutput(...)` instead.' ), ): await agent.run('What is the largest city in Mexico?') # ============================================================================= # VCR integration tests — Gemini 3 supports all combinations # ============================================================================= async def test_native_output_with_function_tools(allow_model_requests: None, google_model: GoogleModelFactory): m = google_model('gemini-3-flash-preview') agent = Agent(m, output_type=NativeOutput(CityLocation)) @agent.tool_plain async def get_user_country() -> str: return 'Mexico' result = await agent.run('What is the largest city in the user country?') assert isinstance(result.output, CityLocation) assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico')) assert result.all_messages() == snapshot( [ ModelRequest( parts=[UserPromptPart(content='What is the largest city in the user country?', timestamp=IsDatetime())], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ ToolCallPart( tool_name='get_user_country', args={}, tool_call_id='w71i0cbt', provider_name='google', provider_details={'thought_signature': IsStr()}, ) ], usage=RequestUsage( input_tokens=29, output_tokens=114, details={'thoughts_tokens': 102, 'text_prompt_tokens': 29} ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='wlP6af6uB-rBz7IP3s-7kAc', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ModelRequest( parts=[ ToolReturnPart( tool_name='get_user_country', content='Mexico', tool_call_id='w71i0cbt', timestamp=IsDatetime() ) ], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ TextPart( content="""\ { "city": "Mexico City", "country": "Mexico" } \ """, provider_name='google', provider_details={'thought_signature': IsStr()}, ) ], usage=RequestUsage( input_tokens=161, output_tokens=72, details={'thoughts_tokens': 51, 'text_prompt_tokens': 161} ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='w1P6aZ3uJ9rTz7IPx-674Aw', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ] ) async def test_native_output_with_function_tools_stream(allow_model_requests: None, google_model: GoogleModelFactory): m = google_model('gemini-3-flash-preview') agent = Agent(m, output_type=NativeOutput(CityLocation)) @agent.tool_plain async def get_user_country() -> str: return 'Mexico' async with agent.run_stream('What is the largest city in the user country?') as result: output = await result.get_output() assert isinstance(output, CityLocation) assert output == snapshot(CityLocation(city='Mexico City', country='Mexico')) assert result.all_messages() == snapshot( [ ModelRequest( parts=[UserPromptPart(content='What is the largest city in the user country?', timestamp=IsDatetime())], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ ToolCallPart( tool_name='get_user_country', args={}, tool_call_id='96c1su3s', provider_name='google', provider_details={'thought_signature': IsStr()}, ) ], usage=RequestUsage( input_tokens=29, output_tokens=81, details={'thoughts_tokens': 69, 'text_prompt_tokens': 29} ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='g1P6aZnMMZq5qtsPj9fyuQ8', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ModelRequest( parts=[ ToolReturnPart( tool_name='get_user_country', content='Mexico', tool_call_id='96c1su3s', timestamp=IsDatetime() ) ], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ TextPart( content="""\ { "city": "Mexico City", "country": "Mexico" } \ """, provider_name='google', provider_details={'thought_signature': IsStr()}, ) ], usage=RequestUsage( input_tokens=128, output_tokens=51, details={'thoughts_tokens': 30, 'text_prompt_tokens': 128} ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='hVP6afiZEuitz7IPypuAsQY', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ] ) async def test_native_output_with_builtin_tools_stream(allow_model_requests: None, google_model: GoogleModelFactory): m = google_model('gemini-3-flash-preview') agent = Agent(m, output_type=NativeOutput(CityLocation), capabilities=[NativeTool(WebSearchTool())]) async with agent.run_stream('What is the largest city in Mexico?') as result: output = await result.get_output() assert isinstance(output, CityLocation) assert output == snapshot(CityLocation(city='Mexico City', country='Mexico')) assert result.all_messages() == snapshot( [ ModelRequest( parts=[UserPromptPart(content='What is the largest city in Mexico?', timestamp=IsDatetime())], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ NativeToolCallPart( tool_name='web_search', args={'queries': ['largest city in Mexico']}, tool_call_id='d6vd9r5q', provider_name='google', provider_details={'thought_signature': IsStr()}, ), NativeToolReturnPart( tool_name='web_search', content={ 'search_suggestions': IsStr(), }, tool_call_id='d6vd9r5q', timestamp=IsDatetime(), provider_name='google', provider_details={'thought_signature': IsStr()}, ), TextPart( content="""\ { "city": "Mexico City", "country": "Mexico" } \ """, provider_name='google', provider_details={'thought_signature': IsStr()}, ), ], usage=RequestUsage( input_tokens=87, output_tokens=78, details={'thoughts_tokens': 78, 'text_prompt_tokens': 87} ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='hlP6ae3uJuqGz7IP-6e9iA0', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ] ) async def test_function_tools_with_builtin_tools(allow_model_requests: None, google_model: GoogleModelFactory): m = google_model('gemini-3-flash-preview') agent = Agent(m, capabilities=[NativeTool(WebSearchTool())]) @agent.tool_plain async def calculator(expression: str) -> str: return str(eval(expression)) result = await agent.run('What is 2+2? Also search for the current weather in Tokyo.') assert isinstance(result.output, str) # This snapshot keeps one literal `search_suggestions` as # samples of what Gemini 3 actually returns (Google-rendered HTML chip). # Every other occurrence in the suite uses `IsStr()` to save disk/tokens. assert result.all_messages() == snapshot( [ ModelRequest( parts=[ UserPromptPart( content='What is 2+2? Also search for the current weather in Tokyo.', timestamp=IsDatetime() ) ], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ ToolCallPart( tool_name='calculator', args={'expression': '2+2'}, tool_call_id='oqeiriep', provider_name='google', provider_details={'thought_signature': IsStr()}, ), NativeToolCallPart( tool_name='web_search', args={'queries': ['current weather in Tokyo']}, tool_call_id='93z4z1x3', provider_name='google', provider_details={'thought_signature': IsStr()}, ), NativeToolReturnPart( tool_name='web_search', content={ 'search_suggestions': """\
""" }, tool_call_id='93z4z1x3', timestamp=IsDatetime(), provider_name='google', provider_details={'thought_signature': IsStr()}, ), ], usage=RequestUsage( input_tokens=47, output_tokens=86, details={'thoughts_tokens': 55, 'text_prompt_tokens': 47} ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='iVP6aaqUGp-fz7IPrb-LgA0', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ModelRequest( parts=[ ToolReturnPart(tool_name='calculator', content='4', tool_call_id='oqeiriep', timestamp=IsDatetime()) ], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ NativeToolCallPart( tool_name='web_search', args={'queries': ['', 'current weather in Tokyo']}, tool_call_id=(web_search_id := IsStr()), provider_name='google', ), NativeToolReturnPart( tool_name='web_search', content=[ { 'domain': None, 'title': 'google.com', 'uri': 'https://www.google.com/search?q=weather+in+Tokyo,+JP', } ], tool_call_id=web_search_id, timestamp=IsDatetime(), provider_name='google', ), TextPart( content="""\ 2 + 2 is **4**. As for the weather in Tokyo, it is currently **cloudy** with a temperature of approximately **58°F (14°C)**. The humidity is around 71%, and there is a 30% chance of precipitation throughout the day.\ """, provider_name='google', provider_details={ 'thought_signature': 'ErwDCrkDAQw51seeQfRmL5VQKB0za3r0rcYB5VnGk/wP9IY25C1mUSd3YKB8PH3IC/C3tdufhZycWv4aJudl2LSVy0ZQXC8T74IPMVk87/VxBr+Pm+BMCFvBRcJQGNvZMgrQIKGbylDY9ZAXkMdFwEXdtIesDpRRhjUx9SfHsdkALn6fvhZb5Ea9VUMnPGCW82QJgfWU7x1WItV9TROPug+XE7eSq4/sGgnf4Gqg+cUWWxspAChvBUi3+hu1rnPGW5+cr+kufGWuaQLI+WMneegbNFSaWoT9AdJluX4hwNMHaLAXj2kyOjNKlUlo6hvoT/0ck+/pHf8+pr/CNCj6m7EbHZ1aKfMS9TlXfOP30XkUDlQ9GSE/XfAiZkuMzHUC1v5gQ4Fkp2fZV1SsQarNXPwU+G5vUBw2wZ4lyGgUAe76IcQfEyaFjVsjA0oHvgzcXeSKSHephYSUzw44E/FoIq60Sr0oaOXRv5wIrBmiKcZyxSaVSd7B92nE6pZsnHw6FwqVS5dShZhaCNJeQbxzAVbFuLK7QbOFg0CcKn3Pi8PaZLHi4Ej23HMXlduStmtS9jXwltMhGg19xOnzHGmM' }, ), ], usage=RequestUsage( input_tokens=132, output_tokens=183, details={'thoughts_tokens': 121, 'text_prompt_tokens': 132} ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='i1P6af7qHca0qtsPqaSfuAI', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ] ) async def test_native_output_with_function_and_builtin_tools( allow_model_requests: None, google_model: GoogleModelFactory ): m = google_model('gemini-3-flash-preview') agent = Agent(m, output_type=NativeOutput(CityLocation), capabilities=[NativeTool(WebSearchTool())]) @agent.tool_plain async def get_user_country() -> str: return 'Mexico' result = await agent.run('What is the largest city in the user country? Search the web to confirm.') assert isinstance(result.output, CityLocation) assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico')) assert result.all_messages() == snapshot( [ ModelRequest( parts=[ UserPromptPart( content='What is the largest city in the user country? Search the web to confirm.', timestamp=IsDatetime(), ) ], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ ToolCallPart( tool_name='get_user_country', args={}, tool_call_id='x8i00o1q', provider_name='google', provider_details={'thought_signature': IsStr()}, ) ], usage=RequestUsage( input_tokens=35, output_tokens=71, details={'thoughts_tokens': 59, 'text_prompt_tokens': 35}, ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='jVP6aY3XK8ucz7IPnO2_sQY', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ModelRequest( parts=[ ToolReturnPart( tool_name='get_user_country', content='Mexico', tool_call_id='x8i00o1q', timestamp=IsDatetime() ) ], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ NativeToolCallPart( tool_name='web_search', args={'queries': ['largest city in Mexico by population']}, tool_call_id='ccnih13d', provider_name='google', provider_details={'thought_signature': IsStr()}, ), NativeToolReturnPart( tool_name='web_search', content={ 'search_suggestions': IsStr(), }, tool_call_id='ccnih13d', timestamp=IsDatetime(), provider_name='google', provider_details={'thought_signature': IsStr()}, ), TextPart( content="""\ { "city": "Mexico City", "country": "Mexico" } \ """, provider_name='google', provider_details={'thought_signature': IsStr()}, ), ], usage=RequestUsage( input_tokens=526, output_tokens=27, details={'thoughts_tokens': 27, 'tool_use_prompt_tokens': 86, 'text_prompt_tokens': 341}, ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='jlP6abq1OuqGz7IP-6e9iA0', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ] ) async def test_native_output_with_builtin_tools(allow_model_requests: None, google_model: GoogleModelFactory): m = google_model('gemini-3-flash-preview') agent = Agent(m, output_type=NativeOutput(CityLocation), capabilities=[NativeTool(WebSearchTool())]) result = await agent.run('What is the largest city in Mexico?') assert isinstance(result.output, CityLocation) assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico')) assert result.all_messages() == snapshot( [ ModelRequest( parts=[UserPromptPart(content='What is the largest city in Mexico?', timestamp=IsDatetime())], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ NativeToolCallPart( tool_name='web_search', args={'queries': ['largest city in Mexico']}, tool_call_id='0yzlft9k', provider_name='google', provider_details={'thought_signature': IsStr()}, ), NativeToolReturnPart( tool_name='web_search', content={ 'search_suggestions': IsStr(), }, tool_call_id='0yzlft9k', timestamp=IsDatetime(), provider_name='google', provider_details={'thought_signature': IsStr()}, ), TextPart( content="""\ { "city": "Mexico City", "country": "Mexico" } \ """, provider_name='google', provider_details={'thought_signature': IsStr()}, ), ], usage=RequestUsage( input_tokens=417, output_tokens=71, details={'thoughts_tokens': 71, 'text_prompt_tokens': 351} ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='klP6aYiLELOLqtsP8sPnwAs', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ] ) async def test_tool_output_with_builtin_tools(allow_model_requests: None, google_model: GoogleModelFactory): m = google_model('gemini-3-flash-preview') agent = Agent(m, output_type=ToolOutput(CityLocation), capabilities=[NativeTool(WebSearchTool())]) result = await agent.run('What is the largest city in Mexico?') assert isinstance(result.output, CityLocation) assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico')) assert result.all_messages() == snapshot( [ ModelRequest( parts=[UserPromptPart(content='What is the largest city in Mexico?', timestamp=IsDatetime())], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ NativeToolCallPart( tool_name='web_search', args={'queries': ['largest city in Mexico by population']}, tool_call_id='jtmvhz2z', provider_name='google', provider_details={'thought_signature': IsStr()}, ), NativeToolReturnPart( tool_name='web_search', content={ 'search_suggestions': IsStr(), }, tool_call_id='jtmvhz2z', timestamp=IsDatetime(), provider_name='google', provider_details={'thought_signature': IsStr()}, ), ToolCallPart( tool_name='final_result', args={'city': 'Mexico City', 'country': 'Mexico'}, tool_call_id='yylqxldm', provider_name='google', provider_details={'thought_signature': IsStr()}, ), ], usage=RequestUsage( input_tokens=279, output_tokens=31, details={'thoughts_tokens': 31, 'text_prompt_tokens': 176} ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='lFP6aZiqNKbXz7IPz-j6gAc', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ModelRequest( parts=[ ToolReturnPart( tool_name='final_result', content='Final result processed.', tool_call_id='yylqxldm', timestamp=IsDatetime(), ) ], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ] ) async def test_auto_mode_with_function_and_builtin_tools(allow_model_requests: None, google_model: GoogleModelFactory): m = google_model('gemini-3-flash-preview') agent = Agent(m, output_type=CityLocation, capabilities=[NativeTool(WebSearchTool())]) @agent.tool_plain async def get_user_country() -> str: return 'Mexico' result = await agent.run('What is the largest city in the user country?') assert isinstance(result.output, CityLocation) assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico')) assert result.all_messages() == snapshot( [ ModelRequest( parts=[UserPromptPart(content='What is the largest city in the user country?', timestamp=IsDatetime())], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ ToolCallPart( tool_name='get_user_country', args={}, tool_call_id='zi06h2mp', provider_name='google', provider_details={'thought_signature': IsStr()}, ) ], usage=RequestUsage( input_tokens=85, output_tokens=72, details={'thoughts_tokens': 60, 'text_prompt_tokens': 85}, ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='l1P6af_FOMXVz7IPi8y54Aw', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ModelRequest( parts=[ ToolReturnPart( tool_name='get_user_country', content='Mexico', tool_call_id='zi06h2mp', timestamp=IsDatetime() ) ], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ NativeToolCallPart( tool_name='web_search', args={'queries': ['largest city in Mexico by population']}, tool_call_id='4f244mfi', provider_name='google', provider_details={'thought_signature': IsStr()}, ), NativeToolReturnPart( tool_name='web_search', content={ 'search_suggestions': IsStr(), }, tool_call_id='4f244mfi', timestamp=IsDatetime(), provider_name='google', provider_details={'thought_signature': IsStr()}, ), ToolCallPart( tool_name='final_result', args={'city': 'Mexico City', 'country': 'Mexico'}, tool_call_id='4jh89wlf', provider_name='google', provider_details={'thought_signature': IsStr()}, ), ], usage=RequestUsage( input_tokens=510, output_tokens=18, details={'thoughts_tokens': 18, 'tool_use_prompt_tokens': 78, 'text_prompt_tokens': 301}, ), model_name='gemini-3-flash-preview', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='mVP6adOKCunUz7IP5vXVqQ0', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ModelRequest( parts=[ ToolReturnPart( tool_name='final_result', content='Final result processed.', tool_call_id='4jh89wlf', timestamp=IsDatetime(), ) ], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ] ) # ============================================================================= # Gemini 2 fallback behavior # ============================================================================= async def test_auto_output_mode_with_builtin_tools_falls_back( allow_model_requests: None, google_model: GoogleModelFactory ): """Gemini 2.5 with auto output mode + builtin tools silently converts to prompted output.""" m = google_model('gemini-2.5-flash') agent = Agent(m, output_type=CityLocation, capabilities=[NativeTool(WebSearchTool())]) result = await agent.run('What is the largest city in Mexico?') assert isinstance(result.output, CityLocation) assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico')) assert result.all_messages() == snapshot( [ ModelRequest( parts=[UserPromptPart(content='What is the largest city in Mexico?', timestamp=IsDatetime())], timestamp=IsDatetime(), run_id=IsStr(), conversation_id=IsStr(), ), ModelResponse( parts=[ NativeToolCallPart( tool_name='web_search', args={'queries': ['largest city in Mexico']}, tool_call_id=IsStr(), provider_name='google', ), NativeToolReturnPart( tool_name='web_search', content=None, tool_call_id=IsStr(), timestamp=IsDatetime(), provider_name='google', ), TextPart(content='{"city": "Mexico City", "country": "Mexico"}'), ], usage=RequestUsage( input_tokens=217, output_tokens=82, details={ 'thoughts_tokens': 54, 'tool_use_prompt_tokens': 132, 'text_prompt_tokens': 85, 'text_tool_use_prompt_tokens': 132, }, ), model_name='gemini-2.5-flash', timestamp=IsDatetime(), provider_name='google', provider_url='https://generativelanguage.googleapis.com/', provider_details={'finish_reason': 'STOP'}, provider_response_id='nFP6aZ_4HPuU6dkP-uetwA0', finish_reason='stop', run_id=IsStr(), conversation_id=IsStr(), ), ] )