diff --git a/litellm/llms/openai/responses/guardrail_translation/handler.py b/litellm/llms/openai/responses/guardrail_translation/handler.py index e5cfbd4d28b..8815e1165ca 100644 --- a/litellm/llms/openai/responses/guardrail_translation/handler.py +++ b/litellm/llms/openai/responses/guardrail_translation/handler.py @@ -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 diff --git a/tests/unit/llms/openai/responses/test_openai_responses_guardrail_handler.py b/tests/unit/llms/openai/responses/test_openai_responses_guardrail_handler.py index d7d4c1b3aae..7a9d36f330c 100644 --- a/tests/unit/llms/openai/responses/test_openai_responses_guardrail_handler.py +++ b/tests/unit/llms/openai/responses/test_openai_responses_guardrail_handler.py @@ -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")