Skip to content

fix: honor field defaults in nested Pydantic model function schemas - #2782

Open
NishchayMahor wants to merge 1 commit into
googleapis:mainfrom
NishchayMahor:fix/afc-nested-model-defaults-required
Open

fix: honor field defaults in nested Pydantic model function schemas#2782
NishchayMahor wants to merge 1 commit into
googleapis:mainfrom
NishchayMahor:fix/afc-nested-model-defaults-required

Conversation

@NishchayMahor

Copy link
Copy Markdown

Summary

When automatic function calling generates a schema for a parameter that is a Pydantic model, sub-fields that have a default are incorrectly marked as required. This forces the model to supply values for parameters that should be optional.

import pydantic
from google.genai import types

class Foo(pydantic.BaseModel):
    a: int              # required
    b: int = 5          # has a default -> should be optional
    c: str = 'hello'    # has a default -> should be optional

def fn(foo: Foo): ...

decl = types.FunctionDeclaration.from_callable(client=client, callable=fn)
# decl.parameters.properties['foo'].required == ['a', 'b', 'c']   # BUG
# expected                                    == ['a']

Root cause

In _automatic_function_calling_util._parse_schema_from_parameter, the Pydantic-model branch builds each sub-field via inspect.Parameter(field_name, ..., annotation=field_info.annotation) but never passes field_info.default. The recursive call therefore sees no default, the sub-schema's default stays None, and _get_required_fields (not nullable and default is None) lists the field as required. Top-level parameters already work because their defaults flow through param.default; only nested-model fields were affected.

Fix

Forward each field's default into the sub-field inspect.Parameter, resolving default_factory via field_info.get_default(call_default_factory=True), and only when the field is not required. This leaves Optional-without-default fields (already handled via nullable) untouched and does not modify the shared _get_required_fields helper.

Verified across concrete defaults (b=5), factory defaults (d: list = Field(default_factory=list)), falsy defaults (f: int = 0), and Optional[int] without a default — only genuinely-required fields remain in required.

Testing

Added test_pydantic_model_with_default_fields to google/genai/tests/types/test_types.py (fails on main, passes with the fix). Existing schema/callable tests unchanged:

pytest google/genai/tests/types/test_types.py -k 'pydantic or default_value or built_in'   # 22 passed
pytest google/genai/tests/afc/ google/genai/tests/transformers/test_t_tool.py test_t_tools.py  # 122 passed

(2 pre-existing ReplayApiClient fixture errors in afc_thoughts are unrelated — they fail identically on clean main.)

This change was developed with AI assistance; I verified the root cause and behavior against the cited code paths, reviewed every line, and ran the tests above.

When generating a function-calling schema for a parameter that is a Pydantic
model, each sub-field was built from an inspect.Parameter without a default,
so its schema default stayed unset and _get_required_fields marked every
non-Optional field as required — even fields with a default. This forced the
model to supply values for optional parameters. Forward the field's default
(resolving default_factory) into the sub-field parameter so defaulted fields
are correctly optional and their default is emitted in the schema.
@Venkaiahbabuneelam Venkaiahbabuneelam self-assigned this Jul 28, 2026
@Venkaiahbabuneelam Venkaiahbabuneelam added the size:L Code changes between 40-100 lines label Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L Code changes between 40-100 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants