Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/google/adk/tools/_function_parameter_parse_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def _parse_schema_from_parameter(
schema.type = types.Type.OBJECT
schema.properties = {}
for field_name, field_info in param.annotation.model_fields.items():
schema.properties[field_name] = _parse_schema_from_parameter(
field_schema = _parse_schema_from_parameter(
variant,
inspect.Parameter(
field_name,
Expand All @@ -298,6 +298,18 @@ def _parse_schema_from_parameter(
),
func_name,
)

if field_info.description:
field_schema.description = field_info.description

schema.properties[field_name] = field_schema

schema.required = [
field_name
for field_name, field_info in param.annotation.model_fields.items()
if field_info.is_required()
]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better performance and code clarity, you can determine the required fields within the main loop that processes each field (lines 291-305), rather than iterating over model_fields a second time here. You could initialize schema.required = [] before the loop, and then append field_name to it inside the loop when field_info.is_required() is true.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed by 7709595...ad8a0e4


_raise_if_schema_unsupported(variant, schema)
return schema
if param.annotation is None:
Expand Down
69 changes: 54 additions & 15 deletions tests/unittests/tools/test_build_function_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from typing import Dict
from typing import List
from typing import Optional

from google.adk.tools import _automatic_function_calling_util
from google.adk.tools.tool_context import ToolContext
Expand All @@ -22,6 +23,7 @@
# TODO: crewai requires python 3.10 as minimum
# from crewai_tools import FileReadTool
from pydantic import BaseModel
from pydantic import Field


def test_string_input():
Expand Down Expand Up @@ -152,34 +154,71 @@ class SimpleFunction(BaseModel):


def test_nested_basemodel_input():
class ChildInput(BaseModel):
input_str: str

class CustomInput(BaseModel):
child: ChildInput
"""Test nested Pydantic models with and without Field annotations."""

def simple_function(input: CustomInput) -> str:
class ChildInput(BaseModel):
name: str = Field(description='The name of the child')
age: int # No Field annotation
nickname: Optional[str] = Field(
default=None, description='Optional nickname'
)

class ParentInput(BaseModel):
title: str = Field(description='The title of the parent')
basic_field: str # No Field annotation
child: ChildInput = Field(description='Child information')
optional_field: Optional[str] = Field(
default='default_value', description='An optional field with default'
)

def simple_function(input: ParentInput) -> str:
return {'result': input}

function_decl = _automatic_function_calling_util.build_function_declaration(
func=simple_function
)

# Check top-level structure
assert function_decl.name == 'simple_function'
assert function_decl.parameters.type == 'OBJECT'
assert function_decl.parameters.properties['input'].type == 'OBJECT'

# Check ParentInput properties with and without Field annotations
parent_props = function_decl.parameters.properties['input'].properties
assert parent_props['title'].type == 'STRING'
assert parent_props['title'].description == 'The title of the parent'
assert parent_props['basic_field'].type == 'STRING'
assert parent_props['basic_field'].description is None # No Field annotation
assert parent_props['child'].type == 'OBJECT'
assert parent_props['child'].description == 'Child information'
assert parent_props['optional_field'].type == 'STRING'
assert (
function_decl.parameters.properties['input'].properties['child'].type
== 'OBJECT'
)
assert (
function_decl.parameters.properties['input']
.properties['child']
.properties['input_str']
.type
== 'STRING'
parent_props['optional_field'].description
== 'An optional field with default'
)

# Check ParentInput required fields
parent_required = function_decl.parameters.properties['input'].required
assert 'title' in parent_required
assert 'basic_field' in parent_required
assert 'child' in parent_required
assert 'optional_field' not in parent_required # Has default value

# Check ChildInput properties with and without Field annotations
child_props = parent_props['child'].properties
assert child_props['name'].type == 'STRING'
assert child_props['name'].description == 'The name of the child'
assert child_props['age'].type == 'INTEGER'
assert child_props['age'].description is None # No Field annotation
assert child_props['nickname'].type == 'STRING'
assert child_props['nickname'].description == 'Optional nickname'

# Check ChildInput required fields
child_required = parent_props['child'].required
assert 'name' in child_required
assert 'age' in child_required
assert 'nickname' not in child_required # Optional with default None


def test_basemodel_with_nested_basemodel():
class ChildInput(BaseModel):
Expand Down