Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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 @@ -303,8 +303,9 @@ def _parse_schema_from_parameter(
schema.default = param.default
schema.type = types.Type.OBJECT
schema.properties = {}
required_fields = []
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 @@ -313,6 +314,17 @@ def _parse_schema_from_parameter(
),
func_name,
)

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

if field_info.is_required():
required_fields.append(field_name)

schema.properties[field_name] = field_schema

schema.required = required_fields

_raise_if_schema_unsupported(variant, schema)
return schema
if param.annotation is None:
Expand Down
78 changes: 64 additions & 14 deletions tests/unittests/tools/test_build_function_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from enum import Enum
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 @@ -23,6 +24,7 @@
# TODO: crewai requires python 3.10 as minimum
# from crewai_tools import FileReadTool
from pydantic import BaseModel
from pydantic import Field
import pytest


Expand Down Expand Up @@ -154,33 +156,82 @@ class SimpleFunction(BaseModel):


def test_nested_basemodel_input():
class ChildInput(BaseModel):
input_str: str
"""Test nested Pydantic models with and without Field annotations."""

class CustomInput(BaseModel):
child: ChildInput
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'
)
email: Optional[str] = None # No Field annotation, Optional with default

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'
)
status: Optional[str] = None # No Field annotation, Optional with default

def simple_function(input: CustomInput) -> str:
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'
parent_props['optional_field'].description
== 'An optional field with default'
)
assert parent_props['status'].type == 'STRING'
assert parent_props['status'].description is None # No Field annotation

# 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
assert (
function_decl.parameters.properties['input']
.properties['child']
.properties['input_str']
.type
== 'STRING'
)
'status' not in parent_required
) # No Field annotation, Optional with default

# 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'
assert child_props['email'].type == 'STRING'
assert child_props['email'].description is None # No Field annotation

# 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
assert (
'email' not in child_required
) # No Field annotation, Optional with default


def test_basemodel_with_nested_basemodel():
Expand Down Expand Up @@ -223,7 +274,6 @@ def simple_function(


def test_enums():

class InputEnum(Enum):
AGENT = 'agent'
TOOL = 'tool'
Expand Down