diff --git a/litellm/router.py b/litellm/router.py index 1e9b23b2fa1..7addf43dcd9 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -700,14 +700,18 @@ class Router: self.deployment_names: list = [] # names of models under litellm_params. ex. azure/chatgpt-v-2 self.deployment_latency_map = {} ### CACHING ### - cache_type: Literal["local", "redis", "redis-semantic", "s3", "disk"] = "local" # default to an in-memory cache + cache_type: Literal["local", "redis", "redis-semantic", "s3", "disk"] = "local" redis_cache = None - cache_config: Final[dict[str, Any]] = {} + cache_config: dict[str, Any] = {} self.client_ttl = client_ttl if redis_url is not None or (redis_host is not None and redis_port is not None): cache_type = "redis" + # Seed with cache_kwargs first, then let explicit constructor + # arguments override so they always win when both are provided. + cache_config.update(cache_kwargs) + if redis_url is not None: cache_config["url"] = redis_url @@ -726,9 +730,12 @@ class Router: ) cache_config["db"] = str(redis_db) - # Add additional key-value pairs from cache_kwargs - cache_config.update(cache_kwargs) redis_cache = self._create_redis_cache(cache_config) + else: + # No Redis configured: honor cache_kwargs for non-Redis backends + # (e.g. disk). Explicit Redis params above take precedence when set. + cache_config.update(cache_kwargs) + cache_type = cache_config.pop("type", cache_type) if cache_responses: if litellm.cache is None: diff --git a/tests/test_litellm/test_router.py b/tests/test_litellm/test_router.py index cceb034a20b..fee3ba54d19 100644 --- a/tests/test_litellm/test_router.py +++ b/tests/test_litellm/test_router.py @@ -3783,6 +3783,62 @@ def test_pre_call_checks_no_messages_or_input_does_not_crash(monkeypatch): assert counted == [] # token counting skipped entirely, so no misleading error is logged +@pytest.mark.asyncio +async def test_router_cache_kwargs_applied_without_redis(): + """cache_kwargs should be applied even when Redis is not configured. + Regression test for #36309: cache_kwargs were silently ignored outside + the Redis block, so disk cache requests fell back to InMemoryCache. + """ + import tempfile + import shutil + from litellm.caching import DiskCache + + tmpdir = tempfile.mkdtemp(prefix="litellm_cache_test_") + try: + router = litellm.Router( + model_list=[ + { + "model_name": "test", + "litellm_params": {"model": "gpt-3.5-turbo", "api_key": "sk-test"}, + } + ], + cache_responses=True, + cache_kwargs={"type": "disk", "disk_cache_dir": tmpdir, "ttl": 60}, + ) + + assert isinstance(litellm.cache.cache, DiskCache), ( + f"Expected DiskCache, got {type(litellm.cache.cache).__name__}" + ) + finally: + shutil.rmtree(tmpdir, ignore_errors=True) + + +@pytest.mark.asyncio +async def test_router_explicit_redis_params_override_cache_kwargs(): + """Explicit Redis constructor args must win over cache_kwargs when both set. + + Regression test for Greptile review on #36309: cache_kwargs applied before + the explicit Redis block previously let a cache_kwargs 'url' override the + explicit redis_url. + """ + with patch("litellm.Router._create_redis_cache") as mock_create: + mock_create.return_value = None + litellm.Router( + model_list=[ + { + "model_name": "test", + "litellm_params": {"model": "gpt-3.5-turbo", "api_key": "sk-test"}, + } + ], + redis_url="redis://explicit-host:6379", + cache_kwargs={"url": "redis://kwargs-host:6379", "type": "redis"}, + ) + # Explicit redis_url must take precedence over cache_kwargs url + mock_create.assert_called_once() + passed_config = mock_create.call_args[0][0] + assert passed_config["url"] == "redis://explicit-host:6379" + + @pytest.mark.asyncio async def test_aresponses_enforces_context_window_pre_call_check(): """