Skip to content

Conversation

@conradlee
Copy link
Contributor

@conradlee conradlee commented Nov 20, 2025

Problem

When using nested Pydantic models with Gemini without NativeOutput, the model incorrectly treats title fields in nested JSON Schema definitions ($defs) as callable Python functions, resulting in MALFORMED_FUNCTION_CALL errors.

Example: Using these Pydantic models:

class NestedModel(BaseModel):
    name: str
    value: int

class MiddleModel(BaseModel):
    title: str
    items: list[NestedModel]

class TopModel(BaseModel):
    name: str
    pages: list[MiddleModel]

agent = Agent('gemini-2.5-flash', output_type=TopModel)
result = await agent.run('Create example with 2 pages')

Schema sent to Gemini (simplified):

{
  "name": "final_result",
  "parameters_json_schema": {
    "title": "TopModel",
    "type": "object",
    "properties": {
      "name": {"type": "string"},
      "pages": {
        "type": "array",
        "items": {"$ref": "#/$defs/MiddleModel"}
      }
    },
    "$defs": {
      "MiddleModel": {
        "title": "MiddleModel",  // ← Gemini treats this as a callable function!
        "type": "object",
        "properties": {
          "title": {"type": "string"},
          "items": {
            "type": "array", 
            "items": {"$ref": "#/$defs/NestedModel"}
          }
        }
      },
      "NestedModel": {
        "title": "NestedModel",  // ← Also treated as callable!
        "type": "object",
        "properties": {
          "name": {"type": "string"},
          "value": {"type": "integer"}
        }
      }
    }
  }
}

What Gemini incorrectly generates:

MiddleModel(title="Page 1", items=[NestedModel(name="Item", value=1)])

→ Error: Unknown tool name: 'MiddleModel'. Available tools: 'final_result'

What we want Gemini to generate:

{"title": "Page 1", "items": [{"name": "Item", "value": 1}]}

Root Cause

Gemini's function calling parser has a bug where it treats the JSON Schema title field in $defs entries as callable function names, violating the JSON Schema spec where title is purely descriptive metadata. When it encounters "title": "MiddleModel" inside a definition, it thinks "MiddleModel is a function I can call" rather than "this is just metadata describing the schema."

This only affects nested schemas in $defs, not the top-level title (which correctly represents the tool name).

Solution

Remove title fields from nested schemas (those in $defs) while preserving:

  • $ref/$defs structure (for better schema organization)
  • Top-level title (needed for the function declaration name)

Transformed schema (after fix):

{
  "$defs": {
    "MiddleModel": {
      // "title" removed!
      "type": "object",
      "properties": {...}
    },
    "NestedModel": {
      // "title" removed!
      "type": "object",
      "properties": {...}
    }
  },
  "title": "TopModel",  // ← Kept at root level
  ...
}

Key insight: We initially thought we needed to inline all $ref definitions, but testing with real API calls revealed that Gemini can handle $ref/$defs correctly—the problem is only with the title fields. This makes the fix much simpler and cleaner.

Implementation

Modified GoogleJsonSchemaTransformer.transform() to detect and remove title from nested schemas:

# Remove 'title' from nested schemas - Gemini treats these as callable function names
if self.defs and 'title' in schema:
    if schema.get('title') in self.defs:  # Is this a $defs entry?
        schema.pop('title', None)

Testing

Related Issues

conradlee and others added 2 commits November 20, 2025 09:43
When using nested Pydantic models with Gemini without NativeOutput, the
model treats 'title' fields in nested schemas as callable function names,
resulting in MALFORMED_FUNCTION_CALL errors. Gemini's tool calling system
doesn't properly handle JSON Schema $ref/$defs and interprets nested model
titles as Python constructors instead of schema definitions.

The fix applies different schema transformations for tool calling versus
structured output modes. For tool schemas, we inline $ref definitions and
remove title fields from nested schemas, which allows Gemini to correctly
parse the schema structure. For NativeOutput mode, we preserve $ref/$defs
to maintain support for complex and recursive schemas as established in
previous work.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
 pydantic#3483)

## Problem
Gemini's function calling parser incorrectly interprets 'title' fields in
nested JSON Schema definitions ($defs) as callable Python functions, causing
MALFORMED_FUNCTION_CALL errors when using nested Pydantic models.

## Root Cause
When a schema like this is sent to Gemini:
```json
{
  "$defs": {
    "MiddleModel": {
      "title": "MiddleModel",  // ← Gemini treats this as a callable
      "type": "object"
    }
  }
}
```

Gemini tries to generate Python constructor calls like:
`MiddleModel(title="...", items=[...])`

Instead of JSON:
`{"title": "...", "items": [...]}`

## Solution
Remove 'title' from nested schemas (those in $defs) while preserving:
- $ref/$defs structure (better schema organization)
- Top-level title (needed for function declaration name)

This is simpler than the original approach of inlining all $ref definitions.

## Testing
- Added test_google_nested_models_without_native_output (reproduces issue)
- Added test_google_nested_models_with_native_output (workaround verification)
- All 81 Google model tests pass
- Verified with real Gemini API calls

## Related Issues
- Fixes pydantic#3483
- Reported to Google SDK: googleapis/python-genai#1732

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@conradlee conradlee changed the title Fix nested Pydantic models in Gemini tool calling Fix Gemini nested models by removing title from $defs (fixes #3483) Nov 20, 2025
Address reviewer feedback from @DouweM:
- Always remove 'title' from all schemas (like OpenAI does)
- Remove unnecessary customize_request_parameters() method
- Remove unused imports (GoogleJsonSchemaTransformer, _customize_tool_def, _customize_output_object)

The original approach of conditionally removing title based on $defs keys
had an edge case where users could customize titles. The simpler approach
of always removing title is safer and cleaner, since the function declaration
name is set separately in ToolDefinition anyway.
@conradlee
Copy link
Contributor Author

conradlee commented Nov 20, 2025

Thanks for the speedy review @DouweM!:

  1. Always dropping title: I've updated the code to always remove title from all schemas I agree that this is simpler and avoids the edge case where a user might have customized the title.

My understanding is that when pydanitc models are transformed into json schemas, the title field is quite redundant:

  • For root/nested models: The title is just the class name, which is already captured in the $defs key
  • For properties: The title is just the capitalized field name, which is already captured in the property key

Is that right? Then we don't have to feel bad about stripping titles.

  1. Removing customize_request_parameters: Yes, as I mentioned above, this was just some leftover cruft. Sorry about that.

conradlee and others added 3 commits November 20, 2025 16:55
The GoogleJsonSchemaTransformer now removes all 'title' fields to fix
Gemini's MALFORMED_FUNCTION_CALL bug. Updated test snapshots to reflect
this change - all 'title' fields are now absent from expected outputs.

This completes the fix for issue pydantic#3483.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Reference the underlying issue in googleapis/python-genai instead of
repeating the detailed explanation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@conradlee conradlee requested a review from DouweM November 20, 2025 16:02
@DouweM DouweM enabled auto-merge (squash) November 20, 2025 16:08
@DouweM DouweM disabled auto-merge November 20, 2025 16:09
@DouweM DouweM changed the title Fix Gemini nested models by removing title from $defs (fixes #3483) Fix Gemini nested tool argument schemas by removing title from $defs Nov 20, 2025
@DouweM DouweM enabled auto-merge (squash) November 20, 2025 16:09
@DouweM DouweM disabled auto-merge November 20, 2025 17:44
@DouweM DouweM merged commit 9c70fb5 into pydantic:main Nov 20, 2025
52 of 58 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants