Skip to content
Closed
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
3 changes: 3 additions & 0 deletions src/google/adk/tools/_automatic_function_calling_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from ..utils._callable_utils import get_callable_doc
from ..utils._callable_utils import unwrap_callable
from ..utils.variant_utils import GoogleLLMVariant
from ._gemini_schema_util import _dereference_schema
from ._gemini_schema_util import _sanitize_schema_formats_for_gemini

logger = logging.getLogger('google_adk.' + __name__)
Expand Down Expand Up @@ -420,6 +421,8 @@ def from_function_with_options(
sanitized_schema = _sanitize_schema_formats_for_gemini(
json_schema_dict
)
else:
sanitized_schema = _dereference_schema(json_schema_dict)
parameters_json_schema[name] = types.Schema.model_validate(
sanitized_schema
)
Expand Down
50 changes: 50 additions & 0 deletions tests/unittests/tools/test_from_function_with_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

from collections.abc import Sequence
import datetime
from typing import Any
from typing import AsyncGenerator
from typing import Dict
Expand Down Expand Up @@ -460,6 +461,55 @@ def complex_tool(
)


@pytest.mark.parametrize('shape', ['model', 'list', 'mapping', 'optional'])
def test_vertex_fallback_resolves_nested_model_references(shape):
"""Nested models keep their fields when another parameter needs fallback."""

class Address(pydantic.BaseModel):
city: str
email: str = pydantic.Field(json_schema_extra={'format': 'email'})
label: str = 'home'

class Person(pydantic.BaseModel):
address: Address

parameter_type = {
'model': Person,
'list': list[Person],
'mapping': dict[str, Person],
'optional': Person | None,
}[shape]

def schedule(person: parameter_type, when: datetime.datetime, notify=True):
return 'ok'

declaration = _automatic_function_calling_util.from_function_with_options(
schedule, GoogleLLMVariant.VERTEX_AI
)

person_schema = declaration.parameters.properties['person']
if shape == 'list':
assert person_schema.type == types.Type.ARRAY
person_schema = person_schema.items
elif shape == 'mapping':
assert person_schema.type == types.Type.OBJECT
person_schema = types.Schema.model_validate(
person_schema.additional_properties
)
elif shape == 'optional':
assert person_schema.any_of[1].type == types.Type.NULL
person_schema = person_schema.any_of[0]
assert person_schema.type == types.Type.OBJECT
address_schema = person_schema.properties['address']
assert address_schema.properties['city'].type == types.Type.STRING
assert address_schema.properties['email'].format == 'email'
assert address_schema.properties['label'].default == 'home'
assert set(address_schema.required) == {'city', 'email'}
assert declaration.parameters.properties['when'].format == 'date-time'
assert declaration.parameters.properties['notify'].default is True
assert set(declaration.parameters.required) == {'person', 'when'}


def test_format_preservation_for_vertex_fallback():
"""Test that format is preserved for VERTEX_AI variant in fallback path."""

Expand Down
Loading