From 7871a9d2a3572a73cb17e8804b3ad1f55dadfa21 Mon Sep 17 00:00:00 2001 From: vovanphuc Date: Thu, 20 Aug 2026 09:30:16 +0700 Subject: [PATCH 1/3] fix(google_genai): read functionDeclarations.parameters in the generateContent bridge The generate_content -> chat/completions bridge only read functionDeclarations[].parametersJsonSchema, the field gemini-cli sends. The google-genai SDKs serialize tool schemas to `parameters`, so those requests reached the upstream provider with a function that had a name and description but no arguments, and the model invented its own. Fall back to `parameters` when `parametersJsonSchema` is absent, and declare it on _GenAIFunctionDeclaration. --- .../google_genai/adapters/transformation.py | 3 + .../google_genai/test_google_genai_adapter.py | 82 +++++++++++++++++++ 2 files changed, 85 insertions(+) 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"], + } From 24e8619db5ea4ad426389a8ffcb6d0d46d3e50bf Mon Sep 17 00:00:00 2001 From: vovanphuc Date: Thu, 20 Aug 2026 09:30:16 +0700 Subject: [PATCH 2/3] fix(google_genai): annotate FunctionDeclaration.parameters as a read-only Mapping The new TypedDict field tripped the LIT001 type-discipline budget by adding one more mutable collection annotation. Declare it as Mapping[str, object] instead, which is the read-only view the rule asks for, leaving the budget at its base total. --- litellm/google_genai/adapters/transformation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/google_genai/adapters/transformation.py b/litellm/google_genai/adapters/transformation.py index 15cbcf02bfc..90303f0cf6a 100644 --- a/litellm/google_genai/adapters/transformation.py +++ b/litellm/google_genai/adapters/transformation.py @@ -1,5 +1,5 @@ import json -from collections.abc import AsyncIterator, Iterator, Sequence +from collections.abc import AsyncIterator, Iterator, Mapping, Sequence from typing import Any, Final, TypedDict, cast from typing_extensions import ReadOnly @@ -47,7 +47,7 @@ class _GenAIPart(TypedDict, total=False): class _GenAIFunctionDeclaration(TypedDict, total=False): name: ReadOnly[str] description: ReadOnly[str] - parameters: ReadOnly[dict[str, object]] + parameters: ReadOnly[Mapping[str, object]] parametersJsonSchema: ReadOnly[dict[str, object]] From ba737e1c5fcd328a56ae6daaaf4b9dbbb09c9580 Mon Sep 17 00:00:00 2001 From: vovanphuc Date: Thu, 20 Aug 2026 09:41:18 +0700 Subject: [PATCH 3/3] test(google_genai): drop the docstrings from the new adapter tests The names and assertions already say what each case covers, so the prose only duplicated them. --- .../test_litellm/google_genai/test_google_genai_adapter.py | 7 ------- 1 file changed, 7 deletions(-) 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 25981dc90c5..447f860d0bc 100644 --- a/tests/test_litellm/google_genai/test_google_genai_adapter.py +++ b/tests/test_litellm/google_genai/test_google_genai_adapter.py @@ -1279,12 +1279,6 @@ def test_inline_data_backward_compatibility_text_only(): 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() @@ -1322,7 +1316,6 @@ def test_tools_transformation_parameters_field(): 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()