Fix Pydantic numeric constraints failing with Anthropic tool-based structured output

The output_format path (newer models) already filters unsupported JSON
schema constraints via filter_anthropic_output_schema(). However the
tool-based path used by older models (map_response_format_to_anthropic_tool)
was not applying this filter, causing Anthropic to reject schemas with
minimum/maximum/exclusiveMinimum/exclusiveMaximum properties.

Added filter_anthropic_output_schema() call in the tool-based path so
both code paths consistently strip unsupported constraints.

Fixes #21016
This commit is contained in:
Atharva Jaiswal 2026-02-17 11:10:03 +05:30
parent 3561bfb96c
commit 316661ef5a
2 changed files with 140 additions and 1 deletions

View file

@ -800,11 +800,18 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
)
if json_schema is None:
return None
# Filter unsupported constraints (minimum, maximum, etc.) from the schema.
# The output_format path (newer models) already does this via
# map_response_format_to_anthropic_output_format, but the tool-based
# path for older models was missing it. See #21016.
json_schema = self.filter_anthropic_output_schema(json_schema)
"""
When using tools in this way: - https://docs.anthropic.com/en/docs/build-with-claude/tool-use#json-mode
- You usually want to provide a single tool
- You should set tool_choice (see Forcing tool use) to instruct the model to explicitly use that tool
- Remember that the model will pass the input to the tool, so the name of the tool and description should be from the models perspective.
- Remember that the model will pass the input to the tool, so the name of the tool and description should be from the model's perspective.
"""
_tool = self._create_json_tool_call_for_response_format(

View file

@ -174,3 +174,135 @@ class TestAnthropicStructuredOutput:
assert "description" in age_schema
assert "minimum value: 0" in age_schema["description"]
assert "maximum value: 150" in age_schema["description"]
class TestAnthropicToolBasedStructuredOutput:
"""
Test that structured output via the tool-based path (older models) also
filters unsupported JSON schema constraints.
The output_format path (newer models like Sonnet 4.5+) already filters
constraints via filter_anthropic_output_schema(). The tool-based path
for older models was missing this filtering, causing Anthropic API errors
like: "For 'integer' type, properties maximum, minimum are not supported"
Related issue: https://github.com/BerriAI/litellm/issues/21016
"""
def test_numeric_constraints_filtered_in_tool_path(self):
"""
Test that ge/le/gt/lt constraints on numeric fields are stripped
when using the tool-based response_format path (older models).
"""
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
class Rating(BaseModel):
score: int = Field(ge=1, le=10, description="Rating score")
confidence: float = Field(gt=0.0, lt=1.0, description="Confidence")
config = AnthropicConfig()
json_schema = config.get_json_schema_from_pydantic_object(Rating)
response_format = {
"type": "json_schema",
"json_schema": json_schema["json_schema"],
}
tool = config.map_response_format_to_anthropic_tool(
value=response_format, optional_params={}, is_thinking_enabled=False
)
assert tool is not None
tool_schema = tool["input_schema"]
# Numeric constraints should be stripped
score_schema = tool_schema["properties"]["score"]
assert "minimum" not in score_schema
assert "maximum" not in score_schema
confidence_schema = tool_schema["properties"]["confidence"]
assert "exclusiveMinimum" not in confidence_schema
assert "exclusiveMaximum" not in confidence_schema
def test_numeric_constraints_moved_to_description_in_tool_path(self):
"""
Test that stripped numeric constraints are added to the description
in the tool-based path, matching the output_format path behavior.
"""
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
class Rating(BaseModel):
score: int = Field(ge=1, le=10, description="Rating score")
config = AnthropicConfig()
json_schema = config.get_json_schema_from_pydantic_object(Rating)
response_format = {
"type": "json_schema",
"json_schema": json_schema["json_schema"],
}
tool = config.map_response_format_to_anthropic_tool(
value=response_format, optional_params={}, is_thinking_enabled=False
)
assert tool is not None
score_schema = tool["input_schema"]["properties"]["score"]
assert "minimum value: 1" in score_schema["description"]
assert "maximum value: 10" in score_schema["description"]
def test_string_constraints_filtered_in_tool_path(self):
"""
Test that minLength/maxLength constraints on string fields are
stripped in the tool-based path.
"""
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
class UserInput(BaseModel):
name: str = Field(min_length=1, max_length=100, description="User name")
config = AnthropicConfig()
json_schema = config.get_json_schema_from_pydantic_object(UserInput)
response_format = {
"type": "json_schema",
"json_schema": json_schema["json_schema"],
}
tool = config.map_response_format_to_anthropic_tool(
value=response_format, optional_params={}, is_thinking_enabled=False
)
assert tool is not None
name_schema = tool["input_schema"]["properties"]["name"]
assert "minLength" not in name_schema
assert "maxLength" not in name_schema
assert "minimum length: 1" in name_schema["description"]
assert "maximum length: 100" in name_schema["description"]
def test_array_constraints_filtered_in_tool_path(self):
"""
Test that minItems/maxItems constraints on list fields are
stripped in the tool-based path.
"""
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
class ItemList(BaseModel):
items: List[str] = Field(min_length=1, max_length=10, description="Items")
config = AnthropicConfig()
json_schema = config.get_json_schema_from_pydantic_object(ItemList)
response_format = {
"type": "json_schema",
"json_schema": json_schema["json_schema"],
}
tool = config.map_response_format_to_anthropic_tool(
value=response_format, optional_params={}, is_thinking_enabled=False
)
assert tool is not None
items_schema = tool["input_schema"]["properties"]["items"]
assert "minItems" not in items_schema
assert "maxItems" not in items_schema