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
40 changes: 40 additions & 0 deletions src/google/adk/tools/_function_parameter_parse_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,45 @@ def _add_unevaluated_items_to_fixed_len_tuple_schema(
return json_schema


def _normalize_tuple_schema_for_genai_schema(
json_schema: Any,
) -> Any:
"""Normalizes tuple schema keywords unsupported by `types.Schema`.

Pydantic emits `prefixItems` for fixed-length tuples. `types.Schema` does not
support `prefixItems`, so we convert tuple item definitions into
`items.anyOf`. We also drop `unevaluatedItems`, which is unsupported by
`types.Schema`.
"""
if isinstance(json_schema, list):
return [
_normalize_tuple_schema_for_genai_schema(item)
for item in json_schema
]
if not isinstance(json_schema, dict):
return json_schema

normalized_schema = {
key: _normalize_tuple_schema_for_genai_schema(value)
for key, value in json_schema.items()
if key != 'unevaluatedItems'
}

prefix_items = normalized_schema.pop('prefixItems', None)
if isinstance(prefix_items, list):
if len(prefix_items) == 1:
normalized_schema['items'] = prefix_items[0]
elif prefix_items:
normalized_schema['items'] = {'anyOf': prefix_items}

# Pydantic can emit `items: false` for tuple schemas, which is unsupported by
# `types.Schema`.
if normalized_schema.get('items') is False:
normalized_schema.pop('items')

return normalized_schema


def _raise_for_unsupported_param(
param: inspect.Parameter,
func_name: str,
Expand Down Expand Up @@ -131,6 +170,7 @@ def _generate_json_schema_for_parameter(
json_schema_dict = _add_unevaluated_items_to_fixed_len_tuple_schema(
json_schema_dict
)
json_schema_dict = _normalize_tuple_schema_for_genai_schema(json_schema_dict)
return json_schema_dict


Expand Down
38 changes: 38 additions & 0 deletions tests/unittests/tools/test_from_function_with_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,3 +361,41 @@ def complex_tool(
),
},
)


def test_tuple_types_work_in_json_schema_fallback():
"""Test that tuple schemas work in json schema fallback."""

def generate_image(
prompt: str,
input_bytes: list[tuple[bytes, str]] | None = None,
) -> dict[str, str]:
"""Generate an image from a prompt."""
del input_bytes
return {'status': prompt}

declaration = _automatic_function_calling_util.from_function_with_options(
generate_image, GoogleLLMVariant.GEMINI_API
)

assert declaration.parameters is not None
assert declaration.parameters.required == ['prompt']
input_bytes_schema = declaration.parameters.properties['input_bytes']
assert input_bytes_schema.nullable is True
assert input_bytes_schema.any_of is not None

array_schema = next(
schema
for schema in input_bytes_schema.any_of
if schema.type == types.Type.ARRAY
)
assert array_schema.items is not None
assert array_schema.items.type == types.Type.ARRAY
assert array_schema.items.max_items == 2
assert array_schema.items.min_items == 2
assert array_schema.items.items is not None
assert array_schema.items.items.any_of is not None
assert len(array_schema.items.items.any_of) == 2
assert array_schema.items.items.any_of[0].type == types.Type.STRING
assert array_schema.items.items.any_of[0].format == 'binary'
assert array_schema.items.items.any_of[1].type == types.Type.STRING
Loading