Skip to content

Commit c67782a

Browse files
Logfire - fix(opentelemetry.py): Fix otel proxy server initialization (#11091)
* fix(opentelemetry.py): Fix otel proxy server initialization Fixes #10349 (comment) * feat(router.py): allow ignoring invalid deployments on model load Prevents invalid models from preventing loading other valid models Fixes issue where on instance spin up invalid models were blocking valid models from being used * test: add additional unit testing * fix(user_api_key_auth.py): return abbreviated key in exception - make it easy to debug which key is invalid for client * docs(config_settings.md): document param * fix(user_api_key_auth.py): fix error string to match previous one
1 parent 9b73928 commit c67782a

File tree

8 files changed

+145
-75
lines changed

8 files changed

+145
-75
lines changed

litellm/integrations/opentelemetry.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,13 @@ def _init_otel_logger_on_litellm_proxy(self):
135135
- Adds Otel as a service callback
136136
- Sets `proxy_server.open_telemetry_logger` to self
137137
"""
138-
from litellm.proxy import proxy_server
138+
try:
139+
from litellm.proxy import proxy_server
140+
except ImportError:
141+
verbose_logger.warning(
142+
"Proxy Server is not installed. Skipping OpenTelemetry initialization."
143+
)
144+
return
139145

140146
# Add Otel as a service callback
141147
if "otel" not in litellm.service_callback:

litellm/proxy/auth/auth_utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,3 +534,7 @@ def get_model_from_request(
534534
model = match.group(1)
535535

536536
return model
537+
538+
539+
def abbreviate_api_key(api_key: str) -> str:
540+
return f"sk-...{api_key[-4:]}"

litellm/proxy/auth/user_api_key_auth.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
)
4040
from litellm.proxy.auth.auth_exception_handler import UserAPIKeyAuthExceptionHandler
4141
from litellm.proxy.auth.auth_utils import (
42+
abbreviate_api_key,
4243
get_end_user_id_from_request_body,
4344
get_model_from_request,
4445
get_request_route,
@@ -751,17 +752,25 @@ async def _user_api_key_auth_builder( # noqa: PLR0915
751752

752753
## check for cache hit (In-Memory Cache)
753754
_user_role = None
755+
abbreviated_api_key = abbreviate_api_key(api_key=api_key)
754756
if api_key.startswith("sk-"):
755757
api_key = hash_token(token=api_key)
756758

757759
if valid_token is None:
758-
valid_token = await get_key_object(
759-
hashed_token=api_key,
760-
prisma_client=prisma_client,
761-
user_api_key_cache=user_api_key_cache,
762-
parent_otel_span=parent_otel_span,
763-
proxy_logging_obj=proxy_logging_obj,
764-
)
760+
try:
761+
valid_token = await get_key_object(
762+
hashed_token=api_key,
763+
prisma_client=prisma_client,
764+
user_api_key_cache=user_api_key_cache,
765+
parent_otel_span=parent_otel_span,
766+
proxy_logging_obj=proxy_logging_obj,
767+
)
768+
except ProxyException as e:
769+
if e.code == 401 or e.code == "401":
770+
e.message = "Authentication Error, Invalid proxy server token passed. Received API Key = {}, Key Hash (Token) ={}. Unable to find token in cache or `LiteLLM_VerificationTokenTable`".format(
771+
abbreviated_api_key, api_key
772+
)
773+
raise e
765774
# update end-user params on valid token
766775
# These can change per request - it's important to update them here
767776
valid_token.end_user_id = end_user_params.get("end_user_id")
@@ -775,13 +784,6 @@ async def _user_api_key_auth_builder( # noqa: PLR0915
775784
valid_token
776785
) # updating it here, allows all downstream reporting / checks to use the updated budget
777786

778-
if valid_token is None:
779-
raise Exception(
780-
"Invalid proxy server token passed. Received API Key (hashed)={}. Unable to find token in cache or `LiteLLM_VerificationTokenTable`".format(
781-
api_key
782-
)
783-
)
784-
785787
user_obj: Optional[LiteLLM_UserTable] = None
786788
valid_token_dict: dict = {}
787789
if valid_token is not None:

litellm/proxy/common_utils/encrypt_decrypt_utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def decrypt_value_helper(
5757
if exception_type == "debug":
5858
verbose_proxy_logger.debug(error_message)
5959
return None
60+
6061
verbose_proxy_logger.error(error_message)
6162
# [Non-Blocking Exception. - this should not block decrypting other values]
6263
return None

litellm/proxy/management_endpoints/key_management_endpoints.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
get_key_object,
3535
get_team_object,
3636
)
37+
from litellm.proxy.auth.auth_utils import abbreviate_api_key
3738
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
3839
from litellm.proxy.common_utils.timezone_utils import get_budget_reset_time
3940
from litellm.proxy.hooks.key_management_event_hooks import KeyManagementEventHooks
@@ -1385,7 +1386,7 @@ async def generate_key_helper_fn( # noqa: PLR0915
13851386
): # allow user to disable storing abbreviated key name (shown in UI, to help figure out which key spent how much)
13861387
pass
13871388
else:
1388-
key_data["key_name"] = f"sk-...{token[-4:]}"
1389+
key_data["key_name"] = abbreviate_api_key(api_key=token)
13891390
saved_token = copy.deepcopy(key_data)
13901391
if isinstance(saved_token["aliases"], str):
13911392
saved_token["aliases"] = json.loads(saved_token["aliases"])

litellm/proxy/proxy_server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,6 +2000,7 @@ async def load_config( # noqa: PLR0915
20002000
router_general_settings=RouterGeneralSettings(
20012001
async_only_mode=True # only init async clients
20022002
),
2003+
ignore_invalid_deployments=True, # don't raise an error if a deployment is invalid
20032004
) # type:ignore
20042005

20052006
if redis_usage_cache is not None and router.cache.redis_cache is None:
@@ -2326,6 +2327,7 @@ async def _update_llm_router(
23262327
router_general_settings=RouterGeneralSettings(
23272328
async_only_mode=True # only init async clients
23282329
),
2330+
ignore_invalid_deployments=True,
23292331
)
23302332
verbose_proxy_logger.debug(f"updated llm_router: {llm_router}")
23312333
else:

litellm/router.py

Lines changed: 82 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ def __init__( # noqa: PLR0915
255255
router_general_settings: Optional[
256256
RouterGeneralSettings
257257
] = RouterGeneralSettings(),
258+
ignore_invalid_deployments: bool = False,
258259
) -> None:
259260
"""
260261
Initialize the Router class with the given parameters for caching, reliability, and routing strategy.
@@ -287,6 +288,7 @@ def __init__( # noqa: PLR0915
287288
routing_strategy_args (dict): Additional args for latency-based routing. Defaults to {}.
288289
alerting_config (AlertingConfig): Slack alerting configuration. Defaults to None.
289290
provider_budget_config (ProviderBudgetConfig): Provider budget configuration. Use this to set llm_provider budget limits. example $100/day to OpenAI, $100/day to Azure, etc. Defaults to None.
291+
ignore_invalid_deployments (bool): Ignores invalid deployments, and continues with other deployments. Default is to raise an error.
290292
Returns:
291293
Router: An instance of the litellm.Router class.
292294
@@ -327,6 +329,7 @@ def __init__( # noqa: PLR0915
327329
from litellm._service_logger import ServiceLogging
328330

329331
self.set_verbose = set_verbose
332+
self.ignore_invalid_deployments = ignore_invalid_deployments
330333
self.debug_level = debug_level
331334
self.enable_pre_call_checks = enable_pre_call_checks
332335
self.enable_tag_filtering = enable_tag_filtering
@@ -502,6 +505,7 @@ def __init__( # noqa: PLR0915
502505
}
503506
}
504507
"""
508+
505509
### ROUTING SETUP ###
506510
self.routing_strategy_init(
507511
routing_strategy=routing_strategy,
@@ -4467,52 +4471,63 @@ def _create_deployment(
44674471
- Deployment: The deployment object
44684472
- None: If the deployment is not active for the current environment (if 'supported_environments' is set in litellm_params)
44694473
"""
4470-
deployment = Deployment(
4471-
**deployment_info,
4472-
model_name=_model_name,
4473-
litellm_params=LiteLLM_Params(**_litellm_params),
4474-
model_info=_model_info,
4475-
)
4474+
try:
4475+
deployment = Deployment(
4476+
**deployment_info,
4477+
model_name=_model_name,
4478+
litellm_params=LiteLLM_Params(**_litellm_params),
4479+
model_info=_model_info,
4480+
)
4481+
for field in CustomPricingLiteLLMParams.model_fields.keys():
4482+
if deployment.litellm_params.get(field) is not None:
4483+
_model_info[field] = deployment.litellm_params[field]
4484+
4485+
## REGISTER MODEL INFO IN LITELLM MODEL COST MAP
4486+
model_id = deployment.model_info.id
4487+
if model_id is not None:
4488+
litellm.register_model(
4489+
model_cost={
4490+
model_id: _model_info,
4491+
}
4492+
)
44764493

4477-
for field in CustomPricingLiteLLMParams.model_fields.keys():
4478-
if deployment.litellm_params.get(field) is not None:
4479-
_model_info[field] = deployment.litellm_params[field]
4494+
## OLD MODEL REGISTRATION ## Kept to prevent breaking changes
4495+
_model_name = deployment.litellm_params.model
4496+
if deployment.litellm_params.custom_llm_provider is not None:
4497+
_model_name = (
4498+
deployment.litellm_params.custom_llm_provider + "/" + _model_name
4499+
)
44804500

4481-
## REGISTER MODEL INFO IN LITELLM MODEL COST MAP
4482-
model_id = deployment.model_info.id
4483-
if model_id is not None:
44844501
litellm.register_model(
44854502
model_cost={
4486-
model_id: _model_info,
4503+
_model_name: _model_info,
44874504
}
44884505
)
44894506

4490-
## OLD MODEL REGISTRATION ## Kept to prevent breaking changes
4491-
_model_name = deployment.litellm_params.model
4492-
if deployment.litellm_params.custom_llm_provider is not None:
4493-
_model_name = (
4494-
deployment.litellm_params.custom_llm_provider + "/" + _model_name
4495-
)
4496-
4497-
litellm.register_model(
4498-
model_cost={
4499-
_model_name: _model_info,
4500-
}
4501-
)
4502-
4503-
## Check if LLM Deployment is allowed for this deployment
4504-
if self.deployment_is_active_for_environment(deployment=deployment) is not True:
4505-
verbose_router_logger.warning(
4506-
f"Ignoring deployment {deployment.model_name} as it is not active for environment {deployment.model_info['supported_environments']}"
4507-
)
4508-
return None
4507+
## Check if LLM Deployment is allowed for this deployment
4508+
if (
4509+
self.deployment_is_active_for_environment(deployment=deployment)
4510+
is not True
4511+
):
4512+
verbose_router_logger.warning(
4513+
f"Ignoring deployment {deployment.model_name} as it is not active for environment {deployment.model_info['supported_environments']}"
4514+
)
4515+
return None
45094516

4510-
deployment = self._add_deployment(deployment=deployment)
4517+
deployment = self._add_deployment(deployment=deployment)
45114518

4512-
model = deployment.to_json(exclude_none=True)
4519+
model = deployment.to_json(exclude_none=True)
45134520

4514-
self.model_list.append(model)
4515-
return deployment
4521+
self.model_list.append(model)
4522+
return deployment
4523+
except Exception as e:
4524+
if self.ignore_invalid_deployments:
4525+
verbose_router_logger.exception(
4526+
f"Error creating deployment: {e}, ignoring and continuing with other deployments."
4527+
)
4528+
return None
4529+
else:
4530+
raise e
45164531

45174532
def deployment_is_active_for_environment(self, deployment: Deployment) -> bool:
45184533
"""
@@ -4760,12 +4775,11 @@ def add_deployment(self, deployment: Deployment) -> Optional[Deployment]:
47604775

47614776
# add to model list
47624777
_deployment = deployment.to_json(exclude_none=True)
4763-
self.model_list.append(_deployment)
4764-
47654778
# initialize client
47664779
self._add_deployment(deployment=deployment)
47674780

47684781
# add to model names
4782+
self.model_list.append(_deployment)
47694783
self.model_names.append(deployment.model_name)
47704784
return deployment
47714785

@@ -4778,31 +4792,40 @@ def upsert_deployment(self, deployment: Deployment) -> Optional[Deployment]:
47784792
Returns:
47794793
- The added/updated deployment
47804794
"""
4781-
# check if deployment already exists
4782-
_deployment_model_id = deployment.model_info.id or ""
4795+
try:
4796+
# check if deployment already exists
4797+
_deployment_model_id = deployment.model_info.id or ""
47834798

4784-
_deployment_on_router: Optional[Deployment] = self.get_deployment(
4785-
model_id=_deployment_model_id
4786-
)
4787-
if _deployment_on_router is not None:
4788-
# deployment with this model_id exists on the router
4789-
if deployment.litellm_params == _deployment_on_router.litellm_params:
4790-
# No need to update
4791-
return None
4799+
_deployment_on_router: Optional[Deployment] = self.get_deployment(
4800+
model_id=_deployment_model_id
4801+
)
4802+
if _deployment_on_router is not None:
4803+
# deployment with this model_id exists on the router
4804+
if deployment.litellm_params == _deployment_on_router.litellm_params:
4805+
# No need to update
4806+
return None
47924807

4793-
# if there is a new litellm param -> then update the deployment
4794-
# remove the previous deployment
4795-
removal_idx: Optional[int] = None
4796-
for idx, model in enumerate(self.model_list):
4797-
if model["model_info"]["id"] == deployment.model_info.id:
4798-
removal_idx = idx
4808+
# if there is a new litellm param -> then update the deployment
4809+
# remove the previous deployment
4810+
removal_idx: Optional[int] = None
4811+
for idx, model in enumerate(self.model_list):
4812+
if model["model_info"]["id"] == deployment.model_info.id:
4813+
removal_idx = idx
47994814

4800-
if removal_idx is not None:
4801-
self.model_list.pop(removal_idx)
4815+
if removal_idx is not None:
4816+
self.model_list.pop(removal_idx)
48024817

4803-
# if the model_id is not in router
4804-
self.add_deployment(deployment=deployment)
4805-
return deployment
4818+
# if the model_id is not in router
4819+
self.add_deployment(deployment=deployment)
4820+
return deployment
4821+
except Exception as e:
4822+
if self.ignore_invalid_deployments:
4823+
verbose_router_logger.warning(
4824+
f"Error upserting deployment: {e}, ignoring and continuing with other deployments."
4825+
)
4826+
return None
4827+
else:
4828+
raise e
48064829

48074830
def delete_deployment(self, id: str) -> Optional[Deployment]:
48084831
"""

tests/litellm/test_router.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,34 @@ async def test_router_amoderation_with_credential_name(mock_amoderation):
296296
)
297297
assert call_kwargs["litellm_credential_name"] == "my-custom-auth"
298298
assert call_kwargs["model"] == "text-moderation-stable"
299+
300+
301+
def test_router_ignore_invalid_deployments():
302+
"""
303+
Test that router.ignore_invalid_deployments is set to True
304+
"""
305+
from litellm.types.router import Deployment
306+
307+
router = litellm.Router(
308+
model_list=[
309+
{
310+
"model_name": "gpt-3.5-turbo",
311+
"litellm_params": {"model": "my-bad-model"},
312+
},
313+
],
314+
ignore_invalid_deployments=True,
315+
)
316+
317+
assert router.ignore_invalid_deployments is True
318+
assert router.get_model_list() == []
319+
320+
## check upsert deployment
321+
router.upsert_deployment(
322+
Deployment(
323+
model_name="gpt-3.5-turbo",
324+
litellm_params={"model": "my-bad-model"},
325+
model_info={"tpm": 1000, "rpm": 1000},
326+
)
327+
)
328+
329+
assert router.get_model_list() == []

0 commit comments

Comments
 (0)