diff --git a/litellm/llms/gemini/chat/transformation.py b/litellm/llms/gemini/chat/transformation.py index dc65c46455..743ad73cf3 100644 --- a/litellm/llms/gemini/chat/transformation.py +++ b/litellm/llms/gemini/chat/transformation.py @@ -83,6 +83,7 @@ class GoogleAIStudioGeminiConfig(VertexGeminiConfig): "logprobs", "frequency_penalty", "modalities", + "parallel_tool_calls", ] if supports_reasoning(model): supported_params.append("reasoning_effort") 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 902f825724..b347d775ed 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 @@ -219,6 +219,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): "logprobs", "top_logprobs", "modalities", + "parallel_tool_calls", ] if supports_reasoning(model): supported_params.append("reasoning_effort") @@ -463,6 +464,19 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): ) if _tool_choice_value is not None: optional_params["tool_choice"] = _tool_choice_value + elif param == "parallel_tool_calls": + if value is False: + tools = non_default_params.get("tools", non_default_params.get("functions")) + num_function_declarations = len(tools) if isinstance(tools, list) else 0 + if num_function_declarations > 1: + raise litellm.utils.UnsupportedParamsError( + message=( + "`parallel_tool_calls=False` is not supported when multiple tools are " + "provided for Gemini. Specify a single tool, or set " + "`parallel_tool_calls=True`." + ), + status_code=400, + ) elif param == "seed": optional_params["seed"] = value elif param == "reasoning_effort" and isinstance(value, str): diff --git a/tests/llm_translation/test_vertex.py b/tests/litellm/llms/vertex_ai/test_vertex.py similarity index 96% rename from tests/llm_translation/test_vertex.py rename to tests/litellm/llms/vertex_ai/test_vertex.py index 1ef8c1513c..05930769f4 100644 --- a/tests/llm_translation/test_vertex.py +++ b/tests/litellm/llms/vertex_ai/test_vertex.py @@ -12,8 +12,7 @@ import litellm.litellm_core_utils.prompt_templates import litellm.litellm_core_utils.prompt_templates.factory load_dotenv() -import io -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import MagicMock sys.path.insert( 0, os.path.abspath("../..") @@ -21,10 +20,8 @@ sys.path.insert( import pytest import litellm from litellm import get_optional_params -from litellm.llms.custom_httpx.http_handler import HTTPHandler from litellm.llms.vertex_ai.gemini.transformation import _process_gemini_image -from litellm.types.llms.vertex_ai import PartType, BlobType -import httpx +from litellm.types.llms.vertex_ai import BlobType def encode_image_to_base64(image_path): @@ -1146,7 +1143,7 @@ def test_process_gemini_image(): from litellm.llms.vertex_ai.gemini.transformation import ( _process_gemini_image, ) - from litellm.types.llms.vertex_ai import PartType, FileDataType, BlobType + from litellm.types.llms.vertex_ai import FileDataType # Test GCS URI gcs_result = _process_gemini_image("gs://bucket/image.png") @@ -1271,10 +1268,6 @@ def test_vertex_embedding_url(model, expected_url): import pytest from unittest.mock import Mock, patch -from typing import Dict, Any - -# Import your actual module here -# from your_module import _process_gemini_image, PartType, FileDataType, BlobType # Add these fixtures below existing fixtures @@ -1409,3 +1402,64 @@ def test_aaavertex_embeddings_distances( text_embedding = text_response.data[0].embedding +def test_vertex_parallel_tool_calls_true(): + """ + Test that parallel_tool_calls = True sets the correct tool_config. + """ + tools = [ + {"type": "function", "function": {"name": "get_weather"}}, + {"type": "function", "function": {"name": "get_time"}}, + ] + optional_params = get_optional_params( + model="gemini-1.5-pro", + custom_llm_provider="vertex_ai", + tools=tools, + parallel_tool_calls=True, + ) + assert "tools" in optional_params + + +def test_vertex_parallel_tool_calls_false_multiple_tools_error(): + """ + Test that parallel_tool_calls = False with multiple tools raises UnsupportedParamsError + when drop_params is False. + """ + tools = [ + {"type": "function", "function": {"name": "get_weather"}}, + {"type": "function", "function": {"name": "get_time"}}, + ] + with pytest.raises(litellm.utils.UnsupportedParamsError) as excinfo: + get_optional_params( + model="gemini-1.5-pro", + custom_llm_provider="vertex_ai", + tools=tools, + parallel_tool_calls=False, + ) + assert "`parallel_tool_calls=False` is not supported when multiple tools are provided" in str(excinfo.value) + + # works when specified as "functions" + with pytest.raises(litellm.utils.UnsupportedParamsError) as excinfo: + get_optional_params( + model="gemini-1.5-pro", + custom_llm_provider="vertex_ai", + functions=tools, + parallel_tool_calls=False, + ) + assert "`parallel_tool_calls=False` is not supported when multiple tools are provided" in str(excinfo.value) + + +def test_vertex_parallel_tool_calls_false_single_tool(): + """ + Test that parallel_tool_calls = False with a single tool does not raise an error + and does not add 'tool_config' if not otherwise specified. + """ + tools = [ + {"type": "function", "function": {"name": "get_weather"}}, + ] + optional_params = get_optional_params( + model="gemini-1.5-pro", + custom_llm_provider="vertex_ai", + tools=tools, + parallel_tool_calls=False, + ) + assert "tools" in optional_params