From 85f1591413d5faa44155ef53ae8080708e311a54 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 2 Aug 2023 13:27:10 -0700 Subject: [PATCH] bug fixes and updates --- build/lib/litellm/__init__.py | 33 +- build/lib/litellm/main.py | 439 +++++++------------ build/lib/litellm/timeout.py | 80 ++++ build/lib/litellm/utils.py | 316 +++++++++++++ dist/litellm-0.1.2-py3-none-any.whl | Bin 6039 -> 0 bytes dist/litellm-0.1.2.tar.gz | Bin 6289 -> 0 bytes dist/litellm-0.1.216-py3-none-any.whl | Bin 0 -> 9879 bytes dist/litellm-0.1.216.tar.gz | Bin 0 -> 9902 bytes litellm.egg-info/PKG-INFO | 2 +- litellm.egg-info/SOURCES.txt | 1 + litellm/__pycache__/__init__.cpython-311.pyc | Bin 788 -> 788 bytes litellm/__pycache__/main.cpython-311.pyc | Bin 11671 -> 11671 bytes litellm/__pycache__/utils.cpython-311.pyc | Bin 17752 -> 17317 bytes litellm/tests/test_client.py | 1 + litellm/tests/test_no_client.py | 23 + litellm/utils.py | 79 ++-- setup.py | 2 +- 17 files changed, 646 insertions(+), 330 deletions(-) create mode 100644 build/lib/litellm/timeout.py create mode 100644 build/lib/litellm/utils.py delete mode 100644 dist/litellm-0.1.2-py3-none-any.whl delete mode 100644 dist/litellm-0.1.2.tar.gz create mode 100644 dist/litellm-0.1.216-py3-none-any.whl create mode 100644 dist/litellm-0.1.216.tar.gz create mode 100644 litellm/tests/test_no_client.py diff --git a/build/lib/litellm/__init__.py b/build/lib/litellm/__init__.py index fd66e12bf32..73257a8bce6 100644 --- a/build/lib/litellm/__init__.py +++ b/build/lib/litellm/__init__.py @@ -1,2 +1,31 @@ -__version__ = "1.0.0" -from .main import * # Import all the symbols from main.py \ No newline at end of file +success_callback = [] +failure_callback = [] +set_verbose=False +telemetry=True +####### COMPLETION MODELS ################### +open_ai_chat_completion_models = [ + 'gpt-3.5-turbo', + 'gpt-4' +] +open_ai_text_completion_models = [ + 'text-davinci-003' +] + +cohere_models = [ + 'command-nightly', +] + +anthropic_models = [ + "claude-2", + "claude-instant-1" +] + +####### EMBEDDING MODELS ################### +open_ai_embedding_models = [ + 'text-embedding-ada-002' +] + +from .timeout import timeout +from .utils import client, logging, exception_type # Import all the symbols from main.py +from .main import * # Import all the symbols from main.py + diff --git a/build/lib/litellm/main.py b/build/lib/litellm/main.py index d4fc60053a3..a90c9bf81fe 100644 --- a/build/lib/litellm/main.py +++ b/build/lib/litellm/main.py @@ -1,49 +1,77 @@ import os, openai, cohere, replicate, sys from typing import Any -from func_timeout import func_set_timeout, FunctionTimedOut from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT -import json import traceback -import threading import dotenv import traceback -import subprocess +import litellm +from litellm import client, logging, exception_type, timeout, success_callback, failure_callback +import random ####### ENVIRONMENT VARIABLES ################### -# Loading env variables using dotenv -dotenv.load_dotenv() -set_verbose = False - -####### COMPLETION MODELS ################### -open_ai_chat_completion_models = [ - 'gpt-3.5-turbo', - 'gpt-4' -] -open_ai_text_completion_models = [ - 'text-davinci-003' -] - -cohere_models = [ - 'command-nightly', -] - -anthropic_models = [ - "claude-2", - "claude-instant-1" -] - -####### EMBEDDING MODELS ################### -open_ai_embedding_models = [ - 'text-embedding-ada-002' -] - -############################################# +dotenv.load_dotenv() # Loading env variables using dotenv +def get_optional_params( + # 12 optional params + functions = [], + function_call = "", + temperature = 1, + top_p = 1, + n = 1, + stream = False, + stop = None, + max_tokens = float('inf'), + presence_penalty = 0, + frequency_penalty = 0, + logit_bias = {}, + user = "", +): + optional_params = {} + if functions != []: + optional_params["functions"] = functions + if function_call != "": + optional_params["function_call"] = function_call + if temperature != 1: + optional_params["temperature"] = temperature + if top_p != 1: + optional_params["top_p"] = top_p + if n != 1: + optional_params["n"] = n + if stream: + optional_params["stream"] = stream + if stop != None: + optional_params["stop"] = stop + if max_tokens != float('inf'): + optional_params["max_tokens"] = max_tokens + if presence_penalty != 0: + optional_params["presence_penalty"] = presence_penalty + if frequency_penalty != 0: + optional_params["frequency_penalty"] = frequency_penalty + if logit_bias != {}: + optional_params["logit_bias"] = logit_bias + if user != "": + optional_params["user"] = user + return optional_params ####### COMPLETION ENDPOINTS ################ ############################################# -@func_set_timeout(10, allowOverride=True) ## https://pypi.org/project/func-timeout/ - timeouts, in case calls hang (e.g. Azure) -def completion(model, messages, max_tokens=None, forceTimeout=10, azure=False, logger_fn=None): +@client +@timeout(60) ## set timeouts, in case calls hang (e.g. Azure) - default is 60s, override with `force_timeout` +def completion( + model, messages, # required params + # Optional OpenAI params: see https://platform.openai.com/docs/api-reference/chat/create + functions=[], function_call="", # optional params + temperature=1, top_p=1, n=1, stream=False, stop=None, max_tokens=float('inf'), + presence_penalty=0, frequency_penalty=0, logit_bias={}, user="", + # Optional liteLLM function params + *, force_timeout=60, azure=False, logger_fn=None, verbose=False + ): try: + # check if user passed in any of the OpenAI optional params + optional_params = get_optional_params( + functions=functions, function_call=function_call, + temperature=temperature, top_p=top_p, n=n, stream=stream, stop=stop, max_tokens=max_tokens, + presence_penalty=presence_penalty, frequency_penalty=frequency_penalty, logit_bias=logit_bias, user=user + ) if azure == True: # azure configs openai.api_type = "azure" @@ -51,21 +79,49 @@ def completion(model, messages, max_tokens=None, forceTimeout=10, azure=False, l openai.api_version = os.environ.get("AZURE_API_VERSION") openai.api_key = os.environ.get("AZURE_API_KEY") ## LOGGING - logging(model=model, input=input, azure=azure, logger_fn=logger_fn) + logging(model=model, input=messages, azure=azure, logger_fn=logger_fn) ## COMPLETION CALL response = openai.ChatCompletion.create( engine=model, - messages = messages + messages = messages, + **optional_params ) - elif "replicate" in model: + elif model in litellm.open_ai_chat_completion_models: + openai.api_type = "openai" + openai.api_base = "https://api.openai.com/v1" + openai.api_version = None + openai.api_key = os.environ.get("OPENAI_API_KEY") + ## LOGGING + logging(model=model, input=messages, azure=azure, logger_fn=logger_fn) + + ## COMPLETION CALL + response = openai.ChatCompletion.create( + model=model, + messages = messages, + **optional_params + ) + elif model in litellm.open_ai_text_completion_models: + openai.api_type = "openai" + openai.api_base = "https://api.openai.com/v1" + openai.api_version = None + openai.api_key = os.environ.get("OPENAI_API_KEY") + prompt = " ".join([message["content"] for message in messages]) + ## LOGGING + logging(model=model, input=prompt, azure=azure, logger_fn=logger_fn) + ## COMPLETION CALL + response = openai.Completion.create( + model=model, + prompt = prompt + ) + elif "replicate" in model: # replicate defaults to os.environ.get("REPLICATE_API_TOKEN") # checking in case user set it to REPLICATE_API_KEY instead - if not os.environ.get("REPLICATE_API_TOKEN") and os.environ.get("REPLICATE_API_KEY"): + if not os.environ.get("REPLICATE_API_TOKEN") and os.environ.get("REPLICATE_API_KEY"): replicate_api_token = os.environ.get("REPLICATE_API_KEY") os.environ["REPLICATE_API_TOKEN"] = replicate_api_token prompt = " ".join([message["content"] for message in messages]) - input = [{"prompt": prompt}] - if max_tokens: + input = {"prompt": prompt} + if max_tokens != float('inf'): input["max_length"] = max_tokens # for t5 models input["max_new_tokens"] = max_tokens # for llama2 models ## LOGGING @@ -90,7 +146,7 @@ def completion(model, messages, max_tokens=None, forceTimeout=10, azure=False, l ] } response = new_response - elif model in anthropic_models: + elif model in litellm.anthropic_models: #anthropic defaults to os.environ.get("ANTHROPIC_API_KEY") prompt = f"{HUMAN_PROMPT}" for message in messages: @@ -103,9 +159,10 @@ def completion(model, messages, max_tokens=None, forceTimeout=10, azure=False, l prompt += f"{HUMAN_PROMPT}{message['content']}" prompt += f"{AI_PROMPT}" anthropic = Anthropic() - if max_tokens: + # check if user passed in max_tokens != float('inf') + if max_tokens != float('inf'): max_tokens_to_sample = max_tokens - else: + else: max_tokens_to_sample = 300 # default in Anthropic docs https://docs.anthropic.com/claude/reference/client-libraries ## LOGGING logging(model=model, input=prompt, azure=azure, additional_args={"max_tokens": max_tokens}, logger_fn=logger_fn) @@ -127,9 +184,9 @@ def completion(model, messages, max_tokens=None, forceTimeout=10, azure=False, l } ] } - print(f"new response: {new_response}") + print_verbose(f"new response: {new_response}") response = new_response - elif model in cohere_models: + elif model in litellm.cohere_models: cohere_key = os.environ.get("COHERE_API_KEY") co = cohere.Client(cohere_key) prompt = " ".join([message["content"] for message in messages]) @@ -146,7 +203,7 @@ def completion(model, messages, max_tokens=None, forceTimeout=10, azure=False, l "finish_reason": "stop", "index": 0, "message": { - "content": response[0], + "content": response[0].text, "role": "assistant" } } @@ -154,7 +211,7 @@ def completion(model, messages, max_tokens=None, forceTimeout=10, azure=False, l } response = new_response - elif model in open_ai_chat_completion_models: + elif model in litellm.open_ai_chat_completion_models: openai.api_type = "openai" openai.api_base = "https://api.openai.com/v1" openai.api_version = None @@ -166,7 +223,7 @@ def completion(model, messages, max_tokens=None, forceTimeout=10, azure=False, l model=model, messages = messages ) - elif model in open_ai_text_completion_models: + elif model in litellm.open_ai_text_completion_models: openai.api_type = "openai" openai.api_base = "https://api.openai.com/v1" openai.api_version = None @@ -181,249 +238,59 @@ def completion(model, messages, max_tokens=None, forceTimeout=10, azure=False, l ) else: logging(model=model, input=messages, azure=azure, logger_fn=logger_fn) + args = locals() + raise ValueError(f"No valid completion model args passed in - {args}") return response except Exception as e: - logging(model=model, input=messages, azure=azure, additional_args={"max_tokens": max_tokens}, logger_fn=logger_fn) - raise e + # log the original exception + logging(model=model, input=messages, azure=azure, additional_args={"max_tokens": max_tokens}, logger_fn=logger_fn, exception=e) + ## Map to OpenAI Exception + raise exception_type(model=model, original_exception=e) ### EMBEDDING ENDPOINTS #################### -@func_set_timeout(60, allowOverride=True) ## https://pypi.org/project/func-timeout/ -def embedding(model, input=[], azure=False, forceTimeout=60, logger_fn=None): - response = None - if azure == True: - # azure configs - openai.api_type = "azure" - openai.api_base = os.environ.get("AZURE_API_BASE") - openai.api_version = os.environ.get("AZURE_API_VERSION") - openai.api_key = os.environ.get("AZURE_API_KEY") - ## LOGGING - logging(model=model, input=input, azure=azure, logger_fn=logger_fn) - ## EMBEDDING CALL - response = openai.Embedding.create(input=input, engine=model) - print_verbose(f"response_value: {str(response)[:50]}") - elif model in open_ai_embedding_models: - openai.api_type = "openai" - openai.api_base = "https://api.openai.com/v1" - openai.api_version = None - openai.api_key = os.environ.get("OPENAI_API_KEY") - ## LOGGING - logging(model=model, input=input, azure=azure, logger_fn=logger_fn) - ## EMBEDDING CALL - response = openai.Embedding.create(input=input, model=model) - print_verbose(f"response_value: {str(response)[:50]}") - else: - logging(model=model, input=input, azure=azure, logger_fn=logger_fn) - - return response - - -### CLIENT CLASS #################### make it easy to push completion/embedding runs to different sources -> sentry/posthog/slack, etc. -class litellm_client: - def __init__(self, success_callback=[], failure_callback=[], verbose=False): # Constructor - set_verbose = verbose - self.success_callback = success_callback - self.failure_callback = failure_callback - self.logger_fn = None # if user passes in their own logging function - self.callback_list = list(set(self.success_callback + self.failure_callback)) - self.set_callbacks() - - ## COMPLETION CALL - def completion(self, model, messages, max_tokens=None, forceTimeout=10, azure=False, logger_fn=None, additional_details={}) -> Any: - try: - self.logger_fn = logger_fn - response = completion(model=model, messages=messages, max_tokens=max_tokens, forceTimeout=forceTimeout, azure=azure, logger_fn=self.handle_input) - my_thread = threading.Thread(target=self.handle_success, args=(model, messages, additional_details)) # don't interrupt execution of main thread - my_thread.start() - return response - except Exception as e: - args = locals() # get all the param values - self.handle_failure(e, args) - raise e - - ## EMBEDDING CALL - def embedding(self, model, input=[], azure=False, logger_fn=None, forceTimeout=60, additional_details={}) -> Any: - try: - self.logger_fn = logger_fn - response = embedding(model, input, azure=azure, logger_fn=self.handle_input) - my_thread = threading.Thread(target=self.handle_success, args=(model, input, additional_details)) # don't interrupt execution of main thread - my_thread.start() - return response - except Exception as e: - args = locals() # get all the param values - self.handle_failure(e, args) - raise e - - - def set_callbacks(self): #instantiate any external packages - for callback in self.callback_list: # only install what's required - if callback == "sentry": - try: - import sentry_sdk - except ImportError: - print_verbose("Package 'sentry_sdk' is missing. Installing it...") - subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'sentry_sdk']) - import sentry_sdk - self.sentry_sdk = sentry_sdk - self.sentry_sdk.init(dsn=os.environ.get("SENTRY_API_URL"), traces_sample_rate=float(os.environ.get("SENTRY_API_TRACE_RATE"))) - self.capture_exception = self.sentry_sdk.capture_exception - self.add_breadcrumb = self.sentry_sdk.add_breadcrumb - elif callback == "posthog": - try: - from posthog import Posthog - except: - print_verbose("Package 'posthog' is missing. Installing it...") - subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'posthog']) - from posthog import Posthog - self.posthog = Posthog( - project_api_key=os.environ.get("POSTHOG_API_KEY"), - host=os.environ.get("POSTHOG_API_URL")) - elif callback == "slack": - try: - from slack_bolt import App - except ImportError: - print_verbose("Package 'slack_bolt' is missing. Installing it...") - subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'slack_bolt']) - from slack_bolt import App - self.slack_app = App( - token=os.environ.get("SLACK_API_TOKEN"), - signing_secret=os.environ.get("SLACK_API_SECRET") - ) - self.alerts_channel = os.environ["SLACK_API_CHANNEL"] - - def handle_input(self, model_call_details={}): - if len(model_call_details.keys()) > 0: - model = model_call_details["model"] if "model" in model_call_details else None - if model: - for callback in self.callback_list: - if callback == "sentry": # add a sentry breadcrumb if user passed in sentry integration - self.add_breadcrumb( - category=f'{model}', - message='Trying request model {} input {}'.format(model, json.dumps(model_call_details)), - level='info', - ) - if self.logger_fn and callable(self.logger_fn): - self.logger_fn(model_call_details) - pass - - def handle_success(self, model, messages, additional_details): - success_handler = additional_details.pop("success_handler", None) - failure_handler = additional_details.pop("failure_handler", None) - additional_details["litellm_model"] = str(model) - additional_details["litellm_messages"] = str(messages) - for callback in self.success_callback: - try: - if callback == "posthog": - ph_obj = {} - for detail in additional_details: - ph_obj[detail] = additional_details[detail] - event_name = additional_details["successful_event"] if "successful_event" in additional_details else "litellm.succes_query" - if "user_id" in additional_details: - self.posthog.capture(additional_details["user_id"], event_name, ph_obj) - else: - self.posthog.capture(event_name, ph_obj) - pass - elif callback == "slack": - slack_msg = "" - if len(additional_details.keys()) > 0: - for detail in additional_details: - slack_msg += f"{detail}: {additional_details[detail]}\n" - slack_msg += f"Successful call" - self.slack_app.client.chat_postMessage(channel=self.alerts_channel, text=slack_msg) - except: - pass - - if success_handler and callable(success_handler): - call_details = { - "model": model, - "messages": messages, - "additional_details": additional_details - } - success_handler(call_details) - pass - - def handle_failure(self, exception, args): - args.pop("self") - additional_details = args.pop("additional_details", {}) - - success_handler = additional_details.pop("success_handler", None) - failure_handler = additional_details.pop("failure_handler", None) - - for callback in self.failure_callback: - try: - if callback == "slack": - slack_msg = "" - for param in args: - slack_msg += f"{param}: {args[param]}\n" - if len(additional_details.keys()) > 0: - for detail in additional_details: - slack_msg += f"{detail}: {additional_details[detail]}\n" - slack_msg += f"Traceback: {traceback.format_exc()}" - self.slack_app.client.chat_postMessage(channel=self.alerts_channel, text=slack_msg) - elif callback == "sentry": - self.capture_exception(exception) - elif callback == "posthog": - if len(additional_details.keys()) > 0: - ph_obj = {} - for param in args: - ph_obj[param] += args[param] - for detail in additional_details: - ph_obj[detail] = additional_details[detail] - event_name = additional_details["failed_event"] if "failed_event" in additional_details else "litellm.failed_query" - if "user_id" in additional_details: - self.posthog.capture(additional_details["user_id"], event_name, ph_obj) - else: - self.posthog.capture(event_name, ph_obj) - else: - pass - except: - print(f"got an error calling {callback} - {traceback.format_exc()}") - - if failure_handler and callable(failure_handler): - call_details = { - "exception": exception, - "additional_details": additional_details - } - failure_handler(call_details) - pass -####### HELPER FUNCTIONS ################ - -#Logging function -> log the exact model details + what's being sent | Non-Blocking -def logging(model, input, azure=False, additional_args={}, logger_fn=None): +@client +@timeout(60) ## set timeouts, in case calls hang (e.g. Azure) - default is 60s, override with `force_timeout` +def embedding(model, input=[], azure=False, force_timeout=60, logger_fn=None): try: - model_call_details = {} - model_call_details["model"] = model - model_call_details["input"] = input - model_call_details["azure"] = azure - model_call_details["additional_args"] = additional_args - if logger_fn and callable(logger_fn): - try: - # log additional call details -> api key, etc. - if azure == True or model in open_ai_chat_completion_models or model in open_ai_chat_completion_models or model in open_ai_embedding_models: - model_call_details["api_type"] = openai.api_type - model_call_details["api_base"] = openai.api_base - model_call_details["api_version"] = openai.api_version - model_call_details["api_key"] = openai.api_key - elif "replicate" in model: - model_call_details["api_key"] = os.environ.get("REPLICATE_API_TOKEN") - elif model in anthropic_models: - model_call_details["api_key"] = os.environ.get("ANTHROPIC_API_KEY") - elif model in cohere_models: - model_call_details["api_key"] = os.environ.get("COHERE_API_KEY") - - logger_fn(model_call_details) # Expectation: any logger function passed in by the user should accept a dict object - except: - print_verbose(f"Basic model call details: {model_call_details}") - print_verbose(f"[Non-Blocking] Exception occurred while logging {traceback.format_exc()}") - pass - else: - print_verbose(f"Basic model call details: {model_call_details}") - pass - except: - pass - -## Set verbose to true -> ```litellm.verbose = True``` + response = None + if azure == True: + # azure configs + openai.api_type = "azure" + openai.api_base = os.environ.get("AZURE_API_BASE") + openai.api_version = os.environ.get("AZURE_API_VERSION") + openai.api_key = os.environ.get("AZURE_API_KEY") + ## LOGGING + logging(model=model, input=input, azure=azure, logger_fn=logger_fn) + ## EMBEDDING CALL + response = openai.Embedding.create(input=input, engine=model) + print_verbose(f"response_value: {str(response)[:50]}") + elif model in litellm.open_ai_embedding_models: + openai.api_type = "openai" + openai.api_base = "https://api.openai.com/v1" + openai.api_version = None + openai.api_key = os.environ.get("OPENAI_API_KEY") + ## LOGGING + logging(model=model, input=input, azure=azure, logger_fn=logger_fn) + ## EMBEDDING CALL + response = openai.Embedding.create(input=input, model=model) + print_verbose(f"response_value: {str(response)[:50]}") + else: + logging(model=model, input=input, azure=azure, logger_fn=logger_fn) + args = locals() + raise ValueError(f"No valid embedding model args passed in - {args}") + + return response + except Exception as e: + # log the original exception + logging(model=model, input=input, azure=azure, logger_fn=logger_fn, exception=e) + ## Map to OpenAI Exception + raise exception_type(model=model, original_exception=e) +####### HELPER FUNCTIONS ################ +## Set verbose to true -> ```litellm.set_verbose = True``` def print_verbose(print_statement): - if set_verbose: + if litellm.set_verbose: print(f"LiteLLM: {print_statement}") - print("Get help - https://discord.com/invite/wuPM9dRgDw") \ No newline at end of file + if random.random() <= 0.3: + print("Get help - https://discord.com/invite/wuPM9dRgDw") + diff --git a/build/lib/litellm/timeout.py b/build/lib/litellm/timeout.py new file mode 100644 index 00000000000..8cbe650ed63 --- /dev/null +++ b/build/lib/litellm/timeout.py @@ -0,0 +1,80 @@ +""" +Module containing "timeout" decorator for sync and async callables. +""" + +import asyncio + +from concurrent import futures +from inspect import iscoroutinefunction +from functools import wraps +from threading import Thread +from openai.error import Timeout + + +def timeout( + timeout_duration: float = None, exception_to_raise = Timeout +): + """ + Wraps a function to raise the specified exception if execution time + is greater than the specified timeout. + + Works with both synchronous and asynchronous callables, but with synchronous ones will introduce + some overhead due to the backend use of threads and asyncio. + + :param float timeout_duration: Timeout duration in seconds. If none callable won't time out. + :param OpenAIError exception_to_raise: Exception to raise when the callable times out. + Defaults to TimeoutError. + :return: The decorated function. + :rtype: callable + """ + + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + async def async_func(): + return func(*args, **kwargs) + + thread = _LoopWrapper() + thread.start() + future = asyncio.run_coroutine_threadsafe(async_func(), thread.loop) + try: + local_timeout_duration = timeout_duration + if "force_timeout" in kwargs: + local_timeout_duration = kwargs["force_timeout"] + result = future.result(timeout=local_timeout_duration) + except futures.TimeoutError: + thread.stop_loop() + raise exception_to_raise() + thread.stop_loop() + return result + + @wraps(func) + async def async_wrapper(*args, **kwargs): + try: + value = await asyncio.wait_for( + func(*args, **kwargs), timeout=timeout_duration + ) + return value + except asyncio.TimeoutError: + raise exception_to_raise() + + if iscoroutinefunction(func): + return async_wrapper + return wrapper + + return decorator + + +class _LoopWrapper(Thread): + def __init__(self): + super().__init__(daemon=True) + self.loop = asyncio.new_event_loop() + + def run(self) -> None: + self.loop.run_forever() + self.loop.call_soon_threadsafe(self.loop.close) + + def stop_loop(self): + for task in asyncio.all_tasks(self.loop): + task.cancel() + self.loop.call_soon_threadsafe(self.loop.stop) \ No newline at end of file diff --git a/build/lib/litellm/utils.py b/build/lib/litellm/utils.py new file mode 100644 index 00000000000..67a3d6df743 --- /dev/null +++ b/build/lib/litellm/utils.py @@ -0,0 +1,316 @@ +import dotenv, json, traceback, threading +import subprocess, os +import litellm, openai +import random, uuid, requests +from openai.error import AuthenticationError, InvalidRequestError, RateLimitError, ServiceUnavailableError, OpenAIError +####### ENVIRONMENT VARIABLES ################### +dotenv.load_dotenv() # Loading env variables using dotenv +sentry_sdk_instance = None +capture_exception = None +add_breadcrumb = None +posthog = None +slack_app = None +alerts_channel = None +callback_list = [] +user_logger_fn = None +additional_details = {} + +def print_verbose(print_statement): + if litellm.set_verbose: + print(f"LiteLLM: {print_statement}") + if random.random() <= 0.3: + print("Get help - https://discord.com/invite/wuPM9dRgDw") + +####### LOGGING ################### +#Logging function -> log the exact model details + what's being sent | Non-Blocking +def logging(model, input, azure=False, additional_args={}, logger_fn=None, exception=None): + try: + model_call_details = {} + model_call_details["model"] = model + model_call_details["azure"] = azure + # log exception details + if exception: + model_call_details["original_exception"] = exception + + if litellm.telemetry: + safe_crash_reporting(model=model, exception=exception, azure=azure) # log usage-crash details. Do not log any user details. If you want to turn this off, set `litellm.telemetry=False`. + + model_call_details["input"] = input + # log additional call details -> api key, etc. + if azure == True or model in litellm.open_ai_chat_completion_models or model in litellm.open_ai_chat_completion_models or model in litellm.open_ai_embedding_models: + model_call_details["api_type"] = openai.api_type + model_call_details["api_base"] = openai.api_base + model_call_details["api_version"] = openai.api_version + model_call_details["api_key"] = openai.api_key + elif "replicate" in model: + model_call_details["api_key"] = os.environ.get("REPLICATE_API_TOKEN") + elif model in litellm.anthropic_models: + model_call_details["api_key"] = os.environ.get("ANTHROPIC_API_KEY") + elif model in litellm.cohere_models: + model_call_details["api_key"] = os.environ.get("COHERE_API_KEY") + model_call_details["additional_args"] = additional_args + ## User Logging -> if you pass in a custom logging function or want to use sentry breadcrumbs + print_verbose(f"Basic model call details: {model_call_details}") + if logger_fn and callable(logger_fn): + try: + logger_fn(model_call_details) # Expectation: any logger function passed in by the user should accept a dict object + except: + print_verbose(f"[Non-Blocking] Exception occurred while logging {traceback.format_exc()}") + except: + traceback.print_exc() + pass + +####### CLIENT ################### +# make it easy to log if completion/embedding runs succeeded or failed + see what happened | Non-Blocking +def client(original_function): + def function_setup(*args, **kwargs): #just run once to check if user wants to send their data anywhere - PostHog/Sentry/Slack/etc. + try: + global callback_list, add_breadcrumb + if (len(litellm.success_callback) > 0 or len(litellm.failure_callback) > 0) and len(callback_list) == 0: + callback_list = list(set(litellm.success_callback + litellm.failure_callback)) + set_callbacks(callback_list=callback_list) + if add_breadcrumb: + add_breadcrumb( + category="litellm.llm_call", + message=f"Positional Args: {args}, Keyword Args: {kwargs}", + level="info", + ) + except: # DO NOT BLOCK running the function because of this + print_verbose(f"[Non-Blocking] {traceback.format_exc()}") + pass + + def wrapper(*args, **kwargs): + try: + function_setup(args, kwargs) + ## MODEL CALL + result = original_function(*args, **kwargs) + ## LOG SUCCESS + my_thread = threading.Thread(target=handle_success, args=(args, kwargs)) # don't interrupt execution of main thread + my_thread.start() + return result + except Exception as e: + traceback_exception = traceback.format_exc() + my_thread = threading.Thread(target=handle_failure, args=(e, traceback_exception, args, kwargs)) # don't interrupt execution of main thread + my_thread.start() + raise e + return wrapper + +####### HELPER FUNCTIONS ################ +def set_callbacks(callback_list): + global sentry_sdk_instance, capture_exception, add_breadcrumb, posthog, slack_app, alerts_channel + try: + for callback in callback_list: + if callback == "sentry": + try: + import sentry_sdk + except ImportError: + print_verbose("Package 'sentry_sdk' is missing. Installing it...") + subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'sentry_sdk']) + import sentry_sdk + sentry_sdk_instance = sentry_sdk + sentry_sdk_instance.init(dsn=os.environ.get("SENTRY_API_URL"), traces_sample_rate=float(os.environ.get("SENTRY_API_TRACE_RATE"))) + capture_exception = sentry_sdk_instance.capture_exception + add_breadcrumb = sentry_sdk_instance.add_breadcrumb + elif callback == "posthog": + try: + from posthog import Posthog + except ImportError: + print_verbose("Package 'posthog' is missing. Installing it...") + subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'posthog']) + from posthog import Posthog + posthog = Posthog( + project_api_key=os.environ.get("POSTHOG_API_KEY"), + host=os.environ.get("POSTHOG_API_URL")) + elif callback == "slack": + try: + from slack_bolt import App + except ImportError: + print_verbose("Package 'slack_bolt' is missing. Installing it...") + subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'slack_bolt']) + from slack_bolt import App + slack_app = App( + token=os.environ.get("SLACK_API_TOKEN"), + signing_secret=os.environ.get("SLACK_API_SECRET") + ) + alerts_channel = os.environ["SLACK_API_CHANNEL"] + print_verbose(f"Initialized Slack App: {slack_app}") + except: + pass + + +def handle_failure(exception, traceback_exception, args, kwargs): + global sentry_sdk_instance, capture_exception, add_breadcrumb, posthog, slack_app, alerts_channel + try: + print_verbose(f"handle_failure args: {args}") + print_verbose(f"handle_failure kwargs: {kwargs}") + + success_handler = additional_details.pop("success_handler", None) + failure_handler = additional_details.pop("failure_handler", None) + + additional_details["Event_Name"] = additional_details.pop("failed_event_name", "litellm.failed_query") + print_verbose(f"self.failure_callback: {litellm.failure_callback}") + + print_verbose(f"additional_details: {additional_details}") + for callback in litellm.failure_callback: + try: + if callback == "slack": + slack_msg = "" + if len(kwargs) > 0: + for key in kwargs: + slack_msg += f"{key}: {kwargs[key]}\n" + if len(args) > 0: + for i, arg in enumerate(args): + slack_msg += f"LiteLLM_Args_{str(i)}: {arg}" + for detail in additional_details: + slack_msg += f"{detail}: {additional_details[detail]}\n" + slack_msg += f"Traceback: {traceback_exception}" + slack_app.client.chat_postMessage(channel=alerts_channel, text=slack_msg) + elif callback == "sentry": + capture_exception(exception) + elif callback == "posthog": + print_verbose(f"inside posthog, additional_details: {len(additional_details.keys())}") + ph_obj = {} + if len(kwargs) > 0: + ph_obj = kwargs + if len(args) > 0: + for i, arg in enumerate(args): + ph_obj["litellm_args_" + str(i)] = arg + for detail in additional_details: + ph_obj[detail] = additional_details[detail] + event_name = additional_details["Event_Name"] + print_verbose(f"ph_obj: {ph_obj}") + print_verbose(f"PostHog Event Name: {event_name}") + if "user_id" in additional_details: + posthog.capture(additional_details["user_id"], event_name, ph_obj) + else: # PostHog calls require a unique id to identify a user - https://posthog.com/docs/libraries/python + unique_id = str(uuid.uuid4()) + posthog.capture(unique_id, event_name) + print_verbose(f"successfully logged to PostHog!") + except: + print_verbose(f"Error Occurred while logging failure: {traceback.format_exc()}") + pass + + if failure_handler and callable(failure_handler): + call_details = { + "exception": exception, + "additional_details": additional_details + } + failure_handler(call_details) + pass + except: + pass + +def handle_success(*args, **kwargs): + try: + success_handler = additional_details.pop("success_handler", None) + failure_handler = additional_details.pop("failure_handler", None) + additional_details["Event_Name"] = additional_details.pop("successful_event_name", "litellm.succes_query") + for callback in litellm.success_callback: + try: + if callback == "posthog": + ph_obj = {} + for detail in additional_details: + ph_obj[detail] = additional_details[detail] + event_name = additional_details["Event_Name"] + if "user_id" in additional_details: + posthog.capture(additional_details["user_id"], event_name, ph_obj) + else: # PostHog calls require a unique id to identify a user - https://posthog.com/docs/libraries/python + unique_id = str(uuid.uuid4()) + posthog.capture(unique_id, event_name, ph_obj) + pass + elif callback == "slack": + slack_msg = "" + for detail in additional_details: + slack_msg += f"{detail}: {additional_details[detail]}\n" + slack_app.client.chat_postMessage(channel=alerts_channel, text=slack_msg) + except: + pass + + if success_handler and callable(success_handler): + success_handler(args, kwargs) + pass + except: + pass + + +def exception_type(model, original_exception): + try: + if isinstance(original_exception, OpenAIError): + # Handle the OpenAIError + raise original_exception + elif model: + error_str = str(original_exception) + if isinstance(original_exception, BaseException): + exception_type = type(original_exception).__name__ + else: + exception_type = "" + if "claude" in model: #one of the anthropics + if "status_code" in original_exception: + print_verbose(f"status_code: {original_exception.status_code}") + if original_exception.status_code == 401: + raise AuthenticationError(f"AnthropicException - {original_exception.message}") + elif original_exception.status_code == 400: + raise InvalidRequestError(f"AnthropicException - {original_exception.message}", f"{model}") + elif original_exception.status_code == 429: + raise RateLimitError(f"AnthropicException - {original_exception.message}") + elif "replicate" in model: + if "Incorrect authentication token" in error_str: + raise AuthenticationError(f"ReplicateException - {error_str}") + elif exception_type == "ModelError": + raise InvalidRequestError(f"ReplicateException - {error_str}", f"{model}") + elif "Request was throttled" in error_str: + raise RateLimitError(f"ReplicateException - {error_str}") + elif exception_type == "ReplicateError": ## ReplicateError implies an error on Replicate server side, not user side + raise ServiceUnavailableError(f"ReplicateException - {error_str}") + elif model == "command-nightly": #Cohere + if "invalid api token" in error_str or "No API key provided." in error_str: + raise AuthenticationError(f"CohereException - {error_str}") + elif "too many tokens" in error_str: + raise InvalidRequestError(f"CohereException - {error_str}", f"{model}") + elif "CohereConnectionError" in exception_type: # cohere seems to fire these errors when we load test it (1k+ messages / min) + raise RateLimitError(f"CohereException - {original_exception.message}") + raise original_exception # base case - return the original exception + else: + raise original_exception + except: + raise original_exception + +def safe_crash_reporting(model=None, exception=None, azure=None): + data = { + "model": model, + "exception": str(exception), + "azure": azure + } + threading.Thread(target=litellm_telemetry, args=(data,), daemon=True).start() + +def litellm_telemetry(data): + # Load or generate the UUID + uuid_file = 'litellm_uuid.txt' + try: + # Try to open the file and load the UUID + with open(uuid_file, 'r') as file: + uuid_value = file.read() + if uuid_value: + uuid_value = uuid_value.strip() + else: + raise FileNotFoundError + except FileNotFoundError: + # Generate a new UUID if the file doesn't exist or is empty + new_uuid = uuid.uuid4() + uuid_value = str(new_uuid) + with open(uuid_file, 'w') as file: + file.write(uuid_value) + + # Prepare the data to send to localhost:3000 + payload = { + 'uuid': uuid_value, + 'data': data + } + print_verbose(f"payload: {payload}") + try: + # Make the POST request to localhost:3000 + response = requests.post('https://litellm.berri.ai/logging', json=payload) + response.raise_for_status() # Raise an exception for HTTP errors + except requests.exceptions.RequestException as e: + # Handle any errors in the request + pass \ No newline at end of file diff --git a/dist/litellm-0.1.2-py3-none-any.whl b/dist/litellm-0.1.2-py3-none-any.whl deleted file mode 100644 index 07cfc4417579ccce5bc6128783e26d3b5095c9fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6039 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 diff --git a/dist/litellm-0.1.2.tar.gz b/dist/litellm-0.1.2.tar.gz deleted file mode 100644 index 853c7db396f2818ac7dd4e8eb95fd52c82101f3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6289 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 H0LTCUG+48B diff --git a/dist/litellm-0.1.216-py3-none-any.whl b/dist/litellm-0.1.216-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..94866790a2e9599941fcd7b2036d16145bd68adc GIT binary patch literal 9879 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 literal 0 HcmV?d00001 diff --git a/litellm.egg-info/PKG-INFO b/litellm.egg-info/PKG-INFO index 021d53b6acc..a9e70364be6 100644 --- a/litellm.egg-info/PKG-INFO +++ b/litellm.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: litellm -Version: 0.1.207 +Version: 0.1.216 Summary: Library to easily interface with LLM API providers Author: BerriAI License-File: LICENSE diff --git a/litellm.egg-info/SOURCES.txt b/litellm.egg-info/SOURCES.txt index d7e65359703..9355b612b0e 100644 --- a/litellm.egg-info/SOURCES.txt +++ b/litellm.egg-info/SOURCES.txt @@ -1,5 +1,6 @@ LICENSE README.md +pyproject.toml setup.py litellm/__init__.py litellm/main.py diff --git a/litellm/__pycache__/__init__.cpython-311.pyc b/litellm/__pycache__/__init__.cpython-311.pyc index e487c18c978291d436faf790e33d2c9f263eb8bf..3dd00dd19d4925839a7e3d0f1f18123153b8ddfb 100644 GIT binary patch delta 19 ZcmbQjHieCAIWI340}$L?zmbcJ82~Jl1X=(9 delta 19 ZcmbQjHieCAIWI340}yOqw2_O882~GX1S
?7_^oSq|_Z~ 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 diff --git a/litellm/tests/test_client.py b/litellm/tests/test_client.py index 9129b5853c2..eb99634015e 100644 --- a/litellm/tests/test_client.py +++ b/litellm/tests/test_client.py @@ -57,3 +57,4 @@ def test_good_azure_embedding(): print(f"response: {str(response)[:50]}") except Exception as e: pytest.fail(f"Error occurred: {e}") + diff --git a/litellm/tests/test_no_client.py b/litellm/tests/test_no_client.py new file mode 100644 index 00000000000..79c47d0da7b --- /dev/null +++ b/litellm/tests/test_no_client.py @@ -0,0 +1,23 @@ +#### What this tests #### +# This tests error logging (with custom user functions) for the `completion` + `embedding` endpoints without callbacks (i.e. slack, posthog, etc. not set) +# Requirements: Remove any env keys you have related to slack/posthog/etc. + anthropic api key (cause an exception) + +import sys, os +import traceback +sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path +import litellm +from litellm import embedding, completion + +litellm.set_verbose = True + +model_fallback_list = ["claude-instant-1", "gpt-3.5-turbo", "chatgpt-test"] + +user_message = "Hello, how are you?" +messages = [{ "content": user_message,"role": "user"}] + +for model in model_fallback_list: + try: + response = embedding(model="text-embedding-ada-002", input=[user_message]) + response = completion(model=model, messages=messages) + except Exception as e: + print(f"error occurred: {traceback.format_exc()}") diff --git a/litellm/utils.py b/litellm/utils.py index e2b4f565541..67a3d6df743 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -234,41 +234,45 @@ def handle_success(*args, **kwargs): def exception_type(model, original_exception): - if isinstance(original_exception, OpenAIError): - # Handle the OpenAIError - raise original_exception - elif model: - error_str = str(original_exception) - if isinstance(original_exception, BaseException): - exception_type = type(original_exception).__name__ + try: + if isinstance(original_exception, OpenAIError): + # Handle the OpenAIError + raise original_exception + elif model: + error_str = str(original_exception) + if isinstance(original_exception, BaseException): + exception_type = type(original_exception).__name__ + else: + exception_type = "" + if "claude" in model: #one of the anthropics + if "status_code" in original_exception: + print_verbose(f"status_code: {original_exception.status_code}") + if original_exception.status_code == 401: + raise AuthenticationError(f"AnthropicException - {original_exception.message}") + elif original_exception.status_code == 400: + raise InvalidRequestError(f"AnthropicException - {original_exception.message}", f"{model}") + elif original_exception.status_code == 429: + raise RateLimitError(f"AnthropicException - {original_exception.message}") + elif "replicate" in model: + if "Incorrect authentication token" in error_str: + raise AuthenticationError(f"ReplicateException - {error_str}") + elif exception_type == "ModelError": + raise InvalidRequestError(f"ReplicateException - {error_str}", f"{model}") + elif "Request was throttled" in error_str: + raise RateLimitError(f"ReplicateException - {error_str}") + elif exception_type == "ReplicateError": ## ReplicateError implies an error on Replicate server side, not user side + raise ServiceUnavailableError(f"ReplicateException - {error_str}") + elif model == "command-nightly": #Cohere + if "invalid api token" in error_str or "No API key provided." in error_str: + raise AuthenticationError(f"CohereException - {error_str}") + elif "too many tokens" in error_str: + raise InvalidRequestError(f"CohereException - {error_str}", f"{model}") + elif "CohereConnectionError" in exception_type: # cohere seems to fire these errors when we load test it (1k+ messages / min) + raise RateLimitError(f"CohereException - {original_exception.message}") + raise original_exception # base case - return the original exception else: - exception_type = "" - if "claude" in model: #one of the anthropics - print_verbose(f"status_code: {original_exception.status_code}") - if original_exception.status_code == 401: - raise AuthenticationError(f"AnthropicException - {original_exception.message}") - elif original_exception.status_code == 400: - raise InvalidRequestError(f"AnthropicException - {original_exception.message}", f"{model}") - elif original_exception.status_code == 429: - raise RateLimitError(f"AnthropicException - {original_exception.message}") - elif "replicate" in model: - if "Incorrect authentication token" in error_str: - raise AuthenticationError(f"ReplicateException - {error_str}") - elif exception_type == "ModelError": - raise InvalidRequestError(f"ReplicateException - {error_str}", f"{model}") - elif "Request was throttled" in error_str: - raise RateLimitError(f"ReplicateException - {error_str}") - elif exception_type == "ReplicateError": ## ReplicateError implies an error on Replicate server side, not user side - raise ServiceUnavailableError(f"ReplicateException - {error_str}") - elif model == "command-nightly": #Cohere - if "invalid api token" in error_str or "No API key provided." in error_str: - raise AuthenticationError(f"CohereException - {error_str}") - elif "too many tokens" in error_str: - raise InvalidRequestError(f"CohereException - {error_str}", f"{model}") - elif "CohereConnectionError" in exception_type: # cohere seems to fire these errors when we load test it (1k+ messages / min) - raise RateLimitError(f"CohereException - {original_exception.message}") - raise original_exception # base case - return the original exception - else: + raise original_exception + except: raise original_exception def safe_crash_reporting(model=None, exception=None, azure=None): @@ -277,11 +281,9 @@ def safe_crash_reporting(model=None, exception=None, azure=None): "exception": str(exception), "azure": azure } - print(f"data in crash reporting: {data}") threading.Thread(target=litellm_telemetry, args=(data,), daemon=True).start() def litellm_telemetry(data): - print(f"data in in litellm telemetry: {data}") # Load or generate the UUID uuid_file = 'litellm_uuid.txt' try: @@ -290,7 +292,6 @@ def litellm_telemetry(data): uuid_value = file.read() if uuid_value: uuid_value = uuid_value.strip() - print(f"Loaded UUID: {uuid_value}") else: raise FileNotFoundError except FileNotFoundError: @@ -299,7 +300,6 @@ def litellm_telemetry(data): uuid_value = str(new_uuid) with open(uuid_file, 'w') as file: file.write(uuid_value) - print(f"Generated and stored UUID: {uuid_value}") # Prepare the data to send to localhost:3000 payload = { @@ -311,7 +311,6 @@ def litellm_telemetry(data): # Make the POST request to localhost:3000 response = requests.post('https://litellm.berri.ai/logging', json=payload) response.raise_for_status() # Raise an exception for HTTP errors - print('Request successfully sent!') except requests.exceptions.RequestException as e: # Handle any errors in the request - print(f'Error: {e}') \ No newline at end of file + pass \ No newline at end of file diff --git a/setup.py b/setup.py index ff035141ca7..0c8a6a7c466 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name='litellm', - version='0.1.214', + version='0.1.216', description='Library to easily interface with LLM API providers', author='BerriAI', packages=[