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

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions