fix(anthropic): preserve optional Responses tool properties

Generated with AI

Co-Authored-By: Claude Code
This commit is contained in:
eugene-yao-zocdoc 2026-07-24 18:19:16 -04:00
parent bb6bb664b1
commit 6dbd305fc9
5 changed files with 85 additions and 1 deletions

View file

@ -742,6 +742,7 @@ class LiteLLMAnthropicMessagesAdapter:
"input_schema",
"description",
"cache_control",
"strict",
"type",
]
@ -771,6 +772,8 @@ class LiteLLMAnthropicMessagesAdapter:
function_chunk["parameters"] = tool["input_schema"] # type: ignore
if "description" in tool:
function_chunk["description"] = tool["description"] # type: ignore
if "strict" in tool:
function_chunk["strict"] = tool["strict"]
for k, v in tool.items():
if k not in mapped_tool_params: # pass additional computer kwargs

View file

@ -187,7 +187,11 @@ class LiteLLMAnthropicToResponsesAPIAdapter:
if (isinstance(tool_type, str) and tool_type.startswith("web_search")) or tool_name == "web_search":
result.append({"type": "web_search_preview"})
continue
func_tool: Dict[str, Any] = {"type": "function", "name": tool_name}
func_tool: Dict[str, Any] = {
"type": "function",
"name": tool_name,
"strict": tool_dict.get("strict", False),
}
if "description" in tool_dict:
func_tool["description"] = tool_dict["description"]
if "input_schema" in tool_dict:

View file

@ -47,6 +47,7 @@ class AnthropicMessagesTool(TypedDict, total=False):
name: Required[str]
description: str
input_schema: Optional[AnthropicInputSchema]
strict: bool
type: Literal["custom"]
cache_control: Optional[Union[dict, ChatCompletionCachedContent]]
defer_loading: bool

View file

@ -3147,3 +3147,27 @@ def test_translate_anthropic_tools_to_openai_preserves_parameters_type():
params = new_tools[0]["function"]["parameters"]
assert params["type"] == "object"
assert new_tools[0]["type"] == "function"
def test_translate_anthropic_tools_to_openai_preserves_top_level_strict():
"""Anthropic strict validation must remain a function-level option."""
adapter = LiteLLMAnthropicMessagesAdapter()
tools = [
{
"type": "custom",
"name": "get_weather",
"strict": True,
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": False,
},
}
]
new_tools, _ = adapter.translate_anthropic_tools_to_openai(tools=tools)
function = new_tools[0]["function"]
assert function["strict"] is True
assert "strict" not in function["parameters"]

View file

@ -502,6 +502,7 @@ class TestTranslateToolsToResponsesAPI:
{
"type": "function",
"name": "get_weather",
"strict": False,
"description": "Get current weather for a city.",
"parameters": {
"type": "object",
@ -511,6 +512,57 @@ class TestTranslateToolsToResponsesAPI:
}
]
def test_tool_with_optional_properties_disables_responses_strict_default(self):
"""Responses must not promote optional Anthropic properties to required."""
tools = [
{
"name": "search",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"cursor": {"type": "string"},
},
"required": ["query"],
"additionalProperties": False,
},
}
]
result = _ADAPTER.translate_tools_to_responses_api(tools) # type: ignore[arg-type]
assert result[0]["strict"] is False
assert result[0]["parameters"]["required"] == ["query"]
def test_tool_preserves_explicit_strict_validation(self):
"""Anthropic's explicit strict setting is forwarded to Responses."""
tools = [
{
"name": "search",
"strict": True,
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
"additionalProperties": False,
},
}
]
result = _ADAPTER.translate_tools_to_responses_api(tools) # type: ignore[arg-type]
assert result[0] == {
"type": "function",
"name": "search",
"strict": True,
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
"additionalProperties": False,
},
}
def test_tool_without_description(self):
"""Tool without a description omits the description key."""
tools = [{"name": "ping", "input_schema": {"type": "object", "properties": {}}}]