mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
test(google_genai): cover the limiter's Google config precedence
Covers an empty config next to a filled generationConfig and both config spellings in one request, for the cap check and the injected cap, and types the new test params as Mapping.
This commit is contained in:
parent
e8423b1515
commit
97ddba2694
3 changed files with 63 additions and 5 deletions
|
|
@ -5,6 +5,7 @@ Test to verify the Google GenAI generate_content adapter functionality
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import unittest
|
import unittest
|
||||||
|
from collections.abc import Mapping
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -273,7 +274,7 @@ def test_list_contents_transformation():
|
||||||
],
|
],
|
||||||
ids=["camelCase", "snake_case"],
|
ids=["camelCase", "snake_case"],
|
||||||
)
|
)
|
||||||
def test_config_parameter_mapping(config: dict[str, object]):
|
def test_config_parameter_mapping(config: Mapping[str, object]):
|
||||||
"""Test that config parameters are correctly mapped"""
|
"""Test that config parameters are correctly mapped"""
|
||||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Test to verify the Google GenAI generate_content adapter functionality
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator, Mapping
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -418,7 +418,7 @@ def _httpx_transport(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||||
|
|
||||||
|
|
||||||
async def _call_entrypoint(entrypoint: str, output_cap: dict[str, dict[str, int]]) -> None:
|
async def _call_entrypoint(entrypoint: str, output_cap: Mapping[str, Mapping[str, int]]) -> None:
|
||||||
from litellm.google_genai.main import (
|
from litellm.google_genai.main import (
|
||||||
agenerate_content,
|
agenerate_content,
|
||||||
agenerate_content_stream,
|
agenerate_content_stream,
|
||||||
|
|
@ -451,7 +451,7 @@ async def _call_entrypoint(entrypoint: str, output_cap: dict[str, dict[str, int]
|
||||||
"entrypoint", ["generate_content", "agenerate_content", "generate_content_stream", "agenerate_content_stream"]
|
"entrypoint", ["generate_content", "agenerate_content", "generate_content_stream", "agenerate_content_stream"]
|
||||||
)
|
)
|
||||||
async def test_adapter_path_sends_the_output_cap_in_every_spelling(
|
async def test_adapter_path_sends_the_output_cap_in_every_spelling(
|
||||||
_httpx_transport: None, entrypoint: str, output_cap: dict[str, dict[str, int]]
|
_httpx_transport: None, entrypoint: str, output_cap: Mapping[str, Mapping[str, int]]
|
||||||
) -> None:
|
) -> None:
|
||||||
with respx.mock() as upstream:
|
with respx.mock() as upstream:
|
||||||
route: Final = upstream.post(f"{_CHAT_UPSTREAM}/chat/completions").mock(side_effect=_chat_reply)
|
route: Final = upstream.post(f"{_CHAT_UPSTREAM}/chat/completions").mock(side_effect=_chat_reply)
|
||||||
|
|
@ -463,7 +463,7 @@ async def test_adapter_path_sends_the_output_cap_in_every_spelling(
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("output_cap", _OUTPUT_CAP_SPELLINGS)
|
@pytest.mark.parametrize("output_cap", _OUTPUT_CAP_SPELLINGS)
|
||||||
async def test_native_gemini_request_carries_the_output_cap_in_every_spelling(
|
async def test_native_gemini_request_carries_the_output_cap_in_every_spelling(
|
||||||
_httpx_transport: None, output_cap: dict[str, dict[str, int]]
|
_httpx_transport: None, output_cap: Mapping[str, Mapping[str, int]]
|
||||||
) -> None:
|
) -> None:
|
||||||
from litellm.google_genai.main import agenerate_content
|
from litellm.google_genai.main import agenerate_content
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1354,6 +1354,63 @@ async def test_project_otpm_injects_google_genai_native_output_cap(
|
||||||
assert "max_tokens" not in data
|
assert "max_tokens" not in data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"configs",
|
||||||
|
[
|
||||||
|
{"config": {}, "generationConfig": {"maxOutputTokens": 100}},
|
||||||
|
{"generationConfig": {"maxOutputTokens": 100}, "generation_config": {"max_output_tokens": 10}},
|
||||||
|
{"config": {"maxOutputTokens": 100}, "generationConfig": {"maxOutputTokens": 10}},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_project_otpm_checks_the_first_non_empty_google_genai_config(rate_limiter, configs):
|
||||||
|
handler, cache = rate_limiter
|
||||||
|
model = "gemini/gemini-3-flash-preview"
|
||||||
|
user_api_key_dict = UserAPIKeyAuth(
|
||||||
|
api_key=hash_token("sk-google-genai-config-precedence"),
|
||||||
|
project_id="project-google-genai-config-precedence",
|
||||||
|
project_metadata={"model_otpm_limit": {model: 50}},
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(Exception, match="Rate limit exceeded for model_per_project_otpm") as exc_info:
|
||||||
|
await handler.async_pre_call_hook(
|
||||||
|
user_api_key_dict=user_api_key_dict,
|
||||||
|
cache=cache,
|
||||||
|
data={"model": model, "contents": [{"role": "user", "parts": [{"text": "Hello"}]}], **configs},
|
||||||
|
call_type="agenerate_content",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert getattr(exc_info.value, "status_code", None) == 429
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_project_otpm_injects_cap_into_the_first_non_empty_google_genai_config(rate_limiter):
|
||||||
|
handler, cache = rate_limiter
|
||||||
|
model = "gemini/gemini-3-flash-preview"
|
||||||
|
user_api_key_dict = UserAPIKeyAuth(
|
||||||
|
api_key=hash_token("sk-google-genai-config-inject-precedence"),
|
||||||
|
project_id="project-google-genai-config-inject-precedence",
|
||||||
|
project_metadata={"model_otpm_limit": {model: 40}},
|
||||||
|
)
|
||||||
|
data: Final = {
|
||||||
|
"model": model,
|
||||||
|
"contents": [{"role": "user", "parts": [{"text": "Hello"}]}],
|
||||||
|
"config": {},
|
||||||
|
"generationConfig": {"temperature": 0},
|
||||||
|
"generation_config": {"max_output_tokens": 5000},
|
||||||
|
}
|
||||||
|
|
||||||
|
await handler.async_pre_call_hook(
|
||||||
|
user_api_key_dict=user_api_key_dict,
|
||||||
|
cache=cache,
|
||||||
|
data=data,
|
||||||
|
call_type="agenerate_content",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert data["generationConfig"] == {"temperature": 0, "maxOutputTokens": 10}
|
||||||
|
assert data["config"] == {}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_project_otpm_over_limit_rolls_back_itpm_reservation(rate_limiter):
|
async def test_project_otpm_over_limit_rolls_back_itpm_reservation(rate_limiter):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue