Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/my-website/release_notes/v1.82.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import TabItem from '@theme/TabItem';
docker run \
-e STORE_MODEL_IN_DB=True \
-p 4000:4000 \
ghcr.io/berriai/litellm:main-1.82.0-stable
ghcr.io/berriai/litellm:main-v1.82.0-stable
```

</TabItem>
Expand Down
21 changes: 21 additions & 0 deletions litellm/google_genai/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@
#################################################


def _get_tool_config_from_kwargs(kwargs: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Read toolConfig/tool_config without dropping intentionally empty dicts."""
if "toolConfig" in kwargs:
return kwargs["toolConfig"]
if "tool_config" in kwargs:
return kwargs["tool_config"]
return None


class GenerateContentSetupResult(BaseModel):
"""Internal Type - Result of setting up a generate content call"""

Expand Down Expand Up @@ -171,12 +180,14 @@ def setup_generate_content_call(
system_instruction = kwargs.get("systemInstruction") or kwargs.get(
"system_instruction"
)
tool_config = _get_tool_config_from_kwargs(kwargs)
request_body = (
generate_content_provider_config.transform_generate_content_request(
model=model,
contents=contents,
tools=tools,
generate_content_config_dict=generate_content_config_dict,
tool_config=tool_config,
system_instruction=system_instruction,
)
)
Expand Down Expand Up @@ -323,6 +334,7 @@ def generate_content(
system_instruction = kwargs.get("systemInstruction") or kwargs.get(
"system_instruction"
)
tool_config = _get_tool_config_from_kwargs(kwargs)

# Check if we should use the adapter (when provider config is None)
if setup_result.generate_content_provider_config is None:
Expand Down Expand Up @@ -354,6 +366,7 @@ def generate_content(
_is_async=_is_async,
client=kwargs.get("client"),
litellm_metadata=kwargs.get("litellm_metadata", {}),
tool_config=tool_config,
system_instruction=system_instruction,
)

Expand Down Expand Up @@ -414,6 +427,7 @@ async def agenerate_content_stream(
system_instruction = kwargs.get("systemInstruction") or kwargs.get(
"system_instruction"
)
tool_config = _get_tool_config_from_kwargs(kwargs)

# Check if we should use the adapter (when provider config is None)
if setup_result.generate_content_provider_config is None:
Expand Down Expand Up @@ -452,6 +466,7 @@ async def agenerate_content_stream(
client=kwargs.get("client"),
stream=True,
litellm_metadata=kwargs.get("litellm_metadata", {}),
tool_config=tool_config,
system_instruction=system_instruction,
)

Expand Down Expand Up @@ -520,6 +535,10 @@ def generate_content_stream(
)

# Call the handler with streaming enabled (sync version)
system_instruction = kwargs.get("systemInstruction") or kwargs.get(
"system_instruction"
)
tool_config = _get_tool_config_from_kwargs(kwargs)
return base_llm_http_handler.generate_content_handler(
model=setup_result.model,
contents=contents,
Expand All @@ -536,6 +555,8 @@ def generate_content_stream(
client=kwargs.get("client"),
stream=True,
litellm_metadata=kwargs.get("litellm_metadata", {}),
tool_config=tool_config,
system_instruction=system_instruction,
)

except Exception as e:
Expand Down
2 changes: 2 additions & 0 deletions litellm/llms/base_llm/google_genai/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def transform_generate_content_request(
contents: GenerateContentContentListUnionDict,
tools: Optional[ToolConfigDict],
generate_content_config_dict: Dict,
tool_config: Optional[Dict[str, Any]] = None,
system_instruction: Optional[Any] = None,
) -> dict:
"""
Expand All @@ -161,6 +162,7 @@ def transform_generate_content_request(
model: The model name
contents: Input contents
tools: Tools
tool_config: Tool configuration
generate_content_config_dict: Generation config parameters
system_instruction: Optional system instruction

Expand Down
5 changes: 5 additions & 0 deletions litellm/llms/custom_httpx/llm_http_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9329,6 +9329,7 @@ def generate_content_handler(
client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None,
stream: bool = False,
litellm_metadata: Optional[Dict[str, Any]] = None,
tool_config: Optional[Dict[str, Any]] = None,
system_instruction: Optional[Any] = None,
) -> Any:
"""
Expand All @@ -9346,6 +9347,7 @@ def generate_content_handler(
generate_content_provider_config=generate_content_provider_config,
generate_content_config_dict=generate_content_config_dict,
tools=tools,
tool_config=tool_config,
custom_llm_provider=custom_llm_provider,
litellm_params=litellm_params,
logging_obj=logging_obj,
Expand Down Expand Up @@ -9384,6 +9386,7 @@ def generate_content_handler(
model=model,
contents=contents,
tools=tools,
tool_config=tool_config,
generate_content_config_dict=generate_content_config_dict,
system_instruction=system_instruction,
)
Expand Down Expand Up @@ -9456,6 +9459,7 @@ async def async_generate_content_handler(
client: Optional[AsyncHTTPHandler] = None,
stream: bool = False,
litellm_metadata: Optional[Dict[str, Any]] = None,
tool_config: Optional[Dict[str, Any]] = None,
system_instruction: Optional[Any] = None,
) -> Any:
"""
Expand Down Expand Up @@ -9493,6 +9497,7 @@ async def async_generate_content_handler(
model=model,
contents=contents,
tools=tools,
tool_config=tool_config,
generate_content_config_dict=generate_content_config_dict,
system_instruction=system_instruction,
)
Expand Down
3 changes: 3 additions & 0 deletions litellm/llms/gemini/google_genai/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ def transform_generate_content_request(
contents: GenerateContentContentListUnionDict,
tools: Optional[ToolConfigDict],
generate_content_config_dict: Dict,
tool_config: Optional[Dict[str, Any]] = None,
system_instruction: Optional[Any] = None,
) -> dict:
from litellm.types.google_genai.main import (
Expand All @@ -326,6 +327,8 @@ def transform_generate_content_request(

if system_instruction is not None:
request_dict["systemInstruction"] = system_instruction
if tool_config is not None:
request_dict["toolConfig"] = tool_config
return request_dict

def transform_generate_content_response(
Expand Down
6 changes: 5 additions & 1 deletion litellm/llms/vertex_ai/google_genai/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def transform_generate_content_request(
contents: Any,
tools: Optional[Any],
generate_content_config_dict: Dict,
tool_config: Optional[Dict[str, Any]] = None,
system_instruction: Optional[Any] = None,
) -> dict:
"""
Expand All @@ -89,8 +90,11 @@ def transform_generate_content_request(
if tools:
result["tools"] = tools

if tool_config is not None:
result["toolConfig"] = tool_config

# Add systemInstruction if provided
if system_instruction:
if system_instruction is not None:
result["systemInstruction"] = system_instruction

# Handle generationConfig - Vertex AI expects it in the same format
Expand Down
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "litellm"
version = "1.82.1"
version = "1.82.2"
description = "Library to easily interface with LLM API providers"
authors = ["BerriAI"]
license = "MIT"
Expand Down Expand Up @@ -183,7 +183,7 @@ requires = ["poetry-core", "wheel"]
build-backend = "poetry.core.masonry.api"

[tool.commitizen]
version = "1.82.1"
version = "1.82.2"
version_files = [
"pyproject.toml:^version"
]
Expand Down
3 changes: 3 additions & 0 deletions tests/proxy_unit_tests/test_google_gemini_proxy_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ async def test_google_gemini_httpx_request_direct():
],
"role": "user"
},
"toolConfig": {"functionCallingConfig": {"mode": "ANY"}},
"config": { # Note: already transformed from generationConfig
"temperature": 0,
"topP": 1,
Expand Down Expand Up @@ -240,6 +241,7 @@ async def test_google_gemini_httpx_request_direct():
generate_content_provider_config=provider_config,
generate_content_config_dict=sample_payload["config"],
tools=None,
tool_config=sample_payload["toolConfig"],
custom_llm_provider="gemini",
litellm_params=litellm_params,
logging_obj=logging_obj,
Expand All @@ -265,6 +267,7 @@ async def test_google_gemini_httpx_request_direct():
request_data = call_kwargs.get('json')
if request_data:
assert 'contents' in request_data, "Expected 'contents' in request data"
assert request_data["toolConfig"] == sample_payload["toolConfig"]

# The config should be included in the request as generationConfig
if 'generationConfig' in request_data:
Expand Down
53 changes: 35 additions & 18 deletions tests/test_litellm/google_genai/test_google_genai_main.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,20 @@
#!/usr/bin/env python3
"""
Test to verify the Google GenAI generate_content adapter functionality
"""
import json
import os
import sys

import pytest

sys.path.insert(
0, os.path.abspath("../../..")
) # Adds the parent directory to the system path
"""Tests for Google GenAI main entrypoints."""

import json
import os
import sys
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

import litellm
sys.path.insert(0, os.path.abspath("../../.."))


@pytest.mark.asyncio
async def test_agenerate_content_stream():
"""
Test that the agenerate_content_stream function works
"""
from unittest.mock import AsyncMock, patch

from litellm.google_genai.main import (
agenerate_content_stream,
base_llm_http_handler,
Expand All @@ -36,10 +23,40 @@ async def test_agenerate_content_stream():
with patch.object(
base_llm_http_handler, "generate_content_handler", new=AsyncMock()
) as mock_post:
result = await agenerate_content_stream(
await agenerate_content_stream(
model="gemini/gemini-2.0-flash-001",
contents="Hello, world!",
stream=True,
)
mock_post.assert_called_once()
mock_post.call_args.kwargs["stream"] == True
assert mock_post.call_args.kwargs["stream"] is True


def test_generate_content_stream_forwards_system_instruction():
"""Test that generate_content_stream forwards systemInstruction and toolConfig."""
from litellm.google_genai.main import (
base_llm_http_handler,
generate_content_stream,
)

mock_response = MagicMock()
tool_config = {"functionCallingConfig": {"mode": "ANY"}}

with patch.object(
base_llm_http_handler, "generate_content_handler", return_value=mock_response
) as mock_post:
result = generate_content_stream(
model="gemini/gemini-2.0-flash-001",
contents="Hello, world!",
stream=True,
systemInstruction={"parts": [{"text": "You are helpful"}]},
toolConfig=tool_config,
)

assert result is mock_response
mock_post.assert_called_once()
assert mock_post.call_args.kwargs["stream"] is True
assert mock_post.call_args.kwargs["tool_config"] == tool_config
assert mock_post.call_args.kwargs["system_instruction"] == {
"parts": [{"text": "You are helpful"}]
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
import pytest

from litellm.llms.gemini.google_genai.transformation import GoogleGenAIConfig
from litellm.llms.vertex_ai.google_genai.transformation import (
VertexAIGoogleGenAIConfig,
)
from litellm.responses.litellm_completion_transformation.transformation import (
LiteLLMCompletionResponsesConfig,
)
Expand Down Expand Up @@ -173,6 +176,26 @@ def test_map_generate_content_optional_params_response_mime_type():
assert "responseJsonSchema" in result


@pytest.mark.parametrize(
"config_cls",
[GoogleGenAIConfig, VertexAIGoogleGenAIConfig],
)
def test_transform_generate_content_request_preserves_tool_config(config_cls):
config = config_cls()
tool_config = {"functionCallingConfig": {"mode": "ANY"}}

result = config.transform_generate_content_request(
model="gemini-3-flash-preview",
contents=[{"role": "user", "parts": [{"text": "hello"}]}],
tools=[{"functionDeclarations": [{"name": "execute_command"}]}],
tool_config=tool_config,
generate_content_config_dict={"temperature": 1},
system_instruction={"parts": [{"text": "system"}]},
)

assert result["toolConfig"] == tool_config


def test_responses_api_reasoning_dict_format():
"""Test that reasoning parameter with dict format is mapped to reasoning_effort"""
from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams
Expand Down Expand Up @@ -274,6 +297,7 @@ def test_transform_generate_content_request_with_system_instruction():
model="gemini-3-flash-preview",
contents=contents,
tools=None,
tool_config=None,
generate_content_config_dict=generate_content_config_dict,
system_instruction=system_instruction,
)
Expand Down Expand Up @@ -305,6 +329,7 @@ def test_transform_generate_content_request_without_system_instruction():
model="gemini-3-flash-preview",
contents=contents,
tools=None,
tool_config=None,
generate_content_config_dict=generate_content_config_dict,
system_instruction=None,
)
Expand Down Expand Up @@ -356,6 +381,7 @@ def test_transform_generate_content_request_system_instruction_with_tools():
model="gemini-3-flash-preview",
contents=contents,
tools=tools,
tool_config=None,
generate_content_config_dict=generate_content_config_dict,
system_instruction=system_instruction,
)
Expand Down
Loading