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
44 changes: 43 additions & 1 deletion src/google/adk/tools/_function_parameter_parse_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import types as typing_types
from typing import _GenericAlias
from typing import Any
from typing import cast
from typing import get_args
from typing import get_origin
from typing import Literal
Expand Down Expand Up @@ -118,6 +119,44 @@ def _raise_for_invalid_enum_value(param: inspect.Parameter):
)


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 _generate_json_schema_for_parameter(
param: inspect.Parameter,
) -> dict[str, Any]:
Expand All @@ -131,7 +170,10 @@ def _generate_json_schema_for_parameter(
json_schema_dict = _add_unevaluated_items_to_fixed_len_tuple_schema(
json_schema_dict
)
return json_schema_dict
return cast(
dict[str, Any],
_normalize_tuple_schema_for_genai_schema(json_schema_dict),
)


def _is_builtin_primitive_or_compound(
Expand Down
94 changes: 94 additions & 0 deletions tests/unittests/tools/test_function_parameter_parse_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from __future__ import annotations

from typing import Any

from google.adk.tools import _function_parameter_parse_util


def test_normalize_strips_prefixItems() -> None:
schema: dict[str, Any] = {
"type": "array",
"prefixItems": [{"type": "string"}, {"type": "number"}],
"minItems": 2,
"maxItems": 2,
"unevaluatedItems": False,
}
normalized = (
_function_parameter_parse_util._normalize_tuple_schema_for_genai_schema(
schema
)
)
assert "prefixItems" not in normalized
assert "unevaluatedItems" not in normalized
assert normalized["items"] == {
"anyOf": [{"type": "string"}, {"type": "number"}]
}


def test_normalize_strips_unevaluatedItems() -> None:
schema: dict[str, Any] = {
"type": "object",
"properties": {
"field1": {"type": "string"},
},
"unevaluatedItems": False,
}
normalized = (
_function_parameter_parse_util._normalize_tuple_schema_for_genai_schema(
schema
)
)
assert "unevaluatedItems" not in normalized
assert normalized["properties"] == {"field1": {"type": "string"}}


def test_normalize_handles_items_false() -> None:
schema: dict[str, Any] = {
"type": "array",
"prefixItems": [{"type": "string"}],
"items": False,
}
normalized = (
_function_parameter_parse_util._normalize_tuple_schema_for_genai_schema(
schema
)
)
assert "items" in normalized
assert normalized["items"] == {"type": "string"}
assert normalized.get("items") is not False


def test_normalize_handles_nested_schemas() -> None:
schema: dict[str, Any] = {
"type": "object",
"properties": {
"field1": {
"type": "array",
"prefixItems": [{"type": "string"}],
"unevaluatedItems": False,
}
},
}
normalized = (
_function_parameter_parse_util._normalize_tuple_schema_for_genai_schema(
schema
)
)
assert "unevaluatedItems" not in normalized["properties"]["field1"]
assert "prefixItems" not in normalized["properties"]["field1"]
assert normalized["properties"]["field1"]["items"] == {"type": "string"}