🔴 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:
importasynciofromtypingimportOptional, Unionfromunittest.mockimportMagicMockimportpydanticfromgoogle.adk.tools.function_toolimportFunctionToolfromgoogle.adk.tools.tool_contextimportToolContextfromgoogle.adk.agents.invocation_contextimportInvocationContextfromgoogle.adk.sessions.sessionimportSessionclassUserProfile(pydantic.BaseModel):
name: strage: intemail: Optional[str] =NoneclassCompanyProfile(pydantic.BaseModel):
company_name: strindustry: stremployee_count: intdefcreate_entity_profile(entity: Union[UserProfile, CompanyProfile]) ->dict:
ifisinstance(entity, UserProfile):
return {"status": "user_profile_created", "entity_type": "user"}
elifisinstance(entity, CompanyProfile):
return {"status": "company_profile_created", "entity_type": "company"}
return {"status": "error", "message": f"Unexpected entity type: {type(entity)}"}
asyncdefmain():
tool=FunctionTool(create_entity_profile)
ctx=MagicMock(spec=ToolContext)
inv=MagicMock(spec=InvocationContext)
inv.session=MagicMock(spec=Session)
ctx.invocation_context=invforargsin [
{"entity": {"name": "Diana", "age": 32, "email": "d@example.com"}},
{"entity": {"company_name": "Acme Corp", "industry": "tech", "employee_count": 50}},
]:
print(awaittool.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