From 9b2278588776b99e75bab1fec163c3789b0fcb8f Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 13 Oct 2026 09:33:58 -0700 Subject: [PATCH] fix(bedrock): answer 400 for the OpenAI tool_search tool on Converse unless drop_params is set --- litellm/llms/anthropic/chat/transformation.py | 30 ++++++++----- .../bedrock/chat/converse_transformation.py | 14 +++++- .../chat/test_converse_transformation.py | 44 +++++++++++++++++++ 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 6212939ba00..30665420b25 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -327,6 +327,23 @@ DROP_UNSUPPORTED_TOOL_SEARCH_WARNING: Final = ( ) +def tools_without_unsupported_tool_search( + tools: Sequence[object], model: str, route: str, drop_params: bool +) -> Sequence[object]: + if not any(_is_openai_tool_search_tool(tool) for tool in tools): + return tools + if not (litellm.drop_params or drop_params): + raise litellm.utils.UnsupportedParamsError( + message=( + f"{model} on {route} cannot run the OpenAI `tool_search` tool. To drop it " + "and the `defer_loading` of the other tools, set `litellm.drop_params = True`." + ), + status_code=400, + ) + litellm.verbose_logger.warning(DROP_UNSUPPORTED_TOOL_SEARCH_WARNING, model, route) + return [_without_deferral(tool) for tool in tools if not _is_openai_tool_search_tool(tool)] + + class AnthropicConfig(AnthropicModelInfo, BaseConfig): """ Reference: https://docs.anthropic.com/claude/reference/messages_post @@ -1045,16 +1062,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): model, self._resolved_provider ): return [anthropic_tool_search_chat_tool() if _is_openai_tool_search_tool(tool) else tool for tool in tools] - if not (litellm.drop_params or drop_params): - raise litellm.utils.UnsupportedParamsError( - message=( - f"{model} on {self._resolved_provider} cannot run the OpenAI `tool_search` tool. To drop it " - "and the `defer_loading` of the other tools, set `litellm.drop_params = True`." - ), - status_code=400, - ) - litellm.verbose_logger.warning(DROP_UNSUPPORTED_TOOL_SEARCH_WARNING, model, self._resolved_provider) - return [_without_deferral(tool) for tool in tools if not _is_openai_tool_search_tool(tool)] + return tools_without_unsupported_tool_search( + tools=tools, model=model, route=self._resolved_provider, drop_params=drop_params + ) def _map_tools( self, diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 1cdcf0d6e4c..89a8669d2a3 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -49,6 +49,7 @@ from litellm.llms.anthropic.chat.transformation import ( DROP_UNSUPPORTED_OUTPUT_CONFIG_WARNING, REASONING_EFFORT_TO_OUTPUT_CONFIG_EFFORT, AnthropicConfig, + tools_without_unsupported_tool_search, ) from litellm.llms.anthropic.common_utils import AnthropicModelInfo from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException @@ -136,6 +137,7 @@ UNSUPPORTED_BEDROCK_CONVERSE_BETA_PATTERNS: Final = [ _TOOLS_AS_SENT: Final = TypeAdapter(tuple[Mapping[str, object], ...]) +_TOOLS_AS_RECEIVED: Final = TypeAdapter(list[object]) def _tools_the_model_accepts( @@ -1085,7 +1087,17 @@ class AmazonConverseConfig(BaseConfig): optional_params["topP" if param == "top_p" else param] = value if param == "tools" and isinstance(value, list): self._apply_tool_call_transformation( - tools=cast(list[OpenAIChatCompletionToolParam], value), + tools=cast( + list[OpenAIChatCompletionToolParam], + list( + tools_without_unsupported_tool_search( + tools=_TOOLS_AS_RECEIVED.validate_python(value), + model=model, + route="bedrock converse", + drop_params=drop_params, + ) + ), + ), model=model, non_default_params=non_default_params, optional_params=optional_params, diff --git a/tests/unit/llms/bedrock/chat/test_converse_transformation.py b/tests/unit/llms/bedrock/chat/test_converse_transformation.py index 469d06f7205..249acfee7c3 100644 --- a/tests/unit/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/unit/llms/bedrock/chat/test_converse_transformation.py @@ -10145,3 +10145,47 @@ async def test_bedrock_user_file_element_becomes_document_block(sync_mode: bool) assert content[0] == {"text": "Summarize this report"} assert content[1]["document"]["source"] == {"bytes": base64.b64encode(pdf_bytes).decode()} assert content[1]["document"]["format"] == "pdf" + + +def _openai_tool_search_with_deferred_functions() -> list[dict[str, object]]: + return [ + {"type": "tool_search", "execution": "server"}, + { + "type": "function", + "function": {"name": "get_weather", "parameters": {"type": "object", "properties": {}}}, + "defer_loading": True, + }, + { + "type": "function", + "function": { + "name": "get_stock_price", + "parameters": {"type": "object", "properties": {}}, + "defer_loading": True, + }, + }, + ] + + +def test_map_openai_params_answers_400_for_the_openai_tool_search_tool_without_drop_params(monkeypatch): + monkeypatch.setattr(litellm, "drop_params", False) + with pytest.raises(litellm.utils.UnsupportedParamsError) as raised: + AmazonConverseConfig().map_openai_params( + non_default_params={"tools": _openai_tool_search_with_deferred_functions()}, + optional_params={}, + model="us.anthropic.claude-sonnet-5-5", + drop_params=False, + ) + assert raised.value.status_code == 400 + assert "tool_search" in str(raised.value) + + +def test_map_openai_params_drops_the_openai_tool_search_tool_and_every_deferral_under_drop_params(monkeypatch): + monkeypatch.setattr(litellm, "drop_params", False) + mapped: Final = AmazonConverseConfig().map_openai_params( + non_default_params={"tools": _openai_tool_search_with_deferred_functions()}, + optional_params={}, + model="us.anthropic.claude-sonnet-5-5", + drop_params=True, + ) + assert [tool["function"]["name"] for tool in mapped["tools"]] == ["get_weather", "get_stock_price"] + assert all("defer_loading" not in tool and "defer_loading" not in tool["function"] for tool in mapped["tools"])