🔴 Required Information
Describe the Bug:
FunctionTool._preprocess_args converts a JSON dict to a single Pydantic BaseModel and handles Optional[BaseModel], but it does not convert Union[BaseModel, BaseModel] arguments. When the LLM calls a tool with a dict matching one branch of the union, the dict is passed through unchanged. Tool code that uses isinstance(entity, UserProfile) then fails with Unexpected entity type: <class 'dict'>.
This is especially visible in ADK 2.0 GA: JSON_SCHEMA_FOR_FUNC_DECL changed from WIP / default OFF to EXPERIMENTAL / default ON (v2 transition, commit 1622793). Tool declaration generation now succeeds for union Pydantic params, but runtime conversion is still missing — so the agent can call the tool and still fail at execution time.
Steps to Reproduce:
- Install ADK 2.0.x (
pip show google-adk)
- Run the minimal repro below (no API key needed for the tool execution path)
- Observe
status: error for both user and company entity payloads
Expected Behavior:
entity: Union[UserProfile, CompanyProfile] should be converted from the LLM JSON dict to the matching Pydantic instance before the tool function runs (as documented in contributing/samples/tools/pydantic_argument/README.md).
Observed Behavior:
# Both return:
# {'status': 'error', 'message': "Unexpected entity type: <class 'dict'>"}
With JSON_SCHEMA_FOR_FUNC_DECL disabled, create_entity_profile also fails at declaration time:
ValueError: Failed to parse the parameter entity: Union[...UserProfile, ...CompanyProfile]
Environment Details:
- ADK Library Version: 2.0.0 (also reproducible on main)
- Desktop OS: macOS
- Python Version: 3.13
Model Information:
- LiteLLM: No
- Model: gemini-2.5-pro on Vertex AI (live repro confirmed; local repro above does not require API)
🟡 Optional Information
Regression:
Not a new conversion implementation in 2.0 — Union[BaseModel, BaseModel] conversion appears never to have been implemented in FunctionTool (only Optional[T] with a single non-None type is handled in _preprocess_args).
What changed in 2.0 GA is that default-on JSON_SCHEMA_FOR_FUNC_DECL allows tool registration/schema generation to succeed, so the broken runtime path is now hit in normal use.
Minimal Reproduction Code:
import asyncio
from typing import Optional, Union
from unittest.mock import MagicMock
import pydantic
from google.adk.tools.function_tool import FunctionTool
from google.adk.tools.tool_context import ToolContext
from google.adk.agents.invocation_context import InvocationContext
from google.adk.sessions.session import Session
class UserProfile(pydantic.BaseModel):
name: str
age: int
email: Optional[str] = None
class CompanyProfile(pydantic.BaseModel):
company_name: str
industry: str
employee_count: int
def create_entity_profile(entity: Union[UserProfile, CompanyProfile]) -> dict:
if isinstance(entity, UserProfile):
return {"status": "user_profile_created", "entity_type": "user"}
elif isinstance(entity, CompanyProfile):
return {"status": "company_profile_created", "entity_type": "company"}
return {"status": "error", "message": f"Unexpected entity type: {type(entity)}"}
async def main():
tool = FunctionTool(create_entity_profile)
ctx = MagicMock(spec=ToolContext)
inv = MagicMock(spec=InvocationContext)
inv.session = MagicMock(spec=Session)
ctx.invocation_context = inv
for args in [
{"entity": {"name": "Diana", "age": 32, "email": "d@example.com"}},
{"entity": {"company_name": "Acme Corp", "industry": "tech", "employee_count": 50}},
]:
print(await tool.run_async(args=args, tool_context=ctx))
asyncio.run(main())
Additional Context:
How often has this issue occurred?:
- Always (100%) for
Union[BaseModel, BaseModel] tool parameters
🔴 Required Information
Describe the Bug:
FunctionTool._preprocess_argsconverts a JSON dict to a singlePydantic BaseModeland handlesOptional[BaseModel], but it does not convertUnion[BaseModel, BaseModel]arguments. When the LLM calls a tool with a dict matching one branch of the union, the dict is passed through unchanged. Tool code that usesisinstance(entity, UserProfile)then fails withUnexpected entity type: <class 'dict'>.This is especially visible in ADK 2.0 GA:
JSON_SCHEMA_FOR_FUNC_DECLchanged fromWIP / default OFFtoEXPERIMENTAL / default ON(v2 transition, commit 1622793). Tool declaration generation now succeeds for union Pydantic params, but runtime conversion is still missing — so the agent can call the tool and still fail at execution time.Steps to Reproduce:
pip show google-adk)status: errorfor both user and company entity payloadsExpected Behavior:
entity: Union[UserProfile, CompanyProfile]should be converted from the LLM JSON dict to the matching Pydantic instance before the tool function runs (as documented incontributing/samples/tools/pydantic_argument/README.md).Observed Behavior:
With
JSON_SCHEMA_FOR_FUNC_DECLdisabled,create_entity_profilealso fails at declaration time:Environment Details:
Model Information:
🟡 Optional Information
Regression:
Not a new conversion implementation in 2.0 —
Union[BaseModel, BaseModel]conversion appears never to have been implemented inFunctionTool(onlyOptional[T]with a single non-None type is handled in_preprocess_args).What changed in 2.0 GA is that default-on
JSON_SCHEMA_FOR_FUNC_DECLallows tool registration/schema generation to succeed, so the broken runtime path is now hit in normal use.Minimal Reproduction Code:
Additional Context:
contributing/samples/tools/pydantic_argument/agent.py(create_entity_profile)contributing/samples/tools/pydantic_argument/tests/test_create_company.json(added in 7fc5b0e, May 2026)create_full_user_account(singleUserProfile+Optional[UserPreferences]) works correctlypydantic.TypeAdapterin_preprocess_argsappears to fix this runtime case on the PR branch, but lacks an explicitUnion[BaseModel, BaseModel]test and is not merged yetHow often has this issue occurred?:
Union[BaseModel, BaseModel]tool parameters