mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
removed /models and v1/models from llm api routes
This commit is contained in:
parent
dd5c14baf8
commit
9485a9482a
3 changed files with 178 additions and 0 deletions
|
|
@ -41,6 +41,10 @@ class EnterpriseRouteChecks:
|
|||
|
||||
return get_secret_bool("DISABLE_ADMIN_ENDPOINTS") is True
|
||||
|
||||
# Routes that should remain accessible even when LLM API endpoints are disabled.
|
||||
# These are read-only model listing routes needed by the Admin UI.
|
||||
LLM_API_EXEMPT_ROUTES = ["/models", "/v1/models"]
|
||||
|
||||
@staticmethod
|
||||
def should_call_route(route: str):
|
||||
"""
|
||||
|
|
@ -58,6 +62,7 @@ class EnterpriseRouteChecks:
|
|||
)
|
||||
elif (
|
||||
RouteChecks.is_llm_api_route(route=route)
|
||||
and route not in EnterpriseRouteChecks.LLM_API_EXEMPT_ROUTES
|
||||
and EnterpriseRouteChecks.is_llm_api_route_disabled()
|
||||
):
|
||||
raise HTTPException(
|
||||
|
|
|
|||
|
|
@ -182,6 +182,76 @@ class TestEnterpriseRouteChecks:
|
|||
EnterpriseRouteChecks.should_call_route("/config/update")
|
||||
|
||||
|
||||
@patch("litellm.proxy.proxy_server.premium_user", True)
|
||||
class TestEnterpriseRouteChecksModelListExemption:
|
||||
"""Test that /models and /v1/models are exempt from DISABLE_LLM_API_ENDPOINTS"""
|
||||
|
||||
@patch.object(EnterpriseRouteChecks, "is_llm_api_route_disabled")
|
||||
@patch.object(EnterpriseRouteChecks, "is_management_routes_disabled")
|
||||
@patch("litellm.proxy.auth.route_checks.RouteChecks.is_llm_api_route")
|
||||
@patch("litellm.proxy.auth.route_checks.RouteChecks.is_management_route")
|
||||
def test_models_route_allowed_when_llm_api_disabled(
|
||||
self,
|
||||
mock_is_management_route,
|
||||
mock_is_llm_api_route,
|
||||
mock_is_management_disabled,
|
||||
mock_is_llm_api_disabled,
|
||||
):
|
||||
"""Test that /models is allowed even when LLM API routes are disabled"""
|
||||
mock_is_management_route.return_value = False
|
||||
mock_is_llm_api_route.return_value = True
|
||||
mock_is_management_disabled.return_value = False
|
||||
mock_is_llm_api_disabled.return_value = True
|
||||
|
||||
# Should not raise exception for /models
|
||||
EnterpriseRouteChecks.should_call_route("/models")
|
||||
|
||||
@patch.object(EnterpriseRouteChecks, "is_llm_api_route_disabled")
|
||||
@patch.object(EnterpriseRouteChecks, "is_management_routes_disabled")
|
||||
@patch("litellm.proxy.auth.route_checks.RouteChecks.is_llm_api_route")
|
||||
@patch("litellm.proxy.auth.route_checks.RouteChecks.is_management_route")
|
||||
def test_v1_models_route_allowed_when_llm_api_disabled(
|
||||
self,
|
||||
mock_is_management_route,
|
||||
mock_is_llm_api_route,
|
||||
mock_is_management_disabled,
|
||||
mock_is_llm_api_disabled,
|
||||
):
|
||||
"""Test that /v1/models is allowed even when LLM API routes are disabled"""
|
||||
mock_is_management_route.return_value = False
|
||||
mock_is_llm_api_route.return_value = True
|
||||
mock_is_management_disabled.return_value = False
|
||||
mock_is_llm_api_disabled.return_value = True
|
||||
|
||||
# Should not raise exception for /v1/models
|
||||
EnterpriseRouteChecks.should_call_route("/v1/models")
|
||||
|
||||
@patch.object(EnterpriseRouteChecks, "is_llm_api_route_disabled")
|
||||
@patch.object(EnterpriseRouteChecks, "is_management_routes_disabled")
|
||||
@patch("litellm.proxy.auth.route_checks.RouteChecks.is_llm_api_route")
|
||||
@patch("litellm.proxy.auth.route_checks.RouteChecks.is_management_route")
|
||||
def test_chat_completions_still_blocked_when_llm_api_disabled(
|
||||
self,
|
||||
mock_is_management_route,
|
||||
mock_is_llm_api_route,
|
||||
mock_is_management_disabled,
|
||||
mock_is_llm_api_disabled,
|
||||
):
|
||||
"""Test that non-exempt LLM routes like /v1/chat/completions are still blocked"""
|
||||
mock_is_management_route.return_value = False
|
||||
mock_is_llm_api_route.return_value = True
|
||||
mock_is_management_disabled.return_value = False
|
||||
mock_is_llm_api_disabled.return_value = True
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
EnterpriseRouteChecks.should_call_route("/v1/chat/completions")
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert "LLM API routes are disabled for this instance." in str(
|
||||
exc_info.value.detail
|
||||
)
|
||||
|
||||
|
||||
class TestEnterpriseRouteChecksErrorMessages:
|
||||
"""Test that error messages correctly identify which feature requires Enterprise license"""
|
||||
|
||||
|
|
|
|||
|
|
@ -906,6 +906,109 @@ def test_proxy_admin_viewer_can_access_global_spend_tags():
|
|||
)
|
||||
|
||||
|
||||
class TestModelsRouteExemptFromDisableLLMEndpoints:
|
||||
"""
|
||||
Test that /models and /v1/models are exempt from DISABLE_LLM_API_ENDPOINTS.
|
||||
|
||||
When DISABLE_LLM_API_ENDPOINTS is set, inference routes like /v1/chat/completions
|
||||
should be blocked, but /models and /v1/models should remain accessible because
|
||||
they are read-only model listing routes needed by the Admin UI.
|
||||
|
||||
Relevant issue: https://github.com/BerriAI/litellm/issues/new (UI breaks with DISABLE_LLM_ENDPOINTS)
|
||||
"""
|
||||
|
||||
def _get_enterprise_route_checks(self):
|
||||
"""Import EnterpriseRouteChecks from the local enterprise source file."""
|
||||
import importlib.util
|
||||
|
||||
local_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"..", "..", "..", "..", "enterprise",
|
||||
"litellm_enterprise", "proxy", "auth", "route_checks.py",
|
||||
)
|
||||
local_file = os.path.abspath(local_file)
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"local_enterprise_route_checks", local_file
|
||||
)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod.EnterpriseRouteChecks
|
||||
|
||||
@patch("litellm.proxy.proxy_server.premium_user", True)
|
||||
def test_should_models_route_allowed_when_llm_api_disabled(self):
|
||||
"""Test that /models is allowed even when LLM API routes are disabled"""
|
||||
EnterpriseRouteChecks = self._get_enterprise_route_checks()
|
||||
|
||||
with patch.object(
|
||||
EnterpriseRouteChecks, "is_llm_api_route_disabled", return_value=True
|
||||
), patch.object(
|
||||
EnterpriseRouteChecks, "is_management_routes_disabled", return_value=False
|
||||
):
|
||||
# /models should NOT raise - it's exempt
|
||||
EnterpriseRouteChecks.should_call_route("/models")
|
||||
|
||||
@patch("litellm.proxy.proxy_server.premium_user", True)
|
||||
def test_should_v1_models_route_allowed_when_llm_api_disabled(self):
|
||||
"""Test that /v1/models is allowed even when LLM API routes are disabled"""
|
||||
EnterpriseRouteChecks = self._get_enterprise_route_checks()
|
||||
|
||||
with patch.object(
|
||||
EnterpriseRouteChecks, "is_llm_api_route_disabled", return_value=True
|
||||
), patch.object(
|
||||
EnterpriseRouteChecks, "is_management_routes_disabled", return_value=False
|
||||
):
|
||||
# /v1/models should NOT raise - it's exempt
|
||||
EnterpriseRouteChecks.should_call_route("/v1/models")
|
||||
|
||||
@patch("litellm.proxy.proxy_server.premium_user", True)
|
||||
def test_should_chat_completions_still_blocked_when_llm_api_disabled(self):
|
||||
"""Test that non-exempt LLM routes like /v1/chat/completions are still blocked"""
|
||||
EnterpriseRouteChecks = self._get_enterprise_route_checks()
|
||||
|
||||
with patch.object(
|
||||
EnterpriseRouteChecks, "is_llm_api_route_disabled", return_value=True
|
||||
), patch.object(
|
||||
EnterpriseRouteChecks, "is_management_routes_disabled", return_value=False
|
||||
):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
EnterpriseRouteChecks.should_call_route("/v1/chat/completions")
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert "LLM API routes are disabled for this instance." in str(
|
||||
exc_info.value.detail
|
||||
)
|
||||
|
||||
@patch("litellm.proxy.proxy_server.premium_user", True)
|
||||
def test_should_embeddings_still_blocked_when_llm_api_disabled(self):
|
||||
"""Test that /v1/embeddings is still blocked when LLM API routes are disabled"""
|
||||
EnterpriseRouteChecks = self._get_enterprise_route_checks()
|
||||
|
||||
with patch.object(
|
||||
EnterpriseRouteChecks, "is_llm_api_route_disabled", return_value=True
|
||||
), patch.object(
|
||||
EnterpriseRouteChecks, "is_management_routes_disabled", return_value=False
|
||||
):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
EnterpriseRouteChecks.should_call_route("/v1/embeddings")
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
|
||||
@patch("litellm.proxy.proxy_server.premium_user", True)
|
||||
def test_should_models_route_allowed_when_llm_api_not_disabled(self):
|
||||
"""Test that /models works normally when LLM API routes are not disabled"""
|
||||
EnterpriseRouteChecks = self._get_enterprise_route_checks()
|
||||
|
||||
with patch.object(
|
||||
EnterpriseRouteChecks, "is_llm_api_route_disabled", return_value=False
|
||||
), patch.object(
|
||||
EnterpriseRouteChecks, "is_management_routes_disabled", return_value=False
|
||||
):
|
||||
# Should not raise
|
||||
EnterpriseRouteChecks.should_call_route("/models")
|
||||
EnterpriseRouteChecks.should_call_route("/v1/models")
|
||||
|
||||
|
||||
def test_route_in_additional_public_routes_wildcard_match():
|
||||
"""
|
||||
Test that route_in_additonal_public_routes supports wildcard patterns.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue