diff --git a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py index 85fda3a6522..3d60cde0360 100644 --- a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py +++ b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py @@ -77,6 +77,15 @@ class AmazonAnthropicClaudeMessagesConfig( BaseAnthropicMessagesConfig.__init__(self, **kwargs) AmazonInvokeConfig.__init__(self, **kwargs) + def handles_web_search_natively(self) -> bool: + """ + Bedrock does not host Anthropic's ``web_search_20250305`` server tool on + any of its APIs, so forwarding it reaches AWS and is rejected. Reporting + False keeps the web-search interception short-circuit in play, which is + the only way this tool can be served on Bedrock. + """ + return False + def validate_anthropic_messages_environment( self, headers: dict, diff --git a/tests/test_litellm/integrations/websearch_interception/test_websearch_short_circuit.py b/tests/test_litellm/integrations/websearch_interception/test_websearch_short_circuit.py index 7de8892b8fc..d46bdaffe7f 100644 --- a/tests/test_litellm/integrations/websearch_interception/test_websearch_short_circuit.py +++ b/tests/test_litellm/integrations/websearch_interception/test_websearch_short_circuit.py @@ -122,27 +122,41 @@ class TestTryShortCircuitSearch: assert result is None @pytest.mark.asyncio - async def test_does_not_short_circuit_bedrock(self): - """Bedrock has native agentic loop support → NOT short-circuited. + async def test_short_circuits_bedrock(self): + """Bedrock → short-circuit fires. - Providers with a BaseAnthropicMessagesConfig (bedrock, vertex_ai, etc.) - use the agentic loop which includes a follow-up LLM synthesis step. - The short-circuit must not fire for them. + Bedrock does not host Anthropic's web_search_20250305 server tool on any + of its APIs, so forwarding the request reaches AWS and is rejected with a + 400. Short-circuiting is the only way the tool can be served there, so a + regression that makes the bedrock config claim native handling again puts + the deterministic 400 straight back. """ logger = WebSearchInterceptionLogger( enabled_providers=["bedrock", "github_copilot"] ) - result = await logger.try_short_circuit_search( - model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", - messages=[{"role": "user", "content": "Search for something"}], - tools=[ - {"type": "web_search_20250305", "name": "web_search", "max_uses": 8} - ], - custom_llm_provider="bedrock", - ) + with patch.object( + logger, "_execute_search", new_callable=AsyncMock + ) as mock_search: + mock_search.return_value = ( + "Title: Result\nURL: https://example.com\nSnippet: test", + None, + ) - assert result is None + result = await logger.try_short_circuit_search( + model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=[{"role": "user", "content": "Search for something"}], + tools=[ + {"type": "web_search_20250305", "name": "web_search", "max_uses": 8} + ], + custom_llm_provider="bedrock", + ) + + assert result is not None + block_types = [b["type"] for b in result["content"]] + assert "server_tool_use" in block_types + assert "web_search_tool_result" in block_types + mock_search.assert_called_once_with("Search for something") @pytest.mark.asyncio async def test_does_not_short_circuit_no_messages(self): diff --git a/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py b/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py index 76bb11cc26d..0cb9725447d 100644 --- a/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py +++ b/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py @@ -2580,3 +2580,22 @@ def test_replayed_intercepted_search_turn_leaves_no_unsupported_block_for_bedroc assert "server_tool_use" not in serialized assert expected_evidence in serialized assert "Rome was founded in 753 BC." in serialized + + +def test_bedrock_messages_config_does_not_handle_web_search_natively(): + """Bedrock hosts none of Anthropic's ``web_search_*`` server tools, so the + config must report ``handles_web_search_natively() is False``. That is what + keeps the web-search interception short-circuit in play; when it reported + True the tool was forwarded verbatim and AWS rejected the request. The + mantle subclass inherits the same answer, and the direct Anthropic config + must keep reporting True so the fix stays scoped to Bedrock.""" + from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( + AnthropicMessagesConfig, + ) + from litellm.llms.bedrock.messages.mantle_transformation import ( + AmazonMantleMessagesConfig, + ) + + assert AmazonAnthropicClaudeMessagesConfig().handles_web_search_natively() is False + assert AmazonMantleMessagesConfig().handles_web_search_natively() is False + assert AnthropicMessagesConfig().handles_web_search_natively() is True