[Feat] /chat/completions - allow using OpenAI style tools for web_search with VertexAI/gemini models (#20280)

* test_gemini_openai_web_search_tool_to_google_search

* feat: Handle OpenAI style web search tools
This commit is contained in:
Ishaan Jaff 2026-02-02 19:36:36 -08:00 committed by GitHub
parent c8f9af1758
commit 5cfcf67d7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 129 additions and 0 deletions

View File

@ -478,6 +478,13 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
if "type" in tool and tool["type"] == "computer_use":
computer_use_config = {k: v for k, v in tool.items() if k != "type"}
tool = {VertexToolName.COMPUTER_USE.value: computer_use_config}
# Handle OpenAI-style web_search and web_search_preview tools
# Transform them to Gemini's googleSearch tool
elif "type" in tool and tool["type"] in ("web_search", "web_search_preview"):
verbose_logger.info(
f"Gemini: Transforming OpenAI-style '{tool['type']}' tool to googleSearch"
)
tool = {VertexToolName.GOOGLE_SEARCH.value: {}}
# Handle tools with 'type' field (OpenAI spec compliance) Ignore this field -> https://github.com/BerriAI/litellm/issues/14644#issuecomment-3342061838
elif "type" in tool:
tool = {k: tool[k] for k in tool if k != "type"}

View File

@ -1435,3 +1435,20 @@ def test_gemini_image_size_limit_exceeded():
error_message = str(excinfo.value)
assert "Image size" in error_message
assert "exceeds maximum allowed size" in error_message
@pytest.mark.asyncio
async def test_gemini_openai_web_search_tool_to_google_search():
"""
Test that OpenAI-style web_search tools are transformed to Gemini's googleSearch.
When passing {"type": "web_search"} or {"type": "web_search_preview"} to Gemini,
these should be transformed to googleSearch, not silently ignored.
"""
response = await litellm.acompletion(
model="gemini/gemini-2.5-flash",
messages=[{"role": "user", "content": "What is the capital of France?"}],
tools=[{"type": "web_search"}],
)
print("response: ", response.model_dump_json(indent=4))
assert hasattr(response, "vertex_ai_grounding_metadata")
assert getattr(response, "vertex_ai_grounding_metadata") is not None

View File

@ -2663,6 +2663,111 @@ def test_vertex_ai_single_tool_type_still_works():
assert tools[0]["code_execution"] == {}
def test_vertex_ai_openai_web_search_tool_transformation():
"""
Test that OpenAI-style web_search and web_search_preview tools are transformed to googleSearch.
This fixes the issue where passing OpenAI-style web search tools like:
{"type": "web_search"} or {"type": "web_search_preview"}
would be silently ignored (the request succeeds but grounding is not applied).
The fix transforms these to Gemini's googleSearch tool.
Input:
value=[{"type": "web_search"}]
Expected Output:
tools=[{"googleSearch": {}}]
"""
v = VertexGeminiConfig()
optional_params = {}
# Test web_search transformation
tools = v._map_function(
value=[{"type": "web_search"}],
optional_params=optional_params
)
assert len(tools) == 1, f"Expected 1 Tool object, got {len(tools)}"
assert "googleSearch" in tools[0], f"Expected googleSearch in tool, got {tools[0].keys()}"
assert tools[0]["googleSearch"] == {}, f"Expected empty googleSearch config, got {tools[0]['googleSearch']}"
def test_vertex_ai_openai_web_search_preview_tool_transformation():
"""
Test that OpenAI-style web_search_preview tool is transformed to googleSearch.
Input:
value=[{"type": "web_search_preview"}]
Expected Output:
tools=[{"googleSearch": {}}]
"""
v = VertexGeminiConfig()
optional_params = {}
# Test web_search_preview transformation
tools = v._map_function(
value=[{"type": "web_search_preview"}],
optional_params=optional_params
)
assert len(tools) == 1, f"Expected 1 Tool object, got {len(tools)}"
assert "googleSearch" in tools[0], f"Expected googleSearch in tool, got {tools[0].keys()}"
assert tools[0]["googleSearch"] == {}, f"Expected empty googleSearch config, got {tools[0]['googleSearch']}"
def test_vertex_ai_openai_web_search_with_function_tools():
"""
Test that OpenAI-style web_search tool works alongside function tools.
Input:
value=[
{"type": "web_search"},
{"type": "function", "function": {"name": "get_weather", "description": "Get weather"}},
]
Expected Output:
tools=[
{"googleSearch": {}},
{"function_declarations": [{"name": "get_weather", "description": "Get weather"}]},
]
"""
v = VertexGeminiConfig()
optional_params = {}
tools = v._map_function(
value=[
{"type": "web_search"},
{"type": "function", "function": {"name": "get_weather", "description": "Get weather"}},
],
optional_params=optional_params
)
# Should have 2 separate Tool objects
assert len(tools) == 2, f"Expected 2 Tool objects, got {len(tools)}"
# Find each tool type
search_tool = None
func_tool = None
for tool in tools:
if "googleSearch" in tool:
search_tool = tool
elif "function_declarations" in tool:
func_tool = tool
# Verify both tools are present
assert search_tool is not None, "googleSearch Tool should be present"
assert func_tool is not None, "function_declarations Tool should be present"
# Verify googleSearch is empty config
assert search_tool["googleSearch"] == {}
# Verify function declaration content
assert func_tool["function_declarations"][0]["name"] == "get_weather"
def test_vertex_ai_multiple_function_declarations_grouped():
"""
Test that multiple function declarations are grouped in ONE Tool object.