mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
fix(vertex_ai): drop search tools when mixed with function declarations (#23337)
Vertex AI rejects requests containing both search tools (googleSearch, enterpriseWebSearch, urlContext) and function declarations with error: 'Multiple tools are supported only when they are all search tools.' When _merge_tools_from_deployment() combines deployment-level search tools with user-request function tools (e.g. via MCP), the mixed tool list causes a 400 error. This fix detects the conflict in _map_function() and drops search tools, keeping function declarations. Non-search tools like code_execution and computerUse are preserved. Fixes #23337
This commit is contained in:
parent
924418aeea
commit
212b249e38
2 changed files with 159 additions and 38 deletions
|
|
@ -633,6 +633,36 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
|
|||
# per Vertex AI API spec: "A Tool object should contain exactly one type of Tool"
|
||||
_tools_list: List[Tools] = []
|
||||
|
||||
# Vertex AI constraint: multiple Tool objects in a request must ALL be
|
||||
# search tools. Mixing function declarations with search tools in the
|
||||
# same request causes a 400 error:
|
||||
# "Multiple tools are supported only when they are all search tools."
|
||||
# When both are present (e.g. deployment config has search tools and
|
||||
# user request adds function calling tools via MCP), drop search tools
|
||||
# and keep function declarations.
|
||||
# Ref: https://github.com/BerriAI/litellm/issues/23337
|
||||
has_search_tools = any(
|
||||
v is not None
|
||||
for v in [
|
||||
googleSearch,
|
||||
googleSearchRetrieval,
|
||||
enterpriseWebSearch,
|
||||
urlContext,
|
||||
]
|
||||
)
|
||||
if gtool_func_declarations and has_search_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."
|
||||
)
|
||||
googleSearch = None
|
||||
googleSearchRetrieval = None
|
||||
enterpriseWebSearch = None
|
||||
urlContext = None
|
||||
|
||||
# Function declarations can be grouped together in one Tool
|
||||
if gtool_func_declarations:
|
||||
func_tool = Tools()
|
||||
|
|
|
|||
|
|
@ -2678,10 +2678,14 @@ def test_vertex_ai_multiple_tool_types_separate_objects():
|
|||
|
||||
def test_vertex_ai_function_declarations_with_other_tools_separate():
|
||||
"""
|
||||
Test that function declarations and other tool types are in separate Tool objects.
|
||||
Test that when function declarations are mixed with search tools AND
|
||||
non-search tools like code_execution, search tools are dropped but
|
||||
non-search tools are preserved.
|
||||
|
||||
This ensures that when using both function calling AND special tools like
|
||||
google_search or code_execution, they are properly separated per API spec.
|
||||
Vertex AI constraint: "Multiple tools are supported only when they are
|
||||
all search tools." So mixing function declarations with googleSearch
|
||||
would cause a 400 error. code_execution is NOT a search tool, so it
|
||||
is preserved.
|
||||
|
||||
Input:
|
||||
value=[
|
||||
|
|
@ -2693,7 +2697,6 @@ def test_vertex_ai_function_declarations_with_other_tools_separate():
|
|||
Expected Output:
|
||||
tools=[
|
||||
{"function_declarations": [{"name": "get_weather", "description": "Get weather"}]},
|
||||
{"googleSearch": {}},
|
||||
{"code_execution": {}},
|
||||
]
|
||||
"""
|
||||
|
|
@ -2709,32 +2712,24 @@ def test_vertex_ai_function_declarations_with_other_tools_separate():
|
|||
optional_params=optional_params
|
||||
)
|
||||
|
||||
# Should have 3 separate Tool objects
|
||||
assert len(tools) == 3, f"Expected 3 separate Tool objects, got {len(tools)}"
|
||||
# Should have 2 Tool objects: function declarations + code_execution
|
||||
# googleSearch is dropped to avoid Vertex AI 400 error
|
||||
assert len(tools) == 2, f"Expected 2 Tool objects, got {len(tools)}"
|
||||
|
||||
# Find each tool type
|
||||
func_tool = None
|
||||
search_tool = None
|
||||
code_tool = None
|
||||
|
||||
for tool in tools:
|
||||
if "function_declarations" in tool:
|
||||
func_tool = tool
|
||||
elif "googleSearch" in tool:
|
||||
search_tool = tool
|
||||
elif "code_execution" in tool:
|
||||
code_tool = tool
|
||||
|
||||
# Verify all tools are present and separate
|
||||
# Verify function declarations and code_execution are present
|
||||
assert func_tool is not None, "function_declarations Tool should be present"
|
||||
assert search_tool is not None, "googleSearch Tool should be present"
|
||||
assert code_tool is not None, "code_execution Tool should be present"
|
||||
|
||||
# Verify each Tool has exactly one type
|
||||
assert len(func_tool.keys()) == 1, "function_declarations Tool should have only one key"
|
||||
assert len(search_tool.keys()) == 1, "googleSearch Tool should have only one key"
|
||||
assert len(code_tool.keys()) == 1, "code_execution Tool should have only one key"
|
||||
|
||||
# Verify function declaration content
|
||||
assert func_tool["function_declarations"][0]["name"] == "get_weather"
|
||||
|
||||
|
|
@ -2762,6 +2757,116 @@ def test_vertex_ai_single_tool_type_still_works():
|
|||
assert tools[0]["code_execution"] == {}
|
||||
|
||||
|
||||
def test_vertex_ai_mixed_search_and_function_tools_drops_search():
|
||||
"""
|
||||
Test that when both search tools and function declarations are present,
|
||||
search tools are dropped to avoid Vertex AI 400 error:
|
||||
"Multiple tools are supported only when they are all search tools."
|
||||
|
||||
This happens when deployment config has search tools (enterpriseWebSearch,
|
||||
urlContext) and user request adds function calling tools (e.g. via MCP).
|
||||
|
||||
Ref: https://github.com/BerriAI/litellm/issues/23337
|
||||
"""
|
||||
v = VertexGeminiConfig()
|
||||
optional_params = {}
|
||||
|
||||
tools = v._map_function(
|
||||
value=[
|
||||
{"enterpriseWebSearch": {}},
|
||||
{"urlContext": {}},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get weather",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"city": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
optional_params=optional_params,
|
||||
)
|
||||
|
||||
# Should only have function declarations (search tools dropped)
|
||||
assert len(tools) == 1, f"Expected 1 Tool object, got {len(tools)}: {tools}"
|
||||
assert "function_declarations" in tools[0]
|
||||
assert tools[0]["function_declarations"][0]["name"] == "get_weather"
|
||||
|
||||
|
||||
def test_vertex_ai_mixed_google_search_and_function_tools_drops_search():
|
||||
"""
|
||||
Test that googleSearch is also dropped when mixed with function declarations.
|
||||
"""
|
||||
v = VertexGeminiConfig()
|
||||
optional_params = {}
|
||||
|
||||
tools = v._map_function(
|
||||
value=[
|
||||
{"googleSearch": {}},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {"name": "my_func", "description": "A function"},
|
||||
},
|
||||
],
|
||||
optional_params=optional_params,
|
||||
)
|
||||
|
||||
assert len(tools) == 1
|
||||
assert "function_declarations" in tools[0]
|
||||
assert tools[0]["function_declarations"][0]["name"] == "my_func"
|
||||
|
||||
|
||||
def test_vertex_ai_search_tools_only_no_drop():
|
||||
"""
|
||||
Test that search tools are preserved when no function declarations are present.
|
||||
"""
|
||||
v = VertexGeminiConfig()
|
||||
optional_params = {}
|
||||
|
||||
tools = v._map_function(
|
||||
value=[
|
||||
{"enterpriseWebSearch": {}},
|
||||
{"urlContext": {}},
|
||||
],
|
||||
optional_params=optional_params,
|
||||
)
|
||||
|
||||
assert len(tools) == 2
|
||||
tool_keys = [list(t.keys())[0] for t in tools]
|
||||
assert "enterpriseWebSearch" in tool_keys
|
||||
assert "url_context" in tool_keys
|
||||
|
||||
|
||||
def test_vertex_ai_function_tools_with_code_execution_preserved():
|
||||
"""
|
||||
Test that code_execution is NOT dropped when mixed with function declarations.
|
||||
Only search tools should be dropped.
|
||||
"""
|
||||
v = VertexGeminiConfig()
|
||||
optional_params = {}
|
||||
|
||||
tools = v._map_function(
|
||||
value=[
|
||||
{"code_execution": {}},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {"name": "my_func", "description": "A function"},
|
||||
},
|
||||
],
|
||||
optional_params=optional_params,
|
||||
)
|
||||
|
||||
assert len(tools) == 2
|
||||
tool_keys = set()
|
||||
for t in tools:
|
||||
tool_keys.update(t.keys())
|
||||
assert "function_declarations" in tool_keys
|
||||
assert "code_execution" in tool_keys
|
||||
|
||||
|
||||
def test_vertex_ai_openai_web_search_tool_transformation():
|
||||
"""
|
||||
Test that OpenAI-style web_search and web_search_preview tools are transformed to googleSearch.
|
||||
|
|
@ -2818,7 +2923,9 @@ def test_vertex_ai_openai_web_search_preview_tool_transformation():
|
|||
|
||||
def test_vertex_ai_openai_web_search_with_function_tools():
|
||||
"""
|
||||
Test that OpenAI-style web_search tool works alongside function tools.
|
||||
Test that when OpenAI-style web_search tool (transformed to googleSearch)
|
||||
is mixed with function tools, search tools are dropped to avoid Vertex AI
|
||||
400 error: "Multiple tools are supported only when they are all search tools."
|
||||
|
||||
Input:
|
||||
value=[
|
||||
|
|
@ -2828,7 +2935,6 @@ def test_vertex_ai_openai_web_search_with_function_tools():
|
|||
|
||||
Expected Output:
|
||||
tools=[
|
||||
{"googleSearch": {}},
|
||||
{"function_declarations": [{"name": "get_weather", "description": "Get weather"}]},
|
||||
]
|
||||
"""
|
||||
|
|
@ -2843,27 +2949,12 @@ def test_vertex_ai_openai_web_search_with_function_tools():
|
|||
optional_params=optional_params
|
||||
)
|
||||
|
||||
# Should have 2 separate Tool objects
|
||||
assert len(tools) == 2, f"Expected 2 Tool objects, got {len(tools)}"
|
||||
# Should have 1 Tool object: function declarations only
|
||||
# googleSearch (from web_search) is dropped to avoid Vertex AI 400 error
|
||||
assert len(tools) == 1, f"Expected 1 Tool object, 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
|
||||
func_tool = tools[0]
|
||||
assert "function_declarations" in func_tool
|
||||
assert func_tool["function_declarations"][0]["name"] == "get_weather"
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue