diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 18af6399180..f0b5afa67fc 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -719,25 +719,9 @@ class Logging(LiteLLMLoggingBaseClass): Masks the headers of the request sent from LiteLLM """ - sensitive_keywords = [ - "authorization", - "token", - "key", - "secret", - ] - return { - k: ( - (v[:-44] + "*" * 44) - if (isinstance(v, str) and len(v) > 44) - else "*****" - ) - for k, v in headers.items() - if not ignore_sensitive_headers - or not any( - sensitive_keyword in k.lower() - for sensitive_keyword in sensitive_keywords - ) - } + return _get_masked_values( + headers, ignore_sensitive_values=ignore_sensitive_headers + ) def post_call( self, original_response, input=None, api_key=None, additional_args={} @@ -2413,6 +2397,58 @@ class Logging(LiteLLMLoggingBaseClass): return result +def _get_masked_values( + sensitive_object: dict, + ignore_sensitive_values: bool = False, + mask_all_values: bool = False, + unmasked_length: int = 44, + number_of_asterisks: Optional[int] = None, +) -> dict: + """ + Internal debugging helper function + + Masks the headers of the request sent from LiteLLM + + Args: + masked_length: Optional length for the masked portion (number of *). If set, will use exactly this many * + regardless of original string length. The total length will be unmasked_length + masked_length. + """ + sensitive_keywords = [ + "authorization", + "token", + "key", + "secret", + ] + return { + k: ( + ( + v[: unmasked_length // 2] + + "*" * number_of_asterisks + + v[-unmasked_length // 2 :] + ) + if ( + isinstance(v, str) + and len(v) > unmasked_length + and number_of_asterisks is not None + ) + else ( + ( + v[: unmasked_length // 2] + + "*" * (len(v) - unmasked_length) + + v[-unmasked_length // 2 :] + ) + if (isinstance(v, str) and len(v) > unmasked_length) + else "*****" + ) + ) + for k, v in sensitive_object.items() + if not ignore_sensitive_values + or not any( + sensitive_keyword in k.lower() for sensitive_keyword in sensitive_keywords + ) + } + + def set_callbacks(callback_list, function_id=None): # noqa: PLR0915 """ Globally sets the callback client diff --git a/litellm/proxy/credential_endpoints/endpoints.py b/litellm/proxy/credential_endpoints/endpoints.py index 75d89502b5b..466766fc2db 100644 --- a/litellm/proxy/credential_endpoints/endpoints.py +++ b/litellm/proxy/credential_endpoints/endpoints.py @@ -2,15 +2,19 @@ CRUD endpoints for storing reusable credentials. """ +from typing import Optional + from fastapi import APIRouter, Depends, HTTPException, Request, Response import litellm from litellm._logging import verbose_proxy_logger from litellm.litellm_core_utils.credential_accessor import CredentialAccessor +from litellm.litellm_core_utils.litellm_logging import _get_masked_values from litellm.proxy._types import CommonProxyErrors, UserAPIKeyAuth from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.common_utils.encrypt_decrypt_utils import encrypt_value_helper from litellm.proxy.utils import handle_exception_on_proxy, jsonify_object +from litellm.types.router import CredentialLiteLLMParams from litellm.types.utils import CredentialItem router = APIRouter() @@ -101,30 +105,76 @@ async def get_credentials( @router.get( - "/credentials/{credential_name}", + "/credentials/by_name/{credential_name}", dependencies=[Depends(user_api_key_auth)], tags=["credential management"], + response_model=CredentialItem, +) +@router.get( + "/credentials/by_model/{model_id}", + dependencies=[Depends(user_api_key_auth)], + tags=["credential management"], + response_model=CredentialItem, ) async def get_credential( request: Request, fastapi_response: Response, - credential_name: str, + credential_name: Optional[str] = None, + model_id: Optional[str] = None, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): """ [BETA] endpoint. This might change unexpectedly. """ + from litellm.proxy.proxy_server import llm_router + try: - for credential in litellm.credential_list: - if credential.credential_name == credential_name: - masked_credential = { - "credential_name": credential.credential_name, - "credential_values": credential.credential_values, - } - return {"success": True, "credential": masked_credential} - return {"success": False, "message": "Credential not found"} + if model_id: + if llm_router is None: + raise HTTPException(status_code=500, detail="LLM router not found") + # get model from router + model = llm_router.get_deployment(model_id) + if model is None: + raise HTTPException( + status_code=404, detail="Model not found. Got model ID: " + model_id + ) + # get credential object from model + credential_values = _get_masked_values( + CredentialLiteLLMParams(**model.litellm_params.model_dump()).model_dump( + exclude_none=True + ), + unmasked_length=4, + number_of_asterisks=4, + ) + credential = CredentialItem( + credential_name="{}-credential-{}".format(model.model_name, model_id), + credential_values=credential_values, + credential_info={}, + ) + # return credential object + return credential + elif credential_name: + for credential in litellm.credential_list: + if credential.credential_name == credential_name: + masked_credential = CredentialItem( + credential_name=credential.credential_name, + credential_values=_get_masked_values( + credential.credential_values + ), + credential_info=credential.credential_info, + ) + return masked_credential + raise HTTPException( + status_code=404, + detail="Credential not found. Got credential name: " + credential_name, + ) + else: + raise HTTPException( + status_code=404, detail="Credential name or model ID required" + ) except Exception as e: - return handle_exception_on_proxy(e) + verbose_proxy_logger.exception(e) + raise handle_exception_on_proxy(e) @router.delete( diff --git a/litellm/types/router.py b/litellm/types/router.py index 9a5fb168dac..e34366aa229 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -144,7 +144,26 @@ class ModelInfo(BaseModel): setattr(self, key, value) -class GenericLiteLLMParams(BaseModel): +class CredentialLiteLLMParams(BaseModel): + api_key: Optional[str] = None + api_base: Optional[str] = None + api_version: Optional[str] = None + ## VERTEX AI ## + vertex_project: Optional[str] = None + vertex_location: Optional[str] = None + vertex_credentials: Optional[Union[str, dict]] = None + ## UNIFIED PROJECT/REGION ## + region_name: Optional[str] = None + + ## AWS BEDROCK / SAGEMAKER ## + aws_access_key_id: Optional[str] = None + aws_secret_access_key: Optional[str] = None + aws_region_name: Optional[str] = None + ## IBM WATSONX ## + watsonx_region_name: Optional[str] = None + + +class GenericLiteLLMParams(CredentialLiteLLMParams): """ LiteLLM Params without 'model' arg (used across completion / assistants api) """ @@ -152,9 +171,6 @@ class GenericLiteLLMParams(BaseModel): custom_llm_provider: Optional[str] = None tpm: Optional[int] = None rpm: Optional[int] = None - api_key: Optional[str] = None - api_base: Optional[str] = None - api_version: Optional[str] = None timeout: Optional[Union[float, str, httpx.Timeout]] = ( None # if str, pass in as os.environ/ ) @@ -167,18 +183,7 @@ class GenericLiteLLMParams(BaseModel): ## LOGGING PARAMS ## litellm_trace_id: Optional[str] = None - ## UNIFIED PROJECT/REGION ## - region_name: Optional[str] = None - ## VERTEX AI ## - vertex_project: Optional[str] = None - vertex_location: Optional[str] = None - vertex_credentials: Optional[Union[str, dict]] = None - ## AWS BEDROCK / SAGEMAKER ## - aws_access_key_id: Optional[str] = None - aws_secret_access_key: Optional[str] = None - aws_region_name: Optional[str] = None - ## IBM WATSONX ## - watsonx_region_name: Optional[str] = None + ## CUSTOM PRICING ## input_cost_per_token: Optional[float] = None output_cost_per_token: Optional[float] = None @@ -245,7 +250,11 @@ class GenericLiteLLMParams(BaseModel): args.pop("__class__", None) if max_retries is not None and isinstance(max_retries, str): max_retries = int(max_retries) # cast to int - super().__init__(max_retries=max_retries, **args, **params) + # We need to keep max_retries in args since it's a parameter of GenericLiteLLMParams + args["max_retries"] = ( + max_retries # Put max_retries back in args after popping it + ) + super().__init__(**args, **params) def __contains__(self, key): # Define custom behavior for the 'in' operator