From 1a805a9a425b8d1d958fa9b4270c0fec699276b1 Mon Sep 17 00:00:00 2001 From: Nishchay Mahor Date: Mon, 27 Jul 2026 14:49:29 -0700 Subject: [PATCH] fix: honor field defaults in nested Pydantic model function schemas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../genai/_automatic_function_calling_util.py | 18 +++++-- google/genai/tests/types/test_types.py | 49 +++++++++++++++++++ 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/google/genai/_automatic_function_calling_util.py b/google/genai/_automatic_function_calling_util.py index ec7f9a702..4c94344a6 100644 --- a/google/genai/_automatic_function_calling_util.py +++ b/google/genai/_automatic_function_calling_util.py @@ -301,13 +301,21 @@ def _parse_schema_from_parameter( # type: ignore[return] schema.type = _py_builtin_type_to_schema_type[dict] schema.properties = {} for field_name, field_info in param.annotation.model_fields.items(): + field_param = inspect.Parameter( + field_name, + inspect.Parameter.POSITIONAL_OR_KEYWORD, + annotation=field_info.annotation, + ) + # Forward the field's default so fields with a default are not marked + # required. Without this the sub-schema default stays unset and every + # non-Optional field is treated as required. + if not field_info.is_required(): + field_param = field_param.replace( + default=field_info.get_default(call_default_factory=True) + ) schema.properties[field_name] = _parse_schema_from_parameter( api_option, - inspect.Parameter( - field_name, - inspect.Parameter.POSITIONAL_OR_KEYWORD, - annotation=field_info.annotation, - ), + field_param, func_name, ) schema.required = _get_required_fields(schema) diff --git a/google/genai/tests/types/test_types.py b/google/genai/tests/types/test_types.py index 50a3f9058..2ada314d9 100644 --- a/google/genai/tests/types/test_types.py +++ b/google/genai/tests/types/test_types.py @@ -1514,6 +1514,55 @@ def func_under_test( assert actual_schema_vertex == expected_schema +def test_pydantic_model_with_default_fields(): + class MyPydanticModel(pydantic.BaseModel): + a: int + b: int = 5 + c: str = 'hello' + d: list[int] = pydantic.Field(default_factory=list) + + def func_under_test( + a: MyPydanticModel, + ): + """test pydantic model with default fields.""" + pass + + expected_schema = types.FunctionDeclaration( + name='func_under_test', + parameters=types.Schema( + type='OBJECT', + properties={ + 'a': types.Schema( + type='OBJECT', + properties={ + 'a': types.Schema(type='INTEGER'), + 'b': types.Schema(type='INTEGER', default=5), + 'c': types.Schema(type='STRING', default='hello'), + 'd': types.Schema( + type='ARRAY', + default=[], + items=types.Schema(type='INTEGER'), + ), + }, + required=['a'], + ), + }, + required=['a'], + ), + description='test pydantic model with default fields.', + ) + + actual_schema_mldev = types.FunctionDeclaration.from_callable( + client=mldev_client, callable=func_under_test + ) + actual_schema_vertex = types.FunctionDeclaration.from_callable( + client=vertex_client, callable=func_under_test + ) + + assert actual_schema_mldev == expected_schema + assert actual_schema_vertex == expected_schema + + @pytest.mark.skip( reason=( 'AFC is in progress of refactoring, this test is failing python 3.14'