From f778b43e6067aa1fd316ff04c5175a0570ca0a46 Mon Sep 17 00:00:00 2001 From: onthebed <1136664562@qq.com> Date: Tue, 28 Apr 2026 18:46:06 +0800 Subject: [PATCH] fix(vertex): drop mixed search tools with functions --- .../vertex_and_google_ai_studio_gemini.py | 32 +++++++++++++++++ .../test_vertex_web_search_tool_order.py | 35 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/test_litellm/llms/vertex_ai/gemini/test_vertex_web_search_tool_order.py diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index a90e05919fb..a68f5c12f11 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -1223,6 +1223,38 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): if "temperature" not in optional_params: optional_params["temperature"] = 1.0 + # web_search_options can be added before function tools are mapped, so + # strip any pre-existing search tools here as well to keep the request + # valid regardless of parameter order. + tools = optional_params.get("tools") + if ( + isinstance(tools, list) + and any("function_declarations" in tool for tool in tools) + and not optional_params.get("include_server_side_tool_invocations", False) + ): + filtered_tools = [ + tool + for tool in tools + if not any( + search_tool in tool + for search_tool in [ + VertexToolName.GOOGLE_SEARCH.value, + VertexToolName.GOOGLE_SEARCH_RETRIEVAL.value, + VertexToolName.ENTERPRISE_WEB_SEARCH.value, + VertexToolName.URL_CONTEXT.value, + ] + ) + ] + if len(filtered_tools) != len(tools): + verbose_logger.warning( + "Vertex AI does not support mixing function declarations with " + "search tools (googleSearch, enterpriseWebSearch, urlContext, " + "googleSearchRetrieval) in the same request. Dropping search " + "tools and keeping function declarations. To use search tools, " + "send a request without function calling tools." + ) + optional_params["tools"] = filtered_tools + return optional_params def get_mapped_special_auth_params(self) -> dict: diff --git a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_web_search_tool_order.py b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_web_search_tool_order.py new file mode 100644 index 00000000000..1c9d505a6e3 --- /dev/null +++ b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_web_search_tool_order.py @@ -0,0 +1,35 @@ +def test_vertex_ai_web_search_options_dropped_when_function_tools_are_present(): + """Test that search tools are dropped even when web_search_options is mapped first.""" + from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( + VertexGeminiConfig, + ) + + v = VertexGeminiConfig() + + non_default_params = { + "web_search_options": {}, + "tools": [ + { + "type": "function", + "function": { + "name": "notion_search", + "description": "Search Notion", + "parameters": { + "type": "object", + "properties": {"query": {"type": "string"}}, + }, + }, + } + ], + } + + result = v.map_openai_params( + non_default_params=non_default_params, + optional_params={}, + model="gemini-2.5-pro", + drop_params=True, + ) + + assert len(result["tools"]) == 1 + assert "function_declarations" in result["tools"][0] + assert "googleSearch" not in result["tools"][0]