fix(ui): treat router redis as configured for the no-redis banner

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
mateo 2026-08-11 02:40:14 +00:00
parent e368eeac49
commit 48fa4a0f06
3 changed files with 123 additions and 8 deletions

View file

@ -0,0 +1,75 @@
---
name: testing-admin-ui-banners
description: How to run the LiteLLM Admin UI dev server against a live proxy (including a second BEFORE/base worktree) to test dashboard shell banners and /health/readiness/details driven UI state.
---
# Testing Admin UI dashboard banners against a live proxy
## Bring up AFTER (branch under test)
```
sudo service postgresql start
cd <repo> && (setsid uv run --no-sync litellm --config litellm/proxy/dev_config.yaml --detailed_debug --port 4000 > /tmp/proxy.log 2>&1 < /dev/null &)
cd ui/litellm-dashboard && (npm run dev > /tmp/ui_dev.log 2>&1 &) # port 3000
```
Proxy startup takes ~45-60s before `/health/readiness/details` answers. Log in at
http://localhost:3000/ (it redirects to the proxy's login page) with `admin` /
the `general_settings.master_key` from `litellm/proxy/dev_config.yaml` (`sk-1234` by default).
In dev (`NODE_ENV=development`) the UI defaults its API base to `http://localhost:4000`, so no
extra env var is needed for the main dev server.
Launcher gotcha: if an `exec` shell call runs longer than ~10s it gets backgrounded and can take
the freshly spawned proxy with it. Keep the launch command short (`setsid ... & ; sleep 6`) and
poll readiness in a separate call.
## Bring up BEFORE (base commit) side by side
```
git worktree add /home/ubuntu/repos/litellm-base <base-branch>
cp -al <repo>/ui/litellm-dashboard/node_modules /home/ubuntu/repos/litellm-base/ui/litellm-dashboard/node_modules
```
Do NOT symlink `node_modules` into a worktree: Turbopack panics with
"Symlink [project]/node_modules is invalid, it points out of the filesystem root". A hardlink copy
(`cp -al`) works and is fast.
Run the base proxy with the main venv but the base source tree, and point the base UI at it:
```
cd /home/ubuntu/repos/litellm-base && PYTHONPATH=$PWD <repo>/.venv/bin/python -m litellm.proxy.proxy_cli --config <repo>/litellm/proxy/dev_config.yaml --detailed_debug --port 4001
cd /home/ubuntu/repos/litellm-base/ui/litellm-dashboard && NEXT_PUBLIC_BASE_URL=http://localhost:4001 npm run dev -- --port 3001
```
`PYTHONPATH` wins over the editable install, so the base proxy really runs base code (verify with
`python -c "import litellm; print(litellm.__file__)"`).
## Banner-specific notes
Dashboard shell banners (`DebugWarningBanner`, `NoRedisWarningBanner`, `LicenseExpiryBanner`) all
read `useHealthReadinessDetails`, which has `staleTime: 5 min` and `retry: false`. After restarting
the proxy with different env, hard-reload the page (ctrl+shift+r) or the cached readiness payload
keeps the old banner state. Running the proxy with `--detailed_debug` always shows the yellow debug
banner, which is a handy control: if it is present but the banner under test is not, the readiness
call succeeded and the banner condition really is false.
Coordination Redis (`litellm.proxy.proxy_server.redis_usage_cache`, which drives
`show_no_redis_warning`) is NOT populated by `REDIS_HOST`/`REDIS_PORT` alone: the env fallback only
runs inside `_init_cache`, which requires a cache block in the config. To get a real coordination
Redis, run `docker run -d -p 6379:6379 redis:7` and add to the config:
```
litellm_settings:
cache: true
cache_params:
type: redis
host: localhost
port: 6379
```
`general_settings.coordination_redis` is the other supported path.
## Devin Secrets Needed
None for banner/UI-state testing; the proxy boots with the bundled dev config and a local Postgres.
Provider keys are only needed when a test actually issues LLM requests.

View file

@ -1453,17 +1453,23 @@ DISABLE_NO_REDIS_WARNING_ENV_VAR: Final = "LITELLM_DISABLE_NO_REDIS_WARNING"
def _show_no_redis_warning() -> bool:
"""
Whether the UI should warn that no coordination Redis is configured.
Whether the UI should warn that no Redis is configured.
Redis is what makes rate limits, budgets, router state, and cache
invalidation consistent across workers, so a proxy running without it is
only safe as a single worker. Operators who know that can silence the
only safe as a single worker. Both places a Redis can land count: the
coordination cache (from a Redis response cache, general_settings.
coordination_redis, or the REDIS_* env fallback) and the router's own
Redis (router_settings.redis_host), which backs cooldowns and usage-based
routing on its own. Operators who know they run one worker can silence the
warning with LITELLM_DISABLE_NO_REDIS_WARNING=true.
"""
from litellm.proxy.proxy_server import redis_usage_cache
from litellm.proxy.proxy_server import llm_router, redis_usage_cache
if redis_usage_cache is not None:
return False
if llm_router is not None and llm_router.cache.redis_cache is not None:
return False
return get_secret_bool(DISABLE_NO_REDIS_WARNING_ENV_VAR, False) is not True

View file

@ -2463,25 +2463,58 @@ class TestConfigBaseForHealthCheck:
class TestNoRedisWarning:
"""`show_no_redis_warning` drives the Admin UI's default-on "no Redis" banner."""
def test_warns_when_no_coordination_redis_is_configured(self, monkeypatch):
@staticmethod
def _router(redis_cache):
return SimpleNamespace(cache=SimpleNamespace(redis_cache=redis_cache))
def test_warns_when_no_redis_is_configured(self, monkeypatch):
monkeypatch.delenv("LITELLM_DISABLE_NO_REDIS_WARNING", raising=False)
with patch("litellm.proxy.proxy_server.redis_usage_cache", None):
with (
patch("litellm.proxy.proxy_server.redis_usage_cache", None),
patch("litellm.proxy.proxy_server.llm_router", self._router(None)),
):
assert _show_no_redis_warning() is True
def test_warns_when_there_is_no_router_at_all(self, monkeypatch):
monkeypatch.delenv("LITELLM_DISABLE_NO_REDIS_WARNING", raising=False)
with (
patch("litellm.proxy.proxy_server.redis_usage_cache", None),
patch("litellm.proxy.proxy_server.llm_router", None),
):
assert _show_no_redis_warning() is True
def test_stays_quiet_when_a_coordination_redis_is_configured(self, monkeypatch):
monkeypatch.delenv("LITELLM_DISABLE_NO_REDIS_WARNING", raising=False)
with patch("litellm.proxy.proxy_server.redis_usage_cache", MagicMock()):
with (
patch("litellm.proxy.proxy_server.redis_usage_cache", MagicMock()),
patch("litellm.proxy.proxy_server.llm_router", self._router(None)),
):
assert _show_no_redis_warning() is False
def test_stays_quiet_when_only_the_router_has_redis(self, monkeypatch):
"""router_settings.redis_host alone backs cooldowns and usage-based routing."""
monkeypatch.delenv("LITELLM_DISABLE_NO_REDIS_WARNING", raising=False)
with (
patch("litellm.proxy.proxy_server.redis_usage_cache", None),
patch("litellm.proxy.proxy_server.llm_router", self._router(MagicMock())),
):
assert _show_no_redis_warning() is False
@pytest.mark.parametrize("value", ["true", "True"])
def test_env_var_suppresses_the_warning(self, monkeypatch, value):
monkeypatch.setenv("LITELLM_DISABLE_NO_REDIS_WARNING", value)
with patch("litellm.proxy.proxy_server.redis_usage_cache", None):
with (
patch("litellm.proxy.proxy_server.redis_usage_cache", None),
patch("litellm.proxy.proxy_server.llm_router", self._router(None)),
):
assert _show_no_redis_warning() is False
def test_env_var_set_false_keeps_the_warning(self, monkeypatch):
monkeypatch.setenv("LITELLM_DISABLE_NO_REDIS_WARNING", "false")
with patch("litellm.proxy.proxy_server.redis_usage_cache", None):
with (
patch("litellm.proxy.proxy_server.redis_usage_cache", None),
patch("litellm.proxy.proxy_server.llm_router", self._router(None)),
):
assert _show_no_redis_warning() is True
@pytest.mark.asyncio
@ -2492,6 +2525,7 @@ class TestNoRedisWarning:
with (
patch("litellm.proxy.proxy_server.prisma_client", prisma_client),
patch("litellm.proxy.proxy_server.redis_usage_cache", None),
patch("litellm.proxy.proxy_server.llm_router", self._router(None)),
patch.object(
_health_endpoints_module,
"_db_health_readiness_check",