fix(guardrails): keep OpenAI tool_search out of the Responses guardrail tool scan

The scan translated tool_search into Anthropic's regex search tool on every
route, so a guardrail returning only function tools removed tool_search from
native OpenAI requests and OpenAI answered 400 for the deferred tools
This commit is contained in:
mateo-berri 2026-10-13 23:04:35 -07:00
parent 063a87296f
commit d9da2df0d3
2 changed files with 25 additions and 1 deletions

View file

@ -64,6 +64,7 @@ from litellm.llms.base_llm.guardrail_translation.utils import (
)
from litellm.llms.openai.responses.guardrail_translation.tool_merge import merge_guardrailed_tools
from litellm.responses.litellm_completion_transformation.custom_tools import without_malformed_tool_flags
from litellm.responses.litellm_completion_transformation.tool_search import is_server_tool_search_tool
from litellm.responses.litellm_completion_transformation.transformation import (
LiteLLMCompletionResponsesConfig,
)
@ -570,7 +571,12 @@ class OpenAIResponsesHandler(BaseTranslation):
)
scanned_tools: Final = tuple(without_malformed_tool_flags(tool) for tool in original_tools)
flattened_tool_groups: Final = tuple(
form.chat_tools for form in LiteLLMCompletionResponsesConfig.responses_tools_to_chat_forms(scanned_tools)
() if is_server_tool_search_tool(tool) else form.chat_tools
for tool, form in zip(
scanned_tools,
LiteLLMCompletionResponsesConfig.responses_tools_to_chat_forms(scanned_tools),
strict=True,
)
)
extracted: Final = self._extract_guardrail_inputs(
data, input_data, flattened_tool_groups, skip_system=skip_system

View file

@ -3216,6 +3216,24 @@ class TestOpenAIResponsesHandlerNamespaceTools:
"apply_patch",
]
@pytest.mark.asyncio
async def test_a_function_only_guardrail_keeps_the_tool_search_tool_it_never_scans(self):
data: Final = _codex_request("hi")
data["tools"] = [{"type": "tool_search"}, {**data["tools"][0], "defer_loading": True}]
expected_tools: Final = copy.deepcopy(data["tools"])
scanned: Final[list[list[dict]]] = []
def keep_functions(tools):
scanned.append(copy.deepcopy(tools))
return [tool for tool in tools if tool["type"] == "function"]
result: Final = await OpenAIResponsesHandler().process_input_messages(
data, ToolEditingGuardrail(edit=keep_functions, guardrail_name="test")
)
assert result["tools"] == expected_tools
assert [tool["type"] for tool in scanned[0]] == ["function"]
@pytest.mark.asyncio
async def test_appending_guardrail_keeps_namespace_and_adds_tool(self):
data = _codex_request("hi")