diff --git a/litellm/constants.py b/litellm/constants.py index b1a0021bcc6..a25aac293fd 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -183,6 +183,11 @@ RUNWAYML_POLLING_TIMEOUT = int( ########## Networking constants ############################################################## _DEFAULT_TTL_FOR_HTTPX_CLIENTS = 3600 # 1 hour, re-use the same httpx client for 1 hour +# Team-scoped model overrides feature flag +LITELLM_TEAM_MODEL_OVERRIDES: bool = ( + os.getenv("LITELLM_TEAM_MODEL_OVERRIDES", "false").lower() == "true" +) + # Aiohttp connection pooling - prevents memory leaks from unbounded connection growth # Set to 0 for unlimited (not recommended for production) AIOHTTP_CONNECTOR_LIMIT = int(os.getenv("AIOHTTP_CONNECTOR_LIMIT", 300)) diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index a82a1849194..a8955f46d15 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -65,7 +65,7 @@ from litellm.utils import get_utc_datetime from .auth_checks_organization import organization_role_based_access_check from .auth_utils import get_model_from_request -from litellm.proxy.management_endpoints.common_utils import _is_team_model_overrides_enabled +from litellm.constants import LITELLM_TEAM_MODEL_OVERRIDES if TYPE_CHECKING: from opentelemetry.trace import Span as _Span @@ -2581,15 +2581,22 @@ async def can_team_access_model( 2. If not allowed natively, falls back to access_group_ids on the team """ models_to_check: List[str] = team_object.models if team_object else [] - if _is_team_model_overrides_enabled() and valid_token: + if LITELLM_TEAM_MODEL_OVERRIDES and valid_token: # Compute effective models: team defaults + per-user overrides effective_models = compute_effective_team_models( team_default_models=valid_token.team_default_models, team_member_models=valid_token.team_member_models, ) - # If effective_models is empty, and feature is enabled, deny access - if len(effective_models) == 0: + if effective_models: + models_to_check = effective_models + elif team_object and team_object.models: + # Fallback: effective models empty but team has models configured. + # Graceful degradation prevents misconfiguration cliff when feature + # is enabled without populating default_models. + models_to_check = team_object.models + else: + # Both effective models and team.models are empty — deny access raise ProxyException( message=f"Team not allowed to access model. No models available for user in this team. Model={model}.", type=ProxyErrorTypes.team_model_access_denied, @@ -2597,8 +2604,6 @@ async def can_team_access_model( code=status.HTTP_403_FORBIDDEN, ) - models_to_check = effective_models - try: return _can_object_call_model( model=model, diff --git a/litellm/proxy/management_endpoints/common_utils.py b/litellm/proxy/management_endpoints/common_utils.py index 45d3e795bab..03043c5e79d 100644 --- a/litellm/proxy/management_endpoints/common_utils.py +++ b/litellm/proxy/management_endpoints/common_utils.py @@ -1,4 +1,3 @@ -import os from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from litellm._logging import verbose_proxy_logger @@ -448,4 +447,6 @@ def _update_metadata_fields(updated_kv: dict) -> None: def _is_team_model_overrides_enabled() -> bool: - return os.getenv("LITELLM_TEAM_MODEL_OVERRIDES", "false").lower() == "true" + from litellm.constants import LITELLM_TEAM_MODEL_OVERRIDES + + return LITELLM_TEAM_MODEL_OVERRIDES diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 166e5ee9a3d..f7ccf0fc202 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -925,14 +925,17 @@ async def _validate_key_models_against_effective_team_models( team_member_models=member_models, ) - # 3. Step 6b: If effective models are empty, deny access (empty list != all access) + # 3. Fallback: if effective models empty but team has models, use team.models if not effective_models: - raise HTTPException( - status_code=403, - detail={ - "error": f"No models available for User={user_id} in Team={team_id}. Admins must set 'default_models' on the team or per-user 'models' overrides." - }, - ) + if team_table.models: + effective_models = team_table.models + else: + raise HTTPException( + status_code=403, + detail={ + "error": f"No models available for User={user_id} in Team={team_id}. Admins must set 'default_models' on the team or per-user 'models' overrides." + }, + ) # 4. Step 6b: If data.models is empty, default to effective models if not data.models: