From 81e782c67edc075176c60c4d22de09c3fb80a7d1 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 15 Feb 2024 19:13:10 -0800 Subject: [PATCH 1/8] feat(proxy_server.py): support key-level permissions --- litellm/proxy/_types.py | 2 ++ litellm/proxy/proxy_server.py | 11 +++++++++++ schema.prisma | 1 + 3 files changed, 14 insertions(+) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 7a6e400c89..411b61a408 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -154,6 +154,7 @@ class GenerateKeyRequest(GenerateRequestBase): duration: Optional[str] = None aliases: Optional[dict] = {} config: Optional[dict] = {} + permissions: Optional[dict] = None class GenerateKeyResponse(GenerateKeyRequest): @@ -381,6 +382,7 @@ class LiteLLM_VerificationToken(LiteLLMBase): budget_duration: Optional[str] = None budget_reset_at: Optional[datetime] = None allowed_cache_controls: Optional[list] = [] + permissions: Optional[dict] = None class UserAPIKeyAuth( diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 2931851910..e3efb5e15c 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -1567,6 +1567,7 @@ async def generate_key_helper_fn( update_key_values: Optional[dict] = None, key_alias: Optional[str] = None, allowed_cache_controls: Optional[list] = [], + permissions: Optional[dict] = None, ): global prisma_client, custom_db_client, user_api_key_cache @@ -1596,6 +1597,9 @@ async def generate_key_helper_fn( duration_s = _duration_in_seconds(duration=budget_duration) reset_at = datetime.utcnow() + timedelta(seconds=duration_s) + if permissions is not None and isinstance(permissions, dict): + permissions = json.dumps(permissions) # type: ignore + aliases_json = json.dumps(aliases) config_json = json.dumps(config) metadata_json = json.dumps(metadata) @@ -1604,6 +1608,7 @@ async def generate_key_helper_fn( tpm_limit = tpm_limit rpm_limit = rpm_limit allowed_cache_controls = allowed_cache_controls + try: # Create a new verification token (you may want to enhance this logic based on your needs) user_data = { @@ -1639,6 +1644,7 @@ async def generate_key_helper_fn( "budget_duration": key_budget_duration, "budget_reset_at": key_reset_at, "allowed_cache_controls": allowed_cache_controls, + "permissions": permissions, } if ( general_settings.get("allow_user_auth", False) == True @@ -1652,6 +1658,10 @@ async def generate_key_helper_fn( saved_token["config"] = json.loads(saved_token["config"]) if isinstance(saved_token["metadata"], str): saved_token["metadata"] = json.loads(saved_token["metadata"]) + if saved_token["permissions"] is not None and isinstance( + saved_token["permissions"], str + ): + saved_token["permissions"] = json.loads(saved_token["permissions"]) if saved_token.get("expires", None) is not None and isinstance( saved_token["expires"], datetime ): @@ -2965,6 +2975,7 @@ async def generate_key_fn( - max_budget: Optional[float] - Specify max budget for a given key. - max_parallel_requests: Optional[int] - Rate limit a user based on the number of parallel requests. Raises 429 error, if user's parallel requests > x. - metadata: Optional[dict] - Metadata for key, store information for key. Example metadata = {"team": "core-infra", "app": "app2", "email": "ishaan@berri.ai" } + - permissions: Optional[dict] - key-specific permissions. Currently just used for turning off pii masking (if connected). Example - {"pii": false} Returns: - key: (str) The generated api key diff --git a/schema.prisma b/schema.prisma index e04ed580a7..dd473bb69e 100644 --- a/schema.prisma +++ b/schema.prisma @@ -63,6 +63,7 @@ model LiteLLM_VerificationToken { budget_duration String? budget_reset_at DateTime? allowed_cache_controls String[] @default([]) + permissions Json? } // store proxy config.yaml From cccd577e756445e0f2c5c328ec081e13b79c20ae Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 15 Feb 2024 20:03:32 -0800 Subject: [PATCH 2/8] feat(proxy_server.py): expose new permissions field for keys --- .pre-commit-config.yaml | 4 ++-- litellm/proxy/_types.py | 4 ++-- litellm/proxy/proxy_server.py | 14 ++++++-------- litellm/proxy/schema.prisma | 1 + 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8978e0d1a0..2a84048e0b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,10 @@ repos: - repo: https://github.com/psf/black - rev: stable + rev: 24.2.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 3.8.4 # The version of flake8 to use + rev: 7.0.0 # The version of flake8 to use hooks: - id: flake8 exclude: ^litellm/tests/|^litellm/proxy/proxy_cli.py|^litellm/integrations/|^litellm/proxy/tests/ diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 411b61a408..e2f3e696f6 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -154,7 +154,7 @@ class GenerateKeyRequest(GenerateRequestBase): duration: Optional[str] = None aliases: Optional[dict] = {} config: Optional[dict] = {} - permissions: Optional[dict] = None + permissions: Optional[dict] = {} class GenerateKeyResponse(GenerateKeyRequest): @@ -167,7 +167,7 @@ class GenerateKeyResponse(GenerateKeyRequest): def set_model_info(cls, values): if values.get("token") is not None: values.update({"key": values.get("token")}) - dict_fields = ["metadata", "aliases", "config"] + dict_fields = ["metadata", "aliases", "config", "permissions"] for field in dict_fields: value = values.get(field) if value is not None and isinstance(value, str): diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index e3efb5e15c..48271be0b7 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -1567,7 +1567,7 @@ async def generate_key_helper_fn( update_key_values: Optional[dict] = None, key_alias: Optional[str] = None, allowed_cache_controls: Optional[list] = [], - permissions: Optional[dict] = None, + permissions: Optional[dict] = {}, ): global prisma_client, custom_db_client, user_api_key_cache @@ -1597,11 +1597,9 @@ async def generate_key_helper_fn( duration_s = _duration_in_seconds(duration=budget_duration) reset_at = datetime.utcnow() + timedelta(seconds=duration_s) - if permissions is not None and isinstance(permissions, dict): - permissions = json.dumps(permissions) # type: ignore - aliases_json = json.dumps(aliases) config_json = json.dumps(config) + permissions_json = json.dumps(permissions) metadata_json = json.dumps(metadata) user_id = user_id or str(uuid.uuid4()) user_role = user_role or "app_user" @@ -1644,7 +1642,7 @@ async def generate_key_helper_fn( "budget_duration": key_budget_duration, "budget_reset_at": key_reset_at, "allowed_cache_controls": allowed_cache_controls, - "permissions": permissions, + "permissions": permissions_json, } if ( general_settings.get("allow_user_auth", False) == True @@ -1824,9 +1822,9 @@ async def initialize( user_api_base = api_base dynamic_config[user_model]["api_base"] = api_base if api_version: - os.environ[ - "AZURE_API_VERSION" - ] = api_version # set this for azure - litellm can read this from the env + os.environ["AZURE_API_VERSION"] = ( + api_version # set this for azure - litellm can read this from the env + ) if max_tokens: # model-specific param user_max_tokens = max_tokens dynamic_config[user_model]["max_tokens"] = max_tokens diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index e04ed580a7..a047951dcc 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -55,6 +55,7 @@ model LiteLLM_VerificationToken { config Json @default("{}") user_id String? team_id String? + permissions Json @default("{}") max_parallel_requests Int? metadata Json @default("{}") tpm_limit BigInt? From cd8d35107bd2ff10eeefa9c061a11ca93cc3cc18 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 15 Feb 2024 20:16:15 -0800 Subject: [PATCH 3/8] fix: check key permissions for turning on/off pii masking --- litellm/proxy/_types.py | 2 +- litellm/proxy/hooks/cache_control_check.py | 14 +++++++------ litellm/proxy/hooks/presidio_pii_masking.py | 22 +++++++++++++++------ schema.prisma | 2 +- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index e2f3e696f6..3f8f1944ed 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -382,7 +382,7 @@ class LiteLLM_VerificationToken(LiteLLMBase): budget_duration: Optional[str] = None budget_reset_at: Optional[datetime] = None allowed_cache_controls: Optional[list] = [] - permissions: Optional[dict] = None + permissions: Dict = {} class UserAPIKeyAuth( diff --git a/litellm/proxy/hooks/cache_control_check.py b/litellm/proxy/hooks/cache_control_check.py index c50c4ec1fc..3160fe97ad 100644 --- a/litellm/proxy/hooks/cache_control_check.py +++ b/litellm/proxy/hooks/cache_control_check.py @@ -30,18 +30,20 @@ class _PROXY_CacheControlCheck(CustomLogger): self.print_verbose(f"Inside Cache Control Check Pre-Call Hook") allowed_cache_controls = user_api_key_dict.allowed_cache_controls - if (allowed_cache_controls is None) or ( - len(allowed_cache_controls) == 0 - ): # assume empty list to be nullable - https://github.com/prisma/prisma/issues/847#issuecomment-546895663 - return - if data.get("cache", None) is None: return cache_args = data.get("cache", None) if isinstance(cache_args, dict): for k, v in cache_args.items(): - if k not in allowed_cache_controls: + if ( + (allowed_cache_controls is not None) + and (isinstance(allowed_cache_controls, list)) + and ( + len(allowed_cache_controls) > 0 + ) # assume empty list to be nullable - https://github.com/prisma/prisma/issues/847#issuecomment-546895663 + and k not in allowed_cache_controls + ): raise HTTPException( status_code=403, detail=f"Not allowed to set {k} as a cache control. Contact admin to change permissions.", diff --git a/litellm/proxy/hooks/presidio_pii_masking.py b/litellm/proxy/hooks/presidio_pii_masking.py index 85e6260745..5152046bc5 100644 --- a/litellm/proxy/hooks/presidio_pii_masking.py +++ b/litellm/proxy/hooks/presidio_pii_masking.py @@ -61,7 +61,7 @@ class _OPTIONAL_PresidioPIIMasking(CustomLogger): except: pass - async def check_pii(self, text: str) -> str: + async def check_pii(self, text: str, output_parse_pii: bool) -> str: """ [TODO] make this more performant for high-throughput scenario """ @@ -92,10 +92,7 @@ class _OPTIONAL_PresidioPIIMasking(CustomLogger): start = item["start"] end = item["end"] replacement = item["text"] # replacement token - if ( - item["operator"] == "replace" - and litellm.output_parse_pii == True - ): + if item["operator"] == "replace" and output_parse_pii == True: # check if token in dict # if exists, add a uuid to the replacement token for swapping back to the original text in llm response output parsing if replacement in self.pii_tokens: @@ -125,13 +122,26 @@ class _OPTIONAL_PresidioPIIMasking(CustomLogger): For multiple messages in /chat/completions, we'll need to call them in parallel. """ + permissions = user_api_key_dict.permissions + + if permissions.get("pii", True) == False: # allow key to turn off pii masking + return data + + output_parse_pii = permissions.get( + "output_parse_pii", litellm.output_parse_pii + ) # allow key to turn on/off output parsing for pii + if call_type == "completion": # /chat/completions requests messages = data["messages"] tasks = [] for m in messages: if isinstance(m["content"], str): - tasks.append(self.check_pii(text=m["content"])) + tasks.append( + self.check_pii( + text=m["content"], output_parse_pii=output_parse_pii + ) + ) responses = await asyncio.gather(*tasks) for index, r in enumerate(responses): if isinstance(messages[index]["content"], str): diff --git a/schema.prisma b/schema.prisma index dd473bb69e..a047951dcc 100644 --- a/schema.prisma +++ b/schema.prisma @@ -55,6 +55,7 @@ model LiteLLM_VerificationToken { config Json @default("{}") user_id String? team_id String? + permissions Json @default("{}") max_parallel_requests Int? metadata Json @default("{}") tpm_limit BigInt? @@ -63,7 +64,6 @@ model LiteLLM_VerificationToken { budget_duration String? budget_reset_at DateTime? allowed_cache_controls String[] @default([]) - permissions Json? } // store proxy config.yaml From 34fce009600f80bf2247f8f46a5f7fa07f3824c5 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 15 Feb 2024 20:36:59 -0800 Subject: [PATCH 4/8] fix: dynamo_db.py handle permissions row --- litellm/proxy/db/dynamo_db.py | 7 ++++++- litellm/proxy/proxy_server.py | 9 +++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/db/dynamo_db.py b/litellm/proxy/db/dynamo_db.py index a9461d9225..206fee7777 100644 --- a/litellm/proxy/db/dynamo_db.py +++ b/litellm/proxy/db/dynamo_db.py @@ -282,7 +282,12 @@ class DynamoDBWrapper(CustomDB): new_response = {} for k, v in response.items(): # handle json string if ( - (k == "aliases" or k == "config" or k == "metadata") + ( + k == "aliases" + or k == "config" + or k == "metadata" + or k == "permissions" + ) and v is not None and isinstance(v, str) ): diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 48271be0b7..afa97dc203 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -1013,7 +1013,10 @@ async def update_database( valid_token.spend = new_spend user_api_key_cache.set_cache(key=token, value=valid_token) except Exception as e: - verbose_proxy_logger.info(f"Update Key DB Call failed to execute") + traceback.print_exc() + verbose_proxy_logger.info( + f"Update Key DB Call failed to execute - {str(e)}" + ) ### UPDATE SPEND LOGS ### async def _insert_spend_log_to_db(): @@ -1656,9 +1659,7 @@ async def generate_key_helper_fn( saved_token["config"] = json.loads(saved_token["config"]) if isinstance(saved_token["metadata"], str): saved_token["metadata"] = json.loads(saved_token["metadata"]) - if saved_token["permissions"] is not None and isinstance( - saved_token["permissions"], str - ): + if isinstance(saved_token["permissions"], str): saved_token["permissions"] = json.loads(saved_token["permissions"]) if saved_token.get("expires", None) is not None and isinstance( saved_token["expires"], datetime From 1b844aafdce7aabd99418a3928545d5430c43870 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 15 Feb 2024 21:25:12 -0800 Subject: [PATCH 5/8] fix(huggingface_restapi.py): fix hf streaming to raise exceptions --- litellm/llms/huggingface_restapi.py | 49 +++++++++++++++++------------ 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/litellm/llms/huggingface_restapi.py b/litellm/llms/huggingface_restapi.py index eb8ce38b97..d898ed8c7f 100644 --- a/litellm/llms/huggingface_restapi.py +++ b/litellm/llms/huggingface_restapi.py @@ -49,9 +49,9 @@ class HuggingfaceConfig: details: Optional[bool] = True # enables returning logprobs + best of max_new_tokens: Optional[int] = None repetition_penalty: Optional[float] = None - return_full_text: Optional[ - bool - ] = False # by default don't return the input as part of the output + return_full_text: Optional[bool] = ( + False # by default don't return the input as part of the output + ) seed: Optional[int] = None temperature: Optional[float] = None top_k: Optional[int] = None @@ -188,9 +188,9 @@ class Huggingface(BaseLLM): "content-type": "application/json", } if api_key and headers is None: - default_headers[ - "Authorization" - ] = f"Bearer {api_key}" # Huggingface Inference Endpoint default is to accept bearer tokens + default_headers["Authorization"] = ( + f"Bearer {api_key}" # Huggingface Inference Endpoint default is to accept bearer tokens + ) headers = default_headers elif headers: headers = headers @@ -399,9 +399,12 @@ class Huggingface(BaseLLM): data = { "inputs": prompt, "parameters": optional_params, - "stream": True - if "stream" in optional_params and optional_params["stream"] == True - else False, + "stream": ( + True + if "stream" in optional_params + and optional_params["stream"] == True + else False + ), } input_text = prompt else: @@ -430,9 +433,12 @@ class Huggingface(BaseLLM): data = { "inputs": prompt, "parameters": inference_params, - "stream": True - if "stream" in optional_params and optional_params["stream"] == True - else False, + "stream": ( + True + if "stream" in optional_params + and optional_params["stream"] == True + else False + ), } input_text = prompt ## LOGGING @@ -561,14 +567,12 @@ class Huggingface(BaseLLM): input_text: str, model: str, optional_params: dict, - timeout: float + timeout: float, ): response = None try: async with httpx.AsyncClient(timeout=timeout) as client: - response = await client.post( - url=api_base, json=data, headers=headers - ) + response = await client.post(url=api_base, json=data, headers=headers) response_json = response.json() if response.status_code != 200: raise HuggingfaceError( @@ -607,7 +611,7 @@ class Huggingface(BaseLLM): headers: dict, model_response: ModelResponse, model: str, - timeout: float + timeout: float, ): async with httpx.AsyncClient(timeout=timeout) as client: response = client.stream( @@ -615,9 +619,10 @@ class Huggingface(BaseLLM): ) async with response as r: if r.status_code != 200: + text = await r.aread() raise HuggingfaceError( status_code=r.status_code, - message="An error occurred while streaming", + message=str(text), ) streamwrapper = CustomStreamWrapper( completion_stream=r.aiter_lines(), @@ -625,8 +630,12 @@ class Huggingface(BaseLLM): custom_llm_provider="huggingface", logging_obj=logging_obj, ) - async for transformed_chunk in streamwrapper: - yield transformed_chunk + + async def generator(): + async for transformed_chunk in streamwrapper: + yield transformed_chunk + + return generator() def embedding( self, From c37aad50ead53caaa102bb19cfee1dc606005143 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 15 Feb 2024 21:26:22 -0800 Subject: [PATCH 6/8] fix(utils.py): add more exception mapping for huggingface --- litellm/utils.py | 183 ++++++++++++++++++++++++++++++----------------- 1 file changed, 119 insertions(+), 64 deletions(-) diff --git a/litellm/utils.py b/litellm/utils.py index 35bf4640f8..93e934d4c8 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -12,6 +12,7 @@ import litellm import dotenv, json, traceback, threading, base64, ast import subprocess, os +from os.path import abspath, join, dirname import litellm, openai import itertools import random, uuid, requests @@ -34,6 +35,7 @@ from dataclasses import ( from importlib import resources # filename = pkg_resources.resource_filename(__name__, "llms/tokenizers") + try: filename = str( resources.files().joinpath("llms/tokenizers") # type: ignore @@ -42,9 +44,10 @@ except: filename = str( resources.files(litellm).joinpath("llms/tokenizers") # for python 3.10 ) # for python 3.10+ -os.environ[ - "TIKTOKEN_CACHE_DIR" -] = filename # use local copy of tiktoken b/c of - https://github.com/BerriAI/litellm/issues/1071 +os.environ["TIKTOKEN_CACHE_DIR"] = ( + filename # use local copy of tiktoken b/c of - https://github.com/BerriAI/litellm/issues/1071 +) + encoding = tiktoken.get_encoding("cl100k_base") import importlib.metadata from ._logging import verbose_logger @@ -82,6 +85,20 @@ from .exceptions import ( BudgetExceededError, UnprocessableEntityError, ) + +# Import Enterprise features +project_path = abspath(join(dirname(__file__), "..", "..")) +# Add the "enterprise" directory to sys.path +verbose_logger.debug(f"current project_path: {project_path}") +enterprise_path = abspath(join(project_path, "enterprise")) +sys.path.append(enterprise_path) + +verbose_logger.debug(f"sys.path: {sys.path}") +try: + from enterprise.callbacks.generic_api_callback import GenericAPILogger +except Exception as e: + verbose_logger.debug(f"Exception import enterprise features {str(e)}") + from typing import cast, List, Dict, Union, Optional, Literal, Any from .caching import Cache from concurrent.futures import ThreadPoolExecutor @@ -107,6 +124,7 @@ customLogger = None langFuseLogger = None dynamoLogger = None s3Logger = None +genericAPILogger = None llmonitorLogger = None aispendLogger = None berrispendLogger = None @@ -1087,12 +1105,12 @@ class Logging: self.call_type == CallTypes.aimage_generation.value or self.call_type == CallTypes.image_generation.value ): - self.model_call_details[ - "response_cost" - ] = litellm.completion_cost( - completion_response=result, - model=self.model, - call_type=self.call_type, + self.model_call_details["response_cost"] = ( + litellm.completion_cost( + completion_response=result, + model=self.model, + call_type=self.call_type, + ) ) else: # check if base_model set on azure @@ -1100,12 +1118,12 @@ class Logging: model_call_details=self.model_call_details ) # base_model defaults to None if not set on model_info - self.model_call_details[ - "response_cost" - ] = litellm.completion_cost( - completion_response=result, - call_type=self.call_type, - model=base_model, + self.model_call_details["response_cost"] = ( + litellm.completion_cost( + completion_response=result, + call_type=self.call_type, + model=base_model, + ) ) verbose_logger.debug( f"Model={self.model}; cost={self.model_call_details['response_cost']}" @@ -1174,9 +1192,9 @@ class Logging: verbose_logger.debug( f"Logging Details LiteLLM-Success Call streaming complete" ) - self.model_call_details[ - "complete_streaming_response" - ] = complete_streaming_response + self.model_call_details["complete_streaming_response"] = ( + complete_streaming_response + ) try: if self.model_call_details.get("cache_hit", False) == True: self.model_call_details["response_cost"] = 0.0 @@ -1186,11 +1204,11 @@ class Logging: model_call_details=self.model_call_details ) # base_model defaults to None if not set on model_info - self.model_call_details[ - "response_cost" - ] = litellm.completion_cost( - completion_response=complete_streaming_response, - model=base_model, + self.model_call_details["response_cost"] = ( + litellm.completion_cost( + completion_response=complete_streaming_response, + model=base_model, + ) ) verbose_logger.debug( f"Model={self.model}; cost={self.model_call_details['response_cost']}" @@ -1369,6 +1387,35 @@ class Logging: user_id=kwargs.get("user", None), print_verbose=print_verbose, ) + if callback == "generic": + global genericAPILogger + verbose_logger.debug("reaches langfuse for success logging!") + kwargs = {} + for k, v in self.model_call_details.items(): + if ( + k != "original_response" + ): # copy.deepcopy raises errors as this could be a coroutine + kwargs[k] = v + # this only logs streaming once, complete_streaming_response exists i.e when stream ends + if self.stream: + verbose_logger.debug( + f"is complete_streaming_response in kwargs: {kwargs.get('complete_streaming_response', None)}" + ) + if complete_streaming_response is None: + break + else: + print_verbose("reaches langfuse for streaming logging!") + result = kwargs["complete_streaming_response"] + if genericAPILogger is None: + genericAPILogger = GenericAPILogger() + genericAPILogger.log_event( + kwargs=kwargs, + response_obj=result, + start_time=start_time, + end_time=end_time, + user_id=kwargs.get("user", None), + print_verbose=print_verbose, + ) if callback == "cache" and litellm.cache is not None: # this only logs streaming once, complete_streaming_response exists i.e when stream ends print_verbose("success_callback: reaches cache for logging!") @@ -1448,10 +1495,10 @@ class Logging: ) else: if self.stream and complete_streaming_response: - self.model_call_details[ - "complete_response" - ] = self.model_call_details.get( - "complete_streaming_response", {} + self.model_call_details["complete_response"] = ( + self.model_call_details.get( + "complete_streaming_response", {} + ) ) result = self.model_call_details["complete_response"] callback.log_success_event( @@ -1531,9 +1578,9 @@ class Logging: verbose_logger.debug( "Async success callbacks: Got a complete streaming response" ) - self.model_call_details[ - "complete_streaming_response" - ] = complete_streaming_response + self.model_call_details["complete_streaming_response"] = ( + complete_streaming_response + ) try: if self.model_call_details.get("cache_hit", False) == True: self.model_call_details["response_cost"] = 0.0 @@ -2272,9 +2319,9 @@ def client(original_function): ): print_verbose(f"Checking Cache") preset_cache_key = litellm.cache.get_cache_key(*args, **kwargs) - kwargs[ - "preset_cache_key" - ] = preset_cache_key # for streaming calls, we need to pass the preset_cache_key + kwargs["preset_cache_key"] = ( + preset_cache_key # for streaming calls, we need to pass the preset_cache_key + ) cached_result = litellm.cache.get_cache(*args, **kwargs) if cached_result != None: if "detail" in cached_result: @@ -2572,17 +2619,17 @@ def client(original_function): cached_result = None elif isinstance(litellm.cache.cache, RedisSemanticCache): preset_cache_key = litellm.cache.get_cache_key(*args, **kwargs) - kwargs[ - "preset_cache_key" - ] = preset_cache_key # for streaming calls, we need to pass the preset_cache_key + kwargs["preset_cache_key"] = ( + preset_cache_key # for streaming calls, we need to pass the preset_cache_key + ) cached_result = await litellm.cache.async_get_cache( *args, **kwargs ) else: preset_cache_key = litellm.cache.get_cache_key(*args, **kwargs) - kwargs[ - "preset_cache_key" - ] = preset_cache_key # for streaming calls, we need to pass the preset_cache_key + kwargs["preset_cache_key"] = ( + preset_cache_key # for streaming calls, we need to pass the preset_cache_key + ) cached_result = litellm.cache.get_cache(*args, **kwargs) if cached_result is not None and not isinstance( @@ -3912,16 +3959,16 @@ def get_optional_params( True # so that main.py adds the function call to the prompt ) if "tools" in non_default_params: - optional_params[ - "functions_unsupported_model" - ] = non_default_params.pop("tools") + optional_params["functions_unsupported_model"] = ( + non_default_params.pop("tools") + ) non_default_params.pop( "tool_choice", None ) # causes ollama requests to hang elif "functions" in non_default_params: - optional_params[ - "functions_unsupported_model" - ] = non_default_params.pop("functions") + optional_params["functions_unsupported_model"] = ( + non_default_params.pop("functions") + ) elif ( litellm.add_function_to_prompt ): # if user opts to add it to prompt instead @@ -4101,9 +4148,9 @@ def get_optional_params( optional_params["top_p"] = top_p if n is not None: optional_params["best_of"] = n - optional_params[ - "do_sample" - ] = True # Need to sample if you want best of for hf inference endpoints + optional_params["do_sample"] = ( + True # Need to sample if you want best of for hf inference endpoints + ) if stream is not None: optional_params["stream"] = stream if stop is not None: @@ -4148,9 +4195,9 @@ def get_optional_params( if max_tokens is not None: optional_params["max_tokens"] = max_tokens if frequency_penalty is not None: - optional_params[ - "repetition_penalty" - ] = frequency_penalty # https://docs.together.ai/reference/inference + optional_params["repetition_penalty"] = ( + frequency_penalty # https://docs.together.ai/reference/inference + ) if stop is not None: optional_params["stop"] = stop if tools is not None: @@ -4259,9 +4306,9 @@ def get_optional_params( optional_params["top_p"] = top_p if n is not None: optional_params["best_of"] = n - optional_params[ - "do_sample" - ] = True # Need to sample if you want best of for hf inference endpoints + optional_params["do_sample"] = ( + True # Need to sample if you want best of for hf inference endpoints + ) if stream is not None: optional_params["stream"] = stream if stop is not None: @@ -4584,9 +4631,9 @@ def get_optional_params( extra_body["safe_mode"] = safe_mode if random_seed is not None: extra_body["random_seed"] = random_seed - optional_params[ - "extra_body" - ] = extra_body # openai client supports `extra_body` param + optional_params["extra_body"] = ( + extra_body # openai client supports `extra_body` param + ) elif custom_llm_provider == "openrouter": supported_params = [ "functions", @@ -4655,9 +4702,9 @@ def get_optional_params( extra_body["models"] = models if route is not None: extra_body["route"] = route - optional_params[ - "extra_body" - ] = extra_body # openai client supports `extra_body` param + optional_params["extra_body"] = ( + extra_body # openai client supports `extra_body` param + ) else: # assume passing in params for openai/azure openai supported_params = [ "functions", @@ -6982,6 +7029,14 @@ def exception_type( model=model, response=original_exception.response, ) + elif original_exception.status_code == 503: + exception_mapping_worked = True + raise ServiceUnavailableError( + message=f"HuggingfaceException - {original_exception.message}", + llm_provider="huggingface", + model=model, + response=original_exception.response, + ) else: exception_mapping_worked = True raise APIError( @@ -8413,10 +8468,10 @@ class CustomStreamWrapper: try: completion_obj["content"] = chunk.text if hasattr(chunk.candidates[0], "finish_reason"): - model_response.choices[ - 0 - ].finish_reason = map_finish_reason( - chunk.candidates[0].finish_reason.name + model_response.choices[0].finish_reason = ( + map_finish_reason( + chunk.candidates[0].finish_reason.name + ) ) except: if chunk.candidates[0].finish_reason.name == "SAFETY": From a910490d3b344a0509cd5d2c3554a1a33dce2987 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 15 Feb 2024 21:27:54 -0800 Subject: [PATCH 7/8] fix: fix tests --- litellm/tests/test_completion.py | 2 ++ litellm/tests/test_streaming.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 283269001b..d102be25d3 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -446,6 +446,8 @@ def hf_test_completion_tgi(): ) # Add any assertions here to check the response print(response) + except litellm.ServiceUnavailableError as e: + pass except Exception as e: pytest.fail(f"Error occurred: {e}") diff --git a/litellm/tests/test_streaming.py b/litellm/tests/test_streaming.py index a5497b539c..30d777d799 100644 --- a/litellm/tests/test_streaming.py +++ b/litellm/tests/test_streaming.py @@ -1031,6 +1031,8 @@ async def test_hf_completion_tgi_stream(): if complete_response.strip() == "": raise Exception("Empty response received") print(f"completion_response: {complete_response}") + except litellm.ServiceUnavailableError as e: + pass except Exception as e: pytest.fail(f"Error occurred: {e}") From 122d644e1037a3e2a03fd84e6f9f4f1b8ddcc945 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 15 Feb 2024 21:51:06 -0800 Subject: [PATCH 8/8] fix(test_async_fn.py): fix test --- litellm/tests/test_async_fn.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/litellm/tests/test_async_fn.py b/litellm/tests/test_async_fn.py index 816ef15093..86cbfafbf1 100644 --- a/litellm/tests/test_async_fn.py +++ b/litellm/tests/test_async_fn.py @@ -192,6 +192,8 @@ async def test_hf_completion_tgi(): ) # Add any assertions here to check the response print(response) + except litellm.APIError as e: + pass except litellm.Timeout as e: pass except Exception as e: