This commit is contained in:
aoright 2026-09-30 10:29:51 -04:00 • committed by GitHub
commit d4e8c84532
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 933 additions and 501 deletions

View file

@ -340,6 +340,84 @@ def _raise_on_strategy_router_write_violation(
)
def _validate_routable_model_deployment(
incoming_params: GenericLiteLLMParams | None,
existing_params: GenericLiteLLMParams | None = None,
) -> str | None:
"""Validate that incoming litellm_params specifies a routable LLM provider before DB insertion.
When adding or patching a model deployment, the router must be able to resolve its provider.
Unroutable models get dropped by the router upon reload, causing a 500 degraded serving error
and leaving dead zombie rows in LiteLLM_ProxyModelTable. Pre-validating the provider returns
a clean 400 Bad Request instead.
"""
if incoming_params is None:
return None
model = getattr(incoming_params, "model", None)
if model is None and existing_params is not None:
model = getattr(existing_params, "model", None)
if not model or not isinstance(model, str):
return None
# Auto-router pseudo-models are validated separately
if model.startswith("auto_router/"):
return None
# Prompt management models resolve provider dynamically at runtime
if "/" in model:
split_model = model.split("/")[0]
if split_model in litellm._known_custom_logger_compatible_callbacks:
return None
custom_llm_provider = getattr(incoming_params, "custom_llm_provider", None) or (
getattr(existing_params, "custom_llm_provider", None) if existing_params else None
)
api_base = getattr(incoming_params, "api_base", None) or (
getattr(existing_params, "api_base", None) if existing_params else None
)
try:
from litellm.litellm_core_utils.get_llm_provider_logic import (
get_llm_provider,
is_registered_custom_provider,
)
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
_model, resolved_provider, _dynamic_api_key, _api_base = get_llm_provider(
model=model,
custom_llm_provider=custom_llm_provider,
api_base=api_base,
)
if (
resolved_provider not in litellm.provider_list
and not JSONProviderRegistry.exists(resolved_provider)
and not is_registered_custom_provider(resolved_provider)
):
return (
f"Model '{model}' is not a supported or routable provider: unsupported provider '{resolved_provider}'"
)
except Exception as e:
return f"Model '{model}' is not a routable provider: {e}"
return None
def _raise_on_unroutable_model_deployment(
incoming_params: GenericLiteLLMParams | None,
existing_params: GenericLiteLLMParams | None = None,
) -> None:
violation = _validate_routable_model_deployment(incoming_params=incoming_params, existing_params=existing_params)
if violation is None:
return
raise ProxyException(
message=violation,
type=ProxyErrorTypes.bad_request_error.value,
code=status.HTTP_400_BAD_REQUEST,
param="litellm_params.model",
)
def _stored_credential_name(existing_litellm_params: GenericLiteLLMParams | None) -> str | None:
if existing_litellm_params is None or existing_litellm_params.litellm_credential_name is None:
return None
@ -1219,6 +1297,10 @@ async def patch_model(
incoming_params=patch_data.litellm_params,
existing_params=db_model.litellm_params,
)
_raise_on_unroutable_model_deployment(
incoming_params=patch_data.litellm_params,
existing_params=db_model.litellm_params,
)
effective_params: Final = _effective_complexity_router_params(
patch_data.litellm_params, db_model.litellm_params
@ -2436,6 +2518,10 @@ async def add_new_model(
incoming_params=model_params.litellm_params,
existing_params=None,
)
_raise_on_unroutable_model_deployment(
incoming_params=model_params.litellm_params,
existing_params=None,
)
_raise_if_rate_limits_required_but_missing(
litellm_params=model_params.litellm_params,