mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
Restore native provider skip for upstream compatibility
Providers with native Anthropic Messages support (anthropic, bedrock, vertex_ai) skip the short-circuit and let their API handle web_search_20250305 natively. Only non-native providers (github_copilot, etc.) get the synthetic native-format response. This preserves upstream behavior: Anthropic's API handles web search server-side for providers that support it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0379a0fe9b
commit
05ddb2eeb5
8 changed files with 74 additions and 35 deletions
|
|
@ -81,13 +81,15 @@ class WebSearchInterceptionLogger(CustomLogger):
|
|||
Short-circuit web-search-only requests by executing the search directly.
|
||||
|
||||
Claude Code sends web search as a separate, standalone /v1/messages
|
||||
request with a simple prompt and only web_search tool(s). We execute
|
||||
the search via the configured provider (SearXNG/Tavily/Perplexity)
|
||||
and return a synthetic response in native Anthropic format
|
||||
(server_tool_use + web_search_tool_result) so Claude Code's
|
||||
WebSearchTool parser works correctly.
|
||||
request with a simple prompt and only web_search tool(s). For providers
|
||||
that don't natively support web search, we execute the search via the
|
||||
configured provider (SearXNG/Tavily/Perplexity) and return a synthetic
|
||||
response in native Anthropic format (server_tool_use +
|
||||
web_search_tool_result) so Claude Code's WebSearchTool parser works.
|
||||
|
||||
All providers are handled uniformly through this single funnel.
|
||||
Providers with native Anthropic Messages support (anthropic, bedrock,
|
||||
vertex_ai, azure_ai) are skipped — their API handles web search
|
||||
natively and returns the correct format already.
|
||||
|
||||
Args:
|
||||
model: Model name from the request
|
||||
|
|
@ -110,6 +112,25 @@ class WebSearchInterceptionLogger(CustomLogger):
|
|||
):
|
||||
return None
|
||||
|
||||
# Skip providers with native Anthropic Messages support — their API
|
||||
# handles web_search_20250305 natively, returning server_tool_use +
|
||||
# web_search_tool_result in the correct format already.
|
||||
try:
|
||||
provider_enum = LlmProviders(provider_str)
|
||||
anthropic_config = (
|
||||
ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
model=model, provider=provider_enum
|
||||
)
|
||||
)
|
||||
if anthropic_config is not None:
|
||||
verbose_logger.debug(
|
||||
f"WebSearchInterception: Skipping short-circuit for {provider_str} "
|
||||
"(provider has native web search support)"
|
||||
)
|
||||
return None
|
||||
except (ValueError, Exception):
|
||||
pass # unknown provider enum → safe to short-circuit
|
||||
|
||||
# All tools must be web search tools
|
||||
if not all(is_web_search_tool(t) for t in tools):
|
||||
return None
|
||||
|
|
|
|||
1
litellm/llms/kimi_code/__init__.py
Normal file
1
litellm/llms/kimi_code/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
1
litellm/llms/kimi_code/chat/__init__.py
Normal file
1
litellm/llms/kimi_code/chat/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
0
litellm/llms/local/__init__.py
Normal file
0
litellm/llms/local/__init__.py
Normal file
0
litellm/llms/local/embedding/__init__.py
Normal file
0
litellm/llms/local/embedding/__init__.py
Normal file
0
litellm/llms/local/transcription/__init__.py
Normal file
0
litellm/llms/local/transcription/__init__.py
Normal file
0
litellm/llms/qwen_portal/__init__.py
Normal file
0
litellm/llms/qwen_portal/__init__.py
Normal file
|
|
@ -62,7 +62,7 @@ class TestTryShortCircuitSearch:
|
|||
@pytest.mark.asyncio
|
||||
async def test_native_format_search_hits(self):
|
||||
"""Search results are structured as web_search_result hits"""
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=["anthropic"])
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=["github_copilot"])
|
||||
|
||||
with patch.object(
|
||||
logger, "_execute_search", new_callable=AsyncMock
|
||||
|
|
@ -73,10 +73,10 @@ class TestTryShortCircuitSearch:
|
|||
)
|
||||
|
||||
result = await logger.try_short_circuit_search(
|
||||
model="anthropic/claude-sonnet-4",
|
||||
model="github_copilot/claude-sonnet-4",
|
||||
messages=[{"role": "user", "content": "Search query"}],
|
||||
tools=[{"type": "web_search_20250305", "name": "web_search"}],
|
||||
custom_llm_provider="anthropic",
|
||||
custom_llm_provider="github_copilot",
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
|
|
@ -90,7 +90,7 @@ class TestTryShortCircuitSearch:
|
|||
@pytest.mark.asyncio
|
||||
async def test_server_tool_use_has_query(self):
|
||||
"""server_tool_use block contains the original search query"""
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=["anthropic"])
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=["github_copilot"])
|
||||
|
||||
with patch.object(
|
||||
logger, "_execute_search", new_callable=AsyncMock
|
||||
|
|
@ -98,10 +98,10 @@ class TestTryShortCircuitSearch:
|
|||
mock_search.return_value = "Title: R\nURL: https://x.com\nSnippet: s"
|
||||
|
||||
result = await logger.try_short_circuit_search(
|
||||
model="anthropic/claude-sonnet-4",
|
||||
model="github_copilot/claude-sonnet-4",
|
||||
messages=[{"role": "user", "content": "trending AI topics"}],
|
||||
tools=[{"type": "web_search_20250305", "name": "web_search"}],
|
||||
custom_llm_provider="anthropic",
|
||||
custom_llm_provider="github_copilot",
|
||||
)
|
||||
|
||||
stu = result["content"][0]
|
||||
|
|
@ -113,7 +113,7 @@ class TestTryShortCircuitSearch:
|
|||
@pytest.mark.asyncio
|
||||
async def test_tool_use_id_links_blocks(self):
|
||||
"""server_tool_use.id matches web_search_tool_result.tool_use_id"""
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=["anthropic"])
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=["github_copilot"])
|
||||
|
||||
with patch.object(
|
||||
logger, "_execute_search", new_callable=AsyncMock
|
||||
|
|
@ -121,10 +121,10 @@ class TestTryShortCircuitSearch:
|
|||
mock_search.return_value = "Title: R\nURL: https://x.com\nSnippet: s"
|
||||
|
||||
result = await logger.try_short_circuit_search(
|
||||
model="anthropic/claude-sonnet-4",
|
||||
model="github_copilot/claude-sonnet-4",
|
||||
messages=[{"role": "user", "content": "query"}],
|
||||
tools=[{"type": "web_search_20250305", "name": "web_search"}],
|
||||
custom_llm_provider="anthropic",
|
||||
custom_llm_provider="github_copilot",
|
||||
)
|
||||
|
||||
assert result["content"][0]["id"] == result["content"][1]["tool_use_id"]
|
||||
|
|
@ -132,7 +132,7 @@ class TestTryShortCircuitSearch:
|
|||
@pytest.mark.asyncio
|
||||
async def test_usage_includes_web_search_requests(self):
|
||||
"""Usage includes server_tool_use.web_search_requests count"""
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=["anthropic"])
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=["github_copilot"])
|
||||
|
||||
with patch.object(
|
||||
logger, "_execute_search", new_callable=AsyncMock
|
||||
|
|
@ -140,10 +140,10 @@ class TestTryShortCircuitSearch:
|
|||
mock_search.return_value = "Title: R\nURL: https://x.com\nSnippet: s"
|
||||
|
||||
result = await logger.try_short_circuit_search(
|
||||
model="anthropic/claude-sonnet-4",
|
||||
model="github_copilot/claude-sonnet-4",
|
||||
messages=[{"role": "user", "content": "query"}],
|
||||
tools=[{"type": "web_search_20250305", "name": "web_search"}],
|
||||
custom_llm_provider="anthropic",
|
||||
custom_llm_provider="github_copilot",
|
||||
)
|
||||
|
||||
assert result["usage"]["server_tool_use"]["web_search_requests"] == 1
|
||||
|
|
@ -210,26 +210,42 @@ class TestTryShortCircuitSearch:
|
|||
assert result is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_short_circuits_all_enabled_providers_uniformly(self):
|
||||
"""All enabled providers (including anthropic, bedrock) go through
|
||||
the same short-circuit funnel — no provider is skipped."""
|
||||
for provider in ["anthropic", "bedrock", "github_copilot"]:
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=[provider])
|
||||
async def test_does_not_short_circuit_native_providers(self):
|
||||
"""Providers with native Anthropic Messages support (anthropic, bedrock,
|
||||
vertex_ai) are skipped — their API handles web search natively."""
|
||||
for provider in ["anthropic", "bedrock"]:
|
||||
logger = WebSearchInterceptionLogger(
|
||||
enabled_providers=[provider, "github_copilot"]
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
logger, "_execute_search", new_callable=AsyncMock
|
||||
) as mock_search:
|
||||
mock_search.return_value = "Title: R\nURL: https://x.com\nSnippet: s"
|
||||
result = await logger.try_short_circuit_search(
|
||||
model=f"{provider}/claude-sonnet-4",
|
||||
messages=[{"role": "user", "content": "search query"}],
|
||||
tools=[{"type": "web_search_20250305", "name": "web_search"}],
|
||||
custom_llm_provider=provider,
|
||||
)
|
||||
|
||||
result = await logger.try_short_circuit_search(
|
||||
model=f"{provider}/claude-sonnet-4",
|
||||
messages=[{"role": "user", "content": "search query"}],
|
||||
tools=[{"type": "web_search_20250305", "name": "web_search"}],
|
||||
custom_llm_provider=provider,
|
||||
)
|
||||
assert result is None, f"Short-circuit should NOT fire for native provider {provider}"
|
||||
|
||||
assert result is not None, f"Short-circuit should fire for {provider}"
|
||||
assert result["content"][0]["type"] == "server_tool_use"
|
||||
@pytest.mark.asyncio
|
||||
async def test_short_circuits_non_native_providers(self):
|
||||
"""Non-native providers (github_copilot, etc.) get short-circuited."""
|
||||
logger = WebSearchInterceptionLogger(enabled_providers=["github_copilot"])
|
||||
|
||||
with patch.object(
|
||||
logger, "_execute_search", new_callable=AsyncMock
|
||||
) as mock_search:
|
||||
mock_search.return_value = "Title: R\nURL: https://x.com\nSnippet: s"
|
||||
|
||||
result = await logger.try_short_circuit_search(
|
||||
model="github_copilot/claude-sonnet-4",
|
||||
messages=[{"role": "user", "content": "search query"}],
|
||||
tools=[{"type": "web_search_20250305", "name": "web_search"}],
|
||||
custom_llm_provider="github_copilot",
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result["content"][0]["type"] == "server_tool_use"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_does_not_short_circuit_no_messages(self):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue