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
1 change: 1 addition & 0 deletions src/google/adk/tools/_automatic_function_calling_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def _remove_any_of(schema: _JsonSchema) -> None:
for type_ in union_types:
if type_.get('type') != 'null':
property_schema.update(type_)
break


def _remove_default(schema: _JsonSchema) -> None:
Expand Down
50 changes: 50 additions & 0 deletions tests/unittests/tools/test_build_function_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from enum import IntEnum
from typing import Annotated
from typing import Any
from typing import List
from typing import Union

from google.adk.features import FeatureName
from google.adk.features._feature_registry import temporary_feature_override
Expand Down Expand Up @@ -1275,3 +1277,51 @@ def tool_func(name: str) -> str:
)

assert decl.parameters.required == ['name']

def test_for_langchain_collapses_a_union_to_its_first_non_null_member(self):
# A heterogeneous (non-Optional) union must collapse to its first non-null
# member, per _remove_any_of's "Take the first non-null type" contract,
# without leaking keywords from the other members (which would otherwise
# produce an invalid schema such as an INTEGER carrying array `items`).
class TagArgs(BaseModel):
tags: Union[List[str], int]

def tool_func(tags) -> str:
return str(tags)

decl = _automatic_function_calling_util.build_function_declaration_for_langchain(
False,
'tag',
'Tag something.',
tool_func,
TagArgs.model_json_schema()['properties'],
)

schema = decl.parameters.properties['tags']
# The first non-null member (list[str]) wins.
assert schema.type == 'ARRAY'
assert schema.items is not None
assert schema.items.type == 'STRING'
# No keywords from the other (integer) member leak through, and the union
# keyword itself is gone.
assert schema.any_of is None

def test_for_crewai_collapses_a_union_to_its_first_non_null_member(self):
class TagArgs(BaseModel):
count: Union[int, str]

def tool_func(count) -> str:
return str(count)

decl = _automatic_function_calling_util.build_function_declaration_for_params_for_crewai(
False,
'tag',
'Tag something.',
tool_func,
TagArgs.model_json_schema(),
)

schema = decl.parameters.properties['count']
# The first non-null member (int) wins, not the last.
assert schema.type == 'INTEGER'
assert schema.any_of is None
Loading