From fa65f960e349a7ba617106fc69992c042c5405cf Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 31 Jul 2023 08:46:23 -0700 Subject: [PATCH] version 0.1.2 --- .DS_Store | Bin 8196 -> 8196 bytes .env.example | 1 - build/lib/litellm/main.py | 488 ++++++++++++++++++----- completion_test.py | 5 - dist/litellm-0.1.0-py3-none-any.whl | Bin 2965 -> 0 bytes dist/litellm-0.1.0.tar.gz | Bin 2495 -> 0 bytes dist/litellm-0.1.1-py3-none-any.whl | Bin 2961 -> 0 bytes dist/litellm-0.1.1.tar.gz | Bin 2488 -> 0 bytes dist/litellm-0.1.2-py3-none-any.whl | Bin 0 -> 6039 bytes dist/litellm-0.1.2.tar.gz | Bin 0 -> 6289 bytes litellm.egg-info/PKG-INFO | 10 +- litellm/.DS_Store | Bin 0 -> 6148 bytes litellm/__pycache__/main.cpython-311.pyc | Bin 6147 -> 19936 bytes litellm/main.py | 487 +++++++++++++++++----- litellm/tests/test_bad_params.py | 20 + litellm/tests/test_client.py | 59 +++ litellm/tests/test_logging.py | 48 +++ setup.py | 4 +- 18 files changed, 888 insertions(+), 234 deletions(-) delete mode 100644 dist/litellm-0.1.0-py3-none-any.whl delete mode 100644 dist/litellm-0.1.0.tar.gz delete mode 100644 dist/litellm-0.1.1-py3-none-any.whl delete mode 100644 dist/litellm-0.1.1.tar.gz create mode 100644 dist/litellm-0.1.2-py3-none-any.whl create mode 100644 dist/litellm-0.1.2.tar.gz create mode 100644 litellm/.DS_Store create mode 100644 litellm/tests/test_bad_params.py create mode 100644 litellm/tests/test_client.py create mode 100644 litellm/tests/test_logging.py diff --git a/.DS_Store b/.DS_Store index 51ab802dc6324429763fc183c8681238d2ca1e91..639e40acb9a4e4059e7fc62399755feea3a8a4a0 100644 GIT binary patch delta 46 zcmZp1XmOa}&nUPtU^hRb;AS2H5f;vr;^ds9{QMlo$$TP*Hh+;3V4m2(vzcAuFFOEJ CMGo2k delta 33 pcmZp1XmOa}&nU1lU^hRbz-Ar+5thl;A{#be;4x?3%r5bl9RRW53T*%Y diff --git a/.env.example b/.env.example index 7889a204c..276feaba8 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,4 @@ OPENAI_API_KEY = "" COHERE_API_KEY = "" -OPENROUTER_API_KEY = "" OR_SITE_URL = "" OR_APP_NAME = "LiteLLM Example app" \ No newline at end of file diff --git a/build/lib/litellm/main.py b/build/lib/litellm/main.py index 053d796e1..d4fc60053 100644 --- a/build/lib/litellm/main.py +++ b/build/lib/litellm/main.py @@ -1,7 +1,17 @@ -import os, openai, cohere, dotenv - +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 +####### ENVIRONMENT VARIABLES ################### # Loading env variables using dotenv dotenv.load_dotenv() +set_verbose = False ####### COMPLETION MODELS ################### open_ai_chat_completion_models = [ @@ -16,16 +26,9 @@ cohere_models = [ 'command-nightly', ] -openrouter_models = [ - 'google/palm-2-codechat-bison', - 'google/palm-2-chat-bison', - 'openai/gpt-3.5-turbo', - 'openai/gpt-3.5-turbo-16k', - 'openai/gpt-4-32k', - 'anthropic/claude-2', - 'anthropic/claude-instant-v1', - 'meta-llama/llama-2-13b-chat', - 'meta-llama/llama-2-70b-chat' +anthropic_models = [ + "claude-2", + "claude-instant-1" ] ####### EMBEDDING MODELS ################### @@ -38,122 +41,389 @@ open_ai_embedding_models = [ ####### COMPLETION ENDPOINTS ################ ############################################# -def completion(model, messages, azure=False): - 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") - response = openai.ChatCompletion.create( - engine=model, - messages = messages - ) - elif "replicate" in model: - prompt = " ".join([message["content"] for message in messages]) - output = replicate.run( - model, - input={ - "prompt": prompt, - }) - print(f"output: {output}") - response = "" - for item in output: - print(f"item: {item}") - response += item - new_response = { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "content": response, - "role": "assistant" - } - } - ] - } - print(f"new response: {new_response}") - response = new_response - elif model in cohere_models: - cohere_key = os.environ.get("COHERE_API_KEY") - co = cohere.Client(cohere_key) - prompt = " ".join([message["content"] for message in messages]) - response = co.generate( - model=model, - prompt = prompt - ) - new_response = { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "content": response[0], - "role": "assistant" - } - } - ], - } - - response = new_response - - elif model in 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") - response = openai.ChatCompletion.create( - model=model, +@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): + try: + 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) + ## COMPLETION CALL + response = openai.ChatCompletion.create( + engine=model, messages = messages - ) - elif model in 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]) - response = openai.Completion.create( + ) + 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"): + 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["max_length"] = max_tokens # for t5 models + input["max_new_tokens"] = max_tokens # for llama2 models + ## LOGGING + logging(model=model, input=input, azure=azure, additional_args={"max_tokens": max_tokens}, logger_fn=logger_fn) + ## COMPLETION CALL + output = replicate.run( + model, + input=input) + response = "" + for item in output: + response += item + new_response = { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": response, + "role": "assistant" + } + } + ] + } + response = new_response + elif model in anthropic_models: + #anthropic defaults to os.environ.get("ANTHROPIC_API_KEY") + prompt = f"{HUMAN_PROMPT}" + for message in messages: + if "role" in message: + if message["role"] == "user": + prompt += f"{HUMAN_PROMPT}{message['content']}" + else: + prompt += f"{AI_PROMPT}{message['content']}" + else: + prompt += f"{HUMAN_PROMPT}{message['content']}" + prompt += f"{AI_PROMPT}" + anthropic = Anthropic() + if max_tokens: + max_tokens_to_sample = max_tokens + 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) + ## COMPLETION CALL + completion = anthropic.completions.create( + model=model, + prompt=prompt, + max_tokens_to_sample=max_tokens_to_sample + ) + new_response = { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": completion.completion, + "role": "assistant" + } + } + ] + } + print(f"new response: {new_response}") + response = new_response + elif model in cohere_models: + cohere_key = os.environ.get("COHERE_API_KEY") + co = cohere.Client(cohere_key) + prompt = " ".join([message["content"] for message in messages]) + ## LOGGING + logging(model=model, input=prompt, azure=azure, logger_fn=logger_fn) + ## COMPLETION CALL + response = co.generate( model=model, prompt = prompt - ) - - elif model in openrouter_models: - openai.api_base = "https://openrouter.ai/api/v1" - openai.api_key = os.environ.get("OPENROUTER_API_KEY") - - prompt = " ".join([message["content"] for message in messages]) - - response = openai.ChatCompletion.create( - model=model, - messages=messages, - headers={ - "HTTP-Referer": os.environ.get("OR_SITE_URL"), # To identify your app - "X-Title": os.environ.get("OR_APP_NAME") - }, - ) - reply = response.choices[0].message - return response + ) + new_response = { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": response[0], + "role": "assistant" + } + } + ], + } + response = new_response + elif model in 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 + ) + elif model in 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 + ) + else: + logging(model=model, input=messages, azure=azure, logger_fn=logger_fn) + 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 ### EMBEDDING ENDPOINTS #################### -def embedding(model, input=[], azure=False): +@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") + 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 -############################################# -############################################# \ No newline at end of file +### 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): + 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``` +def print_verbose(print_statement): + if set_verbose: + print(f"LiteLLM: {print_statement}") + print("Get help - https://discord.com/invite/wuPM9dRgDw") \ No newline at end of file diff --git a/completion_test.py b/completion_test.py index a657e42a4..c9e74932c 100644 --- a/completion_test.py +++ b/completion_test.py @@ -26,9 +26,4 @@ print(response) # cohere call response = completion("command-nightly", messages) print("\nCohere call") -print(response) - -# openrouter call -response = completion("google/palm-2-codechat-bison", messages) -print("\OpenRouter call") print(response) \ No newline at end of file diff --git a/dist/litellm-0.1.0-py3-none-any.whl b/dist/litellm-0.1.0-py3-none-any.whl deleted file mode 100644 index 9bc973ed7442dd787978aa1852409ee3541eaf15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2965 zcmaKuc|4T+7ssD5Q<{k(YnBjKWSJR8xsn>oh_Q_^X0o?f?r1QKJ%x}Zlo(2+)KyAT zh_Wj!#yTV>vQ?Il0RySa+-gMy7St<5Zczgm#5lu<>K0^pd!$r8`cY7rom|}L>8h%Ts)^@ z%dN`(r7t7h^ZM6nBVSg`PtBj;z~Z_x&MMZ0KL~MTYJXB;?h;PL>_0xJeE}my_xv?` zlZ#7Rzmblvu$fr5ofqJ~#e=gO=UMJ-vrRG5GUPm%#@uC96RpiDJgDUBshzP14T;aI z7I_r^aklX<#29+);4eJ;{`+wCrQcPQO~|T3r{DkpvCW-L0$*@Ls#tg%7(>Q zugxqoO9redHE+{L#i5I5kLydWnG6e+RbmXrq^Pi8x6z!AF3wWbcaXHm^U+v`YT}2v zcWd>xs`ZgQ>UDdrk(Vc@EQ=a^iq{I~lce7e4a+AgDv#K#JloUSw+#N)_py)_lKeBX zKLU7tzY_ug^56Pi6NS)1pb+j9dY~r7*V7MaX>N?Qw!^v*Ed2UV0=1K5VZE}b4#9pO z-KQk!!zT?%*(2i|T+yRqK63|lyD35*=nsJI9u?}{%lBoOmKKi=^O~; zEAOQL>is6oK-I@IZMd1}>+P;iezb@2yCFTVB$lp(^pCg~a7;V(IUSJDgqUb7{eXs? zSK7dRrBYG+Ow9(e41Y-CkX6D!@RPKt+{(Gm^!UYv-)t6`m|4BBLuTR73sDg}XaO|_ z?4Fk5wyf3P8qQ(-62l`C4+;=-$QSUb$Tnlt+s1Q`@@MoWZ&ZbyF3O`^ovY2qFeZnr z*LD;~Z$q3kGDjZ`k5|7W9(fT#hwwLTu=Ezc_lyG81{v z#MxoEjx0e?Dr81MW{3nG6VfIIbB>jzofV#SkI|*<&s5tW>&?7xsjElE`w?As@r|Ib zYkE`=ZYsiTCsc?}r4z^zn9|WOYeKRgyGAkc9Eoeq$|^e@#PH%fV-eloa|e?A67BE%LGFpqG`%#)pzV?fd-X3-IrD<&Y_QRlGKp0s(-+ zPukTAOEfewBpP}oH(NJg1h9)=U~8EC%#kTIRYi|IL}GpKb$GGP*&%<->hY2xWsXbk z@xTRYpyIXBv@#qEj_ln9!A+r({F9-zckQcu$+R3hMFahhe-t%i)EkM)mu*Xm;a%w3 zGb!V%a=P(w6-~bYM>U$Ah^}aj1~;B2Gkuwl>M3((&(wrjhW~+vdYEf?8#gOB!QHsy zqwzk*sNSAX|Rwb1T!2<1Cfa2FSKQ&*y!Co zX!bi;Q#-PQtI;i}s)=i5S(Xq~cdA*9z|1Wl5L9pKbhkFQTfDStx&7`9-@1C>2>zzA4l8-hs? zyE!-?1w;*>$WvxRHn8$HC%nhRX2fbCwYW;59LCXhFJP^~xZ~6_G&QmdyJu-+%#aGD~bcda8kSbSBRpvmrhkblP zrj|;kmgD`=!&wnIe0pgan=bh7H+bCh5d3Kg+`-lJI`41)`bopU7Sq`#&b3ZKX}KQt z)w|UYE;LhdzJZzB{@O{w|5&y0p?$v_<4)0__02+f3lN_e_{Y;1uP}c;*Q39F{$m#V z9`IkI)nD!aa6Q`nJHY=3t?!-x>wfy?>2KtH(D+Y~e@P2(v4QtL0DzzOf_Y+$fPP*5 E5A;O6VgLXD diff --git a/dist/litellm-0.1.0.tar.gz b/dist/litellm-0.1.0.tar.gz deleted file mode 100644 index f0c18b9b7a68345045082c62cea9d91f5f965c0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2495 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 JK3f2w008FlF+%_V diff --git a/dist/litellm-0.1.1-py3-none-any.whl b/dist/litellm-0.1.1-py3-none-any.whl deleted file mode 100644 index 43a15ee4077b988f6398f7cd33ceb0f6fa1cf8fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2961 zcmaKucUY6x7sua_04BYX@QabLcGaj3d-HxFTgLv-CdU&iPh=oc49MaJ39}zbt2nP+74`j zy*+U*>ArMukc^E1L6)Wyobh`wO|}bJPrWpB|YXxPfM}SQpis{^0s_#zA zm{SMIQc(0s(ahXglemS&3maXVT(m}eLEF^TISKLI_Se1hvW16VvR*%{o|O=#8orAh z2&WR=h-c@a{KTyeu?=vZqU=Pc`<&#I)HA(uz~AAneAdiz@-dpITd+z+?VY0;YDrI8 z8&!9y3P3YoVxPhR@b5EXTWYg2lFc@E@zSiYU(qur|UpsqUfF395+etvZZe7RjWOZTCy zY;NV|;;P}r)rtNOCco6h^;Ld&_u&i|_9G|rqFQ}aMffp>!6!||ZpjPigV7@fSJ7L8 zefH*TaCPk(Hrr(+X(iKt*cb5H?9JU{<5TWpr=%8#3r`1A8M|$36AZXTN7Ms+3^Es> z;VJnwQe*L_?7+d*!8BZ-k?A=sVpkm91;(9`z#l!6tFJJ$Bu8)IN?#4SERAoQc&@2& zT!`^TPbN(qi0rqPsy35p%}FtX0Lk?$CGmD!fd+A(ToJAJ#QtN8QO_c=uD3EB_B}nF zI*tRAiz3J--iJEOVmRU~S}g)w>-@uG$&QbHvaoa4GJ>viNOO@=?K=pBqEt)z%FiWl zD-h$QWvKabM`F?03u{y5;{2QXO8)rhn~n%)IQ-e9&yewa^IKc!gEvf&?~&n%WvBg$ zk%{=V*(Juq7kIzgv5ZMs=)%Ql6Zo3NONp{7^nt0ZWY}IMs$C+wD6j9EiV;4gX4)mkPh zw^;oiB*^=n5CBmAlJ`hGU46mu@(T_@`UUs|qHxyc82n+3D*+ogq_?SVnk2ceEWSr< zm}1mSls_J8O3ayf&9%Av_<@l1QFh$HUh5$W8Zh5p56?F9Z=?9$^w@$OUSti8Nxm$I zPMW&cJ>ub4Q_-N|S;o^;I2$U>1rm8aYa)k6@I4=)iZ%M9;%u>!gE^4G#IUHuQXU8t zpzLfi_+|q#MC%FFK-^0DxvHCUAoaJDJ&=Jxxy9?@!xLUNxt6^q+#X0oYGMK=_z+^7 z@8uVd)Jvu9XKRH?3Zmg@qqeCp!kR9}=T-4~Gg21jPuk5h&~y7DJ*=XjSL0)LP=jg@ zum?IHbY=ZDg6eEekV=K0AwJx(*fJg%9yU0H40i*ohcynO!Z#l@thQy|;2US|fRO~l`+ zwgcyNe^jgv4kiEUMp6*lTM$tcZeJ3gW%5ati2(6RiG_aG2{^e>UEzMO?8$4mE2#Sx zE=OP1ljI2MMT~gJtRrEU1#ttN!E4Vl$coCkPv>ZyW@rm5`ZFHkjP{Z2107v=3r!f_ zKzdgaZmYo@-fB8F%cqiJ(4~`+c*40rcCA|6B_bc+o>i6{O7|7|37gRM$pCJ&a}#~i zMGop!Gm1K4>C{%Z<^1sT$6?6Z7Ey|EMOU1x^?QpsuzcuE$0cwT{k&DY&LG>2Sgb8& zIc;IFJ41ybVKcLasW%3L;3($@B%5K@lh9GcsEef2=;s74rR zYFpj`g}k=g(I-j>4;z_%@~W zI2#0}@ttFZ1XA6}-eKNk-H?b7W;?=~)!sg_9)@~n0wxJ=RZ38dwtWXiz?j<+EJE3= zutGfm{qjt{1{Wfqgn>(X{%YnB%c*(^oR`vs+`3^Jvy|OT?IQ0FUqdoRY0#b@;KErh zvd!;|&(`g z;d1}EN`(TO%lfqos*CSYK{fq#Jl7oS{Ea%Sg@bPlKKgr9p(e+-P?J~Zo1?v{k#B!) zjAviW#50Js_Xt>{L72nFptQbn^{B!*LcUOZveK*et( zQt>YIJ)x5Y6~FK8U<{^9jhOkGNEB-r(T0Tc`!w|^(*@P;4)S;{1(Bu3h?LYwk2^Tu zle&5LJXd2c@U7-89{(|;zWu~lSi_kWbm)MY9;*pA%<)o&s?R)QILJ1YwCSE&!N#>_ z8Q$fn+88gM5Uw~<-RovHy8ni>%_}cZ!&7B@EJ#QO{O!(5a4&y;HxfQi|Ivqi4fwCN z>aXnpa3jI%E5QGotgko!*W2`EvkUki@85c<*2C6UjxVr{BHxDnT}Rf_)+_Q04J7() z+MoJdk6N#yFQ}{ELH$cp>yhiV^f&SmXyQA_zm#Q<6&CytL4_3zut19u(9f&?0j1=? AhX4Qo diff --git a/dist/litellm-0.1.1.tar.gz b/dist/litellm-0.1.1.tar.gz deleted file mode 100644 index 26e4c3b2c2cec39833eab0b674dedfd554ff8716..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2488 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 C!xp0e diff --git a/dist/litellm-0.1.2-py3-none-any.whl b/dist/litellm-0.1.2-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..07cfc4417579ccce5bc6128783e26d3b5095c9fb GIT binary patch literal 6039 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 literal 0 HcmV?d00001 diff --git a/dist/litellm-0.1.2.tar.gz b/dist/litellm-0.1.2.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..853c7db396f2818ac7dd4e8eb95fd52c82101f3b GIT binary patch literal 6289 zcmV;C7;fhuiwFqeU&Uks|7>Y=Wo&G1Eif)IE;253VR8WNJzaO&NS1lluc%ed8ABRm zz+cmx!^{wy#B^iu0g~Q~*M}5R0cs0LD+xPv){pzNPy4zr^B3nQ?7dYbAqj!)#POut zQum3aQq`?nRrjmzy(+X_ZTD~I^wTM$2J>EgldB@GO@Awuz5UfamRBpaYV8I2^x_Gw zg&$B4x_|LJuiBr@?>WzzL}P;D~# zHv zl>fDAwF>3c1JD;QNaZQ{|C!4Fv-Xg5Or6<2Qio0vYCF^`RxWa?2nU`B~dJjRGKA^MDZQ&u8@L#VwZF7te7;EV%m+NM1v zl<3fj0yTpffbpG4a6>%?l?_6D-_cD9@QC5)i#f9c8eo4D(_%g;1T#j|fo!1`c_#*= zmSWlj--WNpjTy|GML;~}2cD@Tj1nY5{E)^d~Jk9ldi z|0nnVtoeVcKknD|a+ZH~_W$|MZ_A+hdQWQqS8BC`l>g_TRyoY=|0l8kSwIaM(DHjQ zL&1)#lbTjlx^&L!MAC*5z2Zco3>Nb_^_F$mH;g^FK`WF|-?WxsDF)1&P+gedq|-Sg z&GR;KJ?Gj4_2Mhd1(@DmU2H30S0T3^iSp8#lKx#7G^=<`t{viLmyLP%Cu|d z>CDoo>+Vw5-1V6UCA&9^^Rpj~{`BOg7;9qsy5kwzbQ*uJD3ti-+_f0_6ZD0isA{DG zcQzv~)vxH3p&6m0>#655yV)+0=5Gu1mK{6jYlKHTn0bzC>fEGg*@o+Yx#1HP_SIQ*tsO?)cK%batyNiJP*K`Ll;0`}@fU)_Q8QdetLTJ!*x}e;t$$a#t`KBK* zJ0Q*?04OCAuut5t=)xz!qW~wMzA}VAC;G6fu+hT7^%U?fFh1{a`Bc09Q^^%C%QztPQb>FbG4dk9cXo| z0Wyqw`b?WT&eUQW406}-%xkt=o%Z+K59ObJJNY}E{pP+m#&2H-Uia-lf*x}%6=>&#e#YC20`Vfn}v8xnfH-QTMoa+UmEGb#=bxqOKyR&4(8k zpYa`^I<-#JQ$Wxuk(qNt&{?{47JpSss^?e?-!6bm)!R!zm&m@6yp(Y90NiR!g*nW< z)ljEyP~OuH%E7`LJ1V+;Mc<9^rikDqdRxE&mS7~?M%hMMx0W$b90En!z6V;WKBEC5 z6)-=D0VxRxjiI+-eB}_RX@iimEbh_fWv=Olg^@8Y>&_e>iRxOI_CWR*Z#_CDXbKQ> zf>TLufC~uKfqs4R&wu>y|HKH7TEH>ZQqbD&{@g83jlJ^K!PV{<4SQ;qtMH#=j~(hE zgOx)yBf2fiZuwN)%$CG34bmp#g=tB*G$8cqRhI#V6ZAKub5W zG6pdA6sQpx(_?dHEyetqIu9gpnSgBcr-V$vDMyYCZWyyeNfQblA#o75!NNu2SB`<^ zs2%{PB4dM|pw#{wAF2lHw@ZZbh)RA(_S5&_|Jz%)|EszE|0MSRQI`L&^5?yyd=v2e z?0@;R`}q9-u>7xsgM*=G3&qjs7b_UXr%B$BiQ<_4v#A*ntfbuZ@)-hcNOh*Z;Zx|Bm$k z!9jIzKiB`yUjIL?h~KXN_o3`4-2d$#?j553uRu|*|DWKJO5gEI(i>fp-t7|cnDnBS zK>zz9C=e`N9#Rpln)Xr@PZqX5LNS7A)$%1T^H~sch>BV&ilQy^I?fJ+wxYCHGlOTJG>7(sVD|OK`u<|< zdJcNKm6zhe07tvu>tYTtdEe~wblw4Zxqc}xNyniwlytocijakmWwK}SN3)>jNZc2S z3eM0q^TrN${lB3WCgO((9K&ckEuO!7);no+vOC9x8&PwFv4YVmcrfBI4IVa82-!)5 zHg-xREZg5vE+Y^a-r5KP8sPh~L9b0)H_MgE9=25k!K~Fyg+O*nST|bjWOY@y=)z#- zn#vIiA9?V?4$4&(+eXWB_PTY#bHML0DN&Fwdo0oGGBqfSsJ0X9>3)}zxn8aA$$78c z9j;^dK{k&!eNDB|C zc?)~RcPWO8AzEB#Wi^z5I2~Zo&v6+=4|NhFM%mzp2CpPv;Pl2|eU9p@GMNR2dl40q zSOs2bV&cbY0D*#ShFKVX;J9ijS;@2w_6c5AQst7=;7t;$=swE(2+=F8R|<1KsE>&| zU^=LYI=4#JeMq&p>r)w@jg=_ltHD2uOIb6K!de-%+oV~uJ3Q_8&fCXpoE~xNMEx8$ zf^St4TiXmSjWOFqPDku@JvM$EN67rV-X*GCku_6K%$h{zhjnZ|hY(f?O}FtGhbduO zU_}P~9e~ncz6_xaU5Bq%OV(IzK?=p~5^Yrme?~sf-AV{0B{l+bSF^oJ1%#NS7+khh z&YR)req`O@gBIb-vv7pLyz8+EnBun1pumLTmo1*7Z$=9FA%aj$*b_^_SdLzUB*rL< z{WYURipR8760UeSj(Giy`EO(uCd`=Mg>ckk##?mztt6%EnRZZ^s4y>))CBzE({!sA zA1qcA8_Ie_7`!msq)sIf7Na&ioempn$DAPwD=@`JwTix)vA)uN*BzJ%n|a6?geY4% zBO0-|xi67#B=dDre*nR6C;NL4|3>@ZqHt09BFw>xa=oA0NfdlxQeMGl)&$Rh;-BV>Fm8i!OfCT@)U;sm z$wE2nLNFHVD9(;y+<~{y1ILp}Eg6TDchNg*qNV$TTIr&A^)x2v%Ssi+t4B*D83S+x ze&zmq7p4RzP8{-Odc?W0LsH;DX8yTT3{(hyWPxQ5;P4O3Sy0Fx#t+%}7K@3&!|a4_ z{DLH-b$f)!rHaMN#9}>e)GVRc4Hf|EpxbYYNL)=@2GgRbTE+}V_gVYM)HiV{)Ek*& zSly;Z5`WmppB%p#wGE4n5>D;;awNj|fLIjI*M|JA5a3C3L87r-T<8d`o%LAd`m_~` z=y7xGoj@F3;VoSJl#J;MZhSctp6)0+Ph+L=PtXc02S?hN+hop#mL#Idmjh@E$O=%w zFWJJZOP6|dPPl;d<0~l#D=D_X#Bd`lLUS!cmWTAYvY`^>xxtc36W^L;_3fxJ+t~Y8 z5mMOscS1!ECL;0$R3s@TDF6`wS0pk)0}pNF=RLx9n0YoH3n)J77Icf7#z@}6Jgkf9 zI$-VC`~(^Cx#|sU<9B?Rh5uZb9y3;6_?12ZvrA~wShylaX1u;k?cvo%zHzk*DTo;r zdCo1*bG&4LA#Sax=kg2nofV)RJOq=UoCtJ8+5&4HdJZ&Ai$!cnEU`%A0ct*vg$sWP zRvsr2Ocg4To$`DK{&h_k|Hyg3+gPWU#d{4-s;saaHT9j9G&C*>hHp30*5?4MqW%YN zeZK2=__>>yJLO9k`3PN(jfv&Zps=MWo?qA+^_xT3>J*DfEOhDysC!vN-;Fi&*6Jid z08fs_$W6Mpn2$5sCo4o@nKVU7qii!t@tieTD_nwe@t7j6`w)}_^n}zD_Lv~0`wWUt zXjrEazAmJrd$?;K$*s&9DV_HQ!_(f|)lMeqpT)1kx3=N*SKPRG+>*Xy>3E&d*s-ES z#ir|ityqX)KBZV#b-KQm+eayQgIA%hi}6m^jV~m(`Wn7J}_yk%pu^O=w9 zc4s{VBkl3V&d^)(%`zx?23s{T#h-6whSBHS9Sx>p4thn}UzqQ!8H>5=XDnN>xZab+ zu3-lQTr+Xj@F*s8AyQT;CU+rpWWqx7T`@WP$(LD(g${O@>lC$0Gjr1>F~iUleTSP^ z0mj2f`C3(w)owve)l^HI`~*tjmiKNbshSBW>uoO7(18&$2B_n}E5zJETN}youi8Z? zr)=bo^sYW!qJ##xTi3%P%|_1nR}6dJrZ`gI$+LdekWb~fa2OZj-DT#qhA-o}GS|R? zBb&}yc8hQ+Ckt!D>q>#MR-BEZkUilf%Ly9+r%1qR8NlOhs`odLe1y8uc#H zlSIASI{Cbrc5eT}R+Wt)1`%2Cktb><)W0UYj2aI{<0PL6b}L8~+{jSN;vB3-)MaFn zr3~L_6~grHwBSmL+2qSe&L&i7BI`!TmwGxqQjJ7NeI-p-Z`Y?3>a7>1;`=G=3hN?1 zL%@f-Ya!sHy^Gvyivi-T6xL8n-6SAUDnvCihEM{LRaCw!^{`o9r@gKBlGhJmTI^N0 zK9B)n-qIo^#c;eX=QpT(!JpC$_WP6r!|*#NfDwK3B<(8POc#o`Pa_*L#ch7^0AgbbG!Wmwu~)*Bt^D4 z4y0RQV?B-=ugC8;))NURTt494=6cC-sVRwqWg*dR*b=Yrh7+p$C10QwNO7`jI47`v z)a6Hkim*3{tNcXz4gT8DJ<}$?wrX}NNX+?@o;Tlm^qSeie>j`DpN?( zt;4fC?*@S9s-_x4QBtvHey;Zr*bn$}@4lbad`Z8Xzp=Sj*7=uoxgi-nmX*ELT?EY2 z`s6ZQ{4`me$JIoGl>}V*?Z!(k2gR3;GiPBL{LEGtbPh30kc|%isjzK2DwdY>n);?L zrzFm@U_7p4dK(*B8qh^t++M~amyWJ4JP&ZcnSs#|C34)M0u!n>{yESi4M4(IL{B{Vu&@k&+>VP*a}rOzsVi~I zp#$(coio5a)iOj}R8-#rhBIclU`nKq;l?a96NoclvE#Va;`ep_PyYK)|GM*kvVZ^S z=wQE=p91t;&;QZbbXvxs$?l`^|HICIJ*w>==I6iW=fCCh|B26kt0#KiB`c{@)}2BWf#l-6Gw%w@L0Y<)ShO#8~u zt|h+)+3+@S+!0Us{5opQ^?$DabN!!Rx&GfEmmgjKS1Q$%{@>d}{?GOQx4&{$&-H(< z|8xDH>;GK;XXyXFzn1I&r?^B4p!^AsobPk}pX>iz|L1Zb*Z+^` zQ@vaNulfIv4i9tv|AgzGb9}D<^Zf7UsQ)wbziZXX{y{Dio}K;|v0r^MeRTfcs{i*7 zQ~BQqM|-*d|4FV3F!e@g>Rl=UosPx})50?{`GbL{IyOi}$t6Lq|MUI-Gui)F^8Noa z*Z+_9!|(Uh|NHPR(*Jv0{~zZ0e@}CX(?W6C1IMxa)uF;rQX&)6Hb&7=<BX9Quey zq%?LU^~kdo5`|Wj>2eGyuJv4#7>9Y5wVa6Krxqw`ITS!m0$UlU-^|^`ITS!m0$VwyTAS)e+m}# H0LTCUG+48B literal 0 HcmV?d00001 diff --git a/litellm.egg-info/PKG-INFO b/litellm.egg-info/PKG-INFO index cf19261b2..e8f0962bc 100644 --- a/litellm.egg-info/PKG-INFO +++ b/litellm.egg-info/PKG-INFO @@ -1,12 +1,6 @@ Metadata-Version: 2.1 Name: litellm -Version: 0.1.1 +Version: 0.1.2 Summary: Library to easily interface with LLM API providers -Home-page: UNKNOWN -Author: Ishaan Jaffer -License: UNKNOWN -Platform: UNKNOWN +Author: BerriAI License-File: LICENSE - -UNKNOWN - diff --git a/litellm/.DS_Store b/litellm/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b9f40dc6327e2580ac031d717ac6fe2d099ac69f GIT binary patch literal 6148 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�^OMecdDUUuzMlj( zO9nU=f#cnE9RKQnm*W>&P1mbfO|2AXo69`EFs^K2N%9vUt-a-)1nuK5La-L^HY4qh zSJC$ix$k~$?q4CQzE&LQ*+hR$hI(n<+F8F>c;hbpsP`UZyN8vG2QiYFtwds>#*b%P z%i36SWEdz}c8yONRz5wih=ylm`t0Orbma7@=&_MAqlT~A|6p|T%&CcS!(APEcl0MA z+F(UFF3F-{n-f))kBh2dSHx68it(EGjOvb6(kmQ`Og6#)X%N5)iIWUD?^vQTP@h!; z&9CVj&NJizWpJ^itckMrfIOfLx_`jn%12=0>h6AaC~{U66*ZFPFU{~uiRYsn|0%EW!1#oF0%CJguYFpfR z(Bfh;=vJ%l1zvp#&BPhWJ#TgDk2Z{AL13nkCaAO zlJ>~r07X=s2|;TznH$P3_bp*H0WKy286 z;i$Hl5)C@38k{IEND7!J6Bo78+y!104JWQzfNkJk28W$X;v(28+!2mf9M525VBnf) zSZ0%wtYH0VFv>jGdrFaH&2Yz(GeB6>v?LW)n_YuFo{%6y7`C`5iwf=!qDrE?6vaD8 zwCdUAot1gKfNU}EsG-SMRqw`jr2_?u&KGi41*2eGGi+r~EE~G%`Gy6(-f)4df(w^i z)o^TZgMy%ANyCZ}b5V5yn(pe)^f+8wj!w#t@L>3>zlT7dCA!sdP58z6?D=(n*C!`G zo>=p@-JV^MZ^^k`LwWzfg8!g7+TMA$c)@)tjf%O3!MCvOQBEWw;5sC#|cF}=ZGB($~F1lKHUtn=E$^3=77YZG}k zP+$W&HlVYf<=#B&&(36L)>-d*XOG_0yx!iU`+JI3#_uW;1RQ5~6bS;x-e@Z_&Sp0I z-ZyRL*`YhbIY$Vdyk&2}vNvbht9v_xx9=?2cV>^hOdP<_%`4Zh=q{h`Z9&R$H`ny+@S57c=} z>+R{44{v>#>phTf87j04J*RX}lit+%0;RjRh5ltCF>S8o?5wN z$3QYRIKC&x;iLNxAX_!;yq#}3RA@Pb?5w}UBEPJ)anwWWmu($)9iMqV^{g)C+lC5l zLu*Yfw-2uzxpibUoNpQ^G!1|bykI&AIMW|E^alVySp#vm6kLHj?-yJ<3a-BFsBZUV zBWCWa`B?IO!U*~iL=X%h7(}oSfMFjkf3{N&VBh@2h$*-7Id3CdhAgEVX`YYfuN(!S zVhgieDZkk(yOeif;+MGMuK@mQ>21do@9xLm-S^LaK9uwB&U;4+-jVDWq!|pKdvY8; zuN^+CNOze?j~2W~0c9l;gs77tS4pm>m=6Jm1}h}tezRaW6G>i(mO9EX%qTxV@HT=U zB6tS@CVx{zrP3U0Yg2;HY{O6$%_qbo$~cUnVXI#NC^8g9>7*z3I!}=$R8y8Mkj6Y| zE|BINX@+#qH0rb+V&G0XM}iQFAr~`rrQ?T=kIuS_Hb)CxBxR5zzG8q_yI?i|``U_) aQHp{@_8c5zNFca+wn#iU{Q_Xd(|-f&4f|mL diff --git a/litellm/main.py b/litellm/main.py index 14de0b02e..d4fc60053 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1,7 +1,17 @@ -import os, openai, cohere, dotenv - +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 +####### ENVIRONMENT VARIABLES ################### # Loading env variables using dotenv dotenv.load_dotenv() +set_verbose = False ####### COMPLETION MODELS ################### open_ai_chat_completion_models = [ @@ -16,16 +26,9 @@ cohere_models = [ 'command-nightly', ] -openrouter_models = [ - 'google/palm-2-codechat-bison', - 'google/palm-2-chat-bison', - 'openai/gpt-3.5-turbo', - 'openai/gpt-3.5-turbo-16k', - 'openai/gpt-4-32k', - 'anthropic/claude-2', - 'anthropic/claude-instant-v1', - 'meta-llama/llama-2-13b-chat', - 'meta-llama/llama-2-70b-chat' +anthropic_models = [ + "claude-2", + "claude-instant-1" ] ####### EMBEDDING MODELS ################### @@ -38,123 +41,389 @@ open_ai_embedding_models = [ ####### COMPLETION ENDPOINTS ################ ############################################# -def completion(model, messages, azure=False): - 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") - response = openai.ChatCompletion.create( - engine=model, - messages = messages - ) - elif "replicate" in model: - prompt = " ".join([message["content"] for message in messages]) - output = replicate.run( - model, - input={ - "prompt": prompt, - }) - print(f"output: {output}") - response = "" - for item in output: - print(f"item: {item}") - response += item - new_response = { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "content": response, - "role": "assistant" - } - } - ] - } - print(f"new response: {new_response}") - response = new_response - elif model in cohere_models: - cohere_key = os.environ.get("COHERE_API_KEY") - co = cohere.Client(cohere_key) - prompt = " ".join([message["content"] for message in messages]) - response = co.generate( - model=model, - prompt = prompt - ) - new_response = { - "choices": [ - { - "finish_reason": "stop", - "index": 0, - "message": { - "content": response[0], - "role": "assistant" - } - } - ], - } - - response = new_response - - elif model in 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") - response = openai.ChatCompletion.create( - model=model, +@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): + try: + 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) + ## COMPLETION CALL + response = openai.ChatCompletion.create( + engine=model, messages = messages - ) - elif model in 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]) - response = openai.Completion.create( + ) + 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"): + 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["max_length"] = max_tokens # for t5 models + input["max_new_tokens"] = max_tokens # for llama2 models + ## LOGGING + logging(model=model, input=input, azure=azure, additional_args={"max_tokens": max_tokens}, logger_fn=logger_fn) + ## COMPLETION CALL + output = replicate.run( + model, + input=input) + response = "" + for item in output: + response += item + new_response = { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": response, + "role": "assistant" + } + } + ] + } + response = new_response + elif model in anthropic_models: + #anthropic defaults to os.environ.get("ANTHROPIC_API_KEY") + prompt = f"{HUMAN_PROMPT}" + for message in messages: + if "role" in message: + if message["role"] == "user": + prompt += f"{HUMAN_PROMPT}{message['content']}" + else: + prompt += f"{AI_PROMPT}{message['content']}" + else: + prompt += f"{HUMAN_PROMPT}{message['content']}" + prompt += f"{AI_PROMPT}" + anthropic = Anthropic() + if max_tokens: + max_tokens_to_sample = max_tokens + 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) + ## COMPLETION CALL + completion = anthropic.completions.create( + model=model, + prompt=prompt, + max_tokens_to_sample=max_tokens_to_sample + ) + new_response = { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": completion.completion, + "role": "assistant" + } + } + ] + } + print(f"new response: {new_response}") + response = new_response + elif model in cohere_models: + cohere_key = os.environ.get("COHERE_API_KEY") + co = cohere.Client(cohere_key) + prompt = " ".join([message["content"] for message in messages]) + ## LOGGING + logging(model=model, input=prompt, azure=azure, logger_fn=logger_fn) + ## COMPLETION CALL + response = co.generate( model=model, prompt = prompt - ) - - elif model in openrouter_models: - openai.api_base = "https://openrouter.ai/api/v1" - openai.api_key = os.environ.get("OPENROUTER_API_KEY") - - prompt = " ".join([message["content"] for message in messages]) - - response = openai.ChatCompletion.create( - model=model, - messages=messages, - headers={ - "HTTP-Referer": os.environ.get("OR_SITE_URL"), # To identify your app - "X-Title": os.environ.get("OR_APP_NAME") - }, - ) - reply = response.choices[0].message - return response + ) + new_response = { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": response[0], + "role": "assistant" + } + } + ], + } + response = new_response + elif model in 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 + ) + elif model in 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 + ) + else: + logging(model=model, input=messages, azure=azure, logger_fn=logger_fn) + 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 ### EMBEDDING ENDPOINTS #################### -def embedding(model, input=[], azure=False): +@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") + 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): + 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``` +def print_verbose(print_statement): + if set_verbose: + print(f"LiteLLM: {print_statement}") + print("Get help - https://discord.com/invite/wuPM9dRgDw") \ No newline at end of file diff --git a/litellm/tests/test_bad_params.py b/litellm/tests/test_bad_params.py new file mode 100644 index 000000000..2b2e4bbcf --- /dev/null +++ b/litellm/tests/test_bad_params.py @@ -0,0 +1,20 @@ +import sys, os +import traceback +sys.path.append('..') # Adds the parent directory to the system path +import main +from main import litellm_client +client = litellm_client(success_callback=["posthog"], failure_callback=["slack", "sentry", "posthog"], verbose=True) +completion = client.completion +embedding = client.embedding + +main.set_verbose = True + +user_message = "Hello, how are you?" +messages = [{ "content": user_message,"role": "user"}] +model_val = None +# test on empty +try: + response = completion(model=model_val, messages=messages) +except: + print(f"error occurred: {traceback.format_exc()}") + pass diff --git a/litellm/tests/test_client.py b/litellm/tests/test_client.py new file mode 100644 index 000000000..06850ea18 --- /dev/null +++ b/litellm/tests/test_client.py @@ -0,0 +1,59 @@ +import sys, os +import traceback +sys.path.append('..') # Adds the parent directory to the system path +import main +from main import litellm_client +client = litellm_client(success_callback=["posthog"], failure_callback=["slack", "sentry", "posthog"], verbose=True) +completion = client.completion +embedding = client.embedding + +main.set_verbose = True + +def logger_fn(model_call_object: dict): + print(f"model call details: {model_call_object}") + +user_message = "Hello, how are you?" +messages = [{ "content": user_message,"role": "user"}] + +# test on openai completion call +try: + response = completion(model="gpt-3.5-turbo", messages=messages, logger_fn=logger_fn) +except: + print(f"error occurred: {traceback.format_exc()}") + pass + + +# test on openai completion call +try: + response = completion(model="gpt-3.5-turbo", messages=messages, logger_fn=logger_fn) +except: + print(f"error occurred: {traceback.format_exc()}") + pass + +# test on non-openai completion call +try: + response = completion(model="claude-instant-1", messages=messages, logger_fn=logger_fn) +except: + print(f"error occurred: {traceback.format_exc()}") + pass + +# test on openai embedding call +try: + response = embedding(model='text-embedding-ada-002', input=[user_message], logger_fn=logger_fn) + print(f"response: {str(response)[:50]}") +except: + traceback.print_exc() + +# test on bad azure openai embedding call -> missing azure flag and this isn't an embedding model +try: + response = embedding(model='chatgpt-test', input=[user_message], logger_fn=logger_fn) + print(f"response: {str(response)[:50]}") +except: + traceback.print_exc() + +# test on good azure openai embedding call +try: + response = embedding(model='azure-embedding-model', input=[user_message], azure=True, logger_fn=logger_fn) + print(f"response: {str(response)[:50]}") +except: + traceback.print_exc() diff --git a/litellm/tests/test_logging.py b/litellm/tests/test_logging.py new file mode 100644 index 000000000..95a75de47 --- /dev/null +++ b/litellm/tests/test_logging.py @@ -0,0 +1,48 @@ +import sys, os +import traceback +sys.path.append('..') # Adds the parent directory to the system path +import main +from main import completion, embedding + +main.verbose = True ## Replace to: ```litellm.verbose = True``` when using pypi package + +def logger_fn(model_call_object: dict): + print(f"model call details: {model_call_object}") + +user_message = "Hello, how are you?" +messages = [{ "content": user_message,"role": "user"}] + +# test on openai completion call +try: + response = completion(model="gpt-3.5-turbo", messages=messages) +except: + print(f"error occurred: {traceback.format_exc()}") + pass + +# test on non-openai completion call +try: + response = completion(model="claude-instant-1", messages=messages, logger_fn=logger_fn) +except: + print(f"error occurred: {traceback.format_exc()}") + pass + +# test on openai embedding call +try: + response = embedding(model='text-embedding-ada-002', input=[user_message], logger_fn=logger_fn) + print(f"response: {str(response)[:50]}") +except: + traceback.print_exc() + +# test on bad azure openai embedding call -> missing azure flag and this isn't an embedding model +try: + response = embedding(model='chatgpt-test', input=[user_message], logger_fn=logger_fn) + print(f"response: {str(response)[:50]}") +except: + traceback.print_exc() + +# test on good azure openai embedding call +try: + response = embedding(model='azure-embedding-model', input=[user_message], azure=True, logger_fn=logger_fn) + print(f"response: {str(response)[:50]}") +except: + traceback.print_exc() diff --git a/setup.py b/setup.py index 2288a1f82..a143fbe5b 100644 --- a/setup.py +++ b/setup.py @@ -2,9 +2,9 @@ from setuptools import setup, find_packages setup( name='litellm', - version='0.1.202', + version='0.1.2', description='Library to easily interface with LLM API providers', - author='Ishaan Jaffer', + author='BerriAI', packages=[ 'litellm' ],