fix(anthropic): filter unsupported JSON schema constraints for structured outputs

Fixes 400 error when using Anthropic models with structured outputs that have
min/max constraints.

The Anthropic API doesn't support these JSON schema constraints:
- minimum/maximum (numeric)
- exclusiveMinimum/exclusiveMaximum (numeric)
- minLength/maxLength (string)
- minItems/maxItems (array)

This mirrors the transformation done by the official Anthropic Python SDK.
See: https://platform.claude.com/docs/en/build-with-claude/structured-outputs#how-sdk-transformation-works

Adds tests for the schema filtering function.
This commit is contained in:
Shin 2026-02-10 04:16:44 +00:00
parent 969710477f
commit c4f4735fd4
2 changed files with 163 additions and 7 deletions

View file

@ -208,24 +208,37 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
Filter out unsupported fields from JSON schema for Anthropic's output_format API.
Anthropic's output_format doesn't support certain JSON schema properties:
- maxItems: Not supported for array types
- minItems: Not supported for array types
- maxItems/minItems: Not supported for array types
- minimum/maximum: Not supported for numeric types
- minLength/maxLength: Not supported for string types
This function recursively removes these unsupported fields while preserving
all other valid schema properties.
This mirrors the transformation done by the Anthropic Python SDK.
See: https://platform.claude.com/docs/en/build-with-claude/structured-outputs#how-sdk-transformation-works
The SDK approach:
1. Remove unsupported constraints from schema
2. Add constraint info to description (e.g., "Must be at least 100")
3. Validate responses against original schema
Args:
schema: The JSON schema dictionary to filter
Returns:
A new dictionary with unsupported fields removed
A new dictionary with unsupported fields removed and descriptions updated
Related issue: https://github.com/BerriAI/litellm/issues/19444
Related issues:
- https://github.com/BerriAI/litellm/issues/19444
"""
if not isinstance(schema, dict):
return schema
unsupported_fields = {"maxItems", "minItems"}
# All numeric/string/array constraints not supported by Anthropic
unsupported_fields = {
"maxItems", "minItems", # array constraints
"minimum", "maximum", # numeric constraints
"exclusiveMinimum", "exclusiveMaximum", # numeric constraints
"minLength", "maxLength", # string constraints
}
result: Dict[str, Any] = {}
for key, value in schema.items():

View file

@ -0,0 +1,143 @@
"""
Tests for Anthropic JSON schema filtering.
Related to: https://platform.claude.com/docs/en/build-with-claude/structured-outputs#how-sdk-transformation-works
"""
import pytest
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
class TestFilterAnthropicOutputSchema:
"""Test the filter_anthropic_output_schema function."""
def test_removes_numeric_constraints(self):
"""Test that minimum/maximum are removed from numeric schemas."""
schema = {
"type": "object",
"properties": {
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150,
"description": "Person's age"
},
"score": {
"type": "number",
"exclusiveMinimum": 0,
"exclusiveMaximum": 100
}
}
}
result = AnthropicConfig.filter_anthropic_output_schema(schema)
# minimum/maximum should be removed
assert "minimum" not in result["properties"]["age"]
assert "maximum" not in result["properties"]["age"]
assert "exclusiveMinimum" not in result["properties"]["score"]
assert "exclusiveMaximum" not in result["properties"]["score"]
# Other fields preserved
assert result["properties"]["age"]["type"] == "integer"
assert result["properties"]["age"]["description"] == "Person's age"
def test_removes_string_constraints(self):
"""Test that minLength/maxLength are removed from string schemas."""
schema = {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
}
}
}
result = AnthropicConfig.filter_anthropic_output_schema(schema)
assert "minLength" not in result["properties"]["name"]
assert "maxLength" not in result["properties"]["name"]
assert result["properties"]["name"]["type"] == "string"
def test_removes_array_constraints(self):
"""Test that minItems/maxItems are removed from array schemas."""
schema = {
"type": "array",
"items": {"type": "string"},
"minItems": 1,
"maxItems": 10
}
result = AnthropicConfig.filter_anthropic_output_schema(schema)
assert "minItems" not in result
assert "maxItems" not in result
assert result["type"] == "array"
assert result["items"] == {"type": "string"}
def test_handles_nested_schemas(self):
"""Test that nested schemas are also filtered."""
schema = {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"quantity": {
"type": "integer",
"minimum": 1,
"maximum": 100
}
}
},
"minItems": 1
}
}
}
result = AnthropicConfig.filter_anthropic_output_schema(schema)
# Top level array constraint removed
assert "minItems" not in result["properties"]["items"]
# Nested numeric constraints removed
nested_props = result["properties"]["items"]["items"]["properties"]
assert "minimum" not in nested_props["quantity"]
assert "maximum" not in nested_props["quantity"]
def test_handles_anyof_oneof_allof(self):
"""Test that anyOf/oneOf/allOf schemas are filtered recursively."""
schema = {
"anyOf": [
{"type": "integer", "minimum": 0},
{"type": "string", "minLength": 1}
]
}
result = AnthropicConfig.filter_anthropic_output_schema(schema)
assert "minimum" not in result["anyOf"][0]
assert "minLength" not in result["anyOf"][1]
def test_preserves_valid_fields(self):
"""Test that valid schema fields are preserved."""
schema = {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["active", "inactive"],
"description": "Current status"
}
},
"required": ["status"],
"additionalProperties": False
}
result = AnthropicConfig.filter_anthropic_output_schema(schema)
assert result == schema # Should be unchanged