From 51133e38c8d5b1b6ceebcd41a57eed7376c6cef5 Mon Sep 17 00:00:00 2001 From: Nakul Maurya <109911698+nakulmaurya@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:18:14 -0500 Subject: [PATCH 1/6] fix(cache): preserve resolved Redis cache backend --- litellm/proxy/proxy_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index c2ca045236c..fbe3e907a53 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -6549,7 +6549,7 @@ class ProxyConfig: ## to pass a complete url, or set ssl=True, etc. just set it as `os.environ[REDIS_URL] = `, _redis.py checks for REDIS specific environment variables _set_redis_usage_cache( self.init_cache( - cache_params=cache_params, + cache_params={"type": cache_type, **cache_params}, enable_redis_auth_cache=litellm_settings.get("enable_redis_auth_cache", False) is True, ) ) From b2c9237dec08081d0b49492be875fc8ae6d1630d Mon Sep 17 00:00:00 2001 From: Nakul Maurya <109911698+nakulmaurya@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:18:23 -0500 Subject: [PATCH 2/6] Implement test for cache options in proxy server Add test for cache options preserving backend and connection settings. --- tests/unit/proxy/test_proxy_server.py | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/unit/proxy/test_proxy_server.py b/tests/unit/proxy/test_proxy_server.py index 79f4edd4d62..5cfb16bda83 100644 --- a/tests/unit/proxy/test_proxy_server.py +++ b/tests/unit/proxy/test_proxy_server.py @@ -3,6 +3,7 @@ import sys import traceback from collections.abc import AsyncGenerator from contextlib import asynccontextmanager +from pathlib import Path from typing import Final, Literal from unittest import mock @@ -3296,3 +3297,50 @@ def test_get_litellm_model_info(data): ): get_litellm_model_info(model=model) get_info_mock.assert_called_once_with(data["expected"]) + + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("cache_params", "expected_type", "expected_host", "expected_port"), + ( + ({"mode": "default_off"}, "redis", "127.0.0.1", 6384), + ({"mode": "default_off", "host": "127.0.0.2", "port": 6385}, "redis", "127.0.0.2", 6385), + ({"mode": "default_off", "url": "redis://127.0.0.3:6386/3"}, "redis", "127.0.0.3", 6386), + ({"mode": "default_off", "type": "redis", "host": "127.0.0.2", "port": 6385}, "redis", "127.0.0.2", 6385), + ({"mode": "default_off", "type": "local"}, "local", None, None), + ), +) +async def test_cache_options_preserve_resolved_backend_and_explicit_connection( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + cache_params: dict[str, str | int], + expected_type: Literal["redis", "local"], + expected_host: str | None, + expected_port: int | None, +) -> None: + from pytest_socket import socket_allow_hosts + + from litellm.caching.redis_cache import RedisCache + from litellm.proxy.proxy_server import ProxyConfig + + socket_allow_hosts([], allow_unix_socket=True) + monkeypatch.setenv("REDIS_HOST", "127.0.0.1") + monkeypatch.setenv("REDIS_PORT", "6384") + monkeypatch.delenv("REDIS_URL", raising=False) + monkeypatch.delenv("REDIS_PASSWORD", raising=False) + config_path: Final = tmp_path / "cache.yaml" + config_path.write_text( + json.dumps({"model_list": [], "litellm_settings": {"cache": True, "cache_params": cache_params}}) + ) + + await ProxyConfig().load_config(router=None, config_file_path=str(config_path)) + + cache: Final = litellm.cache + assert cache is not None + assert cache.type == expected_type + assert cache.mode == cache_params["mode"] + if expected_type == "redis": + assert isinstance(cache.cache, RedisCache) + connection: Final = cache.cache.redis_client.connection_pool.connection_kwargs + assert (connection["host"], connection["port"]) == (expected_host, expected_port) From c6a7a70a468ba46b9486a5518a87fee6207dc282 Mon Sep 17 00:00:00 2001 From: Nakul Maurya <109911698+nakulmaurya@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:19:14 -0500 Subject: [PATCH 3/6] Remove unnecessary blank line in test_proxy_server.py --- tests/unit/proxy/test_proxy_server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/proxy/test_proxy_server.py b/tests/unit/proxy/test_proxy_server.py index 5cfb16bda83..5fd4a24ef3c 100644 --- a/tests/unit/proxy/test_proxy_server.py +++ b/tests/unit/proxy/test_proxy_server.py @@ -3299,7 +3299,6 @@ def test_get_litellm_model_info(data): get_info_mock.assert_called_once_with(data["expected"]) - @pytest.mark.asyncio @pytest.mark.parametrize( ("cache_params", "expected_type", "expected_host", "expected_port"), From 5f18da4558572df2fe8a5dd55036e45fef3f316a Mon Sep 17 00:00:00 2001 From: Nakul Maurya <109911698+nakulmaurya@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:39:43 -0500 Subject: [PATCH 4/6] test(cache): restore shared Redis state after regression cases --- tests/unit/proxy/test_proxy_server.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unit/proxy/test_proxy_server.py b/tests/unit/proxy/test_proxy_server.py index 5fd4a24ef3c..f7f4c620046 100644 --- a/tests/unit/proxy/test_proxy_server.py +++ b/tests/unit/proxy/test_proxy_server.py @@ -3321,8 +3321,18 @@ async def test_cache_options_preserve_resolved_backend_and_explicit_connection( from pytest_socket import socket_allow_hosts from litellm.caching.redis_cache import RedisCache + from litellm.proxy import proxy_server from litellm.proxy.proxy_server import ProxyConfig + monkeypatch.setattr(proxy_server, "redis_usage_cache", None) + for shared_cache in ( + proxy_server.spend_counter_cache, + proxy_server.cli_sso_session_cache, + proxy_server.litellm_config_cache, + ): + monkeypatch.setattr(shared_cache, "redis_cache", None) + monkeypatch.setattr(shared_cache, "default_redis_ttl", shared_cache.default_redis_ttl) + socket_allow_hosts([], allow_unix_socket=True) monkeypatch.setenv("REDIS_HOST", "127.0.0.1") monkeypatch.setenv("REDIS_PORT", "6384") From 96906ffe14c47381dd3333c420acd6c9cad3552f Mon Sep 17 00:00:00 2001 From: Nakul Maurya <109911698+nakulmaurya@users.noreply.github.com> Date: Tue, 13 Oct 2026 22:34:52 -0500 Subject: [PATCH 5/6] test(cache): pin behavior without Redis configuration --- tests/unit/proxy/test_proxy_server.py | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/unit/proxy/test_proxy_server.py b/tests/unit/proxy/test_proxy_server.py index f7f4c620046..fa217543fa4 100644 --- a/tests/unit/proxy/test_proxy_server.py +++ b/tests/unit/proxy/test_proxy_server.py @@ -3353,3 +3353,45 @@ async def test_cache_options_preserve_resolved_backend_and_explicit_connection( assert isinstance(cache.cache, RedisCache) connection: Final = cache.cache.redis_client.connection_pool.connection_kwargs assert (connection["host"], connection["port"]) == (expected_host, expected_port) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("cache_params", "expected_type"), + ( + ({}, "redis"), + ({"ttl": 60}, "redis"), + ({"mode": "default_off"}, "redis"), + ({"supported_call_types": ["acompletion"]}, "redis"), + ({"type": "local", "ttl": 60}, "local"), + ), +) +async def test_cache_without_redis_connection_requires_explicit_local_backend( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + cache_params: dict[str, str | int | list[str]], + expected_type: Literal["redis", "local"], +) -> None: + from litellm.caching.in_memory_cache import InMemoryCache + from litellm.proxy import proxy_server + + for name in ("REDIS_HOST", "REDIS_PORT", "REDIS_URL", "REDIS_PASSWORD"): + monkeypatch.delenv(name, raising=False) + monkeypatch.delenv("LITELLM_CONFIG_BUCKET_NAME", raising=False) + monkeypatch.setattr(litellm, "cache", None) + monkeypatch.setattr(proxy_server, "prisma_client", None) + monkeypatch.setattr(proxy_server, "store_model_in_db", False) + config_path: Final = tmp_path / "cache.yaml" + config_path.write_text( + json.dumps({"model_list": [], "litellm_settings": {"cache": True, "cache_params": cache_params}}) + ) + + if expected_type == "redis": + with pytest.raises(ValueError, match="Either 'host' or 'url' must be specified for redis"): + await proxy_server.ProxyConfig().load_config(router=None, config_file_path=str(config_path)) + return + + await proxy_server.ProxyConfig().load_config(router=None, config_file_path=str(config_path)) + assert litellm.cache is not None + assert isinstance(litellm.cache.cache, InMemoryCache) + assert litellm.cache.type == expected_type From db942c86f09ec5ab7b44705d528725209f905288 Mon Sep 17 00:00:00 2001 From: Nakul Maurya <109911698+nakulmaurya@users.noreply.github.com> Date: Tue, 13 Oct 2026 22:48:18 -0500 Subject: [PATCH 6/6] test(cache): avoid upstream test append conflicts Refactor test_get_litellm_model_info to restore parameterization and mock behavior. --- tests/unit/proxy/test_proxy_server.py | 118 +++++++++++++------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/tests/unit/proxy/test_proxy_server.py b/tests/unit/proxy/test_proxy_server.py index fa217543fa4..906d19f2c84 100644 --- a/tests/unit/proxy/test_proxy_server.py +++ b/tests/unit/proxy/test_proxy_server.py @@ -3,7 +3,6 @@ import sys import traceback from collections.abc import AsyncGenerator from contextlib import asynccontextmanager -from pathlib import Path from typing import Final, Literal from unittest import mock @@ -17,6 +16,7 @@ import litellm.proxy.proxy_server load_dotenv() import io import json +from pathlib import Path # this file is to test litellm/proxy @@ -3241,64 +3241,6 @@ async def test_update_config_success_callback_normalization(monkeypatch): assert "langfuse" in callbacks -@pytest.mark.parametrize( - "data", - [ - { - "model": { - "model_name": "azure/gpt-4.1-mini", - "litellm_params": {"model": "azure/gpt-4.1-mini"}, - "model_info": {"base_model": "gpt-4.1-mini"}, - }, - "expected": "gpt-4.1-mini", - }, - { - "model": { - "model_name": "openai/gpt-4.1-mini", - "litellm_params": {"model": "openai/gpt-4.1-mini"}, - }, - "expected": "openai/gpt-4.1-mini", - }, - { - "model": { - "model_name": "openai/gpt-4.1-mini", - "litellm_params": {"model": "openai/gpt-4.1-mini"}, - "model_info": {"base_model": "gpt-4.1-mini"}, - }, - "expected": "gpt-4.1-mini", - }, - { - "model": { - "model_name": "claude-sonnet-4-5-20250929", - "litellm_params": {"model": "anthropic/claude-sonnet-4-5@20250929"}, - "model_info": {"base_model": "anthropic/claude-sonnet-4-5-20250929"}, - }, - "expected": "anthropic/claude-sonnet-4-5-20250929", - }, - { - "model": { - "model_name": "gemini-2.5-flash-001", - "litellm_params": {"model": "gemini/gemini-2.5-flash@001"}, - "model_info": {"base_model": "gemini-2.5-flash-001"}, - }, - "expected": "gemini-2.5-flash-001", - }, - ], -) -def test_get_litellm_model_info(data): - from litellm.proxy.proxy_server import get_litellm_model_info - - model = data["model"] - get_info_mock = MagicMock() - - with mock.patch( - "litellm.get_model_info", - new=get_info_mock, - ): - get_litellm_model_info(model=model) - get_info_mock.assert_called_once_with(data["expected"]) - - @pytest.mark.asyncio @pytest.mark.parametrize( ("cache_params", "expected_type", "expected_host", "expected_port"), @@ -3395,3 +3337,61 @@ async def test_cache_without_redis_connection_requires_explicit_local_backend( assert litellm.cache is not None assert isinstance(litellm.cache.cache, InMemoryCache) assert litellm.cache.type == expected_type + + +@pytest.mark.parametrize( + "data", + [ + { + "model": { + "model_name": "azure/gpt-4.1-mini", + "litellm_params": {"model": "azure/gpt-4.1-mini"}, + "model_info": {"base_model": "gpt-4.1-mini"}, + }, + "expected": "gpt-4.1-mini", + }, + { + "model": { + "model_name": "openai/gpt-4.1-mini", + "litellm_params": {"model": "openai/gpt-4.1-mini"}, + }, + "expected": "openai/gpt-4.1-mini", + }, + { + "model": { + "model_name": "openai/gpt-4.1-mini", + "litellm_params": {"model": "openai/gpt-4.1-mini"}, + "model_info": {"base_model": "gpt-4.1-mini"}, + }, + "expected": "gpt-4.1-mini", + }, + { + "model": { + "model_name": "claude-sonnet-4-5-20250929", + "litellm_params": {"model": "anthropic/claude-sonnet-4-5@20250929"}, + "model_info": {"base_model": "anthropic/claude-sonnet-4-5-20250929"}, + }, + "expected": "anthropic/claude-sonnet-4-5-20250929", + }, + { + "model": { + "model_name": "gemini-2.5-flash-001", + "litellm_params": {"model": "gemini/gemini-2.5-flash@001"}, + "model_info": {"base_model": "gemini-2.5-flash-001"}, + }, + "expected": "gemini-2.5-flash-001", + }, + ], +) +def test_get_litellm_model_info(data): + from litellm.proxy.proxy_server import get_litellm_model_info + + model = data["model"] + get_info_mock = MagicMock() + + with mock.patch( + "litellm.get_model_info", + new=get_info_mock, + ): + get_litellm_model_info(model=model) + get_info_mock.assert_called_once_with(data["expected"])