litellm - 💡(How to fix) Fix Vertex AI: tool parameters schema silently dropped when containing anyOf/additionalProperties [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
BerriAI/litellm#23870Fetched 2026-04-08 00:54:11
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Participants
Timeline (top)
labeled ×1

Error Message

  • No error or warning is logged — the schema is silently dropped

Root Cause

async def main(): resp = await client.chat.completions.create( model="google-vertex:gemini-3.1-pro-preview", messages=[ {"role": "system", "content": "Call validate_command_headers with cookie header: session=abc123"}, {"role": "user", "content": "Test auth now."} ], tools=[{ "type": "function", "function": { "name": "validate_command_headers", "description": "Validate headers are authenticated.", "parameters": { "type": "object", "properties": { "method": {"type": "string"}, "body": {"anyOf": [{"type": "object", "additionalProperties": True}, {"type": "null"}]}, "url": {"type": "string"}, "headers": {"type": "object", "additionalProperties": {"type": "string"}} }, "required": ["method", "body", "url", "headers"] } } }], tool_choice="required" ) args = json.loads(resp.choices[0].message.tool_calls[0].function.arguments) print(json.dumps(args, indent=2)) # Output: {"headers": {}} — empty because schema was dropped

Code Example

{
  "type": "function",
  "function": {
    "name": "validate_command_headers",
    "description": "Validate the url and headers are authenticated.",
    "parameters": {
      "type": "object",
      "required": ["method", "body", "url", "headers"],
      "properties": {
        "method": {"type": "string"},
        "body": {"anyOf": [{"type": "object", "additionalProperties": true}, {"type": "null"}]},
        "url": {"type": "string"},
        "headers": {"type": "object", "additionalProperties": {"type": "string"}}
      }
    }
  }
}

---

{
  "type": "function",
  "function": {
    "name": "validate_command_headers",
    "parameters": {},
    "description": "Validate the url and headers are authenticated."
  }
}

---

import asyncio, json
from openai import AsyncOpenAI

client = AsyncOpenAI(base_url="http://localhost:5001/v1", api_key="sk-master-key")

async def main():
    resp = await client.chat.completions.create(
        model="google-vertex:gemini-3.1-pro-preview",
        messages=[
            {"role": "system", "content": "Call validate_command_headers with cookie header: session=abc123"},
            {"role": "user", "content": "Test auth now."}
        ],
        tools=[{
            "type": "function",
            "function": {
                "name": "validate_command_headers",
                "description": "Validate headers are authenticated.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "method": {"type": "string"},
                        "body": {"anyOf": [{"type": "object", "additionalProperties": True}, {"type": "null"}]},
                        "url": {"type": "string"},
                        "headers": {"type": "object", "additionalProperties": {"type": "string"}}
                    },
                    "required": ["method", "body", "url", "headers"]
                }
            }
        }],
        tool_choice="required"
    )
    args = json.loads(resp.choices[0].message.tool_calls[0].function.arguments)
    print(json.dumps(args, indent=2))
    # Output: {"headers": {}} — empty because schema was dropped

asyncio.run(main())

---

model_list:
  - model_name: google-vertex:gemini-3.1-pro-preview
    litellm_params:
      model: vertex_ai/gemini-3.1-pro-preview
      vertex_project: <project>
      vertex_location: global
RAW_BUFFERClick to expand / collapse

Bug Report

When routing OpenAI-format tool calls to Vertex AI (Gemini), LiteLLM silently drops the entire parameters object from the tool definition if the schema contains anyOf or additionalProperties patterns. The model receives "parameters": {} and produces garbage tool call arguments.

Environment

  • LiteLLM version: 1.81.12 (Docker image ghcr.io/berriai/litellm-database:main-stable)
  • Model: vertex_ai/gemini-3.1-pro-preview
  • Client: OpenAI Python SDK 2.8.1 via /v1/chat/completions

Reproduction

Tool schema sent to LiteLLM proxy (correct)

{
  "type": "function",
  "function": {
    "name": "validate_command_headers",
    "description": "Validate the url and headers are authenticated.",
    "parameters": {
      "type": "object",
      "required": ["method", "body", "url", "headers"],
      "properties": {
        "method": {"type": "string"},
        "body": {"anyOf": [{"type": "object", "additionalProperties": true}, {"type": "null"}]},
        "url": {"type": "string"},
        "headers": {"type": "object", "additionalProperties": {"type": "string"}}
      }
    }
  }
}

What LiteLLM sent to Vertex AI (hidden_params.optional_params.tools)

{
  "type": "function",
  "function": {
    "name": "validate_command_headers",
    "parameters": {},
    "description": "Validate the url and headers are authenticated."
  }
}

The entire properties, required, and type information is silently dropped. The model receives a tool with no schema and guesses parameter structure from context, producing empty {} for dict-typed parameters.

Minimal repro (Python)

import asyncio, json
from openai import AsyncOpenAI

client = AsyncOpenAI(base_url="http://localhost:5001/v1", api_key="sk-master-key")

async def main():
    resp = await client.chat.completions.create(
        model="google-vertex:gemini-3.1-pro-preview",
        messages=[
            {"role": "system", "content": "Call validate_command_headers with cookie header: session=abc123"},
            {"role": "user", "content": "Test auth now."}
        ],
        tools=[{
            "type": "function",
            "function": {
                "name": "validate_command_headers",
                "description": "Validate headers are authenticated.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "method": {"type": "string"},
                        "body": {"anyOf": [{"type": "object", "additionalProperties": True}, {"type": "null"}]},
                        "url": {"type": "string"},
                        "headers": {"type": "object", "additionalProperties": {"type": "string"}}
                    },
                    "required": ["method", "body", "url", "headers"]
                }
            }
        }],
        tool_choice="required"
    )
    args = json.loads(resp.choices[0].message.tool_calls[0].function.arguments)
    print(json.dumps(args, indent=2))
    # Output: {"headers": {}} — empty because schema was dropped

asyncio.run(main())

Impact

  • All Vertex AI models with tools containing anyOf or additionalProperties are affected
  • The model "works" but produces incorrect tool arguments because it has no schema to follow
  • No error or warning is logged — the schema is silently dropped

Expected behavior

LiteLLM should translate anyOf/additionalProperties to Gemini-compatible equivalents, or at minimum preserve the properties and required fields and only drop the unsupported keywords.

LiteLLM config

model_list:
  - model_name: google-vertex:gemini-3.1-pro-preview
    litellm_params:
      model: vertex_ai/gemini-3.1-pro-preview
      vertex_project: <project>
      vertex_location: global

extent analysis

Fix Plan

To address the issue of LiteLLM silently dropping the parameters object from the tool definition when the schema contains anyOf or additionalProperties patterns, we need to modify the LiteLLM configuration to handle these patterns correctly.

Here are the steps to fix the issue:

  • Update the LiteLLM configuration to preserve the properties and required fields and only drop the unsupported keywords.
  • Modify the tool schema to use Gemini-compatible equivalents for anyOf and additionalProperties.

Code Changes

We can achieve this by adding a custom JSON schema transformer to the LiteLLM configuration. Here's an example of how to do it:

import json
from jsonschema import FormatChecker

def transform_schema(schema):
    # Remove anyOf and additionalProperties from the schema
    if 'anyOf' in schema:
        del schema['anyOf']
    if 'additionalProperties' in schema:
        del schema['additionalProperties']
    
    return schema

# Load the LiteLLM configuration
with open('litellm_config.yaml', 'r') as f:
    config = yaml.safe_load(f)

# Add the custom schema transformer to the configuration
config['model_list'][0]['litellm_params']['schema_transformer'] = transform_schema

# Save the updated configuration
with open('litellm_config.yaml', 'w') as f:
    yaml.dump(config, f)

We also need to update the tool schema to use Gemini-compatible equivalents for anyOf and additionalProperties. For example:

{
  "type": "function",
  "function": {
    "name": "validate_command_headers",
    "description": "Validate the url and headers are authenticated.",
    "parameters": {
      "type": "object",
      "required": ["method", "body", "url", "headers"],
      "properties": {
        "method": {"type": "string"},
        "body": {"type": "object"},
        "url": {"type": "string"},
        "headers": {"type": "object"}
      }
    }
  }
}

Verification

To verify that the fix worked, we can test the updated LiteLLM configuration with the modified tool schema. We should see that the parameters object is no longer dropped and the model produces the correct tool arguments.

We can use the same Python code to test the updated configuration:

import asyncio, json
from openai import AsyncOpenAI

client = AsyncOpenAI(base_url="http://localhost:5001/v1", api_key="sk-master-key")

async def main():
    resp = await client.chat.completions.create(
        model="google-vertex:gemini-3.1-pro-preview",
        messages=[
            {"role": "system", "content": "Call validate_command_headers with cookie header: session=abc

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

LiteLLM should translate anyOf/additionalProperties to Gemini-compatible equivalents, or at minimum preserve the properties and required fields and only drop the unsupported keywords.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING