fix(gemini): accept top_logprobs on Google AI Studio models

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
mateo 2026-10-31 22:10:57 +00:00
parent 6aa546303e
commit 9155c43c01
8 changed files with 87 additions and 0 deletions

View file

@ -95,6 +95,7 @@ class GoogleAIStudioGeminiConfig(VertexGeminiConfig):
"n",
"stop",
"logprobs",
"top_logprobs",
"frequency_penalty",
"presence_penalty",
"seed",

View file

@ -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)

View file

@ -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)}

View file

@ -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)

View file

View file

@ -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