mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(proxy): load REDIS_* env vars when cache_params has non-connection keys (#26233)
The cache_params env-var fallback in ProxyConfig.load_config was gated on `len(cache_params.keys()) == 0`, so any non-empty cache_params (e.g. just `mode: default_off`) silently dropped the Redis env var config and fell back to in-memory cache. This broke multi-pod deployments because spend_counter_cache.redis_cache never got wired up and each pod tracked counters independently. Replace the length check with a "user did not supply connection details" check: only populate REDIS_HOST / REDIS_PORT / REDIS_PASSWORD from the environment when neither `host` nor `url` is present in cache_params. Other cache_params keys (mode, ttl, etc.) no longer suppress the fallback. Adds two regression tests exercising ProxyConfig.load_config: - cache_params with only non-connection keys → env vars load, RedisCache path - cache_params with explicit host → env vars do not overwrite user config Fixes #26233
This commit is contained in:
parent
b8f7d61400
commit
22a3c50c35
2 changed files with 114 additions and 3 deletions
|
|
@ -3032,9 +3032,13 @@ class ProxyConfig:
|
|||
|
||||
verbose_proxy_logger.debug("passed cache type=%s", cache_type)
|
||||
|
||||
if (
|
||||
cache_type == "redis" or cache_type == "redis-semantic"
|
||||
) and len(cache_params.keys()) == 0:
|
||||
if (cache_type == "redis" or cache_type == "redis-semantic") and (
|
||||
"host" not in cache_params and "url" not in cache_params
|
||||
):
|
||||
# Fall back to REDIS_* env vars whenever the user has not
|
||||
# supplied connection details in cache_params. Gating on
|
||||
# empty cache_params silently dropped to in-memory cache
|
||||
# when unrelated keys like `mode` were set.
|
||||
cache_host = get_secret("REDIS_HOST", None)
|
||||
cache_port = get_secret("REDIS_PORT", None)
|
||||
cache_password = None
|
||||
|
|
|
|||
|
|
@ -4928,3 +4928,110 @@ async def test_increment_spend_counters_team_and_member():
|
|||
finally:
|
||||
ps.user_api_key_cache = original_key_cache
|
||||
ps.spend_counter_cache = original_counter_cache
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_redis_env_vars_loaded_when_cache_params_has_non_connection_keys(
|
||||
monkeypatch,
|
||||
):
|
||||
"""
|
||||
Regression test for https://github.com/BerriAI/litellm/issues/26233.
|
||||
|
||||
When `litellm_settings.cache: true` is combined with a `cache_params`
|
||||
dict that contains only non-connection keys (e.g. `mode: default_off`),
|
||||
the proxy must still fall back to the REDIS_HOST / REDIS_PORT /
|
||||
REDIS_PASSWORD env vars. Previously the env-var fallback was gated on
|
||||
`cache_params` being completely empty, so any unrelated key silently
|
||||
dropped the cache to in-memory — which breaks multi-pod spend tracking.
|
||||
"""
|
||||
import tempfile
|
||||
|
||||
from litellm.proxy.proxy_server import ProxyConfig
|
||||
|
||||
monkeypatch.setenv("REDIS_HOST", "test-redis.internal")
|
||||
monkeypatch.setenv("REDIS_PORT", "6379")
|
||||
monkeypatch.setenv("REDIS_PASSWORD", "fake-test-password")
|
||||
|
||||
test_config = {
|
||||
"model_list": [],
|
||||
"router_settings": {},
|
||||
"litellm_settings": {
|
||||
"cache": True,
|
||||
"cache_params": {
|
||||
"mode": "default_off",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
||||
yaml.dump(test_config, f)
|
||||
config_file_path = f.name
|
||||
|
||||
try:
|
||||
proxy_config = ProxyConfig()
|
||||
with patch.object(ProxyConfig, "_init_cache") as mock_init:
|
||||
await proxy_config.load_config(
|
||||
router=MagicMock(), config_file_path=config_file_path
|
||||
)
|
||||
|
||||
mock_init.assert_called_once()
|
||||
actual_cache_params = mock_init.call_args.kwargs["cache_params"]
|
||||
|
||||
assert actual_cache_params.get("type") == "redis"
|
||||
assert actual_cache_params.get("host") == "test-redis.internal"
|
||||
assert actual_cache_params.get("port") == "6379"
|
||||
assert actual_cache_params.get("password") == "fake-test-password"
|
||||
# Preserved user-supplied non-connection keys
|
||||
assert actual_cache_params.get("mode") == "default_off"
|
||||
finally:
|
||||
os.unlink(config_file_path)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_explicit_cache_params_host_not_overwritten_by_env_vars(monkeypatch):
|
||||
"""
|
||||
Companion to test_redis_env_vars_loaded_when_cache_params_has_non_connection_keys.
|
||||
|
||||
When `cache_params` explicitly specifies a `host`, the REDIS_* env vars
|
||||
must NOT overwrite it. The env-var fallback only applies when the user
|
||||
has supplied no connection details.
|
||||
"""
|
||||
import tempfile
|
||||
|
||||
from litellm.proxy.proxy_server import ProxyConfig
|
||||
|
||||
monkeypatch.setenv("REDIS_HOST", "env-redis.internal")
|
||||
monkeypatch.setenv("REDIS_PORT", "6379")
|
||||
|
||||
test_config = {
|
||||
"model_list": [],
|
||||
"router_settings": {},
|
||||
"litellm_settings": {
|
||||
"cache": True,
|
||||
"cache_params": {
|
||||
"host": "explicit-redis.internal",
|
||||
"port": 1234,
|
||||
"mode": "default_off",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
||||
yaml.dump(test_config, f)
|
||||
config_file_path = f.name
|
||||
|
||||
try:
|
||||
proxy_config = ProxyConfig()
|
||||
with patch.object(ProxyConfig, "_init_cache") as mock_init:
|
||||
await proxy_config.load_config(
|
||||
router=MagicMock(), config_file_path=config_file_path
|
||||
)
|
||||
|
||||
mock_init.assert_called_once()
|
||||
actual_cache_params = mock_init.call_args.kwargs["cache_params"]
|
||||
|
||||
assert actual_cache_params.get("host") == "explicit-redis.internal"
|
||||
assert actual_cache_params.get("port") == 1234
|
||||
assert actual_cache_params.get("mode") == "default_off"
|
||||
finally:
|
||||
os.unlink(config_file_path)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue