🔴 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)
🔴 Required Information
Describe the Bug:
_schema_to_dictingoogle/adk/models/lite_llm.pybuilds its dict withschema.model_dump(), which emits the pydantic field nameany_of, and it only recurses intoitemsandproperties. A union therefore leaves ADK spelledany_of(snake_case) with genai's uppercase type enums inside. Downstream JSON Schema consumers readanyOf, 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:
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
anyOfpayload — collapses toitems: {"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 intypes.Schema— the loss was entirely in this conversion.This is not specific to client-side tools: any
FunctionDeclarationwith a union anywhere in its parameters is affected, including OpenAPI toolsets and MCP tools, on every provider routed throughLiteLlm. The native (non-LiteLLM) path is unaffected, becausetypes.Schemaserializes by alias and reaches the API asanyOf.Environment Details:
mainModel Information:
vertex_ai/gemini-3.5-flash(provider-independent — the loss happens before the provider transform)