From 3684e5cbcbc8f8ec1543001caa20f8d464e68919 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Sat, 19 Sep 2026 00:50:37 -0700 Subject: [PATCH] fix(gemini): drop ttl values outside the protobuf Duration range and the explanatory docstrings --- .../vertex_ai/context_caching/transformation.py | 12 +++--------- ...imental_pass_through_adapters_transformation.py | 14 +------------- .../context_caching/test_context_caching_ttl.py | 8 ++++++-- .../test_vertex_ai_context_caching.py | 5 ----- 4 files changed, 10 insertions(+), 29 deletions(-) diff --git a/litellm/llms/vertex_ai/context_caching/transformation.py b/litellm/llms/vertex_ai/context_caching/transformation.py index ef415dfa19c..79e435b790c 100644 --- a/litellm/llms/vertex_ai/context_caching/transformation.py +++ b/litellm/llms/vertex_ai/context_caching/transformation.py @@ -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 "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" diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py index 471c09153c0..b9a82e3fc68 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py @@ -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", diff --git a/tests/test_litellm/llms/vertex_ai/context_caching/test_context_caching_ttl.py b/tests/test_litellm/llms/vertex_ai/context_caching/test_context_caching_ttl.py index b2da8da4cc5..82f7d3dfc7d 100644 --- a/tests/test_litellm/llms/vertex_ai/context_caching/test_context_caching_ttl.py +++ b/tests/test_litellm/llms/vertex_ai/context_caching/test_context_caching_ttl.py @@ -7,8 +7,6 @@ from litellm.llms.vertex_ai.context_caching.transformation import ( class TestTTLNormalization: - """Gemini only takes "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, ], diff --git a/tests/test_litellm/llms/vertex_ai/context_caching/test_vertex_ai_context_caching.py b/tests/test_litellm/llms/vertex_ai/context_caching/test_vertex_ai_context_caching.py index 7cbfacfc338..34c00e84d2e 100644 --- a/tests/test_litellm/llms/vertex_ai/context_caching/test_vertex_ai_context_caching.py +++ b/tests/test_litellm/llms/vertex_ai/context_caching/test_vertex_ai_context_caching.py @@ -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()