diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index 9451114a223..4a5b0930645 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -1,5 +1,6 @@ # ruff: noqa: T201 import importlib +import importlib.util import json import os import random @@ -239,6 +240,15 @@ class ProxyInitializationHelpers: ignore `reload_includes`.""" from litellm._logging import verbose_proxy_logger + if importlib.util.find_spec("watchfiles") is None: + verbose_proxy_logger.warning( + "LiteLLM --reload: watchfiles is not installed, so uvicorn falls back " + "to its StatReload poller. StatReload stat-scans every *.py under the " + "current directory several times a second (high CPU) and ignores " + "reload_includes, so --config YAML and .env edits will not trigger " + "reloads. Install it with `pip install 'litellm[proxy]'`." + ) + uvicorn_args.update(ProxyInitializationHelpers._get_reload_options(config_path)) os.environ["LITELLM_DEV_ENV_HOT_RELOAD"] = "True" verbose_proxy_logger.warning( diff --git a/tests/test_litellm/proxy/test_proxy_cli.py b/tests/test_litellm/proxy/test_proxy_cli.py index 5301316295c..e8ac199e5bb 100644 --- a/tests/test_litellm/proxy/test_proxy_cli.py +++ b/tests/test_litellm/proxy/test_proxy_cli.py @@ -167,7 +167,10 @@ class TestProxyInitializationHelpers: def test_reload_uses_watchfiles_reloader(self): from uvicorn.supervisors import ChangeReload - assert ChangeReload.__name__ == "WatchFilesReload" + assert ChangeReload.__name__ == "WatchFilesReload", ( + "uvicorn fell back to StatReload; watchfiles must ship with the proxy " + "extra so --reload selects the event-driven WatchFilesReload" + ) def test_reload_includes_match_config_and_env_under_watchfiles( self, tmp_path, monkeypatch @@ -217,6 +220,27 @@ class TestProxyInitializationHelpers: assert "config.yaml" in uvicorn_args["reload_includes"] + def test_configure_dev_reload_warns_when_watchfiles_missing( + self, tmp_path, monkeypatch + ): + config_file = tmp_path / "config.yaml" + config_file.write_text("model_list: []\n") + monkeypatch.chdir(tmp_path) + + uvicorn_args: dict = {} + with ( + patch( + "litellm.proxy.proxy_cli.importlib.util.find_spec", return_value=None + ), + patch("litellm._logging.verbose_proxy_logger.warning") as mock_warning, + ): + ProxyInitializationHelpers._configure_dev_reload( + uvicorn_args, str(config_file) + ) + + warnings = [call.args[0].lower() for call in mock_warning.call_args_list] + assert any("watchfiles" in text for text in warnings) + def test_dev_env_hot_reload_enabled_reads_flag(self, monkeypatch): import litellm