From 37d885e46aea7cc105622c19fca3c8299b342e54 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 29 Aug 2025 18:33:52 -0700 Subject: [PATCH] [Fix] LiteLLM does not support new web_search tool (Responses API) (#14083) * test_basic_openai_responses_with_websearch * fix: ResponsesAPIResponse * fix: StandardBuiltInToolCostTracking --- .../llm_cost_calc/tool_call_cost_tracking.py | 6 ++++- litellm/types/llms/openai.py | 2 +- .../test_openai_responses_api.py | 25 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/litellm/litellm_core_utils/llm_cost_calc/tool_call_cost_tracking.py b/litellm/litellm_core_utils/llm_cost_calc/tool_call_cost_tracking.py index 75bb699292..21ff44ab08 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/tool_call_cost_tracking.py +++ b/litellm/litellm_core_utils/llm_cost_calc/tool_call_cost_tracking.py @@ -580,7 +580,9 @@ class StandardBuiltInToolCostTracking: return WebSearchOptions(**kwargs.get("web_search_options", {})) tools = StandardBuiltInToolCostTracking._get_tools_from_kwargs( - kwargs, "web_search_preview" + kwargs=kwargs, tool_type="web_search_preview" + ) or StandardBuiltInToolCostTracking._get_tools_from_kwargs( + kwargs=kwargs, tool_type="web_search" ) if tools: # Look for web search tool in the tools array @@ -612,6 +614,8 @@ class StandardBuiltInToolCostTracking: def _is_web_search_tool_call(tool: Dict) -> bool: if tool.get("type", None) == "web_search_preview": return True + if tool.get("type", None) == "web_search": + return True if "search_context_size" in tool: return True return False diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 74ff831503..a0c8e5b629 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -1038,7 +1038,7 @@ class ResponsesAPIResponse(BaseLiteLLMOpenAIResponseObject): parallel_tool_calls: bool temperature: Optional[float] tool_choice: ToolChoice - tools: Union[List[Tool], List[ResponseFunctionToolCall]] + tools: Union[List[Tool], List[ResponseFunctionToolCall], List[Dict[str, Any]]] top_p: Optional[float] max_output_tokens: Optional[int] previous_response_id: Optional[str] diff --git a/tests/llm_responses_api_testing/test_openai_responses_api.py b/tests/llm_responses_api_testing/test_openai_responses_api.py index d7042c4304..b3947b2678 100644 --- a/tests/llm_responses_api_testing/test_openai_responses_api.py +++ b/tests/llm_responses_api_testing/test_openai_responses_api.py @@ -1483,3 +1483,28 @@ async def test_openai_gpt5_reasoning_effort_parameter(): # Validate the response print("Response:", json.dumps(response, indent=4, default=str)) + + + + +@pytest.mark.asyncio +@pytest.mark.parametrize("stream", [True, False]) +async def test_basic_openai_responses_with_websearch(stream): + litellm._turn_on_debug() + request_model = "gpt-4o" + response = await litellm.aresponses( + model=request_model, + stream=stream, + input="hi", + tools=[ + { + "type": "web_search", + "search_context_size": "low" + } + ] + ) + if stream: + async for chunk in response: + print("chunk=", json.dumps(chunk, indent=4, default=str)) + else: + print("response=", json.dumps(response, indent=4, default=str))