diff --git a/litellm/llms/gemini/chat/transformation.py b/litellm/llms/gemini/chat/transformation.py index 936919af83d..9ce898fb616 100644 --- a/litellm/llms/gemini/chat/transformation.py +++ b/litellm/llms/gemini/chat/transformation.py @@ -95,6 +95,7 @@ class GoogleAIStudioGeminiConfig(VertexGeminiConfig): "n", "stop", "logprobs", + "top_logprobs", "frequency_penalty", "presence_penalty", "seed", diff --git a/tests/integration/translation/chat_completions/params/__init__.py b/tests/integration/translation/chat_completions/params/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/translation/chat_completions/params/test_chat_completions_params_gemini_logprobs.py b/tests/integration/translation/chat_completions/params/test_chat_completions_params_gemini_logprobs.py new file mode 100644 index 00000000000..e83edc00ac5 --- /dev/null +++ b/tests/integration/translation/chat_completions/params/test_chat_completions_params_gemini_logprobs.py @@ -0,0 +1,30 @@ +from dataclasses import replace +from typing import Final + +import pytest +from integration._support.client import Gateway +from integration._support.provider import SharedProvider +from integration.translation.case import TranslationTestCase +from integration.translation.chat_completions.bases.gemini import GEMINI_3_5_FLASH_TEST_CASE +from integration.translation.params import with_field +from integration.translation.runner import assert_translation + +GEMINI_3_5_FLASH_TOP_LOGPROBS_TEST_CASE: Final = replace( + GEMINI_3_5_FLASH_TEST_CASE, + scenario="logprobs_top_logprobs", + litellm_request={**GEMINI_3_5_FLASH_TEST_CASE.litellm_request, "logprobs": True, "top_logprobs": 3}, + expected_provider_request=with_field( + with_field(GEMINI_3_5_FLASH_TEST_CASE.expected_provider_request, ("generationConfig", "responseLogprobs"), True), + ("generationConfig", "logprobs"), + 3, + ), +) + +CASES: Final = (GEMINI_3_5_FLASH_TOP_LOGPROBS_TEST_CASE,) + + +@pytest.mark.parametrize("case", CASES, ids=lambda case: case.id) +def test_chat_completions_params_gemini_logprobs( + case: TranslationTestCase, gateway: Gateway, provider: SharedProvider +) -> None: + assert_translation(case, gateway, provider) diff --git a/tests/integration/translation/params.py b/tests/integration/translation/params.py new file mode 100644 index 00000000000..30681d3aa71 --- /dev/null +++ b/tests/integration/translation/params.py @@ -0,0 +1,13 @@ +from collections.abc import Mapping, Sequence +from typing import Final + +from pydantic import JsonValue + + +def with_field(mapping: Mapping[str, JsonValue], path: Sequence[str], value: JsonValue) -> Mapping[str, JsonValue]: + """Return a copy of `mapping` with the nested key `path` set to `value`, creating intermediate objects.""" + head: Final = path[0] + if len(path) == 1: + return {**mapping, head: value} + inner: Final = mapping.get(head) + return {**mapping, head: with_field(inner if isinstance(inner, dict) else {}, path[1:], value)} diff --git a/tests/integration/translation/responses/params/__init__.py b/tests/integration/translation/responses/params/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/translation/responses/params/test_responses_params_gemini_logprobs.py b/tests/integration/translation/responses/params/test_responses_params_gemini_logprobs.py new file mode 100644 index 00000000000..0c034e52e71 --- /dev/null +++ b/tests/integration/translation/responses/params/test_responses_params_gemini_logprobs.py @@ -0,0 +1,26 @@ +from dataclasses import replace +from typing import Final + +import pytest +from integration._support.client import Gateway +from integration._support.provider import SharedProvider +from integration.translation.case import TranslationTestCase +from integration.translation.params import with_field +from integration.translation.responses.bases.gemini import GEMINI_3_5_FLASH_TEST_CASE +from integration.translation.runner import assert_translation + +GEMINI_3_5_FLASH_TOP_LOGPROBS_TEST_CASE: Final = replace( + GEMINI_3_5_FLASH_TEST_CASE, + scenario="top_logprobs", + litellm_request={**GEMINI_3_5_FLASH_TEST_CASE.litellm_request, "top_logprobs": 3}, + expected_provider_request=with_field( + GEMINI_3_5_FLASH_TEST_CASE.expected_provider_request, ("generationConfig", "logprobs"), 3 + ), +) + +CASES: Final = (GEMINI_3_5_FLASH_TOP_LOGPROBS_TEST_CASE,) + + +@pytest.mark.parametrize("case", CASES, ids=lambda case: case.id) +def test_responses_params_gemini_logprobs(case: TranslationTestCase, gateway: Gateway, provider: SharedProvider) -> None: + assert_translation(case, gateway, provider) diff --git a/tests/unit/llms/gemini/chat/__init__.py b/tests/unit/llms/gemini/chat/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/llms/gemini/chat/test_transformation.py b/tests/unit/llms/gemini/chat/test_transformation.py new file mode 100644 index 00000000000..f6159649e67 --- /dev/null +++ b/tests/unit/llms/gemini/chat/test_transformation.py @@ -0,0 +1,17 @@ +from litellm.llms.gemini.chat.transformation import GoogleAIStudioGeminiConfig + + +def test_top_logprobs_maps_to_generation_config_logprobs(): + config = GoogleAIStudioGeminiConfig() + + assert "top_logprobs" in config.get_supported_openai_params("gemini-3.5-flash") + + optional_params = config.map_openai_params( + non_default_params={"logprobs": True, "top_logprobs": 3}, + optional_params={}, + model="gemini-3.5-flash", + drop_params=False, + ) + + assert optional_params["responseLogprobs"] is True + assert optional_params["logprobs"] == 3