fix(gemini): drop ttl values outside the protobuf Duration range and the explanatory docstrings

This commit is contained in:
mateo-berri 2026-09-19 00:50:37 -07:00
parent 2303379c20
commit 3684e5cbcb
4 changed files with 10 additions and 29 deletions

View file

@ -89,24 +89,18 @@ def extract_ttl_from_cached_messages(messages: list[AllMessageValues]) -> str |
_TTL_PATTERN: Final = re.compile(r"^([0-9]*\.?[0-9]+)([smh])$")
_TTL_UNIT_SECONDS: Final = MappingProxyType({"s": 1, "m": 60, "h": 3600})
_PROTOBUF_DURATION_MAX_SECONDS: Final = 315_576_000_000
def _normalize_ttl_to_seconds(ttl: object) -> str | None:
"""
Gemini's cachedContents API only takes a TTL as "<seconds>s", while Anthropic clients
(Claude Code among them) send the minute and hour units the Anthropic API defines, "5m"
and "1h". Returns the Gemini form for any of the three units, or None for a missing,
non-positive, or unparseable value so the cache falls back to Gemini's default TTL.
"""
if not isinstance(ttl, str):
return None
match: Final = _TTL_PATTERN.match(ttl)
if match is None:
return None
value: Final = float(match.group(1))
if value <= 0:
seconds: Final = round(float(match.group(1)) * _TTL_UNIT_SECONDS[match.group(2)], 9)
if not 0 < seconds <= _PROTOBUF_DURATION_MAX_SECONDS:
return None
seconds: Final = round(value * _TTL_UNIT_SECONDS[match.group(2)], 9)
return f"{seconds:.9f}".rstrip("0").rstrip(".") + "s"

View file

@ -2102,11 +2102,7 @@ def test_should_add_cache_control_for_anthropic_model():
def test_should_not_add_cache_control_for_non_anthropic_model():
"""Should not add cache_control for providers that reject an explicit cache_control field.
OpenAI/Azure do prompt caching implicitly and 400 on an unexpected
cache_control field, so it must not be forwarded to them.
"""
"""Should not add cache_control for non-Anthropic models."""
adapter = LiteLLMAnthropicMessagesAdapter()
cache_control = {"type": "ephemeral"}
@ -2122,13 +2118,6 @@ def test_should_not_add_cache_control_for_non_anthropic_model():
def test_should_add_cache_control_for_gemini_model():
"""Should add cache_control for Gemini / Vertex Gemini targets.
These consume anthropic-style cache_control blocks via the Gemini context
caching path, so /v1/messages requests (e.g. Claude Code) routed to a
Gemini model must keep it. Regression for the adapter dropping the field
before it reaches the Gemini transformation.
"""
adapter = LiteLLMAnthropicMessagesAdapter()
cache_control = {"type": "ephemeral", "ttl": "1h"}
@ -2146,7 +2135,6 @@ def test_should_add_cache_control_for_gemini_model():
def test_cache_control_preserved_in_text_content_for_gemini():
"""cache_control must survive message translation for a Gemini target."""
anthropic_messages = [
AnthropicMessagesUserMessageParam(
role="user",

View file

@ -7,8 +7,6 @@ from litellm.llms.vertex_ai.context_caching.transformation import (
class TestTTLNormalization:
"""Gemini only takes "<seconds>s"; Anthropic clients send "5m" and "1h" too"""
@pytest.mark.parametrize(
"ttl, expected",
[
@ -23,6 +21,8 @@ class TestTTLNormalization:
("1h", "3600s"),
("0.5h", "1800s"),
("48h", "172800s"),
("315576000000s", "315576000000s"),
("87660000h", "315576000000s"),
],
)
def test_normalizes_supported_units_to_seconds(self, ttl, expected):
@ -44,6 +44,10 @@ class TestTTLNormalization:
"3600 s",
"3600ss",
"1 h",
"0.0000000001s",
"315576000001s",
"87660001h",
"9" * 400 + "h",
None,
123,
],

View file

@ -1399,11 +1399,6 @@ class TestContextCachingEndpoints:
def test_check_and_create_cache_skips_between_default_and_gemini_2_5_pro_minimum(
self, local_model_cost_map
):
"""Gemini 2.5 Pro needs 2048 cached tokens, twice the provider-agnostic default.
Content between the two used to reach Google's cachedContents endpoint and 400
with "Cached content is too small".
"""
model = "gemini-2.5-pro"
self._token_check_patcher.stop()