From 372e6c53343642d1166dcf0550be7dc4712fd718 Mon Sep 17 00:00:00 2001 From: apex-mochen <2756823972@qq.com> Date: Wed, 30 Sep 2026 22:48:36 +0800 Subject: [PATCH] fix(cache): apply default_redis_ttl on the pre/post-call batch write path _set_on_batch fed default_in_memory_ttl to Redis whenever no explicit ttl was passed, so a configured default_redis_ttl was silently ignored on the request/post-call pipeline path (behind async_set_cache_pre_call / async_set_cache_post_call). Chain default_redis_ttl -> default_in_memory_ttl the same way set_cache / async_set_cache / async_set_cache_pipeline do, and add a default_redis_ttl=3600 case beside the #43779 post-call test. --- litellm/caching/dual_cache.py | 5 ++- .../test_request_redis_batch_post_call.py | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/litellm/caching/dual_cache.py b/litellm/caching/dual_cache.py index de63f91c011..ee829998c47 100644 --- a/litellm/caching/dual_cache.py +++ b/litellm/caching/dual_cache.py @@ -553,7 +553,10 @@ class DualCache(BaseCache): effective_ttl: Final = self.default_in_memory_ttl if ttl is None else ttl if self.in_memory_cache is not None: await self.in_memory_cache.async_set_cache(key, value, ttl=effective_ttl) - return batch.set(key, value, effective_ttl) + redis_ttl = self.default_redis_ttl if ttl is None else ttl + if redis_ttl is None: + redis_ttl = effective_ttl + return batch.set(key, value, redis_ttl) # async_batch_set_cache async def async_set_cache_pipeline( diff --git a/tests/unit/caching/test_request_redis_batch_post_call.py b/tests/unit/caching/test_request_redis_batch_post_call.py index fdd328a9a57..bd799c13f0a 100644 --- a/tests/unit/caching/test_request_redis_batch_post_call.py +++ b/tests/unit/caching/test_request_redis_batch_post_call.py @@ -278,6 +278,43 @@ async def test_a_slot_released_before_the_response_reaches_redis_at_once_not_on_ assert client.pipelines == [] +@pytest.mark.asyncio +async def test_a_deferred_response_cache_set_without_a_ttl_expires_in_redis_like_the_direct_path(): + client = FakeClient(_ok_replies) + redis_cache = PostCallFakeRedisCache(client) + dual_cache = DualCache(redis_cache=redis_cache, in_memory_cache=InMemoryCache(), default_in_memory_ttl=300) + + await dual_cache.async_set_cache("direct", {"id": "resp"}) + with request_redis_batch_scope(): + await dual_cache.async_set_cache_post_call("deferred", {"id": "resp"}, None) + await flush_post_call_redis_batches() + + (command,) = client.pipelines[0].commands + assert (command[0], command[1], command[3]) == ("SET", "deferred", redis_cache.alone[0][2]["ttl"]) + assert command[3] == 300 + + +@pytest.mark.asyncio +async def test_a_deferred_response_cache_set_without_a_ttl_uses_default_redis_ttl_when_configured(): + client = FakeClient(_ok_replies) + redis_cache = PostCallFakeRedisCache(client) + dual_cache = DualCache( + redis_cache=redis_cache, + in_memory_cache=InMemoryCache(), + default_in_memory_ttl=300, + default_redis_ttl=3600, + ) + + await dual_cache.async_set_cache("direct", {"id": "resp"}) + with request_redis_batch_scope(): + await dual_cache.async_set_cache_post_call("deferred", {"id": "resp"}, None) + await flush_post_call_redis_batches() + + (command,) = client.pipelines[0].commands + assert (command[0], command[1], command[3]) == ("SET", "deferred", redis_cache.alone[0][2]["ttl"]) + assert command[3] == 3600 + + @pytest.mark.asyncio async def test_a_released_slot_is_free_locally_at_once_and_the_older_redis_count_does_not_overwrite_the_gauge(): def replies(command: tuple[object, ...]) -> object: