diff --git a/litellm/google_genai/adapters/transformation.py b/litellm/google_genai/adapters/transformation.py index 7c86ceafd7f..15cbcf02bfc 100644 --- a/litellm/google_genai/adapters/transformation.py +++ b/litellm/google_genai/adapters/transformation.py @@ -47,6 +47,7 @@ class _GenAIPart(TypedDict, total=False): class _GenAIFunctionDeclaration(TypedDict, total=False): name: ReadOnly[str] description: ReadOnly[str] + parameters: ReadOnly[dict[str, object]] parametersJsonSchema: ReadOnly[dict[str, object]] @@ -359,6 +360,8 @@ class GoogleGenAIAdapter: function_chunk["description"] = func_decl["description"] if "parametersJsonSchema" in func_decl: function_chunk["parameters"] = func_decl["parametersJsonSchema"] + elif "parameters" in func_decl: + function_chunk["parameters"] = func_decl["parameters"] openai_tool: dict[str, object] = {"type": "function", "function": function_chunk} openai_tools.append(openai_tool) diff --git a/tests/test_litellm/google_genai/test_google_genai_adapter.py b/tests/test_litellm/google_genai/test_google_genai_adapter.py index f21564546a8..25981dc90c5 100644 --- a/tests/test_litellm/google_genai/test_google_genai_adapter.py +++ b/tests/test_litellm/google_genai/test_google_genai_adapter.py @@ -1276,3 +1276,85 @@ def test_inline_data_backward_compatibility_text_only(): content, str ), "Content should be a string for text-only messages (backward compatibility)" assert content == "Hello, how are you?" + + +def test_tools_transformation_parameters_field(): + """FunctionDeclaration.parameters must survive the generate_content -> completion bridge. + + The google-genai SDKs serialize tool schemas to `parameters`; only gemini-cli + sends `parametersJsonSchema`. Reading just the latter silently dropped the + schema, so the upstream model received a function with no arguments. + """ + from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter + + adapter = GoogleGenAIAdapter() + + schema = { + "type": "OBJECT", + "properties": {"city": {"type": "STRING", "description": "City name"}}, + "required": ["city"], + } + tools = [ + { + "functionDeclarations": [ + { + "name": "get_weather", + "description": "Get the weather for a city", + "parameters": schema, + } + ] + } + ] + + completion_request = adapter.translate_generate_content_to_completion( + model="gpt-3.5-turbo", + contents={"role": "user", "parts": [{"text": "Weather in Hanoi?"}]}, + tools=tools, + ) + + function = completion_request["tools"][0]["function"] + assert function["name"] == "get_weather" + assert function["parameters"] == { + "type": "object", + "properties": {"city": {"type": "string", "description": "City name"}}, + "required": ["city"], + } + + +def test_tools_transformation_prefers_parameters_json_schema(): + """When both fields are present, parametersJsonSchema wins.""" + from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter + + adapter = GoogleGenAIAdapter() + + tools = [ + { + "functionDeclarations": [ + { + "name": "get_weather", + "parameters": { + "type": "OBJECT", + "properties": {"ignored": {"type": "STRING"}}, + }, + "parametersJsonSchema": { + "type": "object", + "properties": {"city": {"type": "string"}}, + "required": ["city"], + }, + } + ] + } + ] + + completion_request = adapter.translate_generate_content_to_completion( + model="gpt-3.5-turbo", + contents={"role": "user", "parts": [{"text": "Weather in Hanoi?"}]}, + tools=tools, + ) + + function = completion_request["tools"][0]["function"] + assert function["parameters"] == { + "type": "object", + "properties": {"city": {"type": "string"}}, + "required": ["city"], + }