Skip to content

FunctionTool does not convert Union[Pydantic, Pydantic] at runtime (visible in ADK 2.0 GA) #5799

@kkj333

Description

@kkj333

🔴 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:

  1. Install ADK 2.0.x (pip show google-adk)
  2. Run the minimal repro below (no API key needed for the tool execution path)
  3. 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

Metadata

Metadata

Assignees

Labels

tools[Component] This issue is related to tools

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions