fix: handle optional and union pydantic models with string annotations
Merge https://github.com/google/adk-python/pull/6471 Support Optional, Union and | annotations for Pydantic models when string annotations are enabled. PiperOrigin-RevId: 957289257
This commit is contained in:
committed by
Copybara-Service
parent
6396ce60f9
commit
94d08cd69f
@@ -17,6 +17,7 @@ from __future__ import annotations
|
||||
import functools
|
||||
import inspect
|
||||
import logging
|
||||
from types import UnionType
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
@@ -168,9 +169,10 @@ class FunctionTool(BaseTool):
|
||||
target_type = type_hints.get(param_name, param.annotation)
|
||||
if target_type != inspect.Parameter.empty:
|
||||
|
||||
# Handle Optional[PydanticModel] types
|
||||
if get_origin(param.annotation) is Union:
|
||||
union_args = get_args(param.annotation)
|
||||
# Handle Optional/Union types (e.g. Optional[PydanticModel], PydanticModel | None)
|
||||
origin = get_origin(target_type)
|
||||
if origin is Union or origin is UnionType:
|
||||
union_args = get_args(target_type)
|
||||
# Find the non-None type in Optional[T] (which is Union[T, None])
|
||||
non_none_types = [
|
||||
arg for arg in union_args if arg is not type(None)
|
||||
@@ -187,12 +189,12 @@ class FunctionTool(BaseTool):
|
||||
continue
|
||||
try:
|
||||
converted_args[param_name] = pydantic.TypeAdapter(
|
||||
param.annotation
|
||||
target_type
|
||||
).validate_python(args[param_name])
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Failed to convert argument '{param_name}' to"
|
||||
f' {param.annotation}: {e}'
|
||||
f' {target_type}: {e}'
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
|
||||
from google.adk.tools import _automatic_function_calling_util
|
||||
from google.adk.tools.function_tool import FunctionTool
|
||||
@@ -229,3 +230,57 @@ def test_preprocess_args_with_list_of_pydantic_models_and_annotations():
|
||||
assert processed_args['items'][0].name == 'Burger'
|
||||
assert processed_args['items'][0].quantity == 10
|
||||
assert processed_args['items'][1].quantity == 5
|
||||
|
||||
|
||||
def test_preprocess_args_with_optional_pydantic_model_and_annotations():
|
||||
"""Test _preprocess_args converts dict to Optional[Pydantic] model with string annotations."""
|
||||
|
||||
def function_with_optional(item: Optional[ItemModel] = None) -> int:
|
||||
return item.quantity if item else 0
|
||||
|
||||
tool = FunctionTool(function_with_optional)
|
||||
input_args = {'item': {'name': 'Burger', 'quantity': 10}}
|
||||
processed_args = tool._preprocess_args(input_args)
|
||||
|
||||
assert isinstance(processed_args['item'], ItemModel)
|
||||
assert processed_args['item'].name == 'Burger'
|
||||
assert processed_args['item'].quantity == 10
|
||||
|
||||
|
||||
def test_preprocess_args_with_pipe_union_pydantic_model_and_annotations():
|
||||
"""Test _preprocess_args converts dict to BaseModel | None with string annotations."""
|
||||
|
||||
def function_with_pipe_union(item: ItemModel | None = None) -> int:
|
||||
return item.quantity if item else 0
|
||||
|
||||
tool = FunctionTool(function_with_pipe_union)
|
||||
input_args = {'item': {'name': 'Pizza', 'quantity': 5}}
|
||||
processed_args = tool._preprocess_args(input_args)
|
||||
|
||||
assert isinstance(processed_args['item'], ItemModel)
|
||||
assert processed_args['item'].name == 'Pizza'
|
||||
assert processed_args['item'].quantity == 5
|
||||
|
||||
|
||||
def test_preprocess_args_with_optional_list_of_pydantic_models_and_annotations():
|
||||
"""Test _preprocess_args converts dicts in Optional[list[BaseModel]] with string annotations."""
|
||||
|
||||
def function_with_optional_list(
|
||||
items: Optional[list[ItemModel]] = None,
|
||||
) -> int:
|
||||
return sum(item.quantity for item in items) if items else 0
|
||||
|
||||
tool = FunctionTool(function_with_optional_list)
|
||||
input_args = {
|
||||
'items': [
|
||||
{'name': 'Burger', 'quantity': 10},
|
||||
{'name': 'Pizza', 'quantity': 5},
|
||||
]
|
||||
}
|
||||
processed_args = tool._preprocess_args(input_args)
|
||||
|
||||
assert isinstance(processed_args['items'], list)
|
||||
assert len(processed_args['items']) == 2
|
||||
assert all(isinstance(item, ItemModel) for item in processed_args['items'])
|
||||
assert processed_args['items'][0].quantity == 10
|
||||
assert processed_args['items'][1].quantity == 5
|
||||
|
||||
Reference in New Issue
Block a user