mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(bedrock): explain why Anthropic's web_search server tool is rejected
Bedrock hosts none of Anthropic's web_search server tools, so forwarding one earns an opaque "The provided request is not valid" from AWS that names neither the tool nor a way forward. Every Claude model on Bedrock fails the same way, which reads as a broken deployment rather than an unsupported capability. Reject it in the Bedrock Invoke messages transform with a message naming the tool and the web-search interception settings that do serve it. Interception rewrites the tool into LiteLLM's own search tool before this point, so the guard only fires when it is not configured and the working path is untouched. Resolves LIT-5391
This commit is contained in:
parent
9de3315dad
commit
2f5e4c5ae4
2 changed files with 88 additions and 1 deletions
|
|
@ -1,4 +1,4 @@
|
|||
from collections.abc import AsyncIterator
|
||||
from collections.abc import AsyncIterator, Mapping
|
||||
from typing import TYPE_CHECKING, Any, Final, cast
|
||||
|
||||
import httpx
|
||||
|
|
@ -56,6 +56,9 @@ else:
|
|||
LiteLLMLoggingObj = Any
|
||||
|
||||
|
||||
UNSUPPORTED_BEDROCK_SERVER_TOOL_PREFIX: Final = "web_search_"
|
||||
|
||||
|
||||
class AmazonAnthropicClaudeMessagesConfig(
|
||||
AnthropicMessagesConfig,
|
||||
AmazonInvokeConfig,
|
||||
|
|
@ -605,6 +608,39 @@ class AmazonAnthropicClaudeMessagesConfig(
|
|||
normalize_bedrock_opus_output_config_effort(model=model, output_config=clamped)
|
||||
optional_params["reasoning_effort"] = clamped["effort"]
|
||||
|
||||
def _reject_unsupported_server_tools(self, anthropic_messages_request: Mapping[str, object], model: str) -> None:
|
||||
"""Bedrock hosts none of Anthropic's ``web_search_*`` server tools, so
|
||||
forwarding one earns an opaque ``The provided request is not valid`` from
|
||||
AWS that names neither the tool nor a way forward. Web-search
|
||||
interception rewrites the tool before this point, so reaching here means
|
||||
it is not configured for this request.
|
||||
"""
|
||||
tools: Final = anthropic_messages_request.get("tools")
|
||||
if not isinstance(tools, list):
|
||||
return
|
||||
entries: Final = cast(list[Mapping[str, object]], tools) # cast-ok: request body is untyped upstream
|
||||
unsupported: Final = sorted(
|
||||
{
|
||||
tool_type
|
||||
for tool in entries
|
||||
if isinstance(tool_type := tool.get("type"), str)
|
||||
if tool_type.startswith(UNSUPPORTED_BEDROCK_SERVER_TOOL_PREFIX)
|
||||
}
|
||||
)
|
||||
if not unsupported:
|
||||
return
|
||||
raise litellm.BadRequestError(
|
||||
message=(
|
||||
f"Amazon Bedrock does not support the Anthropic server tool(s) {unsupported}. "
|
||||
"Enable LiteLLM's web-search interception to serve them, by adding "
|
||||
'`websearch_interception` to `litellm_settings.callbacks`, listing "bedrock" under '
|
||||
"`litellm_settings.websearch_interception_params.enabled_providers`, and declaring a "
|
||||
"`search_tools` entry. Otherwise remove the tool from the request."
|
||||
),
|
||||
model=model,
|
||||
llm_provider="bedrock",
|
||||
)
|
||||
|
||||
def transform_anthropic_messages_request(
|
||||
self,
|
||||
model: str,
|
||||
|
|
@ -626,6 +662,8 @@ class AmazonAnthropicClaudeMessagesConfig(
|
|||
headers=headers,
|
||||
)
|
||||
self._normalize_system_role_messages(anthropic_messages_request, model=model)
|
||||
request_view: Final = cast(Mapping[str, object], anthropic_messages_request) # cast-ok: body is untyped here
|
||||
self._reject_unsupported_server_tools(request_view, model=model)
|
||||
#########################################################
|
||||
############## BEDROCK Invoke SPECIFIC TRANSFORMATION ###
|
||||
#########################################################
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_tran
|
|||
AmazonAnthropicClaudeMessagesConfig,
|
||||
AmazonAnthropicClaudeMessagesStreamDecoder,
|
||||
)
|
||||
import litellm
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -2580,3 +2582,50 @@ 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_rejects_anthropic_web_search_server_tool():
|
||||
"""Bedrock hosts none of Anthropic's web_search server tools. Forwarding one
|
||||
earns an opaque 'The provided request is not valid' from AWS that names
|
||||
neither the tool nor a way forward, so reject it here with a message that
|
||||
does both."""
|
||||
with pytest.raises(litellm.BadRequestError) as excinfo:
|
||||
AmazonAnthropicClaudeMessagesConfig().transform_anthropic_messages_request(
|
||||
model="us.anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
messages=[{"role": "user", "content": "Anthropic Claude news"}],
|
||||
anthropic_messages_optional_request_params={
|
||||
"max_tokens": 64,
|
||||
"tools": [{"type": "web_search_20250305", "name": "web_search", "max_uses": 3}],
|
||||
},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
message = str(excinfo.value)
|
||||
assert "web_search_20250305" in message
|
||||
assert "websearch_interception" in message
|
||||
|
||||
|
||||
def test_bedrock_messages_allows_the_tool_interception_rewrites_it_into():
|
||||
"""Web-search interception rewrites the native tool into LiteLLM's own search
|
||||
tool before this transform runs, and that rewritten tool carries an
|
||||
input_schema like any custom tool. Rejecting the native type must not touch
|
||||
it, or enabling interception would break the very path that serves search."""
|
||||
body = AmazonAnthropicClaudeMessagesConfig().transform_anthropic_messages_request(
|
||||
model="us.anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
messages=[{"role": "user", "content": "Anthropic Claude news"}],
|
||||
anthropic_messages_optional_request_params={
|
||||
"max_tokens": 64,
|
||||
"tools": [
|
||||
{
|
||||
"name": "litellm_web_search",
|
||||
"description": "Search the web",
|
||||
"input_schema": {"type": "object", "properties": {"query": {"type": "string"}}},
|
||||
}
|
||||
],
|
||||
},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert [tool["name"] for tool in body["tools"]] == ["litellm_web_search"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue