mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(messages): route to Responses API based on model_info.mode
When a /v1/messages request arrives for a non-native model (no
Anthropic messages config), the handler decides between Responses API
and chat/completions adapter. Previously this was provider-only:
only models under the "openai" provider went to Responses API.
Now also check model_info["mode"] == "responses", so models like
github_copilot GPT (configured with mode: responses in config.yaml)
go directly through messages → responses translation instead of
the two-layer messages → chat/completions → responses bridge.
Also fixes tool translation for web search:
- tool_choice: web_search name maps to {"type": "web_search"}
instead of {"type": "function", "name": "web_search"}
- tools: Anthropic allowed_domains on web_search tools are translated
to Responses API filters.allowed_domains (blocked_domains dropped
as Responses API does not support them)
- Use "web_search" tool type instead of deprecated "web_search_preview"
(only "web_search" supports filters.allowed_domains)
This commit is contained in:
parent
bd753aecf3
commit
0b135c0657
3 changed files with 27 additions and 10 deletions
|
|
@ -51,14 +51,23 @@ from .utils import AnthropicMessagesRequestUtils, mock_response
|
|||
_RESPONSES_API_PROVIDERS = frozenset({"openai"})
|
||||
|
||||
|
||||
def _should_route_to_responses_api(custom_llm_provider: Optional[str]) -> bool:
|
||||
def _should_route_to_responses_api(
|
||||
custom_llm_provider: Optional[str],
|
||||
model_info: Optional[Dict] = None,
|
||||
) -> bool:
|
||||
"""Return True when the provider should use the Responses API path.
|
||||
|
||||
Routes to the Responses API when:
|
||||
- The provider is in _RESPONSES_API_PROVIDERS (e.g. openai), OR
|
||||
- The model has mode=responses in model_info (e.g. github_copilot GPT models)
|
||||
|
||||
Set ``litellm.use_chat_completions_url_for_anthropic_messages = True`` to
|
||||
opt out and route OpenAI/Azure requests through chat/completions instead.
|
||||
opt out and route all requests through chat/completions instead.
|
||||
"""
|
||||
if litellm.use_chat_completions_url_for_anthropic_messages:
|
||||
return False
|
||||
if model_info and model_info.get("mode") == "responses":
|
||||
return True
|
||||
return custom_llm_provider in _RESPONSES_API_PROVIDERS
|
||||
|
||||
|
||||
|
|
@ -558,7 +567,7 @@ def anthropic_messages_handler(
|
|||
custom_llm_provider=custom_llm_provider,
|
||||
**kwargs,
|
||||
)
|
||||
if _should_route_to_responses_api(custom_llm_provider):
|
||||
if _should_route_to_responses_api(custom_llm_provider, kwargs.get("model_info")):
|
||||
return LiteLLMMessagesToResponsesAPIHandler.anthropic_messages_handler(**_shared_kwargs)
|
||||
|
||||
# The in-gateway context_management polyfill runs inside
|
||||
|
|
|
|||
|
|
@ -185,7 +185,11 @@ class LiteLLMAnthropicToResponsesAPIAdapter:
|
|||
tool_name = tool_dict.get("name", "")
|
||||
# web_search tool
|
||||
if (isinstance(tool_type, str) and tool_type.startswith("web_search")) or tool_name == "web_search":
|
||||
result.append({"type": "web_search_preview"})
|
||||
web_tool: Dict[str, Any] = {"type": "web_search"}
|
||||
allowed = tool_dict.get("allowed_domains")
|
||||
if allowed:
|
||||
web_tool["filters"] = {"allowed_domains": allowed}
|
||||
result.append(web_tool)
|
||||
continue
|
||||
func_tool: Dict[str, Any] = {"type": "function", "name": tool_name}
|
||||
if "description" in tool_dict:
|
||||
|
|
@ -204,7 +208,11 @@ class LiteLLMAnthropicToResponsesAPIAdapter:
|
|||
if tc_type == "any":
|
||||
return "required"
|
||||
elif tc_type == "tool":
|
||||
return {"type": "function", "name": tool_choice.get("name", "")}
|
||||
name = tool_choice.get("name", "")
|
||||
# web_search is a hosted tool, not a function
|
||||
if name == "web_search":
|
||||
return {"type": "web_search"}
|
||||
return {"type": "function", "name": name}
|
||||
elif tc_type == "none":
|
||||
return "none"
|
||||
return "auto"
|
||||
|
|
|
|||
|
|
@ -527,16 +527,16 @@ class TestTranslateToolsToResponsesAPI:
|
|||
assert "parameters" not in result[0]
|
||||
|
||||
def test_web_search_tool_by_name(self):
|
||||
"""Tool named 'web_search' maps to web_search_preview."""
|
||||
"""Tool named 'web_search' maps to web_search."""
|
||||
tools = [{"name": "web_search", "type": "custom"}]
|
||||
result = _ADAPTER.translate_tools_to_responses_api(tools) # type: ignore[arg-type]
|
||||
assert result == [{"type": "web_search_preview"}]
|
||||
assert result == [{"type": "web_search"}]
|
||||
|
||||
def test_web_search_tool_by_type_prefix(self):
|
||||
"""Tool with type starting with 'web_search' maps to web_search_preview."""
|
||||
"""Tool with type starting with 'web_search' maps to web_search."""
|
||||
tools = [{"name": "search", "type": "web_search_20250305"}]
|
||||
result = _ADAPTER.translate_tools_to_responses_api(tools) # type: ignore[arg-type]
|
||||
assert result == [{"type": "web_search_preview"}]
|
||||
assert result == [{"type": "web_search"}]
|
||||
|
||||
def test_multiple_tools_order_preserved(self):
|
||||
"""Multiple tools are converted in order."""
|
||||
|
|
@ -548,7 +548,7 @@ class TestTranslateToolsToResponsesAPI:
|
|||
result = _ADAPTER.translate_tools_to_responses_api(tools) # type: ignore[arg-type]
|
||||
assert len(result) == 3
|
||||
assert result[0]["name"] == "tool_a"
|
||||
assert result[1] == {"type": "web_search_preview"}
|
||||
assert result[1] == {"type": "web_search"}
|
||||
assert result[2]["name"] == "tool_b"
|
||||
|
||||
def test_empty_tools_list(self):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue