-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Initial Checks
- I confirm that I'm using the latest version of Pydantic AI
- I confirm that I searched for my issue in https://github.com/pydantic/pydantic-ai/issues before opening this issue
Description
When using deeply nested Pydantic models as output_type in pydantic-ai agents, the library incorrectly treats nested models as tool calls instead of part of the structured output schema. This causes the LLM to attempt calling nested model names as tools, resulting in errors. This seems to only affect Gemini models
Error Message
Agent run error
exception_type: pydantic_ai.exceptions.ToolRetryError
pydantic_ai.exceptions.ModelRetry: Unknown tool name: 'NestedModel'. Available tools: 'final_result'
Final error:
'e': UnexpectedModelBehavior('Exceeded maximum retries (5) for output validation'),
Reproduction
This is an example below that recreates the issue. The attached image shows the output on logfire
Expected Behavior
The agent should treat the entire nested structure as a single JSON schema for structured output, not create separate tool calls for each nested model.
Workaround
Wrap the output type with NativeOutput to force JSON schema mode:
from pydantic_ai.output import NativeOutput
agent = Agent(
'gemini-2.5-flash',
output_type=NativeOutput(TopModel), # ✅ Works correctly
system_prompt="You are a helpful assistant that creates structured data."
)
result = await agent.run("Create a simple example with 2 pages, each with 2 items")
# Successfully returns TopModel with nested structure
Additional Context (Generated by Claude Code)
The issue occurs because pydantic-ai's OutputToolset.build() method converts nested Pydantic models into separate tool definitions when using the default output mode. This behavior differs between model providers:
Google (Gemini): Strictly follows tool definitions, fails when nested models are treated as tools
OpenAI (GPT): More lenient, sometimes handles nested structures without NativeOutput
Example Code
'''
Example demonstrating the pydantic-ai issue with nested Pydantic models.
When using deeply nested Pydantic models as output_type, pydantic-ai treats
nested models as tool calls instead of structured output schema, causing:
ModelRetry: Unknown tool name: 'NestedModel'. Available tools: 'final_result'
'''
import os
import asyncio
import logfire
from pydantic import BaseModel, Field
from pydantic_ai import Agent
from pydantic_ai.output import NativeOutput
from dotenv import load_dotenv
load_dotenv()
# Configure logfire
logfire.configure(token = os.environ.get('LOGFIRE_API_KEY'), service_name = 'output_issue')
logfire.instrument_pydantic_ai()
class NestedModel(BaseModel):
'''This represents the 'Blocks' class in your code'''
name: str = Field(..., description='Name of the item')
value: int = Field(..., description='Value of the item')
class MiddleModel(BaseModel):
'''This represents the 'BlocksPage' class in your code'''
title: str = Field(..., description='Title of the page')
items: list[NestedModel] = Field(..., description='List of nested items')
class TopModel(BaseModel):
'''This represents the 'Itinerary' class in your code'''
name: str = Field(..., description='Name of the collection')
pages: list[MiddleModel] = Field(..., description='List of pages')
class Test:
def __init__(self):
None
@logfire.instrument('output_issue')
async def run_test(self):
try:
logfire.info('Testing WITH NativeOutput GEMINI (THIS SHOULD WORK)')
agent = Agent(
'gemini-2.5-flash',
output_type = NativeOutput(TopModel),
system_prompt = 'You are a helpful assistant that creates structured data.',
retries = 5,
output_retries = 5
)
result = await agent.run('Create a simple example with 2 pages, each with 2 items')
logfire.info('Success with NativeOutput', output=result.output.model_dump())
print(f'✅ Success: {result.output}')
logfire.info('Testing WITHOUT NativeOutput OPENAI (THIS SHOULD WORK)')
agent = Agent(
'gpt-5-mini',
output_type = TopModel,
system_prompt = 'You are a helpful assistant that creates structured data.',
retries = 5,
output_retries = 5
)
result = await agent.run('Create a simple example with 2 pages, each with 2 items')
logfire.info('Success without NativeOutput', output=result.output.model_dump())
print(f'✅ Success: {result.output}')
logfire.info('Testing WITHOUT NativeOutput GEMINI (THIS WILL FAIL)')
agent = Agent(
'gemini-2.5-flash',
output_type = TopModel,
system_prompt = 'You are a helpful assistant that creates structured data.',
retries = 5,
output_retries = 5
)
result = await agent.run('Create a simple example with 2 pages, each with 2 items')
logfire.info('Success with NativeOutput', output=result.output.model_dump())
print(f'✅ Success!')
print(f'Result: {result.output.model_dump_json(indent=2)}')
except Exception as e:
logfire.error(f'Error: {e}')
raise e
async def main():
test = Test()
await test.run_test()
if __name__ == '__main__':
asyncio.run(main())Python, Pydantic AI & LLM client version
pydantic-ai version: 1.20.0
Python version: 3.11.9
Models tested:
gemini-2.5-flash - Fails without NativeOutput
gpt-5-mini - Works without NativeOutput (OpenAI handles it differently)
Using API for both Gemini and OpenAI