fix(anthropic): enforce type:'object' on tool input schemas

Anthropic's API requires all tool input_schema to have type:'object'
at the root level. When OpenAI-format tools have parameters with a
missing or non-'object' type field (common with MCP tool servers),
the schema was passed through unchanged, causing Anthropic to reject
with: 'tools.N.custom.input_schema.type: Input should be object'.

The existing default handles the case where parameters is entirely
missing, but does not normalize schemas that ARE provided with a
wrong or absent type field.

Fix: After extracting _input_schema in _map_tool_helper(), ensure
type is set to 'object' and properties exists. This matches the
normalization already done implicitly by the Bedrock handler.

Added 4 unit tests covering: missing type, wrong type, valid schema
(no-op), and entirely missing parameters.

Related issues: #12020, #64, #1671
This commit is contained in:
netbrah 2026-03-08 07:31:13 -04:00 • committed by Palanisamy, Dinesh
parent 160e2d9642
commit 78159212d9
2 changed files with 120 additions and 0 deletions

View file

@ -395,6 +395,14 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
},
)
# Anthropic requires input_schema.type to be "object". Normalize
# schemas from external sources (MCP servers, OpenAI callers) that
# may omit the type field or use a non-object type.
if _input_schema.get("type") != "object":
_input_schema["type"] = "object"
if "properties" not in _input_schema:
_input_schema["properties"] = {}
_allowed_properties = set(AnthropicInputSchema.__annotations__.keys())
input_schema_filtered = {
k: v for k, v in _input_schema.items() if k in _allowed_properties

View file

@ -3175,3 +3175,115 @@ def test_map_openai_params_max_tokens_normalized_to_int():
assert "max_tokens" in result
assert result["max_tokens"] == 1
# ========================================================================
# Tool schema normalization tests
# ========================================================================
def test_map_tool_helper_enforces_object_type_when_missing():
"""
Anthropic requires input_schema.type to be "object". When an OpenAI tool
has parameters without a 'type' field (common with MCP servers), LiteLLM
should inject type:"object" before forwarding to Anthropic.
Without this fix, Anthropic rejects with:
tools.N.custom.input_schema.type: Input should be 'object'
"""
config = AnthropicConfig()
# Tool with parameters that has properties but no 'type' field
tool = {
"type": "function",
"function": {
"name": "search_code",
"description": "Search for code patterns",
"parameters": {
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"],
},
},
}
result, _ = config._map_tool_helper(tool)
assert result is not None
assert result["input_schema"]["type"] == "object"
assert "properties" in result["input_schema"]
assert "query" in result["input_schema"]["properties"]
def test_map_tool_helper_enforces_object_type_when_wrong_type():
"""
If a tool schema has type:"string" or type:"array" at the root level,
LiteLLM should normalize it to type:"object" for Anthropic compatibility.
"""
config = AnthropicConfig()
tool = {
"type": "function",
"function": {
"name": "echo",
"description": "Echo input",
"parameters": {
"type": "string",
"description": "The input to echo",
},
},
}
result, _ = config._map_tool_helper(tool)
assert result is not None
assert result["input_schema"]["type"] == "object"
def test_map_tool_helper_preserves_valid_object_schema():
"""
When a tool schema already has type:"object", it should be preserved
without modification.
"""
config = AnthropicConfig()
tool = {
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
},
"required": ["city"],
},
},
}
result, _ = config._map_tool_helper(tool)
assert result is not None
assert result["input_schema"]["type"] == "object"
assert "city" in result["input_schema"]["properties"]
assert result["input_schema"]["required"] == ["city"]
def test_map_tool_helper_empty_parameters_get_default():
"""
When parameters is entirely missing, the existing default should still
produce a valid {type:"object", properties:{}} schema.
"""
config = AnthropicConfig()
tool = {
"type": "function",
"function": {
"name": "no_params_tool",
"description": "Tool with no parameters",
},
}
result, _ = config._map_tool_helper(tool)
assert result is not None
assert result["input_schema"]["type"] == "object"
assert result["input_schema"].get("properties") == {}