Merge pull request #35987 from BerriAI/litellm_bedrock_mantle_web_search

fix(bedrock_mantle): stop dropping the web_search tool on /v1/responses
This commit is contained in:
Mateo Wang 2026-09-03 10:45:28 -07:00 committed by GitHub
commit 7d6781fe6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 116 additions and 17 deletions

View file

@ -48,7 +48,9 @@ _BASE_SUFFIXES_TO_STRIP: Final = (
)
# Per Bedrock Mantle Responses API validation errors.
_BEDROCK_MANTLE_SUPPORTED_RESPONSE_TOOL_TYPES = frozenset({"function", "mcp", "custom", "namespace", "tool_search"})
_BEDROCK_MANTLE_SUPPORTED_RESPONSE_TOOL_TYPES: Final = frozenset(
{"function", "mcp", "custom", "namespace", "tool_search", "web_search"}
)
_BEDROCK_MANTLE_SUPPORTED_SERVICE_TIERS: Final = frozenset({"auto", "default"})

View file

@ -52933,7 +52933,8 @@
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true
"supports_vision": true,
"supports_web_search": true
},
"bedrock_mantle/openai.gpt-5.6-terra": {
"input_cost_per_token": 2.2e-06,
@ -52966,7 +52967,8 @@
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true
"supports_vision": true,
"supports_web_search": true
},
"bedrock_mantle/openai.gpt-5.6-cyber": {
"input_cost_per_token": 1.375e-05,
@ -53027,7 +53029,8 @@
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true
"supports_vision": true,
"supports_web_search": true
},
"us.openai.gpt-5.6-sol": {
"input_cost_per_token": 4.4e-06,
@ -53213,7 +53216,8 @@
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true
"supports_vision": true,
"supports_web_search": true
},
"bedrock_mantle/openai.gpt-5.4": {
"input_cost_per_token": 2.75e-06,
@ -53243,7 +53247,8 @@
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true
"supports_vision": true,
"supports_web_search": true
},
"bedrock_mantle/google.gemma-4-31b": {
"input_cost_per_token": 1.4e-07,

View file

@ -52933,7 +52933,8 @@
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true
"supports_vision": true,
"supports_web_search": true
},
"bedrock_mantle/openai.gpt-5.6-terra": {
"input_cost_per_token": 2.2e-06,
@ -52966,7 +52967,8 @@
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true
"supports_vision": true,
"supports_web_search": true
},
"bedrock_mantle/openai.gpt-5.6-cyber": {
"input_cost_per_token": 1.375e-05,
@ -53027,7 +53029,8 @@
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true
"supports_vision": true,
"supports_web_search": true
},
"us.openai.gpt-5.6-sol": {
"input_cost_per_token": 4.4e-06,
@ -53213,7 +53216,8 @@
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true
"supports_vision": true,
"supports_web_search": true
},
"bedrock_mantle/openai.gpt-5.4": {
"input_cost_per_token": 2.75e-06,
@ -53243,7 +53247,8 @@
"supports_reasoning": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true
"supports_vision": true,
"supports_web_search": true
},
"bedrock_mantle/google.gemma-4-31b": {
"input_cost_per_token": 1.4e-07,

View file

@ -332,7 +332,7 @@ class TestBedrockMantleResponsesTools:
params = cfg.map_openai_params(
response_api_optional_params={
"tools": [
{"type": "web_search"},
{"type": "file_search", "vector_store_ids": ["vs_123"]},
{"type": "function", "name": "exec_command"},
]
},
@ -344,7 +344,7 @@ class TestBedrockMantleResponsesTools:
def test_map_openai_params_removes_tools_when_all_unsupported(self):
cfg = BedrockMantleResponsesAPIConfig()
params = cfg.map_openai_params(
response_api_optional_params={"tools": [{"type": "web_search"}]},
response_api_optional_params={"tools": [{"type": "file_search", "vector_store_ids": ["vs_123"]}]},
model="openai.gpt-5.5",
drop_params=False,
)
@ -358,12 +358,95 @@ class TestBedrockMantleResponsesTools:
"litellm.llms.bedrock_mantle.responses.transformation.verbose_logger.warning"
) as mock_warning:
cfg.map_openai_params(
response_api_optional_params={"tools": [{"type": "web_search"}]},
response_api_optional_params={"tools": [{"type": "file_search", "vector_store_ids": ["vs_123"]}]},
model="openai.gpt-5.5",
drop_params=False,
)
assert mock_warning.call_count == 1
assert "web_search" in str(mock_warning.call_args)
assert "file_search" in str(mock_warning.call_args)
class TestBedrockMantleResponsesWebSearch:
"""Web Search on Amazon Bedrock is a server-side built-in tool that Mantle runs
itself when the caller passes {"type": "web_search"} on the Responses path, so
the config must forward the tool and its options untouched instead of filtering
it out and returning an ungrounded answer."""
_WEB_SEARCH_TOOL = {"type": "web_search", "external_web_access": False}
def test_web_search_survives_map_openai_params_with_its_options(self):
cfg = BedrockMantleResponsesAPIConfig()
params = cfg.map_openai_params(
response_api_optional_params={"tools": [self._WEB_SEARCH_TOOL]},
model="openai.gpt-5.6-sol",
drop_params=False,
)
assert params["tools"] == [self._WEB_SEARCH_TOOL]
def test_web_search_reaches_outbound_body_alongside_function_tools(self):
cfg = BedrockMantleResponsesAPIConfig()
function_tool = {"type": "function", "name": "exec_command"}
params = cfg.map_openai_params(
response_api_optional_params={"tools": [self._WEB_SEARCH_TOOL, function_tool]},
model="openai.gpt-5.6-sol",
drop_params=False,
)
body = cfg.transform_responses_api_request(
model="openai.gpt-5.6-sol",
input="What did AWS announce today?",
response_api_optional_request_params=params,
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert body["tools"] == [self._WEB_SEARCH_TOOL, function_tool]
def test_web_search_is_not_logged_as_dropped(self, caplog):
cfg = BedrockMantleResponsesAPIConfig()
def drop_warnings() -> list[str]:
return [r.getMessage() for r in caplog.records if "dropping unsupported tool type" in r.getMessage()]
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
cfg.map_openai_params(
response_api_optional_params={"tools": [{"type": "file_search"}]},
model="openai.gpt-5.6-sol",
drop_params=False,
)
assert len(drop_warnings()) == 1
caplog.clear()
cfg.map_openai_params(
response_api_optional_params={"tools": [self._WEB_SEARCH_TOOL]},
model="openai.gpt-5.6-sol",
drop_params=False,
)
assert drop_warnings() == []
def test_hoisted_web_search_tool_survives(self):
cfg = BedrockMantleResponsesAPIConfig()
body = cfg.transform_responses_api_request(
model="openai.gpt-5.6-sol",
input=[
{"type": "additional_tools", "role": "developer", "tools": [self._WEB_SEARCH_TOOL]},
{"type": "message", "role": "user", "content": [{"type": "input_text", "text": "hi"}]},
],
response_api_optional_request_params={},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert body["tools"] == [self._WEB_SEARCH_TOOL]
@pytest.mark.parametrize(
"model",
[
"bedrock_mantle/openai.gpt-5.6-sol",
"bedrock_mantle/openai.gpt-5.6-terra",
"bedrock_mantle/openai.gpt-5.6-luna",
"bedrock_mantle/openai.gpt-5.5",
"bedrock_mantle/openai.gpt-5.4",
],
)
def test_cost_map_advertises_web_search_support(self, model):
assert litellm.supports_web_search(model=model) is True
def _codex_exec_tool():
@ -551,7 +634,7 @@ class TestBedrockMantleCodexAdditionalTools:
"type": "additional_tools",
"role": "developer",
"tools": [
{"type": "web_search"},
{"type": "file_search", "vector_store_ids": ["vs_123"]},
{"type": "function", "name": "wait"},
],
},
@ -563,7 +646,11 @@ class TestBedrockMantleCodexAdditionalTools:
def test_item_stripped_even_when_no_hoisted_tool_survives(self):
body = self._transform(
input=[
{"type": "additional_tools", "role": "developer", "tools": [{"type": "web_search"}]},
{
"type": "additional_tools",
"role": "developer",
"tools": [{"type": "file_search", "vector_store_ids": ["vs_123"]}],
},
self._USER_MESSAGE,
]
)