mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(anthropic): preserve optional Responses tool properties
Generated with AI Co-Authored-By: Claude Code
This commit is contained in:
parent
bb6bb664b1
commit
6dbd305fc9
5 changed files with 85 additions and 1 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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": {}}}]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue