diff --git a/litellm/proxy/_lazy_features.py b/litellm/proxy/_lazy_features.py index 7299f32bbaa..9f034575222 100644 --- a/litellm/proxy/_lazy_features.py +++ b/litellm/proxy/_lazy_features.py @@ -257,6 +257,13 @@ class LazyFeatureMiddleware: self.app = app self._fastapi_app = fastapi_app self._features = features + # SERVER_ROOT_PATH is a process-startup env var, cache the normalized + # form once instead of recomputing per request. Lazy import to avoid + # pulling proxy.utils into this module's import graph at startup + # (proxy_server imports both). + from litellm.proxy.utils import get_server_root_path + + self._root_path = get_server_root_path().rstrip("/") # Loaded set / per-feature locks live on app.state so the warm endpoint # and the middleware share them — preventing duplicate registrations # when both paths fire for the same feature. @@ -273,10 +280,6 @@ class LazyFeatureMiddleware: if scope["type"] in ("http", "websocket") and len(self._loaded) < len( self._features ): - # Lazy import to avoid pulling proxy.utils into this module's - # import graph (proxy_server imports both). - from litellm.proxy.utils import get_server_root_path - path = scope.get("path", "") # Strip SERVER_ROOT_PATH so prefix matching works under a server # root path. Without this, requests like /api/v1/policies/... never @@ -285,9 +288,8 @@ class LazyFeatureMiddleware: # `+ "/"` boundary prevents false-positive matches (e.g. /apiv2 # against root /api). If the path doesn't start with the prefix # (e.g. a reverse proxy already stripped it), we leave it alone. - root_path = get_server_root_path().rstrip("/") - if root_path and path.startswith(root_path + "/"): - path = path[len(root_path) :] + if self._root_path and path.startswith(self._root_path + "/"): + path = path[len(self._root_path) :] for feat in self._features: if feat.module_path in self._loaded: continue