mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(anthropic-adapter): pass provider-native and OpenAI-format tools through on /v1/messages
This commit is contained in:
parent
f677292901
commit
f7af44a505
2 changed files with 68 additions and 0 deletions
|
|
@ -18,6 +18,22 @@ TOOL_NAME_PREFIX_LENGTH: Final = OPENAI_MAX_TOOL_NAME_LENGTH - TOOL_NAME_HASH_LE
|
|||
PROVIDERS_PROXYING_AN_UNKNOWN_BACKEND: Final = frozenset({"litellm_proxy"})
|
||||
|
||||
|
||||
_ANTHROPIC_TOOL_SCHEMA_KEYS: Final = frozenset(
|
||||
{"name", "type", "input_schema", "description", "cache_control", "strict"}
|
||||
)
|
||||
|
||||
|
||||
def _is_openai_function_tool(tool: Mapping[str, object]) -> bool:
|
||||
return tool.get("type") == "function" and "function" in tool
|
||||
|
||||
|
||||
def _is_provider_native_tool_dict(tool: Mapping[str, object]) -> bool:
|
||||
if len(tool) != 1:
|
||||
return False
|
||||
key, value = next(iter(tool.items()))
|
||||
return key not in _ANTHROPIC_TOOL_SCHEMA_KEYS and isinstance(value, dict)
|
||||
|
||||
|
||||
def truncate_tool_name(name: str) -> str:
|
||||
"""
|
||||
Truncate tool names that exceed OpenAI's 64-character limit.
|
||||
|
|
@ -770,6 +786,10 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
new_tools.append(tool)
|
||||
continue
|
||||
|
||||
if _is_openai_function_tool(tool) or _is_provider_native_tool_dict(tool):
|
||||
new_tools.append(cast(ChatCompletionToolParam, tool)) # cast-ok: passed through verbatim to provider
|
||||
continue
|
||||
|
||||
raw_name = tool.get("name")
|
||||
if raw_name is None or (isinstance(raw_name, str) and not str(raw_name).strip()):
|
||||
original_name = f"litellm_unnamed_tool_{idx}"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from litellm.litellm_core_utils.prompt_templates.factory import (
|
|||
)
|
||||
from litellm.llms.anthropic.experimental_pass_through.adapters.transformation import (
|
||||
OPENAI_MAX_TOOL_NAME_LENGTH,
|
||||
AnthropicAdapter,
|
||||
LiteLLMAnthropicMessagesAdapter,
|
||||
create_tool_name_mapping,
|
||||
truncate_tool_name,
|
||||
|
|
@ -2307,6 +2308,53 @@ def test_translate_anthropic_tools_to_openai_fills_missing_tool_name():
|
|||
assert result[1]["function"]["name"] == "litellm_unnamed_tool_1"
|
||||
|
||||
|
||||
def test_translate_anthropic_tools_to_openai_passes_provider_native_tool_dicts_through():
|
||||
"""Deployment-level provider-native tools (e.g. Gemini googleMaps) must reach the provider transformation verbatim (LIT-6286)."""
|
||||
tools = [
|
||||
{"googleMaps": {}},
|
||||
{"googleSearch": {}},
|
||||
{
|
||||
"name": "get_weather",
|
||||
"input_schema": {"type": "object", "properties": {"location": {"type": "string"}}},
|
||||
},
|
||||
]
|
||||
adapter = LiteLLMAnthropicMessagesAdapter()
|
||||
result, tool_name_mapping = adapter.translate_anthropic_tools_to_openai(tools=tools, model=None)
|
||||
assert result[0] == {"googleMaps": {}}
|
||||
assert result[1] == {"googleSearch": {}}
|
||||
assert result[2]["function"]["name"] == "get_weather"
|
||||
assert tool_name_mapping == {}
|
||||
|
||||
|
||||
def test_translate_anthropic_tools_to_openai_passes_openai_function_tools_through():
|
||||
"""A tool already in OpenAI function format must pass through unchanged instead of becoming litellm_unnamed_tool_N."""
|
||||
openai_tool = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"parameters": {"type": "object", "properties": {"location": {"type": "string"}}},
|
||||
},
|
||||
}
|
||||
adapter = LiteLLMAnthropicMessagesAdapter()
|
||||
result, _ = adapter.translate_anthropic_tools_to_openai(tools=[openai_tool], model=None)
|
||||
assert result == [openai_tool]
|
||||
|
||||
|
||||
def test_translate_completion_input_params_keeps_provider_native_tools():
|
||||
"""/v1/messages request translation must keep router-merged provider-native tools in kwargs['tools'] (LIT-6286)."""
|
||||
adapter = AnthropicAdapter()
|
||||
translated = adapter.translate_completion_input_params(
|
||||
{
|
||||
"model": "gemini/gemini-2.5-flash",
|
||||
"max_tokens": 1024,
|
||||
"messages": [{"role": "user", "content": "coffee shops near Union Square"}],
|
||||
"tools": [{"googleMaps": {}}],
|
||||
}
|
||||
)
|
||||
assert translated is not None
|
||||
assert translated["tools"] == [{"googleMaps": {}}]
|
||||
|
||||
|
||||
def test_translate_openai_content_to_anthropic_reasoning_content_without_thinking_blocks():
|
||||
"""
|
||||
Test that reasoning_content is converted to thinking block when thinking_blocks is not present.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue