Skip to content

Nested Pydantic Models Treated as Tool Calls Instead of Structured Output for Gemini Models #3483

@SharhadBashar

Description

@SharhadBashar

Initial Checks

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

Image

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions