feat(endpoints.py): enable retrieving existing credentials by model name

Enables reusing existing credentials
This commit is contained in:
Krrish Dholakia 2025-03-14 12:02:50 -07:00
parent 6629354329
commit 605a4d1121
3 changed files with 142 additions and 47 deletions

View file

@ -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

View file

@ -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(

View file

@ -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