Skip to content

LiteLLM tool schemas drop anyOf, so union arguments reach the model as `{"type": "object"}" #6738

Description

@benclarkeio

🔴 Required Information

Describe the Bug:

_schema_to_dict in google/adk/models/lite_llm.py builds its dict with schema.model_dump(), which emits the pydantic field name any_of, and it only recurses into items and properties. A union therefore leaves ADK spelled any_of (snake_case) with genai's uppercase type enums inside. Downstream JSON Schema consumers read anyOf, so LiteLLM's provider transforms drop the unrecognised key and backfill {"type": "object"}.

The result is that any union-typed tool argument reaches the model as an untyped object. Nothing raises, and no 400 comes back — the model is simply handed a schema that does not describe the argument, and it guesses.

Steps to Reproduce:

from google.adk.models.lite_llm import _function_declaration_to_tool_param
from google.genai import types

declaration = types.FunctionDeclaration(
    name="lookup",
    description="Look a record up.",
    parameters=types.Schema.model_validate({
        "type": "object",
        "properties": {
            "value": {
                "anyOf": [{"type": "string"}, {"type": "number"}],
                "description": "id or index",
            }
        },
        "required": ["value"],
    }),
)

print(_function_declaration_to_tool_param(declaration)["function"]["parameters"])

Expected Behavior:

The union survives as anyOf, with its branch types lowercased like every other type ADK converts:

{"type": "object",
 "properties": {"value": {"anyOf": [{"type": "string"}, {"type": "number"}],
                          "description": "id or index"}},
 "required": ["value"]}

Observed Behavior:

ADK hands LiteLLM a snake_case key with uppercase enum types:

{"type": "object",
 "properties": {"value": {"any_of": [{"type": "STRING"}, {"type": "NUMBER"}],
                          "description": "id or index"}},
 "required": ["value"]}

and by the time LiteLLM has built the Vertex payload the union is gone:

{"type": "object",
 "properties": {"value": {"description": "id or index", "type": "object"}},
 "required": ["value"]}

A string-or-number argument is now advertised to the model as an object.

Impact

It gets worse the more the union carries. A discriminated union of object variants — the usual shape for "one block of a UI", "one step of a plan", an OpenAPI anyOf payload — collapses to items: {"type": "object"}, taking every variant, every discriminator value and every nested description with it.

I hit this with an 11-variant block union on a client-side tool. The model could see only children: array of object, so it invented field names, and recovered the real schema one validation error at a time across six round trips before giving up. The declaration was correct at every layer above ADK, and correct in types.Schema — the loss was entirely in this conversion.

This is not specific to client-side tools: any FunctionDeclaration with a union anywhere in its parameters is affected, including OpenAPI toolsets and MCP tools, on every provider routed through LiteLlm. The native (non-LiteLLM) path is unaffected, because types.Schema serializes by alias and reaches the API as anyOf.

Environment Details:

  • ADK Library Version (pip show google-adk): 1.31.1, and current main
  • Desktop OS: macOS
  • Python Version (python -V): 3.13.5

Model Information:

  • Are you using LiteLLM: Yes
  • Which model is being used: vertex_ai/gemini-3.5-flash (provider-independent — the loss happens before the provider transform)

Metadata

Metadata

Labels

request clarification[Status] The maintainer need clarification or more information from the authortools[Component] This issue is related to tools

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions