litellm - ✅(Solved) Fix [Bug]: `_remove_strict_from_schema` deletes user-defined properties named `strict` without updating `required` (breaks Vertex AI cachedContents) [1 pull requests, 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#27853Fetched 2026-05-14 03:30:07
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Participants
Timeline (top)
cross-referenced ×1labeled ×1

litellm.utils._remove_strict_from_schema (litellm/utils.py L3449 on main) is intended to strip OpenAI's tools-API strict: bool keyword from JSON Schemas before sending to providers that don't accept it (e.g. Gemini). The implementation recursively visits every dict in the schema tree and deletes any key literally named "strict", which conflates two different things:

  1. The OpenAI tools-API keyword strict at the function-declaration root (should be stripped — intended behavior).
  2. A user-defined property name "strict" inside a properties block (should not be touched — accidental over-strip).

The over-strip also doesn't update the schema's required array, so the property gets deleted but its name stays in required, producing a schema that references a property that doesn't exist.

When the resulting schema reaches Vertex AI's cachedContents endpoint (via litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py), Vertex returns 400:

CreateCachedContentRequest.cached_content.tools[0]
  .function_declarations[N].parameters
  .properties[...].required[N]: property is not defined

Root Cause

litellm.utils._remove_strict_from_schema (litellm/utils.py L3449 on main) is intended to strip OpenAI's tools-API strict: bool keyword from JSON Schemas before sending to providers that don't accept it (e.g. Gemini). The implementation recursively visits every dict in the schema tree and deletes any key literally named "strict", which conflates two different things:

  1. The OpenAI tools-API keyword strict at the function-declaration root (should be stripped — intended behavior).
  2. A user-defined property name "strict" inside a properties block (should not be touched — accidental over-strip).

The over-strip also doesn't update the schema's required array, so the property gets deleted but its name stays in required, producing a schema that references a property that doesn't exist.

When the resulting schema reaches Vertex AI's cachedContents endpoint (via litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py), Vertex returns 400:

CreateCachedContentRequest.cached_content.tools[0]
  .function_declarations[N].parameters
  .properties[...].required[N]: property is not defined

Fix Action

Fix / Workaround

A canopy-runtime plugin in our internal hosted-LLM gateway had a Zod schema with a user-defined property strict: z.boolean(). We sent it via POST /v1/messages to a model alias routed to Vertex Gemini. The cron-driven failure ran every ~25 min on prod for several hours before being noticed; we patched on our side by renaming the user-facing property (strictstrictMode). Filing upstream so the next caller doesn't lose the same time.

PR fix notes

PR #27889: fix: preserve strict schema properties

Description (problem / solution / changelog)

## Summary

  • keep user-defined properties named strict when sanitizing JSON schemas
  • still remove schema-level strict keywords before provider requests
  • add a regression test that preserves required: ["strict", ...]

Fixes #27853.

To verify

  • python -m py_compile litellm/utils.py tests/litellm_utils_tests/test_utils.py
  • python -m pytest tests/litellm_utils_tests/test_utils.py::test_remove_strict_from_schema_preserves_property_named_strict -q --basetemp .tmp\\pytest
  • python -m ruff check litellm/utils.py
  • git diff --check

Changed files

  • litellm/utils.py (modified, +5/-2)
  • tests/litellm_utils_tests/test_utils.py (modified, +21/-0)

Code Example

CreateCachedContentRequest.cached_content.tools[0]
  .function_declarations[N].parameters
  .properties[...].required[N]: property is not defined

---

from litellm.utils import _remove_strict_from_schema

schema = {
    "type": "object",
    "properties": {
        "strict": {"type": "boolean", "description": "User-defined property literally named 'strict'"},
        "field":  {"type": "string"},
    },
    "required": ["strict", "field"],
}

_remove_strict_from_schema(schema)
print(schema)

---

{
    "type": "object",
    "properties": {
        "field": {"type": "string"}      # strict deleted
    },
    "required": ["strict", "field"]      # but still listed as required → references missing property
}

---

curl -sS "$LITELLM/v1/messages" \
  -H "x-api-key: $KEY" -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "<gemini-routed alias>",
    "max_tokens": 64,
    "messages": [{"role":"user","content":"hi"}],
    "tools": [{
      "name": "demo",
      "description": "demo",
      "input_schema": {
        "type": "object",
        "properties": {
          "strict": {"type":"boolean"},
          "field":  {"type":"string"}
        },
        "required": ["strict","field"]
      }
    }]
  }'

---

def _remove_strict_from_schema(schema):
    if isinstance(schema, dict):
        schema.pop("strict", None)
        for key, value in schema.items():
            if key == "properties" and isinstance(value, dict):
                # property names are user-defined identifiers, not schema keywords
                for prop_schema in value.values():
                    _remove_strict_from_schema(prop_schema)
            else:
                _remove_strict_from_schema(value)
    elif isinstance(schema, list):
        for item in schema:
            _remove_strict_from_schema(item)
    return schema
RAW_BUFFERClick to expand / collapse

Summary

litellm.utils._remove_strict_from_schema (litellm/utils.py L3449 on main) is intended to strip OpenAI's tools-API strict: bool keyword from JSON Schemas before sending to providers that don't accept it (e.g. Gemini). The implementation recursively visits every dict in the schema tree and deletes any key literally named "strict", which conflates two different things:

  1. The OpenAI tools-API keyword strict at the function-declaration root (should be stripped — intended behavior).
  2. A user-defined property name "strict" inside a properties block (should not be touched — accidental over-strip).

The over-strip also doesn't update the schema's required array, so the property gets deleted but its name stays in required, producing a schema that references a property that doesn't exist.

When the resulting schema reaches Vertex AI's cachedContents endpoint (via litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py), Vertex returns 400:

CreateCachedContentRequest.cached_content.tools[0]
  .function_declarations[N].parameters
  .properties[...].required[N]: property is not defined

Minimal reproduction (pure Python — no provider call needed)

from litellm.utils import _remove_strict_from_schema

schema = {
    "type": "object",
    "properties": {
        "strict": {"type": "boolean", "description": "User-defined property literally named 'strict'"},
        "field":  {"type": "string"},
    },
    "required": ["strict", "field"],
}

_remove_strict_from_schema(schema)
print(schema)

Expected: schema unchanged (no top-level strict keyword to strip).

Actual:

{
    "type": "object",
    "properties": {
        "field": {"type": "string"}      # strict deleted
    },
    "required": ["strict", "field"]      # but still listed as required → references missing property
}

End-to-end reproduction (real provider call)

Send an Anthropic-format request via the proxy with a tool whose parameters contain a property named strict, and route to a Vertex-AI Gemini model:

curl -sS "$LITELLM/v1/messages" \
  -H "x-api-key: $KEY" -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "<gemini-routed alias>",
    "max_tokens": 64,
    "messages": [{"role":"user","content":"hi"}],
    "tools": [{
      "name": "demo",
      "description": "demo",
      "input_schema": {
        "type": "object",
        "properties": {
          "strict": {"type":"boolean"},
          "field":  {"type":"string"}
        },
        "required": ["strict","field"]
      }
    }]
  }'

Result: 400 from Vertex with required[N]: property is not defined (context caching is the default path for tool-heavy requests in 1.81.x).

Affected versions

  • Verified on v1.81.3-stable.
  • Same code present on main (commit at time of filing).
  • The function has had this shape since the originally-referenced issues (#6136, #6088).

Suggested fix

The intent is to strip the OpenAI keyword wherever it appears as a schema field. Property names inside a properties block aren't schema fields — they're user-defined identifiers. Recurse into properties values without treating the keys as schema dictionaries:

def _remove_strict_from_schema(schema):
    if isinstance(schema, dict):
        schema.pop("strict", None)
        for key, value in schema.items():
            if key == "properties" and isinstance(value, dict):
                # property names are user-defined identifiers, not schema keywords
                for prop_schema in value.values():
                    _remove_strict_from_schema(prop_schema)
            else:
                _remove_strict_from_schema(value)
    elif isinstance(schema, list):
        for item in schema:
            _remove_strict_from_schema(item)
    return schema

Defense-in-depth follow-up: reconcile required[] against properties after the strip (drops dangling references to keys that were removed for any reason). The per-key recursion fix above is the minimal correctness fix.

How we hit this

A canopy-runtime plugin in our internal hosted-LLM gateway had a Zod schema with a user-defined property strict: z.boolean(). We sent it via POST /v1/messages to a model alias routed to Vertex Gemini. The cron-driven failure ran every ~25 min on prod for several hours before being noticed; we patched on our side by renaming the user-facing property (strictstrictMode). Filing upstream so the next caller doesn't lose the same time.

Happy to send a PR with the fix + a unit test if it'd be useful.

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING

litellm - ✅(Solved) Fix [Bug]: `_remove_strict_from_schema` deletes user-defined properties named `strict` without updating `required` (breaks Vertex AI cachedContents) [1 pull requests, 1 participants]