From 8ad1ae73e5076dd00128ef0814bfe89a75c85668 Mon Sep 17 00:00:00 2001 From: Brian Schultheiss Date: Sun, 23 Jun 2024 12:51:25 -0700 Subject: [PATCH 001/124] Support aws_session_token for bedrock client. https://github.com/BerriAI/litellm/issues/4346 --- litellm/llms/bedrock_httpx.py | 34 +++++++++++++ litellm/main.py | 94 +++++++++++++---------------------- 2 files changed, 68 insertions(+), 60 deletions(-) diff --git a/litellm/llms/bedrock_httpx.py b/litellm/llms/bedrock_httpx.py index 84ab10907..3eb8acd39 100644 --- a/litellm/llms/bedrock_httpx.py +++ b/litellm/llms/bedrock_httpx.py @@ -301,6 +301,7 @@ class BedrockLLM(BaseLLM): self, aws_access_key_id: Optional[str] = None, aws_secret_access_key: Optional[str] = None, + aws_session_token: Optional[str] = None, aws_region_name: Optional[str] = None, aws_session_name: Optional[str] = None, aws_profile_name: Optional[str] = None, @@ -316,6 +317,7 @@ class BedrockLLM(BaseLLM): params_to_check: List[Optional[str]] = [ aws_access_key_id, aws_secret_access_key, + aws_session_token, aws_region_name, aws_session_name, aws_profile_name, @@ -333,6 +335,7 @@ class BedrockLLM(BaseLLM): ( aws_access_key_id, aws_secret_access_key, + aws_session_token, aws_region_name, aws_session_name, aws_profile_name, @@ -426,6 +429,18 @@ class BedrockLLM(BaseLLM): client = boto3.Session(profile_name=aws_profile_name) return client.get_credentials() + elif ( + aws_access_key_id is not None + and aws_secret_access_key is not None + and aws_session_token is not None + ): ### CHECK FOR AWS SESSION TOKEN ### + from botocore.credentials import Credentials + credentials = Credentials( + access_key=aws_access_key_id, + secret_key=aws_secret_access_key, + token=aws_session_token, + ) + return credentials else: session = boto3.Session( aws_access_key_id=aws_access_key_id, @@ -733,6 +748,7 @@ class BedrockLLM(BaseLLM): # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) + aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) aws_role_name = optional_params.pop("aws_role_name", None) aws_session_name = optional_params.pop("aws_session_name", None) @@ -764,6 +780,7 @@ class BedrockLLM(BaseLLM): credentials: Credentials = self.get_credentials( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, aws_region_name=aws_region_name, aws_session_name=aws_session_name, aws_profile_name=aws_profile_name, @@ -1418,6 +1435,7 @@ class BedrockConverseLLM(BaseLLM): self, aws_access_key_id: Optional[str] = None, aws_secret_access_key: Optional[str] = None, + aws_session_token: Optional[str] = None, aws_region_name: Optional[str] = None, aws_session_name: Optional[str] = None, aws_profile_name: Optional[str] = None, @@ -1433,6 +1451,7 @@ class BedrockConverseLLM(BaseLLM): params_to_check: List[Optional[str]] = [ aws_access_key_id, aws_secret_access_key, + aws_session_token, aws_region_name, aws_session_name, aws_profile_name, @@ -1450,6 +1469,7 @@ class BedrockConverseLLM(BaseLLM): ( aws_access_key_id, aws_secret_access_key, + aws_session_token, aws_region_name, aws_session_name, aws_profile_name, @@ -1543,6 +1563,18 @@ class BedrockConverseLLM(BaseLLM): client = boto3.Session(profile_name=aws_profile_name) return client.get_credentials() + elif ( + aws_access_key_id is not None + and aws_secret_access_key is not None + and aws_session_token is not None + ): ### CHECK FOR AWS SESSION TOKEN ### + from botocore.credentials import Credentials + credentials = Credentials( + access_key=aws_access_key_id, + secret_key=aws_secret_access_key, + token=aws_session_token, + ) + return credentials else: session = boto3.Session( aws_access_key_id=aws_access_key_id, @@ -1678,6 +1710,7 @@ class BedrockConverseLLM(BaseLLM): # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) + aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) aws_role_name = optional_params.pop("aws_role_name", None) aws_session_name = optional_params.pop("aws_session_name", None) @@ -1709,6 +1742,7 @@ class BedrockConverseLLM(BaseLLM): credentials: Credentials = self.get_credentials( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, aws_region_name=aws_region_name, aws_session_name=aws_session_name, aws_profile_name=aws_profile_name, diff --git a/litellm/main.py b/litellm/main.py index 307659c8a..92cd2fee1 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -2176,13 +2176,23 @@ def completion( # boto3 reads keys from .env custom_prompt_dict = custom_prompt_dict or litellm.custom_prompt_dict - if ( - "aws_bedrock_client" in optional_params - ): # use old bedrock flow for aws_bedrock_client users. - response = bedrock.completion( + + if ("aws_bedrock_client" in optional_params): + # Extract credentials for legacy boto3 client and pass thru to httpx + aws_bedrock_client = optional_params.pop("aws_bedrock_client") + creds = aws_bedrock_client._get_credentials().get_frozen_credentials() + if creds.access_key: + optional_params["aws_access_key_id"] = creds.access_key + if creds.secret_key: + optional_params["aws_secret_access_key"] = creds.secret_key + if creds.token: + optional_params["aws_session_token"] = creds.token + + if model.startswith("anthropic"): + response = bedrock_converse_chat_completion.completion( model=model, messages=messages, - custom_prompt_dict=litellm.custom_prompt_dict, + custom_prompt_dict=custom_prompt_dict, model_response=model_response, print_verbose=print_verbose, optional_params=optional_params, @@ -2192,63 +2202,27 @@ def completion( logging_obj=logging, extra_headers=extra_headers, timeout=timeout, + acompletion=acompletion, + client=client, + ) + else: + response = bedrock_chat_completion.completion( + model=model, + messages=messages, + custom_prompt_dict=custom_prompt_dict, + model_response=model_response, + print_verbose=print_verbose, + optional_params=optional_params, + litellm_params=litellm_params, + logger_fn=logger_fn, + encoding=encoding, + logging_obj=logging, + extra_headers=extra_headers, + timeout=timeout, + acompletion=acompletion, + client=client, ) - if ( - "stream" in optional_params - and optional_params["stream"] == True - and not isinstance(response, CustomStreamWrapper) - ): - # don't try to access stream object, - if "ai21" in model: - response = CustomStreamWrapper( - response, - model, - custom_llm_provider="bedrock", - logging_obj=logging, - ) - else: - response = CustomStreamWrapper( - iter(response), - model, - custom_llm_provider="bedrock", - logging_obj=logging, - ) - else: - if model.startswith("anthropic"): - response = bedrock_converse_chat_completion.completion( - model=model, - messages=messages, - custom_prompt_dict=custom_prompt_dict, - model_response=model_response, - print_verbose=print_verbose, - optional_params=optional_params, - litellm_params=litellm_params, - logger_fn=logger_fn, - encoding=encoding, - logging_obj=logging, - extra_headers=extra_headers, - timeout=timeout, - acompletion=acompletion, - client=client, - ) - else: - response = bedrock_chat_completion.completion( - model=model, - messages=messages, - custom_prompt_dict=custom_prompt_dict, - model_response=model_response, - print_verbose=print_verbose, - optional_params=optional_params, - litellm_params=litellm_params, - logger_fn=logger_fn, - encoding=encoding, - logging_obj=logging, - extra_headers=extra_headers, - timeout=timeout, - acompletion=acompletion, - client=client, - ) if optional_params.get("stream", False): ## LOGGING logging.post_call( From 7f91e5354886fa1a445d953e2a368d507c3c0c3f Mon Sep 17 00:00:00 2001 From: Brian Schultheiss Date: Sun, 23 Jun 2024 13:15:04 -0700 Subject: [PATCH 002/124] updated documentation to reference boto3.client credential extraction, and update boto3.client creation to support session_token. --- docs/my-website/docs/providers/bedrock.md | 5 +++++ litellm/llms/bedrock.py | 25 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index f380a6a50..adbc54caa 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -476,6 +476,7 @@ response = completion( messages=[{ "content": "Hello, how are you?","role": "user"}], aws_access_key_id="", aws_secret_access_key="", + aws_session_token="", aws_region_name="", ) ``` @@ -549,6 +550,10 @@ response = completion( This is a deprecated flow. Boto3 is not async. And boto3.client does not let us make the http call through httpx. Pass in your aws params through the method above 👆. [See Auth Code](https://github.com/BerriAI/litellm/blob/55a20c7cce99a93d36a82bf3ae90ba3baf9a7f89/litellm/llms/bedrock_httpx.py#L284) [Add new auth flow](https://github.com/BerriAI/litellm/issues) + +Experimental - 2024-Jun-23: + aws_access_key_id, aws_secret_access_key=, and aws_session_token will be extracted from boto3.client and be passed onto the httpx client + ::: Pass an external BedrockRuntime.Client object as a parameter to litellm.completion. Useful when using an AWS credentials profile, SSO session, assumed role session, or if environment variables are not available for auth. diff --git a/litellm/llms/bedrock.py b/litellm/llms/bedrock.py index d0d3bef6d..6c941bb55 100644 --- a/litellm/llms/bedrock.py +++ b/litellm/llms/bedrock.py @@ -558,6 +558,7 @@ def init_bedrock_client( region_name=None, aws_access_key_id: Optional[str] = None, aws_secret_access_key: Optional[str] = None, + aws_session_token: Optional[str] = None, aws_region_name: Optional[str] = None, aws_bedrock_runtime_endpoint: Optional[str] = None, aws_session_name: Optional[str] = None, @@ -591,6 +592,7 @@ def init_bedrock_client( ( aws_access_key_id, aws_secret_access_key, + aws_session_token, aws_region_name, aws_bedrock_runtime_endpoint, aws_session_name, @@ -668,6 +670,21 @@ def init_bedrock_client( endpoint_url=endpoint_url, config=config, ) + elif ( + aws_access_key_id is not None + and aws_secret_access_key is not None + and aws_session_token is not None + ): ### CHECK FOR AWS SESSION TOKEN ### + client = boto3.client( + service_name="bedrock-runtime", + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + region_name=region_name, + endpoint_url=endpoint_url, + config=config, + ) + elif aws_role_name is not None and aws_session_name is not None: # use sts if role name passed in sts_client = boto3.client( @@ -786,9 +803,10 @@ def completion( _is_function_call = False json_schemas: dict = {} try: - # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) + aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) aws_role_name = optional_params.pop("aws_role_name", None) aws_session_name = optional_params.pop("aws_session_name", None) @@ -806,6 +824,7 @@ def completion( client = init_bedrock_client( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, aws_region_name=aws_region_name, aws_bedrock_runtime_endpoint=aws_bedrock_runtime_endpoint, aws_role_name=aws_role_name, @@ -1328,6 +1347,7 @@ def embedding( # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) + aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) aws_role_name = optional_params.pop("aws_role_name", None) aws_session_name = optional_params.pop("aws_session_name", None) @@ -1340,6 +1360,7 @@ def embedding( client = init_bedrock_client( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, aws_region_name=aws_region_name, aws_bedrock_runtime_endpoint=aws_bedrock_runtime_endpoint, aws_web_identity_token=aws_web_identity_token, @@ -1419,6 +1440,7 @@ def image_generation( # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) + aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) aws_role_name = optional_params.pop("aws_role_name", None) aws_session_name = optional_params.pop("aws_session_name", None) @@ -1431,6 +1453,7 @@ def image_generation( client = init_bedrock_client( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, aws_region_name=aws_region_name, aws_bedrock_runtime_endpoint=aws_bedrock_runtime_endpoint, aws_web_identity_token=aws_web_identity_token, From 3fbb25f903808ebae31924d0c72a5da000019e1c Mon Sep 17 00:00:00 2001 From: Brian Schultheiss Date: Sun, 23 Jun 2024 13:37:38 -0700 Subject: [PATCH 003/124] Updated more references to AWS session token --- docs/my-website/docs/providers/aws_sagemaker.md | 1 + docs/my-website/docs/providers/bedrock.md | 3 ++- litellm/llms/bedrock.py | 5 +++-- litellm/llms/bedrock_httpx.py | 2 +- litellm/llms/sagemaker.py | 14 ++++++++++++-- litellm/proxy/proxy_server.py | 2 ++ litellm/types/router.py | 4 ++++ 7 files changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/my-website/docs/providers/aws_sagemaker.md b/docs/my-website/docs/providers/aws_sagemaker.md index 2b65709e8..5793fb05a 100644 --- a/docs/my-website/docs/providers/aws_sagemaker.md +++ b/docs/my-website/docs/providers/aws_sagemaker.md @@ -59,6 +59,7 @@ response = completion( messages=[{ "content": "Hello, how are you?","role": "user"}], aws_access_key_id="", aws_secret_access_key="", + aws_session_token="", aws_region_name="", ) ``` diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index adbc54caa..7f9b21b96 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -538,6 +538,7 @@ response = completion( aws_region_name=aws_region_name, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, aws_role_name=aws_role_name, aws_session_name="my-test-session", ) @@ -553,7 +554,7 @@ This is a deprecated flow. Boto3 is not async. And boto3.client does not let us Experimental - 2024-Jun-23: aws_access_key_id, aws_secret_access_key=, and aws_session_token will be extracted from boto3.client and be passed onto the httpx client - + ::: Pass an external BedrockRuntime.Client object as a parameter to litellm.completion. Useful when using an AWS credentials profile, SSO session, assumed role session, or if environment variables are not available for auth. diff --git a/litellm/llms/bedrock.py b/litellm/llms/bedrock.py index 6c941bb55..2403edf81 100644 --- a/litellm/llms/bedrock.py +++ b/litellm/llms/bedrock.py @@ -576,6 +576,7 @@ def init_bedrock_client( params_to_check = [ aws_access_key_id, aws_secret_access_key, + aws_session_token, aws_region_name, aws_bedrock_runtime_endpoint, aws_session_name, @@ -1344,7 +1345,7 @@ def embedding( encoding=None, ): ### BOTO3 INIT ### - # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) aws_session_token = optional_params.pop("aws_session_token", None) @@ -1437,7 +1438,7 @@ def image_generation( Bedrock Image Gen endpoint support """ ### BOTO3 INIT ### - # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) aws_session_token = optional_params.pop("aws_session_token", None) diff --git a/litellm/llms/bedrock_httpx.py b/litellm/llms/bedrock_httpx.py index 3eb8acd39..d00695f87 100644 --- a/litellm/llms/bedrock_httpx.py +++ b/litellm/llms/bedrock_httpx.py @@ -745,7 +745,7 @@ class BedrockLLM(BaseLLM): provider = model.split(".")[0] ## CREDENTIALS ## - # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) aws_session_token = optional_params.pop("aws_session_token", None) diff --git a/litellm/llms/sagemaker.py b/litellm/llms/sagemaker.py index 8e75428bb..7d639b7bb 100644 --- a/litellm/llms/sagemaker.py +++ b/litellm/llms/sagemaker.py @@ -162,9 +162,10 @@ def completion( ): import boto3 - # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) + aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) model_id = optional_params.pop("model_id", None) @@ -175,6 +176,7 @@ def completion( service_name="sagemaker-runtime", aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, region_name=aws_region_name, ) else: @@ -249,6 +251,7 @@ def completion( model_id=model_id, aws_secret_access_key=aws_secret_access_key, aws_access_key_id=aws_access_key_id, + aws_session_token=aws_session_token, aws_region_name=aws_region_name, ) return response @@ -281,6 +284,7 @@ def completion( model_id=model_id, aws_secret_access_key=aws_secret_access_key, aws_access_key_id=aws_access_key_id, + aws_session_token=aws_session_token, aws_region_name=aws_region_name, ) data = json.dumps({"inputs": prompt, "parameters": inference_params}).encode( @@ -414,6 +418,7 @@ async def async_streaming( aws_secret_access_key: Optional[str], aws_access_key_id: Optional[str], aws_region_name: Optional[str], + aws_session_token: Optional[str] = None, ): """ Use aioboto3 @@ -429,6 +434,7 @@ async def async_streaming( service_name="sagemaker-runtime", aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, region_name=aws_region_name, ) else: @@ -481,6 +487,7 @@ async def async_completion( aws_secret_access_key: Optional[str], aws_access_key_id: Optional[str], aws_region_name: Optional[str], + aws_session_token: Optional[str] = None, ): """ Use aioboto3 @@ -496,6 +503,7 @@ async def async_completion( service_name="sagemaker-runtime", aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, region_name=aws_region_name, ) else: @@ -639,9 +647,10 @@ def embedding( ### BOTO3 INIT import boto3 - # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) + aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) if aws_access_key_id is not None: @@ -651,6 +660,7 @@ def embedding( service_name="sagemaker-runtime", aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, region_name=aws_region_name, ) else: diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 30b90abe6..9c1039f51 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -6343,6 +6343,7 @@ async def model_info_v2( _model["litellm_params"].pop("vertex_credentials", None) _model["litellm_params"].pop("aws_access_key_id", None) _model["litellm_params"].pop("aws_secret_access_key", None) + _model["litellm_params"].pop("aws_session_token", None) verbose_proxy_logger.debug("all_models: %s", all_models) return {"data": all_models} @@ -6859,6 +6860,7 @@ async def model_info_v1( model["litellm_params"].pop("vertex_credentials", None) model["litellm_params"].pop("aws_access_key_id", None) model["litellm_params"].pop("aws_secret_access_key", None) + model["litellm_params"].pop("aws_session_token", None) verbose_proxy_logger.debug("all_models: %s", all_models) return {"data": all_models} diff --git a/litellm/types/router.py b/litellm/types/router.py index e6864ffe2..059a8620e 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -145,6 +145,7 @@ class GenericLiteLLMParams(BaseModel): ## AWS BEDROCK / SAGEMAKER ## aws_access_key_id: Optional[str] = None aws_secret_access_key: Optional[str] = None + aws_session_token: Optional[str] = None aws_region_name: Optional[str] = None ## IBM WATSONX ## watsonx_region_name: Optional[str] = None @@ -178,6 +179,7 @@ class GenericLiteLLMParams(BaseModel): ## AWS BEDROCK / SAGEMAKER ## aws_access_key_id: Optional[str] = None, aws_secret_access_key: Optional[str] = None, + aws_session_token: Optional[str] = None, aws_region_name: Optional[str] = None, ## IBM WATSONX ## watsonx_region_name: Optional[str] = None, @@ -242,6 +244,7 @@ class LiteLLM_Params(GenericLiteLLMParams): ## AWS BEDROCK / SAGEMAKER ## aws_access_key_id: Optional[str] = None, aws_secret_access_key: Optional[str] = None, + aws_session_token: Optional[str] = None, aws_region_name: Optional[str] = None, **params, ): @@ -307,6 +310,7 @@ class LiteLLMParamsTypedDict(TypedDict, total=False): ## AWS BEDROCK / SAGEMAKER ## aws_access_key_id: Optional[str] aws_secret_access_key: Optional[str] + aws_session_token: Optional[str] aws_region_name: Optional[str] ## IBM WATSONX ## watsonx_region_name: Optional[str] From 5a6588342cab071322737d69043a3731e34d0468 Mon Sep 17 00:00:00 2001 From: Brian Schultheiss Date: Sun, 23 Jun 2024 15:19:54 -0700 Subject: [PATCH 004/124] added test for change --- docs/my-website/docs/providers/bedrock.md | 8 +- litellm/tests/test_bedrock_completion.py | 181 ++++++++++++++++++++++ 2 files changed, 185 insertions(+), 4 deletions(-) diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index 7f9b21b96..1b073ad25 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -474,10 +474,10 @@ from litellm import completion response = completion( model="bedrock/anthropic.claude-instant-v1", messages=[{ "content": "Hello, how are you?","role": "user"}], + aws_region_name="", aws_access_key_id="", aws_secret_access_key="", - aws_session_token="", - aws_region_name="", + aws_session_token=None, ) ``` @@ -553,7 +553,7 @@ This is a deprecated flow. Boto3 is not async. And boto3.client does not let us Experimental - 2024-Jun-23: - aws_access_key_id, aws_secret_access_key=, and aws_session_token will be extracted from boto3.client and be passed onto the httpx client + `aws_access_key_id`, `aws_secret_access_key`, and `aws_session_token` will be extracted from boto3.client and be passed onto the httpx client ::: @@ -569,7 +569,7 @@ bedrock = boto3.client( region_name="us-east-1", aws_access_key_id="", aws_secret_access_key="", - aws_session_token="", + aws_session_token=None, ) response = completion( diff --git a/litellm/tests/test_bedrock_completion.py b/litellm/tests/test_bedrock_completion.py index b953ca2a3..614d1b76f 100644 --- a/litellm/tests/test_bedrock_completion.py +++ b/litellm/tests/test_bedrock_completion.py @@ -15,6 +15,7 @@ from litellm import embedding, completion, completion_cost, Timeout, ModelRespon from litellm import RateLimitError from litellm.llms.custom_httpx.http_handler import HTTPHandler, AsyncHTTPHandler from unittest.mock import patch, AsyncMock, Mock +from litellm.llms.bedrock_httpx import BedrockLLM # litellm.num_retries = 3 litellm.cache = None @@ -205,6 +206,186 @@ def test_completion_bedrock_claude_sts_client_auth(): except Exception as e: pytest.fail(f"Error occurred: {e}") +@pytest.fixture() +def bedrock_session_token_creds(): + print("\ncalling oidc auto to get aws_session_token credentials") + import os + + aws_region_name = os.environ["AWS_REGION_NAME"] + aws_session_token = os.environ.get("AWS_SESSION_TOKEN") + + bllm = BedrockLLM() + if aws_session_token is not None: + # For local testing + creds = bllm.get_credentials( + aws_region_name=aws_region_name, + aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'], + aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'], + aws_session_token=aws_session_token + ) + else: + # For circle-ci testing + # aws_role_name = os.environ["AWS_TEMP_ROLE_NAME"] + # TODO: This is using ai.moda's IAM role, we should use LiteLLM's IAM role eventually + aws_role_name = "arn:aws:iam::335785316107:role/litellm-github-unit-tests-circleci" + aws_web_identity_token = "oidc/circleci_v2/" + + creds = bllm.get_credentials( + aws_region_name=aws_region_name, + aws_web_identity_token=aws_web_identity_token, + aws_role_name=aws_role_name, + aws_session_name="my-test-session", + ) + return creds + +@pytest.mark.skipif( + os.environ.get("CIRCLE_OIDC_TOKEN_V2") is None, + reason="Cannot run without being in CircleCI Runner", +) +def test_completion_bedrock_claude_aws_session_token(bedrock_session_token_creds): + print("\ncalling bedrock claude with aws_session_token auth") + + import os + aws_region_name = os.environ["AWS_REGION_NAME"] + aws_access_key_id = bedrock_session_token_creds.access_key + aws_secret_access_key = bedrock_session_token_creds.secret_key + aws_session_token = bedrock_session_token_creds.token + + try: + litellm.set_verbose = True + + response_1 = completion( + model="bedrock/anthropic.claude-3-haiku-20240307-v1:0", + messages=messages, + max_tokens=10, + temperature=0.1, + aws_region_name=aws_region_name, + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + ) + print(response_1) + assert len(response_1.choices) > 0 + assert len(response_1.choices[0].message.content) > 0 + + # This second call is to verify that the cache isn't breaking anything + response_2 = completion( + model="bedrock/anthropic.claude-3-haiku-20240307-v1:0", + messages=messages, + max_tokens=5, + temperature=0.2, + aws_region_name=aws_region_name, + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + ) + print(response_2) + assert len(response_2.choices) > 0 + assert len(response_2.choices[0].message.content) > 0 + + # This third call is to verify that the cache isn't used for a different region + response_3 = completion( + model="bedrock/anthropic.claude-3-haiku-20240307-v1:0", + messages=messages, + max_tokens=6, + temperature=0.3, + aws_region_name="us-east-1", + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + ) + print(response_3) + assert len(response_3.choices) > 0 + assert len(response_3.choices[0].message.content) > 0 + + except RateLimitError: + pass + except Exception as e: + pytest.fail(f"Error occurred: {e}") + +@pytest.mark.skipif( + os.environ.get("CIRCLE_OIDC_TOKEN_V2") is None, + reason="Cannot run without being in CircleCI Runner", +) +def test_completion_bedrock_claude_aws_bedrock_client(bedrock_session_token_creds): + print("\ncalling bedrock claude with aws_session_token auth") + + import os + import boto3 + from botocore.client import Config + + aws_region_name = os.environ["AWS_REGION_NAME"] + aws_access_key_id = bedrock_session_token_creds.access_key + aws_secret_access_key = bedrock_session_token_creds.secret_key + aws_session_token = bedrock_session_token_creds.token + + aws_bedrock_client_west = boto3.client( + service_name="bedrock-runtime", + region_name=aws_region_name, + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + config= Config( + read_timeout=600 + ) + ) + + + try: + litellm.set_verbose = True + + response_1 = completion( + model="bedrock/anthropic.claude-3-haiku-20240307-v1:0", + messages=messages, + max_tokens=10, + temperature=0.1, + aws_bedrock_client=aws_bedrock_client_west, + ) + print(response_1) + assert len(response_1.choices) > 0 + assert len(response_1.choices[0].message.content) > 0 + + # This second call is to verify that the cache isn't breaking anything + response_2 = completion( + model="bedrock/anthropic.claude-3-haiku-20240307-v1:0", + messages=messages, + max_tokens=5, + temperature=0.2, + aws_bedrock_client=aws_bedrock_client_west, + ) + print(response_2) + assert len(response_2.choices) > 0 + assert len(response_2.choices[0].message.content) > 0 + + # This third call is to verify that the cache isn't used for a different region + aws_bedrock_client_east = boto3.client( + service_name="bedrock-runtime", + region_name="us-east-1", + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + config= Config( + read_timeout=600 + ) + ) + + response_3 = completion( + model="bedrock/anthropic.claude-3-haiku-20240307-v1:0", + messages=messages, + max_tokens=6, + temperature=0.3, + aws_bedrock_client=aws_bedrock_client_east, + ) + print(response_3) + assert len(response_3.choices) > 0 + assert len(response_3.choices[0].message.content) > 0 + + except RateLimitError: + pass + except Exception as e: + pytest.fail(f"Error occurred: {e}") + + # test_completion_bedrock_claude_sts_client_auth() From 80b4af7abec2393288aaa4cc01584b34a5d0e6db Mon Sep 17 00:00:00 2001 From: Brian Schultheiss Date: Tue, 25 Jun 2024 13:29:33 -0700 Subject: [PATCH 005/124] Revert some non-essential changes --- docs/my-website/docs/providers/aws_sagemaker.md | 1 - litellm/llms/sagemaker.py | 14 ++------------ litellm/proxy/proxy_server.py | 2 -- litellm/types/router.py | 4 ---- 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/docs/my-website/docs/providers/aws_sagemaker.md b/docs/my-website/docs/providers/aws_sagemaker.md index 5793fb05a..2b65709e8 100644 --- a/docs/my-website/docs/providers/aws_sagemaker.md +++ b/docs/my-website/docs/providers/aws_sagemaker.md @@ -59,7 +59,6 @@ response = completion( messages=[{ "content": "Hello, how are you?","role": "user"}], aws_access_key_id="", aws_secret_access_key="", - aws_session_token="", aws_region_name="", ) ``` diff --git a/litellm/llms/sagemaker.py b/litellm/llms/sagemaker.py index 7d639b7bb..8e75428bb 100644 --- a/litellm/llms/sagemaker.py +++ b/litellm/llms/sagemaker.py @@ -162,10 +162,9 @@ def completion( ): import boto3 - # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) - aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) model_id = optional_params.pop("model_id", None) @@ -176,7 +175,6 @@ def completion( service_name="sagemaker-runtime", aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, region_name=aws_region_name, ) else: @@ -251,7 +249,6 @@ def completion( model_id=model_id, aws_secret_access_key=aws_secret_access_key, aws_access_key_id=aws_access_key_id, - aws_session_token=aws_session_token, aws_region_name=aws_region_name, ) return response @@ -284,7 +281,6 @@ def completion( model_id=model_id, aws_secret_access_key=aws_secret_access_key, aws_access_key_id=aws_access_key_id, - aws_session_token=aws_session_token, aws_region_name=aws_region_name, ) data = json.dumps({"inputs": prompt, "parameters": inference_params}).encode( @@ -418,7 +414,6 @@ async def async_streaming( aws_secret_access_key: Optional[str], aws_access_key_id: Optional[str], aws_region_name: Optional[str], - aws_session_token: Optional[str] = None, ): """ Use aioboto3 @@ -434,7 +429,6 @@ async def async_streaming( service_name="sagemaker-runtime", aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, region_name=aws_region_name, ) else: @@ -487,7 +481,6 @@ async def async_completion( aws_secret_access_key: Optional[str], aws_access_key_id: Optional[str], aws_region_name: Optional[str], - aws_session_token: Optional[str] = None, ): """ Use aioboto3 @@ -503,7 +496,6 @@ async def async_completion( service_name="sagemaker-runtime", aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, region_name=aws_region_name, ) else: @@ -647,10 +639,9 @@ def embedding( ### BOTO3 INIT import boto3 - # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) - aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) if aws_access_key_id is not None: @@ -660,7 +651,6 @@ def embedding( service_name="sagemaker-runtime", aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, region_name=aws_region_name, ) else: diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 9c1039f51..30b90abe6 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -6343,7 +6343,6 @@ async def model_info_v2( _model["litellm_params"].pop("vertex_credentials", None) _model["litellm_params"].pop("aws_access_key_id", None) _model["litellm_params"].pop("aws_secret_access_key", None) - _model["litellm_params"].pop("aws_session_token", None) verbose_proxy_logger.debug("all_models: %s", all_models) return {"data": all_models} @@ -6860,7 +6859,6 @@ async def model_info_v1( model["litellm_params"].pop("vertex_credentials", None) model["litellm_params"].pop("aws_access_key_id", None) model["litellm_params"].pop("aws_secret_access_key", None) - model["litellm_params"].pop("aws_session_token", None) verbose_proxy_logger.debug("all_models: %s", all_models) return {"data": all_models} diff --git a/litellm/types/router.py b/litellm/types/router.py index 059a8620e..e6864ffe2 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -145,7 +145,6 @@ class GenericLiteLLMParams(BaseModel): ## AWS BEDROCK / SAGEMAKER ## aws_access_key_id: Optional[str] = None aws_secret_access_key: Optional[str] = None - aws_session_token: Optional[str] = None aws_region_name: Optional[str] = None ## IBM WATSONX ## watsonx_region_name: Optional[str] = None @@ -179,7 +178,6 @@ class GenericLiteLLMParams(BaseModel): ## AWS BEDROCK / SAGEMAKER ## aws_access_key_id: Optional[str] = None, aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, aws_region_name: Optional[str] = None, ## IBM WATSONX ## watsonx_region_name: Optional[str] = None, @@ -244,7 +242,6 @@ class LiteLLM_Params(GenericLiteLLMParams): ## AWS BEDROCK / SAGEMAKER ## aws_access_key_id: Optional[str] = None, aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, aws_region_name: Optional[str] = None, **params, ): @@ -310,7 +307,6 @@ class LiteLLMParamsTypedDict(TypedDict, total=False): ## AWS BEDROCK / SAGEMAKER ## aws_access_key_id: Optional[str] aws_secret_access_key: Optional[str] - aws_session_token: Optional[str] aws_region_name: Optional[str] ## IBM WATSONX ## watsonx_region_name: Optional[str] From ac7bc0025e1e925dea0d4f2a714b2482a88503ea Mon Sep 17 00:00:00 2001 From: Brian Schultheiss Date: Tue, 25 Jun 2024 13:33:40 -0700 Subject: [PATCH 006/124] Revert some non-essential changes --- docs/my-website/docs/providers/bedrock.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index 1b073ad25..b72dac10b 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -474,10 +474,9 @@ from litellm import completion response = completion( model="bedrock/anthropic.claude-instant-v1", messages=[{ "content": "Hello, how are you?","role": "user"}], - aws_region_name="", aws_access_key_id="", aws_secret_access_key="", - aws_session_token=None, + aws_region_name="", ) ``` @@ -538,7 +537,6 @@ response = completion( aws_region_name=aws_region_name, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, aws_role_name=aws_role_name, aws_session_name="my-test-session", ) @@ -553,7 +551,7 @@ This is a deprecated flow. Boto3 is not async. And boto3.client does not let us Experimental - 2024-Jun-23: - `aws_access_key_id`, `aws_secret_access_key`, and `aws_session_token` will be extracted from boto3.client and be passed onto the httpx client + `aws_access_key_id`, `aws_secret_access_key`, and `aws_session_token` will be extracted from boto3.client and be passed into the httpx client ::: @@ -569,7 +567,7 @@ bedrock = boto3.client( region_name="us-east-1", aws_access_key_id="", aws_secret_access_key="", - aws_session_token=None, + aws_session_token="", ) response = completion( From 746f864fb2fbc15fc844932a89cf1d440f58c807 Mon Sep 17 00:00:00 2001 From: Brian Schultheiss Date: Tue, 25 Jun 2024 13:57:12 -0700 Subject: [PATCH 007/124] Revert some non-essential changes --- litellm/llms/bedrock.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/litellm/llms/bedrock.py b/litellm/llms/bedrock.py index 2403edf81..07d9834bf 100644 --- a/litellm/llms/bedrock.py +++ b/litellm/llms/bedrock.py @@ -558,7 +558,6 @@ def init_bedrock_client( region_name=None, aws_access_key_id: Optional[str] = None, aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, aws_region_name: Optional[str] = None, aws_bedrock_runtime_endpoint: Optional[str] = None, aws_session_name: Optional[str] = None, @@ -576,7 +575,6 @@ def init_bedrock_client( params_to_check = [ aws_access_key_id, aws_secret_access_key, - aws_session_token, aws_region_name, aws_bedrock_runtime_endpoint, aws_session_name, @@ -593,7 +591,6 @@ def init_bedrock_client( ( aws_access_key_id, aws_secret_access_key, - aws_session_token, aws_region_name, aws_bedrock_runtime_endpoint, aws_session_name, @@ -1438,10 +1435,9 @@ def image_generation( Bedrock Image Gen endpoint support """ ### BOTO3 INIT ### - # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) - aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) aws_role_name = optional_params.pop("aws_role_name", None) aws_session_name = optional_params.pop("aws_session_name", None) @@ -1454,7 +1450,6 @@ def image_generation( client = init_bedrock_client( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, aws_region_name=aws_region_name, aws_bedrock_runtime_endpoint=aws_bedrock_runtime_endpoint, aws_web_identity_token=aws_web_identity_token, From 5dce53579ec413e0d59807af838f852beb99636f Mon Sep 17 00:00:00 2001 From: Brian Schultheiss Date: Tue, 25 Jun 2024 14:09:55 -0700 Subject: [PATCH 008/124] Revert some non-essential changes --- litellm/llms/bedrock.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/litellm/llms/bedrock.py b/litellm/llms/bedrock.py index 07d9834bf..d0d3bef6d 100644 --- a/litellm/llms/bedrock.py +++ b/litellm/llms/bedrock.py @@ -668,21 +668,6 @@ def init_bedrock_client( endpoint_url=endpoint_url, config=config, ) - elif ( - aws_access_key_id is not None - and aws_secret_access_key is not None - and aws_session_token is not None - ): ### CHECK FOR AWS SESSION TOKEN ### - client = boto3.client( - service_name="bedrock-runtime", - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, - region_name=region_name, - endpoint_url=endpoint_url, - config=config, - ) - elif aws_role_name is not None and aws_session_name is not None: # use sts if role name passed in sts_client = boto3.client( @@ -801,10 +786,9 @@ def completion( _is_function_call = False json_schemas: dict = {} try: - # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) - aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) aws_role_name = optional_params.pop("aws_role_name", None) aws_session_name = optional_params.pop("aws_session_name", None) @@ -822,7 +806,6 @@ def completion( client = init_bedrock_client( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, aws_region_name=aws_region_name, aws_bedrock_runtime_endpoint=aws_bedrock_runtime_endpoint, aws_role_name=aws_role_name, @@ -1342,10 +1325,9 @@ def embedding( encoding=None, ): ### BOTO3 INIT ### - # pop aws_secret_access_key, aws_access_key_id, aws_session_token, aws_region_name from kwargs, since completion calls fail with them + # pop aws_secret_access_key, aws_access_key_id, aws_region_name from kwargs, since completion calls fail with them aws_secret_access_key = optional_params.pop("aws_secret_access_key", None) aws_access_key_id = optional_params.pop("aws_access_key_id", None) - aws_session_token = optional_params.pop("aws_session_token", None) aws_region_name = optional_params.pop("aws_region_name", None) aws_role_name = optional_params.pop("aws_role_name", None) aws_session_name = optional_params.pop("aws_session_name", None) @@ -1358,7 +1340,6 @@ def embedding( client = init_bedrock_client( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, aws_region_name=aws_region_name, aws_bedrock_runtime_endpoint=aws_bedrock_runtime_endpoint, aws_web_identity_token=aws_web_identity_token, From 09492ccebace713fa94e5d368ebbff4c4e67fd30 Mon Sep 17 00:00:00 2001 From: Brian Schultheiss Date: Tue, 25 Jun 2024 14:33:40 -0700 Subject: [PATCH 009/124] Update tests to verify streaming works --- litellm/tests/test_bedrock_completion.py | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/litellm/tests/test_bedrock_completion.py b/litellm/tests/test_bedrock_completion.py index 614d1b76f..d21f30549 100644 --- a/litellm/tests/test_bedrock_completion.py +++ b/litellm/tests/test_bedrock_completion.py @@ -238,6 +238,20 @@ def bedrock_session_token_creds(): ) return creds +def process_stream_response(res, messages): + import types + if isinstance(res, litellm.utils.CustomStreamWrapper): + chunks = [] + for part in res: + chunks.append(part) + text = part.choices[0].delta.content or "" + print(text, end="") + res = litellm.stream_chunk_builder(chunks, messages=messages) + else: + raise ValueError("Response object is not a streaming response") + + return res + @pytest.mark.skipif( os.environ.get("CIRCLE_OIDC_TOKEN_V2") is None, reason="Cannot run without being in CircleCI Runner", @@ -298,6 +312,23 @@ def test_completion_bedrock_claude_aws_session_token(bedrock_session_token_creds assert len(response_3.choices) > 0 assert len(response_3.choices[0].message.content) > 0 + # This fourth call is to verify streaming api works + response_4 = completion( + model="bedrock/anthropic.claude-3-haiku-20240307-v1:0", + messages=messages, + max_tokens=6, + temperature=0.3, + aws_region_name="us-east-1", + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + stream=True + ) + response_4 = process_stream_response(response_4, messages) + print(response_4) + assert len(response_4.choices) > 0 + assert len(response_4.choices[0].message.content) > 0 + except RateLimitError: pass except Exception as e: @@ -380,6 +411,21 @@ def test_completion_bedrock_claude_aws_bedrock_client(bedrock_session_token_cred assert len(response_3.choices) > 0 assert len(response_3.choices[0].message.content) > 0 + # This fourth call is to verify streaming api works + response_4 = completion( + model="bedrock/anthropic.claude-3-haiku-20240307-v1:0", + messages=messages, + max_tokens=6, + temperature=0.3, + aws_bedrock_client=aws_bedrock_client_east, + stream=True + ) + response_4 = process_stream_response(response_4, messages) + print(response_4) + assert len(response_4.choices) > 0 + assert len(response_4.choices[0].message.content) > 0 + + except RateLimitError: pass except Exception as e: From b4c8af771d29e1acbf5b8dc206e444d379a9c24f Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Jun 2024 18:20:39 -0700 Subject: [PATCH 010/124] fix(langfuse.py): use clean metadata instead of deepcopy --- litellm/integrations/langfuse.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/litellm/integrations/langfuse.py b/litellm/integrations/langfuse.py index eae8b8e22..983ec3942 100644 --- a/litellm/integrations/langfuse.py +++ b/litellm/integrations/langfuse.py @@ -311,22 +311,17 @@ class LangFuseLogger: try: tags = [] - try: - metadata = copy.deepcopy( - metadata - ) # Avoid modifying the original metadata - except: - new_metadata = {} - for key, value in metadata.items(): - if ( - isinstance(value, list) - or isinstance(value, dict) - or isinstance(value, str) - or isinstance(value, int) - or isinstance(value, float) - ): - new_metadata[key] = copy.deepcopy(value) - metadata = new_metadata + new_metadata = {} + for key, value in metadata.items(): + if ( + isinstance(value, list) + or isinstance(value, dict) + or isinstance(value, str) + or isinstance(value, int) + or isinstance(value, float) + ): + new_metadata[key] = copy.deepcopy(value) + metadata = new_metadata supports_tags = Version(langfuse.version.__version__) >= Version("2.6.3") supports_prompt = Version(langfuse.version.__version__) >= Version("2.7.3") From adcd55fca0c8ecb0854a818df6839023090691e9 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Jun 2024 22:33:26 -0700 Subject: [PATCH 011/124] fix(initial-commit): decrypts aws keys in entrypoint.sh --- entrypoint.sh | 57 +++++++---- .../secret_managers/aws_secret_manager.py | 94 ++++++++++++++++++- tests/test_entrypoint.py | 57 +++++++++++ 3 files changed, 186 insertions(+), 22 deletions(-) create mode 100644 tests/test_entrypoint.py diff --git a/entrypoint.sh b/entrypoint.sh index 80adf8d07..a76f126a3 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,5 +1,20 @@ #!/bin/sh +echo "Current working directory: $(pwd)" + + +# Check if SET_AWS_KMS in env +if [ -n "$SET_AWS_KMS" ]; then + # Call Python function to decrypt and reset environment variables + env_vars=$(python -c 'from litellm.proxy.secret_managers.aws_secret_manager import decrypt_and_reset_env_var; env_vars = decrypt_and_reset_env_var();') + echo "Received env_vars: ${env_vars}" + # Export decrypted environment variables to the current Bash environment + while IFS='=' read -r name value; do + export "$name=$value" + done <<< "$env_vars" +fi + +echo "DATABASE_URL post kms: $($DATABASE_URL)" # Check if DATABASE_URL is not set if [ -z "$DATABASE_URL" ]; then # Check if all required variables are provided @@ -13,36 +28,38 @@ if [ -z "$DATABASE_URL" ]; then fi fi +echo "DATABASE_URL: $($DATABASE_URL)" + # Set DIRECT_URL to the value of DATABASE_URL if it is not set, required for migrations if [ -z "$DIRECT_URL" ]; then export DIRECT_URL=$DATABASE_URL fi -# Apply migrations -retry_count=0 -max_retries=3 -exit_code=1 +# # Apply migrations +# retry_count=0 +# max_retries=3 +# exit_code=1 -until [ $retry_count -ge $max_retries ] || [ $exit_code -eq 0 ] -do - retry_count=$((retry_count+1)) - echo "Attempt $retry_count..." +# until [ $retry_count -ge $max_retries ] || [ $exit_code -eq 0 ] +# do +# retry_count=$((retry_count+1)) +# echo "Attempt $retry_count..." - # Run the Prisma db push command - prisma db push --accept-data-loss +# # Run the Prisma db push command +# prisma db push --accept-data-loss - exit_code=$? +# exit_code=$? - if [ $exit_code -ne 0 ] && [ $retry_count -lt $max_retries ]; then - echo "Retrying in 10 seconds..." - sleep 10 - fi -done +# if [ $exit_code -ne 0 ] && [ $retry_count -lt $max_retries ]; then +# echo "Retrying in 10 seconds..." +# sleep 10 +# fi +# done -if [ $exit_code -ne 0 ]; then - echo "Unable to push database changes after $max_retries retries." - exit 1 -fi +# if [ $exit_code -ne 0 ]; then +# echo "Unable to push database changes after $max_retries retries." +# exit 1 +# fi echo "Database push successful!" diff --git a/litellm/proxy/secret_managers/aws_secret_manager.py b/litellm/proxy/secret_managers/aws_secret_manager.py index 8dd6772cf..49c79b68b 100644 --- a/litellm/proxy/secret_managers/aws_secret_manager.py +++ b/litellm/proxy/secret_managers/aws_secret_manager.py @@ -8,9 +8,12 @@ Requires: * `pip install boto3>=1.28.57` """ -import litellm +import ast +import base64 import os -from typing import Optional +from typing import Any, Optional + +import litellm from litellm.proxy._types import KeyManagementSystem @@ -57,3 +60,90 @@ def load_aws_kms(use_aws_kms: Optional[bool]): except Exception as e: raise e + + +class AWSKeyManagementService: + """ + V2 Clean Class for decrypting keys from AWS KeyManagementService + """ + + def __init__(self) -> None: + self.validate_environment() + self.kms_client = self.load_aws_kms(use_aws_kms=True) + + def validate_environment( + self, + ): + if "AWS_REGION_NAME" not in os.environ: + raise ValueError("Missing required environment variable - AWS_REGION_NAME") + + def load_aws_kms(self, use_aws_kms: Optional[bool]): + if use_aws_kms is None or use_aws_kms is False: + return + try: + import boto3 + + validate_environment() + + # Create a Secrets Manager client + kms_client = boto3.client("kms", region_name=os.getenv("AWS_REGION_NAME")) + + litellm.secret_manager_client = kms_client + litellm._key_management_system = KeyManagementSystem.AWS_KMS + return kms_client + except Exception as e: + raise e + + def decrypt_value(self, secret_name: str) -> Any: + if self.kms_client is None: + raise ValueError("kms_client is None") + encrypted_value = os.getenv(secret_name, None) + if encrypted_value is None: + raise Exception( + "AWS KMS - Encrypted Value of Key={} is None".format(secret_name) + ) + if isinstance(encrypted_value, str) and encrypted_value.startswith("aws_kms/"): + encrypted_value = encrypted_value.replace("aws_kms/", "") + + # Decode the base64 encoded ciphertext + ciphertext_blob = base64.b64decode(encrypted_value) + + # Set up the parameters for the decrypt call + params = {"CiphertextBlob": ciphertext_blob} + # Perform the decryption + response = self.kms_client.decrypt(**params) + + # Extract and decode the plaintext + plaintext = response["Plaintext"] + secret = plaintext.decode("utf-8") + if isinstance(secret, str): + secret = secret.strip() + try: + secret_value_as_bool = ast.literal_eval(secret) + if isinstance(secret_value_as_bool, bool): + return secret_value_as_bool + except Exception: + pass + + return secret + + +""" +- look for all values in the env with `aws_kms/` +- decrypt keys +- rewrite env var with decrypted key +""" + + +def decrypt_and_reset_env_var() -> dict: + # setup client class + aws_kms = AWSKeyManagementService() + # iterate through env - for `aws_kms/` + new_values = {} + for k, v in os.environ.items(): + if v is not None and isinstance(v, str) and v.startswith("aws_kms/"): + decrypted_value = aws_kms.decrypt_value(secret_name=k) + # reset env var + new_values[k] = decrypted_value + + return new_values diff --git a/tests/test_entrypoint.py b/tests/test_entrypoint.py new file mode 100644 index 000000000..cbf14c6ea --- /dev/null +++ b/tests/test_entrypoint.py @@ -0,0 +1,57 @@ +# What is this? +## Unit tests for 'entrypoint.sh' + +import pytest +import sys +import os + +sys.path.insert( + 0, os.path.abspath("../") +) # Adds the parent directory to the system path +import litellm +import subprocess + + +def test_decrypt_and_reset_env(): + os.environ["DATABASE_URL"] = ( + "aws_kms/AQICAHgwddjZ9xjVaZ9CNCG8smFU6FiQvfdrjL12DIqi9vUAQwHwF6U7caMgHQa6tK+TzaoMAAAAzjCBywYJKoZIhvcNAQcGoIG9MIG6AgEAMIG0BgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDCmu+DVeKTm5tFZu6AIBEICBhnOFQYviL8JsciGk0bZsn9pfzeYWtNkVXEsl01AdgHBqT9UOZOI4ZC+T3wO/fXA7wdNF4o8ASPDbVZ34ZFdBs8xt4LKp9niufL30WYBkuuzz89ztly0jvE9pZ8L6BMw0ATTaMgIweVtVSDCeCzEb5PUPyxt4QayrlYHBGrNH5Aq/axFTe0La" + ) + from litellm.proxy.secret_managers.aws_secret_manager import ( + decrypt_and_reset_env_var, + ) + + decrypt_and_reset_env_var() + + assert os.environ["DATABASE_URL"] is not None + assert isinstance(os.environ["DATABASE_URL"], str) + assert not os.environ["DATABASE_URL"].startswith("aws_kms/") + + print("DATABASE_URL={}".format(os.environ["DATABASE_URL"])) + + +def test_entrypoint_decrypt_and_reset(): + os.environ["DATABASE_URL"] = ( + "aws_kms/AQICAHgwddjZ9xjVaZ9CNCG8smFU6FiQvfdrjL12DIqi9vUAQwHwF6U7caMgHQa6tK+TzaoMAAAAzjCBywYJKoZIhvcNAQcGoIG9MIG6AgEAMIG0BgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDCmu+DVeKTm5tFZu6AIBEICBhnOFQYviL8JsciGk0bZsn9pfzeYWtNkVXEsl01AdgHBqT9UOZOI4ZC+T3wO/fXA7wdNF4o8ASPDbVZ34ZFdBs8xt4LKp9niufL30WYBkuuzz89ztly0jvE9pZ8L6BMw0ATTaMgIweVtVSDCeCzEb5PUPyxt4QayrlYHBGrNH5Aq/axFTe0La" + ) + command = "./entrypoint.sh" + directory = ".." # Relative to the current directory + + # Run the command using subprocess + result = subprocess.run( + command, shell=True, cwd=directory, capture_output=True, text=True + ) + + # Print the output for debugging purposes + print("STDOUT:", result.stdout) + print("STDERR:", result.stderr) + + # Assert the script ran successfully + assert result.returncode == 0, "The shell script did not execute successfully" + assert ( + "DECRYPTS VALUE" in result.stdout + ), "Expected output not found in script output" + assert ( + "Database push successful!" in result.stdout + ), "Expected output not found in script output" + + assert False From a7122f91a163b9777b834fc1ee39f0965427c815 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 10:38:19 -0700 Subject: [PATCH 012/124] fix(support-'alt=sse'-param): Fixes https://github.com/BerriAI/litellm/issues/4459 --- litellm/llms/custom_httpx/http_handler.py | 10 +++- litellm/llms/vertex_httpx.py | 66 ++++++++--------------- litellm/tests/test_streaming.py | 50 ++--------------- 3 files changed, 36 insertions(+), 90 deletions(-) diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index a3c5865fa..d24acaecc 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -1,6 +1,11 @@ +import asyncio +import os +import traceback +from typing import Any, Mapping, Optional, Union + +import httpx + import litellm -import httpx, asyncio, traceback, os -from typing import Optional, Union, Mapping, Any # https://www.python-httpx.org/advanced/timeouts _DEFAULT_TIMEOUT = httpx.Timeout(timeout=5.0, connect=5.0) @@ -208,6 +213,7 @@ class HTTPHandler: headers: Optional[dict] = None, stream: bool = False, ): + req = self.client.build_request( "POST", url, data=data, json=json, params=params, headers=headers # type: ignore ) diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index 18b1088ba..523120457 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -491,7 +491,7 @@ def make_sync_call( raise VertexAIError(status_code=response.status_code, message=response.read()) completion_stream = ModelResponseIterator( - streaming_response=response.iter_bytes(chunk_size=2056), sync_stream=True + streaming_response=response.iter_bytes(), sync_stream=True ) # LOGGING @@ -811,12 +811,13 @@ class VertexLLM(BaseLLM): endpoint = "generateContent" if stream is True: endpoint = "streamGenerateContent" - - url = ( - "https://generativelanguage.googleapis.com/v1beta/{}:{}?key={}".format( + url = "https://generativelanguage.googleapis.com/v1beta/{}:{}?key={}&alt=sse".format( + _gemini_model_name, endpoint, gemini_api_key + ) + else: + url = "https://generativelanguage.googleapis.com/v1beta/{}:{}?key={}".format( _gemini_model_name, endpoint, gemini_api_key ) - ) else: auth_header, vertex_project = self._ensure_access_token( credentials=vertex_credentials, project_id=vertex_project @@ -827,7 +828,9 @@ class VertexLLM(BaseLLM): endpoint = "generateContent" if stream is True: endpoint = "streamGenerateContent" - url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}" + url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}?alt=sse" + else: + url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}" if ( api_base is not None @@ -840,6 +843,9 @@ class VertexLLM(BaseLLM): else: url = "{}:{}".format(api_base, endpoint) + if stream is True: + url = url + "?alt=sse" + return auth_header, url async def async_streaming( @@ -1268,11 +1274,6 @@ class VertexLLM(BaseLLM): class ModelResponseIterator: def __init__(self, streaming_response, sync_stream: bool): self.streaming_response = streaming_response - if sync_stream: - self.response_iterator = iter(self.streaming_response) - - self.events = ijson.sendable_list() - self.coro = ijson.items_coro(self.events, "item") def chunk_parser(self, chunk: dict) -> GenericStreamingChunk: try: @@ -1322,28 +1323,18 @@ class ModelResponseIterator: # Sync iterator def __iter__(self): + self.response_iterator = self.streaming_response return self def __next__(self): try: chunk = self.response_iterator.__next__() - self.coro.send(chunk) - if self.events: - event = self.events.pop(0) - json_chunk = event - return self.chunk_parser(chunk=json_chunk) - return GenericStreamingChunk( - text="", - is_finished=False, - finish_reason="", - usage=None, - index=0, - tool_use=None, - ) + chunk = chunk.decode() + chunk = chunk.replace("data:", "") + chunk = chunk.strip() + json_chunk = json.loads(chunk) + return self.chunk_parser(chunk=json_chunk) except StopIteration: - if self.events: # flush the events - event = self.events.pop(0) # Remove the first event - return self.chunk_parser(chunk=event) raise StopIteration except ValueError as e: raise RuntimeError(f"Error parsing chunk: {e}") @@ -1356,23 +1347,12 @@ class ModelResponseIterator: async def __anext__(self): try: chunk = await self.async_response_iterator.__anext__() - self.coro.send(chunk) - if self.events: - event = self.events.pop(0) - json_chunk = event - return self.chunk_parser(chunk=json_chunk) - return GenericStreamingChunk( - text="", - is_finished=False, - finish_reason="", - usage=None, - index=0, - tool_use=None, - ) + chunk = chunk.decode() + chunk = chunk.replace("data:", "") + chunk = chunk.strip() + json_chunk = json.loads(chunk) + return self.chunk_parser(chunk=json_chunk) except StopAsyncIteration: - if self.events: # flush the events - event = self.events.pop(0) # Remove the first event - return self.chunk_parser(chunk=event) raise StopAsyncIteration except ValueError as e: raise RuntimeError(f"Error parsing chunk: {e}") diff --git a/litellm/tests/test_streaming.py b/litellm/tests/test_streaming.py index 3042e91b3..5cd0e35a9 100644 --- a/litellm/tests/test_streaming.py +++ b/litellm/tests/test_streaming.py @@ -742,7 +742,10 @@ def test_completion_palm_stream(): # test_completion_palm_stream() -@pytest.mark.parametrize("sync_mode", [False]) # True, +@pytest.mark.parametrize( + "sync_mode", + [True, False], +) # , @pytest.mark.asyncio async def test_completion_gemini_stream(sync_mode): try: @@ -807,49 +810,6 @@ async def test_completion_gemini_stream(sync_mode): pytest.fail(f"Error occurred: {e}") -@pytest.mark.asyncio -async def test_acompletion_gemini_stream(): - try: - litellm.set_verbose = True - print("Streaming gemini response") - messages = [ - # {"role": "system", "content": "You are a helpful assistant."}, - { - "role": "user", - "content": "What do you know?", - }, - ] - print("testing gemini streaming") - response = await acompletion( - model="gemini/gemini-pro", messages=messages, max_tokens=50, stream=True - ) - print(f"type of response at the top: {response}") - complete_response = "" - idx = 0 - # Add any assertions here to check, the response - async for chunk in response: - print(f"chunk in acompletion gemini: {chunk}") - print(chunk.choices[0].delta) - chunk, finished = streaming_format_tests(idx, chunk) - if finished: - break - print(f"chunk: {chunk}") - complete_response += chunk - idx += 1 - print(f"completion_response: {complete_response}") - if complete_response.strip() == "": - raise Exception("Empty response received") - except litellm.APIError as e: - pass - except litellm.RateLimitError as e: - pass - except Exception as e: - if "429 Resource has been exhausted" in str(e): - pass - else: - pytest.fail(f"Error occurred: {e}") - - # asyncio.run(test_acompletion_gemini_stream()) @@ -1071,7 +1031,7 @@ def test_completion_claude_stream_bad_key(): # test_completion_replicate_stream() -@pytest.mark.parametrize("provider", ["vertex_ai"]) # "vertex_ai_beta" +@pytest.mark.parametrize("provider", ["vertex_ai_beta"]) # "" def test_vertex_ai_stream(provider): from litellm.tests.test_amazing_vertex_completion import load_vertex_ai_credentials From aa6f7665c4ead800b150e7f522094297b7017043 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Jun 2024 16:02:23 -0700 Subject: [PATCH 013/124] fix(router.py): only return 'max_tokens', 'input_cost_per_token', etc. in 'get_router_model_info' if base_model is set --- litellm/router.py | 39 +++++++++++-- litellm/tests/test_router.py | 104 +++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 6 deletions(-) diff --git a/litellm/router.py b/litellm/router.py index e2f7ce8b2..d069fa9d3 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -105,7 +105,9 @@ class Router: def __init__( self, - model_list: Optional[List[Union[DeploymentTypedDict, Dict]]] = None, + model_list: Optional[ + Union[List[DeploymentTypedDict], List[dict[str, Any]], List[Dict[str, Any]]] + ] = None, ## ASSISTANTS API ## assistants_config: Optional[AssistantsTypedDict] = None, ## CACHING ## @@ -3970,16 +3972,36 @@ class Router: Augment litellm info with additional params set in `model_info`. + For azure models, ignore the `model:`. Only set max tokens, cost values if base_model is set. + Returns - ModelInfo - If found -> typed dict with max tokens, input cost, etc. + + Raises: + - ValueError -> If model is not mapped yet """ - ## SET MODEL NAME + ## GET BASE MODEL base_model = deployment.get("model_info", {}).get("base_model", None) if base_model is None: base_model = deployment.get("litellm_params", {}).get("base_model", None) - model = base_model or deployment.get("litellm_params", {}).get("model", None) - ## GET LITELLM MODEL INFO + model = base_model + + ## GET PROVIDER + _model, custom_llm_provider, _, _ = litellm.get_llm_provider( + model=deployment.get("litellm_params", {}).get("model", ""), + litellm_params=LiteLLM_Params(**deployment.get("litellm_params", {})), + ) + + ## SET MODEL TO 'model=' - if base_model is None + not azure + if custom_llm_provider == "azure" and base_model is None: + verbose_router_logger.error( + "Could not identify azure model. Set azure 'base_model' for accurate max tokens, cost tracking, etc.- https://docs.litellm.ai/docs/proxy/cost_tracking#spend-tracking-for-azure-openai-models" + ) + else: + model = deployment.get("litellm_params", {}).get("model", None) + + ## GET LITELLM MODEL INFO - raises exception, if model is not mapped model_info = litellm.get_model_info(model=model) ## CHECK USER SET MODEL INFO @@ -4365,7 +4387,7 @@ class Router: """ Filter out model in model group, if: - - model context window < message length + - model context window < message length. For azure openai models, requires 'base_model' is set. - https://docs.litellm.ai/docs/proxy/cost_tracking#spend-tracking-for-azure-openai-models - filter models above rpm limits - if region given, filter out models not in that region / unknown region - [TODO] function call and model doesn't support function calling @@ -4382,6 +4404,11 @@ class Router: try: input_tokens = litellm.token_counter(messages=messages) except Exception as e: + verbose_router_logger.error( + "litellm.router.py::_pre_call_checks: failed to count tokens. Returning initial list of deployments. Got - {}".format( + str(e) + ) + ) return _returned_deployments _context_window_error = False @@ -4425,7 +4452,7 @@ class Router: ) continue except Exception as e: - verbose_router_logger.debug("An error occurs - {}".format(str(e))) + verbose_router_logger.error("An error occurs - {}".format(str(e))) _litellm_params = deployment.get("litellm_params", {}) model_id = deployment.get("model_info", {}).get("id", "") diff --git a/litellm/tests/test_router.py b/litellm/tests/test_router.py index 3237c8084..db240e358 100644 --- a/litellm/tests/test_router.py +++ b/litellm/tests/test_router.py @@ -16,6 +16,7 @@ sys.path.insert( import os from collections import defaultdict from concurrent.futures import ThreadPoolExecutor +from unittest.mock import AsyncMock, MagicMock, patch import httpx from dotenv import load_dotenv @@ -1884,3 +1885,106 @@ async def test_router_model_usage(mock_response): else: print(f"allowed_fails: {allowed_fails}") raise e + + +@pytest.mark.parametrize( + "model, base_model, llm_provider", + [ + ("azure/gpt-4", None, "azure"), + ("azure/gpt-4", "azure/gpt-4-0125-preview", "azure"), + ("gpt-4", None, "openai"), + ], +) +def test_router_get_model_info(model, base_model, llm_provider): + """ + Test if router get model info works based on provider + + For azure -> only if base model set + For openai -> use model= + """ + router = Router( + model_list=[ + { + "model_name": "gpt-4", + "litellm_params": { + "model": model, + "api_key": "my-fake-key", + "api_base": "my-fake-base", + }, + "model_info": {"base_model": base_model, "id": "1"}, + } + ] + ) + + deployment = router.get_deployment(model_id="1") + + assert deployment is not None + + if llm_provider == "openai" or (base_model is not None and llm_provider == "azure"): + router.get_router_model_info(deployment=deployment.to_json()) + else: + try: + router.get_router_model_info(deployment=deployment.to_json()) + pytest.fail("Expected this to raise model not mapped error") + except Exception as e: + if "This model isn't mapped yet" in str(e): + pass + + +@pytest.mark.parametrize( + "model, base_model, llm_provider", + [ + ("azure/gpt-4", None, "azure"), + ("azure/gpt-4", "azure/gpt-4-0125-preview", "azure"), + ("gpt-4", None, "openai"), + ], +) +def test_router_context_window_pre_call_check(model, base_model, llm_provider): + """ + - For an azure model + - if no base model set + - don't enforce context window limits + """ + try: + model_list = [ + { + "model_name": "gpt-4", + "litellm_params": { + "model": model, + "api_key": "my-fake-key", + "api_base": "my-fake-base", + }, + "model_info": {"base_model": base_model, "id": "1"}, + } + ] + router = Router( + model_list=model_list, + set_verbose=True, + enable_pre_call_checks=True, + num_retries=0, + ) + + litellm.token_counter = MagicMock() + + def token_counter_side_effect(*args, **kwargs): + # Process args and kwargs if needed + return 1000000 + + litellm.token_counter.side_effect = token_counter_side_effect + try: + updated_list = router._pre_call_checks( + model="gpt-4", + healthy_deployments=model_list, + messages=[{"role": "user", "content": "Hey, how's it going?"}], + ) + if llm_provider == "azure" and base_model is None: + assert len(updated_list) == 1 + else: + pytest.fail("Expected to raise an error. Got={}".format(updated_list)) + except Exception as e: + if ( + llm_provider == "azure" and base_model is not None + ) or llm_provider == "openai": + pass + except Exception as e: + pytest.fail(f"Got unexpected exception on router! - {str(e)}") From 224148d6133ee50801cb129cbd21ccc213992e25 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 11:00:36 -0700 Subject: [PATCH 014/124] docs(anthropic.md): add claude-3-5-sonnet example to docs --- docs/my-website/docs/providers/anthropic.md | 4 ++++ litellm/proxy/_super_secret_config.yaml | 3 +++ 2 files changed, 7 insertions(+) diff --git a/docs/my-website/docs/providers/anthropic.md b/docs/my-website/docs/providers/anthropic.md index e7d3352f9..a662129d0 100644 --- a/docs/my-website/docs/providers/anthropic.md +++ b/docs/my-website/docs/providers/anthropic.md @@ -168,8 +168,12 @@ print(response) ## Supported Models +`Model Name` 👉 Human-friendly name. +`Function Call` 👉 How to call the model in LiteLLM. + | Model Name | Function Call | |------------------|--------------------------------------------| +| claude-3-5-sonnet | `completion('claude-3-5-sonnet-20240620', messages)` | `os.environ['ANTHROPIC_API_KEY']` | | claude-3-haiku | `completion('claude-3-haiku-20240307', messages)` | `os.environ['ANTHROPIC_API_KEY']` | | claude-3-opus | `completion('claude-3-opus-20240229', messages)` | `os.environ['ANTHROPIC_API_KEY']` | | claude-3-5-sonnet-20240620 | `completion('claude-3-5-sonnet-20240620', messages)` | `os.environ['ANTHROPIC_API_KEY']` | diff --git a/litellm/proxy/_super_secret_config.yaml b/litellm/proxy/_super_secret_config.yaml index b8c26fd2a..c28bb4901 100644 --- a/litellm/proxy/_super_secret_config.yaml +++ b/litellm/proxy/_super_secret_config.yaml @@ -1,4 +1,7 @@ model_list: +- model_name: claude-3-5-sonnet + litellm_params: + model: anthropic/claude-3-5-sonnet - model_name: gemini-1.5-flash-gemini litellm_params: model: gemini/gemini-1.5-flash From 00016830363fded6516c95f1576655c362af1e35 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 14:53:00 -0700 Subject: [PATCH 015/124] fix(cost_calculator.py): handle unexpected error in cost_calculator.py --- litellm/cost_calculator.py | 8 ++++++++ litellm/tests/test_streaming.py | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 9a7df7ebe..062e98be9 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -1,6 +1,7 @@ # What is this? ## File for 'response_cost' calculation in Logging import time +import traceback from typing import List, Literal, Optional, Tuple, Union import litellm @@ -668,3 +669,10 @@ def response_cost_calculator( f"Model={model} for LLM Provider={custom_llm_provider} not found in completion cost map." ) return None + except Exception as e: + verbose_logger.error( + "litellm.cost_calculator.py::response_cost_calculator - Exception occurred - {}/n{}".format( + str(e), traceback.format_exc() + ) + ) + return None diff --git a/litellm/tests/test_streaming.py b/litellm/tests/test_streaming.py index 5cd0e35a9..eec6929f2 100644 --- a/litellm/tests/test_streaming.py +++ b/litellm/tests/test_streaming.py @@ -1040,14 +1040,27 @@ def test_vertex_ai_stream(provider): litellm.vertex_project = "adroit-crow-413218" import random - test_models = ["gemini-1.0-pro"] + test_models = ["gemini-1.5-pro"] for model in test_models: try: print("making request", model) response = completion( model="{}/{}".format(provider, model), messages=[ - {"role": "user", "content": "write 10 line code code for saying hi"} + {"role": "user", "content": "Hey, how's it going?"}, + { + "role": "assistant", + "content": "I'm doing well. Would like to hear the rest of the story?", + }, + {"role": "user", "content": "Na"}, + { + "role": "assistant", + "content": "No problem, is there anything else i can help you with today?", + }, + { + "role": "user", + "content": "I think you're getting cut off sometimes", + }, ], stream=True, ) @@ -1064,6 +1077,8 @@ def test_vertex_ai_stream(provider): raise Exception("Empty response received") print(f"completion_response: {complete_response}") assert is_finished == True + + assert False except litellm.RateLimitError as e: pass except Exception as e: From f52cc18adb0dd68b4484d9643eb208cc789acfde Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 15:03:21 -0700 Subject: [PATCH 016/124] feat - support pass through endpoints --- .../pass_through_endpoints.py | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 litellm/proxy/pass_through_endpoints/pass_through_endpoints.py diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py new file mode 100644 index 000000000..1d2006671 --- /dev/null +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -0,0 +1,101 @@ +import ast +import traceback + +import httpx +from fastapi import APIRouter, FastAPI, HTTPException, Request, Response, status +from fastapi.responses import StreamingResponse + +from litellm._logging import verbose_proxy_logger +from litellm.proxy._types import ProxyException + +async_client = httpx.AsyncClient() + + +async def pass_through_request(request: Request, target: str, custom_headers: dict): + try: + + url = httpx.URL(target) + + # Start with the original request headers + headers = custom_headers + # headers = dict(request.headers) + + request_body = await request.body() + _parsed_body = ast.literal_eval(request_body.decode("utf-8")) + + verbose_proxy_logger.debug( + "Pass through endpoint sending request to \nURL {}\nheaders: {}\nbody: {}\n".format( + url, headers, _parsed_body + ) + ) + + response = await async_client.request( + method=request.method, + url=url, + headers=headers, + params=request.query_params, + json=_parsed_body, + ) + + if response.status_code != 200: + raise HTTPException(status_code=response.status_code, detail=response.text) + + content = await response.aread() + return Response( + content=content, + status_code=response.status_code, + headers=dict(response.headers), + ) + except Exception as e: + verbose_proxy_logger.error( + "litellm.proxy.proxy_server.pass through endpoint(): Exception occured - {}".format( + str(e) + ) + ) + verbose_proxy_logger.debug(traceback.format_exc()) + if isinstance(e, HTTPException): + raise ProxyException( + message=getattr(e, "message", str(e.detail)), + type=getattr(e, "type", "None"), + param=getattr(e, "param", "None"), + code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + ) + else: + error_msg = f"{str(e)}" + raise ProxyException( + message=getattr(e, "message", error_msg), + type=getattr(e, "type", "None"), + param=getattr(e, "param", "None"), + code=getattr(e, "status_code", 500), + ) + + +def create_pass_through_route(endpoint, target, custom_headers=None): + async def endpoint_func(request: Request): + return await pass_through_request(request, target, custom_headers) + + return endpoint_func + + +async def initialize_pass_through_endpoints(pass_through_endpoints: list): + + verbose_proxy_logger.debug("initializing pass through endpoints") + from litellm.proxy.proxy_server import app + + for endpoint in pass_through_endpoints: + _target = endpoint.get("target", None) + _path = endpoint.get("path", None) + _custom_headers = endpoint.get("headers", None) + + if _target is None: + continue + + verbose_proxy_logger.debug("adding pass through endpoint: %s", _path) + + app.add_api_route( + path=_path, + endpoint=create_pass_through_route(_path, _target, _custom_headers), + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], + ) + + verbose_proxy_logger.debug("Added new pass through endpoint: %s", _path) From 954c6ec9eda9ce72e1883c1aaba7af72e4dbad6d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 15:06:51 -0700 Subject: [PATCH 017/124] fix support pass through endpoints --- litellm/proxy/proxy_server.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 553c8a48c..ff4b1e663 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -161,6 +161,9 @@ from litellm.proxy.management_endpoints.key_management_endpoints import ( router as key_management_router, ) from litellm.proxy.management_endpoints.team_endpoints import router as team_router +from litellm.proxy.pass_through_endpoints.pass_through_endpoints import ( + initialize_pass_through_endpoints, +) from litellm.proxy.secret_managers.aws_secret_manager import ( load_aws_kms, load_aws_secret_manager, @@ -1856,6 +1859,11 @@ class ProxyConfig: user_custom_key_generate = get_instance_fn( value=custom_key_generate, config_file_path=config_file_path ) + ## pass through endpoints + if general_settings.get("pass_through_endpoints", None) is not None: + await initialize_pass_through_endpoints( + pass_through_endpoints=general_settings["pass_through_endpoints"] + ) ## dynamodb database_type = general_settings.get("database_type", None) if database_type is not None and ( From c151a1d2449eb645631c0e36ea037bf1a21f4ec2 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 15:12:38 -0700 Subject: [PATCH 018/124] fix(http_handler.py): raise more detailed http status errors --- litellm/llms/anthropic.py | 38 ++++++++++++++++------- litellm/llms/custom_httpx/http_handler.py | 5 +++ litellm/utils.py | 5 ++- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/litellm/llms/anthropic.py b/litellm/llms/anthropic.py index 808813c05..1051a56b7 100644 --- a/litellm/llms/anthropic.py +++ b/litellm/llms/anthropic.py @@ -1,23 +1,28 @@ -import os, types +import copy import json -from enum import Enum -import requests, copy # type: ignore +import os import time +import types +from enum import Enum from functools import partial -from typing import Callable, Optional, List, Union -import litellm.litellm_core_utils -from litellm.utils import ModelResponse, Usage, CustomStreamWrapper -from litellm.litellm_core_utils.core_helpers import map_finish_reason +from typing import Callable, List, Optional, Union + +import httpx # type: ignore +import requests # type: ignore + import litellm -from .prompt_templates.factory import prompt_factory, custom_prompt +import litellm.litellm_core_utils +from litellm.litellm_core_utils.core_helpers import map_finish_reason from litellm.llms.custom_httpx.http_handler import ( AsyncHTTPHandler, _get_async_httpx_client, _get_httpx_client, ) -from .base import BaseLLM -import httpx # type: ignore from litellm.types.llms.anthropic import AnthropicMessagesToolChoice +from litellm.utils import CustomStreamWrapper, ModelResponse, Usage + +from .base import BaseLLM +from .prompt_templates.factory import custom_prompt, prompt_factory class AnthropicConstants(Enum): @@ -179,10 +184,19 @@ async def make_call( if client is None: client = _get_async_httpx_client() # Create a new client if none provided - response = await client.post(api_base, headers=headers, data=data, stream=True) + try: + response = await client.post(api_base, headers=headers, data=data, stream=True) + except httpx.HTTPStatusError as e: + raise AnthropicError( + status_code=e.response.status_code, message=await e.response.aread() + ) + except Exception as e: + raise AnthropicError(status_code=500, message=str(e)) if response.status_code != 200: - raise AnthropicError(status_code=response.status_code, message=response.text) + raise AnthropicError( + status_code=response.status_code, message=await response.aread() + ) completion_stream = response.aiter_lines() diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index d24acaecc..dfb11f191 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -114,6 +114,11 @@ class AsyncHTTPHandler: finally: await new_client.aclose() except httpx.HTTPStatusError as e: + setattr(e, "status_code", e.response.status_code) + if stream is True: + setattr(e, "message", await e.response.aread()) + else: + setattr(e, "message", e.response.text) raise e except Exception as e: raise e diff --git a/litellm/utils.py b/litellm/utils.py index c53e8f338..0eedd259c 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5728,7 +5728,10 @@ def exception_type( print() # noqa try: if model: - error_str = str(original_exception) + if hasattr(original_exception, "message"): + error_str = str(original_exception.message) + else: + error_str = str(original_exception) if isinstance(original_exception, BaseException): exception_type = type(original_exception).__name__ else: From 8f2931937a9970d225f2c5b215432f08927c570a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 15:30:31 -0700 Subject: [PATCH 019/124] fix use os.environ/ vars for pass through endpoints --- .../pass_through_endpoints.py | 38 +++++++++++++++++-- litellm/proxy/proxy_config.yaml | 8 +++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 1d2006671..802c84d84 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -5,20 +5,49 @@ import httpx from fastapi import APIRouter, FastAPI, HTTPException, Request, Response, status from fastapi.responses import StreamingResponse +import litellm from litellm._logging import verbose_proxy_logger from litellm.proxy._types import ProxyException async_client = httpx.AsyncClient() +async def set_env_variables_in_header(custom_headers: dict): + """ + checks if nay headers on config.yaml are defined as os.environ/COHERE_API_KEY etc + + only runs for headers defined on config.yaml + + example header can be + + {"Authorization": "bearer os.environ/COHERE_API_KEY"} + """ + headers = {} + for key, value in custom_headers.items(): + headers[key] = value + if isinstance(value, str) and "os.environ/" in value: + verbose_proxy_logger.debug( + "pass through endpoint - looking up 'os.environ/' variable" + ) + # get string section that is os.environ/ + start_index = value.find("os.environ/") + _variable_name = value[start_index:] + + verbose_proxy_logger.debug( + "pass through endpoint - getting secret for variable name: %s", + _variable_name, + ) + _secret_value = litellm.get_secret(_variable_name) + new_value = value.replace(_variable_name, _secret_value) + headers[key] = new_value + return headers + + async def pass_through_request(request: Request, target: str, custom_headers: dict): try: url = httpx.URL(target) - - # Start with the original request headers headers = custom_headers - # headers = dict(request.headers) request_body = await request.body() _parsed_body = ast.literal_eval(request_body.decode("utf-8")) @@ -86,6 +115,9 @@ async def initialize_pass_through_endpoints(pass_through_endpoints: list): _target = endpoint.get("target", None) _path = endpoint.get("path", None) _custom_headers = endpoint.get("headers", None) + _custom_headers = await set_env_variables_in_header( + custom_headers=_custom_headers + ) if _target is None: continue diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 0c0365f43..04f12a844 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -22,6 +22,13 @@ general_settings: master_key: sk-1234 alerting: ["slack", "email"] public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate"] + pass_through_endpoints: + - path: "/v1/rerank" + target: "https://api.cohere.com/v1/rerank" + headers: + Authorization: "bearer os.environ/COHERE_API_KEY" + content-type: application/json + accept: application/json litellm_settings: @@ -34,6 +41,5 @@ litellm_settings: - user - metadata - metadata.generation_name - cache: True From ac066462df29ba21d36d0e42c036d8c2cd166536 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 15:55:29 -0700 Subject: [PATCH 020/124] docs - add pass through routes on litelm proxy --- docs/my-website/docs/proxy/pass_through.md | 97 ++++++++++++++++++++++ docs/my-website/sidebars.js | 3 +- 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 docs/my-website/docs/proxy/pass_through.md diff --git a/docs/my-website/docs/proxy/pass_through.md b/docs/my-website/docs/proxy/pass_through.md new file mode 100644 index 000000000..c19c93cec --- /dev/null +++ b/docs/my-website/docs/proxy/pass_through.md @@ -0,0 +1,97 @@ +# ➡️ Create Pass Through Endpoints + +Add pass through routes to LiteLLM Proxy + +**Example:** Add a route `/v1/rerank` that forwards requests to `https://api.cohere.com/v1/rerank` through LiteLLM Proxy + + +💡 This allows making the following Request to LiteLLM Proxy +```shell +curl --request POST \ + --url http://localhost:4000/v1/rerank \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --data '{ + "model": "rerank-english-v3.0", + "query": "What is the capital of the United States?", + "top_n": 3, + "documents": ["Carson City is the capital city of the American state of Nevada."] + }' +``` + +## Tutorial - Setup Cohere Re-Rank Endpoint on LiteLLM Proxy + +**Step 1** Define pass through routes on [litellm config.yaml](configs.md) + +```yaml +general_settings: + master_key: sk-1234 + pass_through_endpoints: + - path: "/v1/rerank" # route you want to add to LiteLLM Proxy Server + target: "https://api.cohere.com/v1/rerank" # URL this route should forward requests to + headers: # headers to forward to this URL + Authorization: "bearer os.environ/COHERE_API_KEY" # (Optional) Auth Header to forward to your Endpoint + content-type: application/json # (Optional) Extra Headers to pass to this endpoint + accept: application/json +``` + +**Step 2** Start Proxy Server in detailed_debug mode + +```shell +litellm --config config.yaml +``` +**Step 3** Make Request to pass through endpoint + +```shell +curl --request POST \ + --url http://localhost:4000/v1/rerank \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --data '{ + "model": "rerank-english-v3.0", + "query": "What is the capital of the United States?", + "top_n": 3, + "documents": ["Carson City is the capital city of the American state of Nevada.", + "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan.", + "Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States. It is a federal district.", + "Capitalization or capitalisation in English grammar is the use of a capital letter at the start of a word. English usage varies from capitalization in other languages.", + "Capital punishment (the death penalty) has existed in the United States since beforethe United States was a country. As of 2017, capital punishment is legal in 30 of the 50 states."] + }' +``` + + +🎉 **Expected Response** + +This request got forwarded from LiteLLM Proxy -> Defined Target URL (with headers) + +```shell +{ + "id": "37103a5b-8cfb-48d3-87c7-da288bedd429", + "results": [ + { + "index": 2, + "relevance_score": 0.999071 + }, + { + "index": 4, + "relevance_score": 0.7867867 + }, + { + "index": 0, + "relevance_score": 0.32713068 + } + ], + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "search_units": 1 + } + } +} +``` + + + + diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 31bc6abcb..82f4bd260 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -48,6 +48,7 @@ const sidebars = { "proxy/billing", "proxy/user_keys", "proxy/virtual_keys", + "proxy/token_auth", "proxy/alerting", { type: "category", @@ -56,11 +57,11 @@ const sidebars = { }, "proxy/ui", "proxy/prometheus", + "proxy/pass_through", "proxy/email", "proxy/multiple_admins", "proxy/team_based_routing", "proxy/customer_routing", - "proxy/token_auth", { type: "category", label: "Extra Load Balancing", From b84d335624dc9265b9154f296aa165d6e6393676 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 16:03:56 -0700 Subject: [PATCH 021/124] fix(proxy_cli.py): run aws kms decrypt before starting proxy server --- entrypoint.sh | 58 +++++++------------ litellm/proxy/proxy_cli.py | 15 +++++ .../secret_managers/aws_secret_manager.py | 16 +++-- 3 files changed, 45 insertions(+), 44 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index a76f126a3..6e47dde12 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,20 +1,5 @@ #!/bin/sh -echo "Current working directory: $(pwd)" - - -# Check if SET_AWS_KMS in env -if [ -n "$SET_AWS_KMS" ]; then - # Call Python function to decrypt and reset environment variables - env_vars=$(python -c 'from litellm.proxy.secret_managers.aws_secret_manager import decrypt_and_reset_env_var; env_vars = decrypt_and_reset_env_var();') - echo "Received env_vars: ${env_vars}" - # Export decrypted environment variables to the current Bash environment - while IFS='=' read -r name value; do - export "$name=$value" - done <<< "$env_vars" -fi - -echo "DATABASE_URL post kms: $($DATABASE_URL)" # Check if DATABASE_URL is not set if [ -z "$DATABASE_URL" ]; then # Check if all required variables are provided @@ -28,38 +13,35 @@ if [ -z "$DATABASE_URL" ]; then fi fi -echo "DATABASE_URL: $($DATABASE_URL)" - # Set DIRECT_URL to the value of DATABASE_URL if it is not set, required for migrations if [ -z "$DIRECT_URL" ]; then export DIRECT_URL=$DATABASE_URL fi -# # Apply migrations -# retry_count=0 -# max_retries=3 -# exit_code=1 +# Apply migrations +retry_count=0 +max_retries=3 +exit_code=1 -# until [ $retry_count -ge $max_retries ] || [ $exit_code -eq 0 ] -# do -# retry_count=$((retry_count+1)) -# echo "Attempt $retry_count..." +until [ $retry_count -ge $max_retries ] || [ $exit_code -eq 0 ] +do + retry_count=$((retry_count+1)) + echo "Attempt $retry_count..." -# # Run the Prisma db push command -# prisma db push --accept-data-loss + # Run the Prisma db push command + prisma db push --accept-data-loss -# exit_code=$? + exit_code=$? -# if [ $exit_code -ne 0 ] && [ $retry_count -lt $max_retries ]; then -# echo "Retrying in 10 seconds..." -# sleep 10 -# fi -# done + if [ $exit_code -ne 0 ] && [ $retry_count -lt $max_retries ]; then + echo "Retrying in 10 seconds..." + sleep 10 + fi +done -# if [ $exit_code -ne 0 ]; then -# echo "Unable to push database changes after $max_retries retries." -# exit 1 -# fi +if [ $exit_code -ne 0 ]; then + echo "Unable to push database changes after $max_retries retries." + exit 1 +fi echo "Database push successful!" - diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index 6e6d1f4a9..e98704642 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -442,6 +442,20 @@ def run_server( db_connection_pool_limit = 100 db_connection_timeout = 60 + ### DECRYPT ENV VAR ### + + from litellm.proxy.secret_managers.aws_secret_manager import decrypt_env_var + + if ( + os.getenv("USE_AWS_KMS", None) is not None + and os.getenv("USE_AWS_KMS") == "True" + ): + ## V2 IMPLEMENTATION OF AWS KMS - USER WANTS TO DECRYPT MULTIPLE KEYS IN THEIR ENV + new_env_var = decrypt_env_var() + + for k, v in new_env_var.items(): + os.environ[k] = v + if config is not None: """ Allow user to pass in db url via config @@ -459,6 +473,7 @@ def run_server( proxy_config = ProxyConfig() _config = asyncio.run(proxy_config.get_config(config_file_path=config)) + ### LITELLM SETTINGS ### litellm_settings = _config.get("litellm_settings", None) if ( diff --git a/litellm/proxy/secret_managers/aws_secret_manager.py b/litellm/proxy/secret_managers/aws_secret_manager.py index 49c79b68b..9e5d5befe 100644 --- a/litellm/proxy/secret_managers/aws_secret_manager.py +++ b/litellm/proxy/secret_managers/aws_secret_manager.py @@ -62,7 +62,7 @@ def load_aws_kms(use_aws_kms: Optional[bool]): raise e -class AWSKeyManagementService: +class AWSKeyManagementService_V2: """ V2 Clean Class for decrypting keys from AWS KeyManagementService """ @@ -77,6 +77,12 @@ class AWSKeyManagementService: if "AWS_REGION_NAME" not in os.environ: raise ValueError("Missing required environment variable - AWS_REGION_NAME") + ## CHECK IF LICENSE IN ENV ## - premium feature + if os.getenv("LITELLM_LICENSE", None) is None: + raise ValueError( + "AWSKeyManagementService V2 is an Enterprise Feature. Please add a valid LITELLM_LICENSE to your envionment." + ) + def load_aws_kms(self, use_aws_kms: Optional[bool]): if use_aws_kms is None or use_aws_kms is False: return @@ -88,8 +94,6 @@ class AWSKeyManagementService: # Create a Secrets Manager client kms_client = boto3.client("kms", region_name=os.getenv("AWS_REGION_NAME")) - litellm.secret_manager_client = kms_client - litellm._key_management_system = KeyManagementSystem.AWS_KMS return kms_client except Exception as e: raise e @@ -131,13 +135,13 @@ class AWSKeyManagementService: """ - look for all values in the env with `aws_kms/` - decrypt keys -- rewrite env var with decrypted key +- rewrite env var with decrypted key (). Note: this environment variable will only be available to the current process and any child processes spawned from it. Once the Python script ends, the environment variable will not persist. """ -def decrypt_and_reset_env_var() -> dict: +def decrypt_env_var() -> dict[str, Any]: # setup client class - aws_kms = AWSKeyManagementService() + aws_kms = AWSKeyManagementService_V2() # iterate through env - for `aws_kms/` new_values = {} for k, v in os.environ.items(): From e2046bd59c4deaa77d354490e69fbc45352d0901 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 16:13:44 -0700 Subject: [PATCH 022/124] feat - laker return orig response from lakera api --- enterprise/enterprise_hooks/lakera_ai.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/enterprise/enterprise_hooks/lakera_ai.py b/enterprise/enterprise_hooks/lakera_ai.py index dd37ae2c1..2a4ad418b 100644 --- a/enterprise/enterprise_hooks/lakera_ai.py +++ b/enterprise/enterprise_hooks/lakera_ai.py @@ -114,7 +114,11 @@ class _ENTERPRISE_lakeraAI_Moderation(CustomLogger): if flagged == True: raise HTTPException( - status_code=400, detail={"error": "Violated content safety policy"} + status_code=400, + detail={ + "error": "Violated content safety policy", + "lakera_ai_response": _json_response, + }, ) pass From 0223e52b214fd91c6952d5e281d373768d70cde2 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 16:14:26 -0700 Subject: [PATCH 023/124] test - lakera ai detection --- .../tests/test_lakera_ai_prompt_injection.py | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/litellm/tests/test_lakera_ai_prompt_injection.py b/litellm/tests/test_lakera_ai_prompt_injection.py index 6227eabaa..3e328c824 100644 --- a/litellm/tests/test_lakera_ai_prompt_injection.py +++ b/litellm/tests/test_lakera_ai_prompt_injection.py @@ -1,10 +1,16 @@ # What is this? ## This tests the Lakera AI integration -import sys, os, asyncio, time, random -from datetime import datetime +import asyncio +import os +import random +import sys +import time import traceback +from datetime import datetime + from dotenv import load_dotenv +from fastapi import HTTPException load_dotenv() import os @@ -12,17 +18,19 @@ import os sys.path.insert( 0, os.path.abspath("../..") ) # Adds the parent directory to the system path +import logging + import pytest + import litellm +from litellm import Router, mock_completion +from litellm._logging import verbose_proxy_logger +from litellm.caching import DualCache +from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.enterprise.enterprise_hooks.lakera_ai import ( _ENTERPRISE_lakeraAI_Moderation, ) -from litellm import Router, mock_completion from litellm.proxy.utils import ProxyLogging, hash_token -from litellm.proxy._types import UserAPIKeyAuth -from litellm.caching import DualCache -from litellm._logging import verbose_proxy_logger -import logging verbose_proxy_logger.setLevel(logging.DEBUG) @@ -55,10 +63,12 @@ async def test_lakera_prompt_injection_detection(): call_type="completion", ) pytest.fail(f"Should have failed") - except Exception as e: - print("Got exception: ", e) - assert "Violated content safety policy" in str(e) - pass + except HTTPException as http_exception: + print("http exception details=", http_exception.detail) + + # Assert that the laker ai response is in the exception raise + assert "lakera_ai_response" in http_exception.detail + assert "Violated content safety policy" in str(http_exception) @pytest.mark.asyncio From b78dd6416ac05392bf7a5c8823ce0fe2f5278947 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 16:31:37 -0700 Subject: [PATCH 024/124] fix(prisma_migration.py): support decrypting variables in a python script --- entrypoint.sh | 52 ++++------------------- litellm/proxy/prisma_migration.py | 68 +++++++++++++++++++++++++++++++ tests/test_entrypoint.py | 2 + 3 files changed, 79 insertions(+), 43 deletions(-) create mode 100644 litellm/proxy/prisma_migration.py diff --git a/entrypoint.sh b/entrypoint.sh index 6e47dde12..a028e5426 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,47 +1,13 @@ -#!/bin/sh +#!/bin/bash +echo $(pwd) -# Check if DATABASE_URL is not set -if [ -z "$DATABASE_URL" ]; then - # Check if all required variables are provided - if [ -n "$DATABASE_HOST" ] && [ -n "$DATABASE_USERNAME" ] && [ -n "$DATABASE_PASSWORD" ] && [ -n "$DATABASE_NAME" ]; then - # Construct DATABASE_URL from the provided variables - DATABASE_URL="postgresql://${DATABASE_USERNAME}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}" - export DATABASE_URL - else - echo "Error: Required database environment variables are not set. Provide a postgres url for DATABASE_URL." - exit 1 - fi -fi +# Run the Python migration script +python3 litellm/proxy/prisma_migration.py -# Set DIRECT_URL to the value of DATABASE_URL if it is not set, required for migrations -if [ -z "$DIRECT_URL" ]; then - export DIRECT_URL=$DATABASE_URL -fi - -# Apply migrations -retry_count=0 -max_retries=3 -exit_code=1 - -until [ $retry_count -ge $max_retries ] || [ $exit_code -eq 0 ] -do - retry_count=$((retry_count+1)) - echo "Attempt $retry_count..." - - # Run the Prisma db push command - prisma db push --accept-data-loss - - exit_code=$? - - if [ $exit_code -ne 0 ] && [ $retry_count -lt $max_retries ]; then - echo "Retrying in 10 seconds..." - sleep 10 - fi -done - -if [ $exit_code -ne 0 ]; then - echo "Unable to push database changes after $max_retries retries." +# Check if the Python script executed successfully +if [ $? -eq 0 ]; then + echo "Migration script ran successfully!" +else + echo "Migration script failed!" exit 1 fi - -echo "Database push successful!" diff --git a/litellm/proxy/prisma_migration.py b/litellm/proxy/prisma_migration.py new file mode 100644 index 000000000..6ee09c22b --- /dev/null +++ b/litellm/proxy/prisma_migration.py @@ -0,0 +1,68 @@ +# What is this? +## Script to apply initial prisma migration on Docker setup + +import os +import subprocess +import sys +import time + +sys.path.insert( + 0, os.path.abspath("./") +) # Adds the parent directory to the system path +from litellm.proxy.secret_managers.aws_secret_manager import decrypt_env_var + +if os.getenv("USE_AWS_KMS", None) is not None and os.getenv("USE_AWS_KMS") == "True": + ## V2 IMPLEMENTATION OF AWS KMS - USER WANTS TO DECRYPT MULTIPLE KEYS IN THEIR ENV + new_env_var = decrypt_env_var() + + for k, v in new_env_var.items(): + os.environ[k] = v + +# Check if DATABASE_URL is not set +database_url = os.getenv("DATABASE_URL") +if not database_url: + # Check if all required variables are provided + database_host = os.getenv("DATABASE_HOST") + database_username = os.getenv("DATABASE_USERNAME") + database_password = os.getenv("DATABASE_PASSWORD") + database_name = os.getenv("DATABASE_NAME") + + if database_host and database_username and database_password and database_name: + # Construct DATABASE_URL from the provided variables + database_url = f"postgresql://{database_username}:{database_password}@{database_host}/{database_name}" + os.environ["DATABASE_URL"] = database_url + else: + print( # noqa + "Error: Required database environment variables are not set. Provide a postgres url for DATABASE_URL." # noqa + ) + exit(1) + +# Set DIRECT_URL to the value of DATABASE_URL if it is not set, required for migrations +direct_url = os.getenv("DIRECT_URL") +if not direct_url: + os.environ["DIRECT_URL"] = database_url + +# Apply migrations +retry_count = 0 +max_retries = 3 +exit_code = 1 + +while retry_count < max_retries and exit_code != 0: + retry_count += 1 + print(f"Attempt {retry_count}...") # noqa + + # Run the Prisma db push command + result = subprocess.run( + ["prisma", "db", "push", "--accept-data-loss"], capture_output=True + ) + exit_code = result.returncode + + if exit_code != 0 and retry_count < max_retries: + print("Retrying in 10 seconds...") # noqa + time.sleep(10) + +if exit_code != 0: + print(f"Unable to push database changes after {max_retries} retries.") # noqa + exit(1) + +print("Database push successful!") # noqa diff --git a/tests/test_entrypoint.py b/tests/test_entrypoint.py index cbf14c6ea..803135e35 100644 --- a/tests/test_entrypoint.py +++ b/tests/test_entrypoint.py @@ -12,6 +12,7 @@ import litellm import subprocess +@pytest.mark.skip(reason="local test") def test_decrypt_and_reset_env(): os.environ["DATABASE_URL"] = ( "aws_kms/AQICAHgwddjZ9xjVaZ9CNCG8smFU6FiQvfdrjL12DIqi9vUAQwHwF6U7caMgHQa6tK+TzaoMAAAAzjCBywYJKoZIhvcNAQcGoIG9MIG6AgEAMIG0BgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDCmu+DVeKTm5tFZu6AIBEICBhnOFQYviL8JsciGk0bZsn9pfzeYWtNkVXEsl01AdgHBqT9UOZOI4ZC+T3wO/fXA7wdNF4o8ASPDbVZ34ZFdBs8xt4LKp9niufL30WYBkuuzz89ztly0jvE9pZ8L6BMw0ATTaMgIweVtVSDCeCzEb5PUPyxt4QayrlYHBGrNH5Aq/axFTe0La" @@ -29,6 +30,7 @@ def test_decrypt_and_reset_env(): print("DATABASE_URL={}".format(os.environ["DATABASE_URL"])) +@pytest.mark.skip(reason="local test") def test_entrypoint_decrypt_and_reset(): os.environ["DATABASE_URL"] = ( "aws_kms/AQICAHgwddjZ9xjVaZ9CNCG8smFU6FiQvfdrjL12DIqi9vUAQwHwF6U7caMgHQa6tK+TzaoMAAAAzjCBywYJKoZIhvcNAQcGoIG9MIG6AgEAMIG0BgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDCmu+DVeKTm5tFZu6AIBEICBhnOFQYviL8JsciGk0bZsn9pfzeYWtNkVXEsl01AdgHBqT9UOZOI4ZC+T3wO/fXA7wdNF4o8ASPDbVZ34ZFdBs8xt4LKp9niufL30WYBkuuzz89ztly0jvE9pZ8L6BMw0ATTaMgIweVtVSDCeCzEb5PUPyxt4QayrlYHBGrNH5Aq/axFTe0La" From 1980a07f322220f2feffa48cf69adefab0974b8e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 16:54:28 -0700 Subject: [PATCH 025/124] fix test custom callback router --- litellm/llms/azure.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/litellm/llms/azure.py b/litellm/llms/azure.py index 11bf5c3f0..e127ecea6 100644 --- a/litellm/llms/azure.py +++ b/litellm/llms/azure.py @@ -717,11 +717,32 @@ class AzureChatCompletion(BaseLLM): model_response_object=model_response, ) except AzureOpenAIError as e: + ## LOGGING + logging_obj.post_call( + input=data["messages"], + api_key=api_key, + additional_args={"complete_input_dict": data}, + original_response=str(e), + ) exception_mapping_worked = True raise e except asyncio.CancelledError as e: + ## LOGGING + logging_obj.post_call( + input=data["messages"], + api_key=api_key, + additional_args={"complete_input_dict": data}, + original_response=str(e), + ) raise AzureOpenAIError(status_code=500, message=str(e)) except Exception as e: + ## LOGGING + logging_obj.post_call( + input=data["messages"], + api_key=api_key, + additional_args={"complete_input_dict": data}, + original_response=str(e), + ) if hasattr(e, "status_code"): raise e else: From d172a3ef6bfd474f7ea094fc68c6bfe85ac77c23 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 16:58:57 -0700 Subject: [PATCH 026/124] fix python3.8 install --- litellm/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/router.py b/litellm/router.py index d069fa9d3..5d0cde44f 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -106,7 +106,7 @@ class Router: def __init__( self, model_list: Optional[ - Union[List[DeploymentTypedDict], List[dict[str, Any]], List[Dict[str, Any]]] + Union[List[DeploymentTypedDict], List[Dict[str, Any]]] ] = None, ## ASSISTANTS API ## assistants_config: Optional[AssistantsTypedDict] = None, From 6af12933841f23dbe595de0e7e6d40df788a8201 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 17:27:13 -0700 Subject: [PATCH 027/124] feat - pass through langfuse requests --- .../pass_through_endpoints.py | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index 802c84d84..d4b448496 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -1,5 +1,6 @@ import ast import traceback +from base64 import b64encode import httpx from fastapi import APIRouter, FastAPI, HTTPException, Request, Response, status @@ -24,22 +25,41 @@ async def set_env_variables_in_header(custom_headers: dict): """ headers = {} for key, value in custom_headers.items(): - headers[key] = value - if isinstance(value, str) and "os.environ/" in value: - verbose_proxy_logger.debug( - "pass through endpoint - looking up 'os.environ/' variable" - ) - # get string section that is os.environ/ - start_index = value.find("os.environ/") - _variable_name = value[start_index:] + # langfuse Api requires base64 encoded headers - it's simpleer to just ask litellm users to set their langfuse public and secret keys + # we can then get the b64 encoded keys here + if key == "LANGFUSE_PUBLIC_KEY" or key == "LANGFUSE_SECRET_KEY": + # langfuse requires b64 encoded headers - we construct that here + _langfuse_public_key = custom_headers["LANGFUSE_PUBLIC_KEY"] + _langfuse_secret_key = custom_headers["LANGFUSE_SECRET_KEY"] + if isinstance( + _langfuse_public_key, str + ) and _langfuse_public_key.startswith("os.environ/"): + _langfuse_public_key = litellm.get_secret(_langfuse_public_key) + if isinstance( + _langfuse_secret_key, str + ) and _langfuse_secret_key.startswith("os.environ/"): + _langfuse_secret_key = litellm.get_secret(_langfuse_secret_key) + headers["Authorization"] = "Basic " + b64encode( + f"{_langfuse_public_key}:{_langfuse_secret_key}".encode("utf-8") + ).decode("ascii") + else: + # for all other headers + headers[key] = value + if isinstance(value, str) and "os.environ/" in value: + verbose_proxy_logger.debug( + "pass through endpoint - looking up 'os.environ/' variable" + ) + # get string section that is os.environ/ + start_index = value.find("os.environ/") + _variable_name = value[start_index:] - verbose_proxy_logger.debug( - "pass through endpoint - getting secret for variable name: %s", - _variable_name, - ) - _secret_value = litellm.get_secret(_variable_name) - new_value = value.replace(_variable_name, _secret_value) - headers[key] = new_value + verbose_proxy_logger.debug( + "pass through endpoint - getting secret for variable name: %s", + _variable_name, + ) + _secret_value = litellm.get_secret(_variable_name) + new_value = value.replace(_variable_name, _secret_value) + headers[key] = new_value return headers From 40d9278dcbfd10ca8ae7fd090990f23750a8703b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 17:28:21 -0700 Subject: [PATCH 028/124] test - pass through langfuse requests --- litellm/proxy/tests/test_pass_through_langfuse.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 litellm/proxy/tests/test_pass_through_langfuse.py diff --git a/litellm/proxy/tests/test_pass_through_langfuse.py b/litellm/proxy/tests/test_pass_through_langfuse.py new file mode 100644 index 000000000..dfc91ee1b --- /dev/null +++ b/litellm/proxy/tests/test_pass_through_langfuse.py @@ -0,0 +1,14 @@ +from langfuse import Langfuse + +langfuse = Langfuse( + host="http://localhost:4000", + public_key="anything", + secret_key="anything", +) + +print("sending langfuse trace request") +trace = langfuse.trace(name="test-trace-litellm-proxy-passthrough") +print("flushing langfuse request") +langfuse.flush() + +print("flushed langfuse request") From bc1c96ca356a39f62da345b7cb2d661867cfa071 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 17:29:11 -0700 Subject: [PATCH 029/124] pass through langfuse "/api/public/ingestion" --- litellm/proxy/proxy_config.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 04f12a844..40e0386f4 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -20,8 +20,6 @@ model_list: general_settings: master_key: sk-1234 - alerting: ["slack", "email"] - public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate"] pass_through_endpoints: - path: "/v1/rerank" target: "https://api.cohere.com/v1/rerank" @@ -29,7 +27,11 @@ general_settings: Authorization: "bearer os.environ/COHERE_API_KEY" content-type: application/json accept: application/json - + - path: "/api/public/ingestion" + target: "https://us.cloud.langfuse.com/api/public/ingestion" + headers: + LANGFUSE_PUBLIC_KEY: "os.environ/LANGFUSE_DEV_PUBLIC_KEY" + LANGFUSE_SECRET_KEY: "os.environ/LANGFUSE_DEV_SK_KEY" litellm_settings: success_callback: ["prometheus"] From c6c2617d709714b40c47345527efee6700f5c552 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 17:53:13 -0700 Subject: [PATCH 030/124] docs - pass through langfuse requests on proxy --- docs/my-website/docs/proxy/pass_through.md | 61 ++++++++++++++++++++- docs/my-website/img/proxy_langfuse.png | Bin 0 -> 217561 bytes 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 docs/my-website/img/proxy_langfuse.png diff --git a/docs/my-website/docs/proxy/pass_through.md b/docs/my-website/docs/proxy/pass_through.md index c19c93cec..c479d36cc 100644 --- a/docs/my-website/docs/proxy/pass_through.md +++ b/docs/my-website/docs/proxy/pass_through.md @@ -1,3 +1,5 @@ +import Image from '@theme/IdealImage'; + # ➡️ Create Pass Through Endpoints Add pass through routes to LiteLLM Proxy @@ -19,7 +21,7 @@ curl --request POST \ }' ``` -## Tutorial - Setup Cohere Re-Rank Endpoint on LiteLLM Proxy +## Tutorial - Pass through Cohere Re-Rank Endpoint **Step 1** Define pass through routes on [litellm config.yaml](configs.md) @@ -38,10 +40,12 @@ general_settings: **Step 2** Start Proxy Server in detailed_debug mode ```shell -litellm --config config.yaml +litellm --config config.yaml --detailed_debug ``` **Step 3** Make Request to pass through endpoint +Here `http://localhost:4000` is your litellm proxy endpoint + ```shell curl --request POST \ --url http://localhost:4000/v1/rerank \ @@ -92,6 +96,59 @@ This request got forwarded from LiteLLM Proxy -> Defined Target URL (with header } ``` +## Tutorial - Pass Through Langfuse Requests +**Step 1** Define pass through routes on [litellm config.yaml](configs.md) + +```yaml +general_settings: + master_key: sk-1234 + pass_through_endpoints: + - path: "/api/public/ingestion" # route you want to add to LiteLLM Proxy Server + target: "https://us.cloud.langfuse.com/api/public/ingestion" # URL this route should forward + headers: + LANGFUSE_PUBLIC_KEY: "os.environ/LANGFUSE_DEV_PUBLIC_KEY" # your langfuse account public key + LANGFUSE_SECRET_KEY: "os.environ/LANGFUSE_DEV_SK_KEY" # your langfuse account secret key +``` + +**Step 2** Start Proxy Server in detailed_debug mode + +```shell +litellm --config config.yaml --detailed_debug +``` +**Step 3** Make Request to pass through endpoint + +Run this code to make a sample trace +```python +from langfuse import Langfuse + +langfuse = Langfuse( + host="http://localhost:4000", # your litellm proxy endpoint + public_key="anything", # no key required since this is a pass through + secret_key="anything", # no key required since this is a pass through +) + +print("sending langfuse trace request") +trace = langfuse.trace(name="test-trace-litellm-proxy-passthrough") +print("flushing langfuse request") +langfuse.flush() + +print("flushed langfuse request") +``` + + +🎉 **Expected Response** + +On success +Expect to see the following Trace Generated on your Langfuse Dashboard + + + +You will see the following endpoint called on your litellm proxy server logs + +```shell +POST /api/public/ingestion HTTP/1.1" 207 Multi-Status +``` + diff --git a/docs/my-website/img/proxy_langfuse.png b/docs/my-website/img/proxy_langfuse.png new file mode 100644 index 0000000000000000000000000000000000000000..4a3ca28eef3c4df6a7db5e3b6673959a0e4db132 GIT binary patch literal 217561 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

`z;On; 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 Date: Fri, 28 Jun 2024 18:03:33 -0700 Subject: [PATCH 031/124] fix gemini tool calling issue --- litellm/llms/vertex_httpx.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index 523120457..a0cfc98e7 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -183,10 +183,17 @@ class GoogleAIStudioGeminiConfig: # key diff from VertexAI - 'frequency_penalty if param == "tools" and isinstance(value, list): gtool_func_declarations = [] for tool in value: + _parameters = tool.get("function", {}).get("parameters", {}) + _properties = _parameters.get("properties", {}) + if isinstance(_properties, dict): + for _, _property in _properties.items(): + if "enum" in _property and "format" not in _property: + _property["format"] = "enum" + gtool_func_declaration = FunctionDeclaration( name=tool["function"]["name"], description=tool["function"].get("description", ""), - parameters=tool["function"].get("parameters", {}), + parameters=_parameters, ) gtool_func_declarations.append(gtool_func_declaration) optional_params["tools"] = [ From 1ee18ce6714152cdbf68fe433683a1feb115a403 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 28 Jun 2024 18:05:03 -0700 Subject: [PATCH 032/124] ci/cd run again --- litellm/tests/test_completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 5138e9b61..1c10ef461 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -23,7 +23,7 @@ from litellm import RateLimitError, Timeout, completion, completion_cost, embedd from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.llms.prompt_templates.factory import anthropic_messages_pt -# litellm.num_retries = 3 +# litellm.num_retries=3 litellm.cache = None litellm.success_callback = [] user_message = "Write a short poem about the sky" From d10912beeb305a0d5141ff996f3af45bd545ea18 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 21:26:22 -0700 Subject: [PATCH 033/124] fix(main.py): pass in openrouter as custom provider for openai client call Fixes https://github.com/BerriAI/litellm/issues/4414 --- litellm/llms/openai.py | 6 +++--- litellm/main.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/litellm/llms/openai.py b/litellm/llms/openai.py index 7d14fa450..32e63b957 100644 --- a/litellm/llms/openai.py +++ b/litellm/llms/openai.py @@ -678,17 +678,17 @@ class OpenAIChatCompletion(BaseLLM): if headers: optional_params["extra_headers"] = headers if model is None or messages is None: - raise OpenAIError(status_code=422, message=f"Missing model or messages") + raise OpenAIError(status_code=422, message="Missing model or messages") if not isinstance(timeout, float) and not isinstance( timeout, httpx.Timeout ): raise OpenAIError( status_code=422, - message=f"Timeout needs to be a float or httpx.Timeout", + message="Timeout needs to be a float or httpx.Timeout", ) - if custom_llm_provider != "openai": + if custom_llm_provider is not None and custom_llm_provider != "openai": model_response.model = f"{custom_llm_provider}/{model}" # process all OpenAI compatible provider logic here if custom_llm_provider == "mistral": diff --git a/litellm/main.py b/litellm/main.py index 69ce61fab..9945e1b95 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1828,6 +1828,7 @@ def completion( logging_obj=logging, acompletion=acompletion, timeout=timeout, # type: ignore + custom_llm_provider="openrouter", ) ## LOGGING logging.post_call( From ca04244a0ab76291a819f0f9a475f5e0706d0808 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 21:50:21 -0700 Subject: [PATCH 034/124] fix(utils.py): correctly raise openrouter error --- litellm/tests/test_utils.py | 54 +++++++++++++++++++++++++++++++++++++ litellm/utils.py | 21 +++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/litellm/tests/test_utils.py b/litellm/tests/test_utils.py index 09715e6c1..78b64270c 100644 --- a/litellm/tests/test_utils.py +++ b/litellm/tests/test_utils.py @@ -609,3 +609,57 @@ def test_logging_trace_id(langfuse_trace_id, langfuse_existing_trace_id): litellm_logging_obj._get_trace_id(service_name="langfuse") == litellm_call_id ) + + +def test_convert_model_response_object(): + """ + Unit test to ensure model response object correctly handles openrouter errors. + """ + args = { + "response_object": { + "id": None, + "choices": None, + "created": None, + "model": None, + "object": None, + "service_tier": None, + "system_fingerprint": None, + "usage": None, + "error": { + "message": '{"type":"error","error":{"type":"invalid_request_error","message":"Output blocked by content filtering policy"}}', + "code": 400, + }, + }, + "model_response_object": litellm.ModelResponse( + id="chatcmpl-b88ce43a-7bfc-437c-b8cc-e90d59372cfb", + choices=[ + litellm.Choices( + finish_reason="stop", + index=0, + message=litellm.Message(content="default", role="assistant"), + ) + ], + created=1719376241, + model="openrouter/anthropic/claude-3.5-sonnet", + object="chat.completion", + system_fingerprint=None, + usage=litellm.Usage(), + ), + "response_type": "completion", + "stream": False, + "start_time": None, + "end_time": None, + "hidden_params": None, + } + + try: + litellm.convert_to_model_response_object(**args) + pytest.fail("Expected this to fail") + except Exception as e: + assert hasattr(e, "status_code") + assert e.status_code == 400 + assert hasattr(e, "message") + assert ( + e.message + == '{"type":"error","error":{"type":"invalid_request_error","message":"Output blocked by content filtering policy"}}' + ) diff --git a/litellm/utils.py b/litellm/utils.py index 0eedd259c..70d077063 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5273,6 +5273,27 @@ def convert_to_model_response_object( hidden_params: Optional[dict] = None, ): received_args = locals() + ### CHECK IF ERROR IN RESPONSE ### - openrouter returns these in the dictionary + if ( + response_object is not None + and "error" in response_object + and response_object["error"] is not None + ): + error_args = {"status_code": 422, "message": "Error in response object"} + if isinstance(response_object["error"], dict): + if "code" in response_object["error"]: + error_args["status_code"] = response_object["error"]["code"] + if "message" in response_object["error"]: + if isinstance(response_object["error"]["message"], dict): + message_str = json.dumps(response_object["error"]["message"]) + else: + message_str = str(response_object["error"]["message"]) + error_args["message"] = message_str + raised_exception = Exception() + setattr(raised_exception, "status_code", error_args["status_code"]) + setattr(raised_exception, "message", error_args["message"]) + raise raised_exception + try: if response_type == "completion" and ( model_response_object is None From 849f7ca5907c8c14739a5b9117cfec88f807fd9f Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 21:52:07 -0700 Subject: [PATCH 035/124] =?UTF-8?q?bump:=20version=201.40.31=20=E2=86=92?= =?UTF-8?q?=201.40.32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4aee21cd9..9084f5778 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.40.31" +version = "1.40.32" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -90,7 +90,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.40.31" +version = "1.40.32" version_files = [ "pyproject.toml:^version" ] From 9556bfda81a645e5cd2f872b72d5deae8fe14735 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 21:56:14 -0700 Subject: [PATCH 036/124] fix(aws_secret_manager.py): fix typing error --- litellm/proxy/secret_managers/aws_secret_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/secret_managers/aws_secret_manager.py b/litellm/proxy/secret_managers/aws_secret_manager.py index 9e5d5befe..b737640b3 100644 --- a/litellm/proxy/secret_managers/aws_secret_manager.py +++ b/litellm/proxy/secret_managers/aws_secret_manager.py @@ -11,7 +11,7 @@ Requires: import ast import base64 import os -from typing import Any, Optional +from typing import Any, Dict, Optional import litellm from litellm.proxy._types import KeyManagementSystem @@ -139,7 +139,7 @@ class AWSKeyManagementService_V2: """ -def decrypt_env_var() -> dict[str, Any]: +def decrypt_env_var() -> Dict[str, Any]: # setup client class aws_kms = AWSKeyManagementService_V2() # iterate through env - for `aws_kms/` From c9a424d28d23b798e1f4c5c00d95cfa0cf0eb13c Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 22:13:29 -0700 Subject: [PATCH 037/124] fix(router.py): fix get_router_model_info for azure models --- litellm/router.py | 4 ++-- litellm/tests/test_router.py | 1 + pyproject.toml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/litellm/router.py b/litellm/router.py index 5d0cde44f..ba3f13b8e 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -3998,8 +3998,8 @@ class Router: verbose_router_logger.error( "Could not identify azure model. Set azure 'base_model' for accurate max tokens, cost tracking, etc.- https://docs.litellm.ai/docs/proxy/cost_tracking#spend-tracking-for-azure-openai-models" ) - else: - model = deployment.get("litellm_params", {}).get("model", None) + elif custom_llm_provider != "azure": + model = _model ## GET LITELLM MODEL INFO - raises exception, if model is not mapped model_info = litellm.get_model_info(model=model) diff --git a/litellm/tests/test_router.py b/litellm/tests/test_router.py index db240e358..7c59611d7 100644 --- a/litellm/tests/test_router.py +++ b/litellm/tests/test_router.py @@ -812,6 +812,7 @@ def test_router_context_window_check_pre_call_check(): "base_model": "azure/gpt-35-turbo", "mock_response": "Hello world 1!", }, + "model_info": {"base_model": "azure/gpt-35-turbo"}, }, { "model_name": "gpt-3.5-turbo", # openai model name diff --git a/pyproject.toml b/pyproject.toml index 9084f5778..a3267dfa8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.40.32" +version = "1.41.0" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -90,7 +90,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.40.32" +version = "1.41.0" version_files = [ "pyproject.toml:^version" ] From 831745e71080b75ca972cde454a7ccbe5ca6749c Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 22:19:44 -0700 Subject: [PATCH 038/124] test(test_streaming.py): try-except replicate api instability --- litellm/tests/test_streaming.py | 2 ++ litellm/utils.py | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/litellm/tests/test_streaming.py b/litellm/tests/test_streaming.py index eec6929f2..fa9e49f87 100644 --- a/litellm/tests/test_streaming.py +++ b/litellm/tests/test_streaming.py @@ -1265,6 +1265,8 @@ async def test_completion_replicate_llama3_streaming(sync_mode): raise Exception("finish reason not set") if complete_response.strip() == "": raise Exception("Empty response received") + except litellm.UnprocessableEntityError as e: + pass except Exception as e: pytest.fail(f"Error occurred: {e}") diff --git a/litellm/utils.py b/litellm/utils.py index 70d077063..fef4976ca 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -6160,7 +6160,6 @@ def exception_type( ) elif ( original_exception.status_code == 400 - or original_exception.status_code == 422 or original_exception.status_code == 413 ): exception_mapping_worked = True @@ -6170,6 +6169,14 @@ def exception_type( llm_provider="replicate", response=original_exception.response, ) + elif original_exception.status_code == 422: + exception_mapping_worked = True + raise UnprocessableEntityError( + message=f"ReplicateException - {original_exception.message}", + model=model, + llm_provider="replicate", + response=original_exception.response, + ) elif original_exception.status_code == 408: exception_mapping_worked = True raise Timeout( From 8571cb45e80cc561dc34bc6aa89611eb96b9fe3e Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 22:35:26 -0700 Subject: [PATCH 039/124] fix(http_handler.py): add retry logic for httpx.ConnectError --- litellm/llms/custom_httpx/http_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index dfb11f191..9b01c96b1 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -98,7 +98,7 @@ class AsyncHTTPHandler: response = await self.client.send(req, stream=stream) response.raise_for_status() return response - except httpx.RemoteProtocolError: + except (httpx.RemoteProtocolError, httpx.ConnectError): # Retry the request with a new session if there is a connection error new_client = self.create_client(timeout=self.timeout, concurrent_limit=1) try: From 40e4ad52a4020d422e63487d6f5bf634bc5b01d1 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 22:56:22 -0700 Subject: [PATCH 040/124] docs(aws-key-manager): add key decryption with aws key manager to docs --- docs/my-website/docs/proxy/enterprise.md | 36 ++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/my-website/docs/proxy/enterprise.md b/docs/my-website/docs/proxy/enterprise.md index 9fff879e5..4baa385e4 100644 --- a/docs/my-website/docs/proxy/enterprise.md +++ b/docs/my-website/docs/proxy/enterprise.md @@ -6,7 +6,7 @@ import TabItem from '@theme/TabItem'; :::tip -Get in touch with us [here](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat) +To get a license, get in touch with us [here](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat) ::: @@ -21,6 +21,7 @@ Features: - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) - ✅ Reject calls from Blocked User list - ✅ Reject calls (incoming / outgoing) with Banned Keywords (e.g. competitors) +- [BETA] AWS Key Manager v2 - Key Decryption ## Audit Logs @@ -1019,4 +1020,35 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \ Share a public page of available models for users - \ No newline at end of file + + + +## [BETA] AWS Key Manager - Key Decryption + +This is a beta feature, and subject to changes. + + +**Step 1.** Add `USE_AWS_KMS` to env + +```env +USE_AWS_KMS="True" +``` + +**Step 2.** Add `aws_kms/` to encrypted keys in env + +```env +DATABASE_URL="aws_kms/AQICAH.." +``` + +**Step 3.** Start proxy + +``` +$ litellm +``` + +How it works? +- Key Decryption runs before server starts up. [**Code**](https://github.com/BerriAI/litellm/blob/8571cb45e80cc561dc34bc6aa89611eb96b9fe3e/litellm/proxy/proxy_cli.py#L445) +- It adds the decrypted value to the `os.environ` for the python process. + +**Note:** Setting an environment variable within a Python script using os.environ will not make that variable accessible via SSH sessions or any other new processes that are started independently of the Python script. Environment variables set this way only affect the current process and its child processes. + From 062ccf35add7e81a2f29c5f7765178dda9c9dc91 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 22:58:15 -0700 Subject: [PATCH 041/124] docs(secret.md): update docs --- docs/my-website/docs/secret.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/my-website/docs/secret.md b/docs/my-website/docs/secret.md index 08c2e89d1..91ae38368 100644 --- a/docs/my-website/docs/secret.md +++ b/docs/my-website/docs/secret.md @@ -8,7 +8,13 @@ LiteLLM supports reading secrets from Azure Key Vault and Infisical - [Infisical Secret Manager](#infisical-secret-manager) - [.env Files](#env-files) -## AWS Key Management Service +## AWS Key Management V1 + +:::tip + +[BETA] AWS Key Management v2 is on the enterprise tier. Go [here for docs](./proxy/enterprise.md#beta-aws-key-manager---key-decryption) + +::: Use AWS KMS to storing a hashed copy of your Proxy Master Key in the environment. From 555c02dbc3eed3c3d7bade85a047a1093b746a07 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 28 Jun 2024 22:58:47 -0700 Subject: [PATCH 042/124] docs(enterprise.md): add links --- docs/my-website/docs/proxy/enterprise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/docs/proxy/enterprise.md b/docs/my-website/docs/proxy/enterprise.md index 4baa385e4..e4329f882 100644 --- a/docs/my-website/docs/proxy/enterprise.md +++ b/docs/my-website/docs/proxy/enterprise.md @@ -21,7 +21,7 @@ Features: - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) - ✅ Reject calls from Blocked User list - ✅ Reject calls (incoming / outgoing) with Banned Keywords (e.g. competitors) -- [BETA] AWS Key Manager v2 - Key Decryption +- [[BETA] AWS Key Manager v2 - Key Decryption](#beta-aws-key-manager---key-decryption) ## Audit Logs From f961b41046755422db3aff872b3925693fed73f1 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 08:03:51 -0700 Subject: [PATCH 043/124] doc add spec for pass through --- docs/my-website/docs/proxy/pass_through.md | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/my-website/docs/proxy/pass_through.md b/docs/my-website/docs/proxy/pass_through.md index c479d36cc..fcf7c2fd6 100644 --- a/docs/my-website/docs/proxy/pass_through.md +++ b/docs/my-website/docs/proxy/pass_through.md @@ -152,3 +152,31 @@ POST /api/public/ingestion HTTP/1.1" 207 Multi-Status ``` +## `pass_through_endpoints` Spec on config.yaml + +All possible values for `pass_through_endpoints` and what they mean + +**Example config** +```yaml +general_settings: + pass_through_endpoints: + - path: "/v1/rerank" # route you want to add to LiteLLM Proxy Server + target: "https://api.cohere.com/v1/rerank" # URL this route should forward requests to + headers: # headers to forward to this URL + Authorization: "bearer os.environ/COHERE_API_KEY" # (Optional) Auth Header to forward to your Endpoint + content-type: application/json # (Optional) Extra Headers to pass to this endpoint + accept: application/json +``` + +**Spec** + +* `pass_through_endpoints` *list*: A collection of endpoint configurations for request forwarding. + * `path` *string*: The route to be added to the LiteLLM Proxy Server. + * `target` *string*: The URL to which requests for this path should be forwarded. + * `headers` *object*: Key-value pairs of headers to be forwarded with the request. You can set any key value pair here and it will be forwarded to your target endpoint + * `Authorization` *string*: The authentication header for the target API. + * `content-type` *string*: The format specification for the request body. + * `accept` *string*: The expected response format from the server. + * `LANGFUSE_PUBLIC_KEY` *string*: Your Langfuse account public key - only set this when forwarding to Langfuse. + * `LANGFUSE_SECRET_KEY` *string*: Your Langfuse account secret key - only set this when forwarding to Langfuse. + * `` *string*: Pass any custom header key/value pair \ No newline at end of file From c57881643442f650222d82680223340b31869d97 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 08:38:44 -0700 Subject: [PATCH 044/124] feat - setting up auth on pass through endpoint --- .../pass_through_endpoints.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py index d4b448496..218032e01 100644 --- a/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/pass_through_endpoints.py @@ -3,12 +3,21 @@ import traceback from base64 import b64encode import httpx -from fastapi import APIRouter, FastAPI, HTTPException, Request, Response, status +from fastapi import ( + APIRouter, + Depends, + FastAPI, + HTTPException, + Request, + Response, + status, +) from fastapi.responses import StreamingResponse import litellm from litellm._logging import verbose_proxy_logger from litellm.proxy._types import ProxyException +from litellm.proxy.auth.user_api_key_auth import user_api_key_auth async_client = httpx.AsyncClient() @@ -129,7 +138,8 @@ def create_pass_through_route(endpoint, target, custom_headers=None): async def initialize_pass_through_endpoints(pass_through_endpoints: list): verbose_proxy_logger.debug("initializing pass through endpoints") - from litellm.proxy.proxy_server import app + from litellm.proxy._types import CommonProxyErrors, LiteLLMRoutes + from litellm.proxy.proxy_server import app, premium_user for endpoint in pass_through_endpoints: _target = endpoint.get("target", None) @@ -138,6 +148,15 @@ async def initialize_pass_through_endpoints(pass_through_endpoints: list): _custom_headers = await set_env_variables_in_header( custom_headers=_custom_headers ) + _auth = endpoint.get("auth", None) + _dependencies = None + if _auth is not None and str(_auth).lower() == "true": + if premium_user is not True: + raise ValueError( + f"Error Setting Authentication on Pass Through Endpoint: {CommonProxyErrors.not_premium_user}" + ) + _dependencies = [Depends(user_api_key_auth)] + LiteLLMRoutes.openai_routes.value.append(_path) if _target is None: continue @@ -148,6 +167,7 @@ async def initialize_pass_through_endpoints(pass_through_endpoints: list): path=_path, endpoint=create_pass_through_route(_path, _target, _custom_headers), methods=["GET", "POST", "PUT", "DELETE", "PATCH"], + dependencies=_dependencies, ) verbose_proxy_logger.debug("Added new pass through endpoint: %s", _path) From 0d62553afac260c4be7addc78cbba390375ab734 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 09:08:57 -0700 Subject: [PATCH 045/124] doc - setting auth on pass through endpoints --- docs/my-website/docs/proxy/pass_through.md | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/my-website/docs/proxy/pass_through.md b/docs/my-website/docs/proxy/pass_through.md index fcf7c2fd6..1348a2fc1 100644 --- a/docs/my-website/docs/proxy/pass_through.md +++ b/docs/my-website/docs/proxy/pass_through.md @@ -152,6 +152,44 @@ POST /api/public/ingestion HTTP/1.1" 207 Multi-Status ``` +## ✨ [Enterprise] - Use LiteLLM keys/authentication on Pass Through Endpoints + +Use this if you want the pass through endpoint to honour LiteLLM keys/authentication + +Usage - set `auth: true` on the config +```yaml +general_settings: + master_key: sk-1234 + pass_through_endpoints: + - path: "/v1/rerank" + target: "https://api.cohere.com/v1/rerank" + auth: true # 👈 Key change to use LiteLLM Auth / Keys + headers: + Authorization: "bearer os.environ/COHERE_API_KEY" + content-type: application/json + accept: application/json +``` + +Test Request with LiteLLM Key + +```shell +curl --request POST \ + --url http://localhost:4000/v1/rerank \ + --header 'accept: application/json' \ + --header 'Authorization: Bearer sk-1234'\ + --header 'content-type: application/json' \ + --data '{ + "model": "rerank-english-v3.0", + "query": "What is the capital of the United States?", + "top_n": 3, + "documents": ["Carson City is the capital city of the American state of Nevada.", + "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan.", + "Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States. It is a federal district.", + "Capitalization or capitalisation in English grammar is the use of a capital letter at the start of a word. English usage varies from capitalization in other languages.", + "Capital punishment (the death penalty) has existed in the United States since beforethe United States was a country. As of 2017, capital punishment is legal in 30 of the 50 states."] + }' +``` + ## `pass_through_endpoints` Spec on config.yaml All possible values for `pass_through_endpoints` and what they mean From 2a7592d026c0b58fca6ec80be592a92992f6f9cd Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 09:09:16 -0700 Subject: [PATCH 046/124] doc - pass through auth --- docs/my-website/docs/enterprise.md | 34 +++++++++++++++--------- docs/my-website/docs/proxy/enterprise.md | 31 +++++++++++++-------- litellm/proxy/proxy_config.yaml | 2 ++ 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/docs/my-website/docs/enterprise.md b/docs/my-website/docs/enterprise.md index 875aec57f..cfab07c22 100644 --- a/docs/my-website/docs/enterprise.md +++ b/docs/my-website/docs/enterprise.md @@ -2,26 +2,36 @@ For companies that need SSO, user management and professional support for LiteLLM Proxy :::info - +Interested in Enterprise? Schedule a meeting with us here 👉 [Talk to founders](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat) ::: This covers: -- ✅ **Features under the [LiteLLM Commercial License (Content Mod, Custom Tags, etc.)](https://docs.litellm.ai/docs/proxy/enterprise)** -- ✅ [**Secure UI access with Single Sign-On**](../docs/proxy/ui.md#setup-ssoauth-for-ui) -- ✅ [**Audit Logs with retention policy**](../docs/proxy/enterprise.md#audit-logs) -- ✅ [**JWT-Auth**](../docs/proxy/token_auth.md) -- ✅ [**Control available public, private routes**](../docs/proxy/enterprise.md#control-available-public-private-routes) -- ✅ [**Guardrails, Content Moderation, PII Masking, Secret/API Key Masking**](../docs/proxy/enterprise.md#prompt-injection-detection---lakeraai) -- ✅ [**Prompt Injection Detection**](../docs/proxy/enterprise.md#prompt-injection-detection---lakeraai) -- ✅ [**Invite Team Members to access `/spend` Routes**](../docs/proxy/cost_tracking#allowing-non-proxy-admins-to-access-spend-endpoints) +- **Enterprise Features** + - **Security** + - ✅ [SSO for Admin UI](./ui.md#✨-enterprise-features) + - ✅ [Audit Logs with retention policy](#audit-logs) + - ✅ [JWT-Auth](../docs/proxy/token_auth.md) + - ✅ [Control available public, private routes](#control-available-public-private-routes) + - ✅ [[BETA] AWS Key Manager v2 - Key Decryption](#beta-aws-key-manager---key-decryption) + - ✅ [Use LiteLLM keys/authentication on Pass Through Endpoints](pass_through#✨-enterprise---use-litellm-keysauthentication-on-pass-through-endpoints) + - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) + - **Spend Tracking** + - ✅ [Tracking Spend for Custom Tags](#tracking-spend-for-custom-tags) + - **Guardrails, PII Masking, Content Moderation** + - ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](#content-moderation) + - ✅ [Prompt Injection Detection (with LakeraAI API)](#prompt-injection-detection---lakeraai) + - ✅ Reject calls from Blocked User list + - ✅ Reject calls (incoming / outgoing) with Banned Keywords (e.g. competitors) + - **Custom Branding** + - ✅ [Custom Branding + Routes on Swagger Docs](#swagger-docs---custom-routes--branding) + - ✅ [Public Model Hub](../docs/proxy/enterprise.md#public-model-hub) + - ✅ [Custom Email Branding](../docs/proxy/email.md#customizing-email-branding) - ✅ **Feature Prioritization** - ✅ **Custom Integrations** - ✅ **Professional Support - Dedicated discord + slack** -- ✅ [**Custom Swagger**](../docs/proxy/enterprise.md#swagger-docs---custom-routes--branding) -- ✅ [**Public Model Hub**](../docs/proxy/enterprise.md#public-model-hub) -- ✅ [**Custom Email Branding**](../docs/proxy/email.md#customizing-email-branding) + diff --git a/docs/my-website/docs/proxy/enterprise.md b/docs/my-website/docs/proxy/enterprise.md index e4329f882..d580f58b6 100644 --- a/docs/my-website/docs/proxy/enterprise.md +++ b/docs/my-website/docs/proxy/enterprise.md @@ -11,17 +11,26 @@ To get a license, get in touch with us [here](https://calendly.com/d/4mp-gd3-k5k ::: Features: -- ✅ [SSO for Admin UI](./ui.md#✨-enterprise-features) -- ✅ [Audit Logs](#audit-logs) -- ✅ [Tracking Spend for Custom Tags](#tracking-spend-for-custom-tags) -- ✅ [Control available public, private routes](#control-available-public-private-routes) -- ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](#content-moderation) -- ✅ [Prompt Injection Detection (with LakeraAI API)](#prompt-injection-detection---lakeraai) -- ✅ [Custom Branding + Routes on Swagger Docs](#swagger-docs---custom-routes--branding) -- ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) -- ✅ Reject calls from Blocked User list -- ✅ Reject calls (incoming / outgoing) with Banned Keywords (e.g. competitors) -- [[BETA] AWS Key Manager v2 - Key Decryption](#beta-aws-key-manager---key-decryption) + +- **Security** + - ✅ [SSO for Admin UI](./ui.md#✨-enterprise-features) + - ✅ [Audit Logs with retention policy](#audit-logs) + - ✅ [JWT-Auth](../docs/proxy/token_auth.md) + - ✅ [Control available public, private routes](#control-available-public-private-routes) + - ✅ [[BETA] AWS Key Manager v2 - Key Decryption](#beta-aws-key-manager---key-decryption) + - ✅ [Use LiteLLM keys/authentication on Pass Through Endpoints](pass_through#✨-enterprise---use-litellm-keysauthentication-on-pass-through-endpoints) + - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) +- **Spend Tracking** + - ✅ [Tracking Spend for Custom Tags](#tracking-spend-for-custom-tags) +- **Guardrails, PII Masking, Content Moderation** + - ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](#content-moderation) + - ✅ [Prompt Injection Detection (with LakeraAI API)](#prompt-injection-detection---lakeraai) + - ✅ Reject calls from Blocked User list + - ✅ Reject calls (incoming / outgoing) with Banned Keywords (e.g. competitors) +- **Custom Branding** + - ✅ [Custom Branding + Routes on Swagger Docs](#swagger-docs---custom-routes--branding) + - ✅ [Public Model Hub](../docs/proxy/enterprise.md#public-model-hub) + - ✅ [Custom Email Branding](../docs/proxy/email.md#customizing-email-branding) ## Audit Logs diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 40e0386f4..88b778a6d 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -23,12 +23,14 @@ general_settings: pass_through_endpoints: - path: "/v1/rerank" target: "https://api.cohere.com/v1/rerank" + auth: true # 👈 Key change to use LiteLLM Auth / Keys headers: Authorization: "bearer os.environ/COHERE_API_KEY" content-type: application/json accept: application/json - path: "/api/public/ingestion" target: "https://us.cloud.langfuse.com/api/public/ingestion" + auth: true headers: LANGFUSE_PUBLIC_KEY: "os.environ/LANGFUSE_DEV_PUBLIC_KEY" LANGFUSE_SECRET_KEY: "os.environ/LANGFUSE_DEV_SK_KEY" From 4f32f283a3442b4abe73469f250a6a85bc517c68 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 09:09:23 -0700 Subject: [PATCH 047/124] fix(vertex_httpx.py): fix streaming for cloudflare proxy calls --- litellm/llms/vertex_httpx.py | 66 ++++++++++++++----- litellm/proxy/_super_secret_config.yaml | 6 +- .../tests/test_amazing_vertex_completion.py | 42 ++++++++++++ 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index a0cfc98e7..a6dcd3daa 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -467,7 +467,7 @@ async def make_call( raise VertexAIError(status_code=response.status_code, message=response.text) completion_stream = ModelResponseIterator( - streaming_response=response.aiter_bytes(), sync_stream=False + streaming_response=response.aiter_lines(), sync_stream=False ) # LOGGING logging_obj.post_call( @@ -498,7 +498,7 @@ def make_sync_call( raise VertexAIError(status_code=response.status_code, message=response.read()) completion_stream = ModelResponseIterator( - streaming_response=response.iter_bytes(), sync_stream=True + streaming_response=response.iter_lines(), sync_stream=True ) # LOGGING @@ -1028,7 +1028,7 @@ class VertexLLM(BaseLLM): data["generationConfig"] = generation_config headers = { - "Content-Type": "application/json; charset=utf-8", + "Content-Type": "application/json", } if auth_header is not None: headers["Authorization"] = f"Bearer {auth_header}" @@ -1310,9 +1310,9 @@ class ModelResponseIterator: if "usageMetadata" in processed_chunk: usage = ChatCompletionUsageBlock( prompt_tokens=processed_chunk["usageMetadata"]["promptTokenCount"], - completion_tokens=processed_chunk["usageMetadata"][ - "candidatesTokenCount" - ], + completion_tokens=processed_chunk["usageMetadata"].get( + "candidatesTokenCount", 0 + ), total_tokens=processed_chunk["usageMetadata"]["totalTokenCount"], ) @@ -1336,15 +1336,30 @@ class ModelResponseIterator: def __next__(self): try: chunk = self.response_iterator.__next__() - chunk = chunk.decode() - chunk = chunk.replace("data:", "") - chunk = chunk.strip() - json_chunk = json.loads(chunk) - return self.chunk_parser(chunk=json_chunk) except StopIteration: raise StopIteration except ValueError as e: - raise RuntimeError(f"Error parsing chunk: {e}") + raise RuntimeError(f"Error receiving chunk from stream: {e}") + + try: + chunk = chunk.replace("data:", "") + chunk = chunk.strip() + if len(chunk) > 0: + json_chunk = json.loads(chunk) + return self.chunk_parser(chunk=json_chunk) + else: + return GenericStreamingChunk( + text="", + is_finished=False, + finish_reason="", + usage=None, + index=0, + tool_use=None, + ) + except StopIteration: + raise StopIteration + except ValueError as e: + raise RuntimeError(f"Error parsing chunk: {e},\nReceived chunk: {chunk}") # Async iterator def __aiter__(self): @@ -1354,12 +1369,27 @@ class ModelResponseIterator: async def __anext__(self): try: chunk = await self.async_response_iterator.__anext__() - chunk = chunk.decode() - chunk = chunk.replace("data:", "") - chunk = chunk.strip() - json_chunk = json.loads(chunk) - return self.chunk_parser(chunk=json_chunk) except StopAsyncIteration: raise StopAsyncIteration except ValueError as e: - raise RuntimeError(f"Error parsing chunk: {e}") + raise RuntimeError(f"Error receiving chunk from stream: {e}") + + try: + chunk = chunk.replace("data:", "") + chunk = chunk.strip() + if len(chunk) > 0: + json_chunk = json.loads(chunk) + return self.chunk_parser(chunk=json_chunk) + else: + return GenericStreamingChunk( + text="", + is_finished=False, + finish_reason="", + usage=None, + index=0, + tool_use=None, + ) + except StopAsyncIteration: + raise StopAsyncIteration + except ValueError as e: + raise RuntimeError(f"Error parsing chunk: {e},\nReceived chunk: {chunk}") diff --git a/litellm/proxy/_super_secret_config.yaml b/litellm/proxy/_super_secret_config.yaml index c28bb4901..ede853094 100644 --- a/litellm/proxy/_super_secret_config.yaml +++ b/litellm/proxy/_super_secret_config.yaml @@ -4,10 +4,8 @@ model_list: model: anthropic/claude-3-5-sonnet - model_name: gemini-1.5-flash-gemini litellm_params: - model: gemini/gemini-1.5-flash -- model_name: gemini-1.5-flash-gemini - litellm_params: - model: gemini/gemini-1.5-flash + model: vertex_ai_beta/gemini-1.5-flash + api_base: https://gateway.ai.cloudflare.com/v1/fa4cdcab1f32b95ca3b53fd36043d691/test/google-vertex-ai/v1/projects/adroit-crow-413218/locations/us-central1/publishers/google/models/gemini-1.5-flash - litellm_params: api_base: http://0.0.0.0:8080 api_key: '' diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index 901d68ef3..6de3e11b8 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -914,6 +914,48 @@ async def test_gemini_pro_httpx_custom_api_base(provider): assert "hello" in mock_call.call_args.kwargs["headers"] +@pytest.mark.parametrize("sync_mode", [True, False]) +@pytest.mark.parametrize("provider", ["vertex_ai_beta"]) # "vertex_ai", +@pytest.mark.asyncio +async def test_gemini_pro_httpx_custom_api_base_streaming_real_call( + provider, sync_mode +): + load_vertex_ai_credentials() + import random + + litellm.set_verbose = True + messages = [ + { + "role": "user", + "content": "Hey, how's it going?", + } + ] + + vertex_region = random.sample(["asia-southeast1", "us-central1"], k=1)[0] + if sync_mode is True: + response = completion( + model="vertex_ai_beta/gemini-1.5-flash", + messages=messages, + api_base="https://gateway.ai.cloudflare.com/v1/fa4cdcab1f32b95ca3b53fd36043d691/test/google-vertex-ai/v1/projects/adroit-crow-413218/locations/us-central1/publishers/google/models/gemini-1.5-flash", + stream=True, + vertex_region=vertex_region, + ) + + for chunk in response: + print(chunk) + else: + response = await litellm.acompletion( + model="vertex_ai_beta/gemini-1.5-flash", + messages=messages, + api_base="https://gateway.ai.cloudflare.com/v1/fa4cdcab1f32b95ca3b53fd36043d691/test/google-vertex-ai/v1/projects/adroit-crow-413218/locations/us-central1/publishers/google/models/gemini-1.5-flash", + stream=True, + vertex_region=vertex_region, + ) + + async for chunk in response: + print(chunk) + + @pytest.mark.skip(reason="exhausted vertex quota. need to refactor to mock the call") @pytest.mark.parametrize("sync_mode", [True]) @pytest.mark.parametrize("provider", ["vertex_ai"]) From e1f84b1bd96f02cc9b1d7358a31035cf3fb0ff5a Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 09:23:21 -0700 Subject: [PATCH 048/124] docs(vertex.md): add vertex pdf example to docs --- docs/my-website/docs/providers/vertex.md | 80 ++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index de1b5811f..5e2c72060 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -645,6 +645,86 @@ assert isinstance( ``` +## Usage - PDF / Videos / etc. Files + +Pass any file supported by Vertex AI, through LiteLLM. + + + + +```python +from litellm import completion + +response = completion( + model="vertex_ai/gemini-1.5-flash", + messages=[ + { + "role": "user", + "content": [ + {"type": "text", "text": "You are a very professional document summarization specialist. Please summarize the given document."}, + { + "type": "image_url", + "image_url": "gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf", + }, + ], + } + ], + max_tokens=300, +) + +print(response.choices[0]) + +``` + + + +1. Add model to config + +```yaml +- model_name: gemini-1.5-flash + litellm_params: + model: vertex_ai/gemini-1.5-flash + vertex_credentials: "/path/to/service_account.json" +``` + +2. Start Proxy + +``` +litellm --config /path/to/config.yaml +``` + +3. Test it! + +```bash +curl http://0.0.0.0:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "model": "gemini-1.5-flash", + "messages": [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "You are a very professional document summarization specialist. Please summarize the given document" + }, + { + "type": "image_url", + "image_url": "gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf", + }, + } + ] + } + ], + "max_tokens": 300 + }' + +``` + + + + ## Chat Models | Model Name | Function Call | |------------------|--------------------------------------| From d085ce2d9742e166902fef17a88e707504b36ffc Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 09:36:51 -0700 Subject: [PATCH 049/124] test - pass through endpoint --- litellm/tests/test_pass_through_endpoints.py | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 litellm/tests/test_pass_through_endpoints.py diff --git a/litellm/tests/test_pass_through_endpoints.py b/litellm/tests/test_pass_through_endpoints.py new file mode 100644 index 000000000..0287f6f42 --- /dev/null +++ b/litellm/tests/test_pass_through_endpoints.py @@ -0,0 +1,51 @@ +import os +import sys + +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds-the parent directory to the system path + +import asyncio + +import httpx + +from litellm.proxy.proxy_server import app, initialize_pass_through_endpoints + + +# Mock the async_client used in the pass_through_request function +async def mock_request(*args, **kwargs): + return httpx.Response(200, json={"message": "Mocked response"}) + + +@pytest.fixture +def client(): + return TestClient(app) + + +@pytest.mark.asyncio +async def test_pass_through_endpoint(client, monkeypatch): + # Mock the httpx.AsyncClient.request method + monkeypatch.setattr("httpx.AsyncClient.request", mock_request) + + # Define a pass-through endpoint + pass_through_endpoints = [ + { + "path": "/test-endpoint", + "target": "https://api.example.com/v1/chat/completions", + "headers": {"Authorization": "Bearer test-token"}, + } + ] + + # Initialize the pass-through endpoint + await initialize_pass_through_endpoints(pass_through_endpoints) + + # Make a request to the pass-through endpoint + response = client.post("/test-endpoint", json={"prompt": "Hello, world!"}) + + # Assert the response + assert response.status_code == 200 + assert response.json() == {"message": "Mocked response"} From f860cbca34f061e09704c245a29690e028e3eb6b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 10:49:10 -0700 Subject: [PATCH 050/124] test test_pass_through_endpoint_rerank --- litellm/tests/test_pass_through_endpoints.py | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/litellm/tests/test_pass_through_endpoints.py b/litellm/tests/test_pass_through_endpoints.py index 0287f6f42..0f234dfa8 100644 --- a/litellm/tests/test_pass_through_endpoints.py +++ b/litellm/tests/test_pass_through_endpoints.py @@ -49,3 +49,37 @@ async def test_pass_through_endpoint(client, monkeypatch): # Assert the response assert response.status_code == 200 assert response.json() == {"message": "Mocked response"} + + +@pytest.mark.asyncio +async def test_pass_through_endpoint_rerank(client): + _cohere_api_key = os.environ.get("COHERE_API_KEY") + + # Define a pass-through endpoint + pass_through_endpoints = [ + { + "path": "/v1/rerank", + "target": "https://api.cohere.com/v1/rerank", + "headers": {"Authorization": f"bearer {_cohere_api_key}"}, + } + ] + + # Initialize the pass-through endpoint + await initialize_pass_through_endpoints(pass_through_endpoints) + + _json_data = { + "model": "rerank-english-v3.0", + "query": "What is the capital of the United States?", + "top_n": 3, + "documents": [ + "Carson City is the capital city of the American state of Nevada." + ], + } + + # Make a request to the pass-through endpoint + response = client.post("/v1/rerank", json=_json_data) + + print("JSON response: ", _json_data) + + # Assert the response + assert response.status_code == 200 From b09c283cc4a49bbd70ab32efa66d69ee64a42751 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 11:33:02 -0700 Subject: [PATCH 051/124] feat - add spend report grouped by api key --- .../spend_management_endpoints.py | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 1fbd95b3c..aafd14c62 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -817,9 +817,9 @@ async def get_global_spend_report( default=None, description="Time till which to view spend", ), - group_by: Optional[Literal["team", "customer"]] = fastapi.Query( + group_by: Optional[Literal["team", "customer", "api_key"]] = fastapi.Query( default="team", - description="Group spend by internal team or customer", + description="Group spend by internal team or customer or api_key", ), ): """ @@ -992,6 +992,48 @@ async def get_global_spend_report( return [] return db_response + elif group_by == "api_key": + sql_query = """ + WITH SpendByModelApiKey AS ( + SELECT + sl.api_key, + sl.model, + SUM(sl.spend) AS model_cost, + SUM(sl.prompt_tokens) AS model_input_tokens, + SUM(sl.completion_tokens) AS model_output_tokens + FROM + "LiteLLM_SpendLogs" sl + WHERE + sl."startTime" BETWEEN $1::date AND $2::date + GROUP BY + sl.api_key, + sl.model + ) + SELECT + api_key, + SUM(model_cost) AS total_cost, + SUM(model_input_tokens) AS total_input_tokens, + SUM(model_output_tokens) AS total_output_tokens, + jsonb_agg(jsonb_build_object( + 'model', model, + 'total_cost', model_cost, + 'total_input_tokens', model_input_tokens, + 'total_output_tokens', model_output_tokens + )) AS model_details + FROM + SpendByModelApiKey + GROUP BY + api_key + ORDER BY + total_cost DESC; + """ + db_response = await prisma_client.db.query_raw( + sql_query, start_date_obj, end_date_obj + ) + if db_response is None: + return [] + + return db_response except Exception as e: raise HTTPException( From e73e9e12bc55a1dde44625013140ebdf9b0eb2e5 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 11:33:19 -0700 Subject: [PATCH 052/124] fix(vertex_httpx.py): support passing response_schema to gemini --- litellm/llms/vertex_httpx.py | 10 ++++- .../tests/test_amazing_vertex_completion.py | 45 +++++++++++++++++++ litellm/utils.py | 5 +++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index a6dcd3daa..940016ecb 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -356,6 +356,7 @@ class VertexGeminiConfig: model: str, non_default_params: dict, optional_params: dict, + drop_params: bool, ): for param, value in non_default_params.items(): if param == "temperature": @@ -375,8 +376,13 @@ class VertexGeminiConfig: optional_params["stop_sequences"] = value if param == "max_tokens": optional_params["max_output_tokens"] = value - if param == "response_format" and value["type"] == "json_object": # type: ignore - optional_params["response_mime_type"] = "application/json" + if param == "response_format" and isinstance(value, dict): # type: ignore + if value["type"] == "json_object": + optional_params["response_mime_type"] = "application/json" + elif value["type"] == "text": + optional_params["response_mime_type"] = "text/plain" + if "response_schema" in value: + optional_params["response_schema"] = value["response_schema"] if param == "frequency_penalty": optional_params["frequency_penalty"] = value if param == "presence_penalty": diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index 6de3e11b8..e6f2634f4 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -880,6 +880,51 @@ Using this JSON schema: mock_call.assert_called_once() +@pytest.mark.parametrize("provider", ["vertex_ai_beta"]) # "vertex_ai", +@pytest.mark.asyncio +async def test_gemini_pro_json_schema_httpx(provider): + load_vertex_ai_credentials() + litellm.set_verbose = True + messages = [{"role": "user", "content": "List 5 cookie recipes"}] + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + response_schema = { + "type": "array", + "items": { + "type": "object", + "properties": { + "recipe_name": { + "type": "string", + }, + }, + "required": ["recipe_name"], + }, + } + + client = HTTPHandler() + + with patch.object(client, "post", new=MagicMock()) as mock_call: + try: + response = completion( + model="vertex_ai_beta/gemini-1.5-pro-001", + messages=messages, + response_format={ + "type": "json_object", + "response_schema": response_schema, + }, + client=client, + ) + except Exception as e: + pass + + mock_call.assert_called_once() + print(mock_call.call_args.kwargs) + print(mock_call.call_args.kwargs["json"]["generationConfig"]) + assert ( + "response_schema" in mock_call.call_args.kwargs["json"]["generationConfig"] + ) + + @pytest.mark.parametrize("provider", ["vertex_ai_beta"]) # "vertex_ai", @pytest.mark.asyncio async def test_gemini_pro_httpx_custom_api_base(provider): diff --git a/litellm/utils.py b/litellm/utils.py index fef4976ca..dc2bcb25a 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2756,6 +2756,11 @@ def get_optional_params( non_default_params=non_default_params, optional_params=optional_params, model=model, + drop_params=( + drop_params + if drop_params is not None and isinstance(drop_params, bool) + else False + ), ) elif ( custom_llm_provider == "vertex_ai" and model in litellm.vertex_anthropic_models From 171923aec8ad60741eef51262224310b88649422 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 11:34:07 -0700 Subject: [PATCH 053/124] fix spend report doc --- docs/my-website/docs/proxy/cost_tracking.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/my-website/docs/proxy/cost_tracking.md b/docs/my-website/docs/proxy/cost_tracking.md index 3ccf8f383..8096cc2a0 100644 --- a/docs/my-website/docs/proxy/cost_tracking.md +++ b/docs/my-website/docs/proxy/cost_tracking.md @@ -145,16 +145,16 @@ Navigate to the Usage Tab on the LiteLLM UI (found on https://your-proxy-endpoin + + + ## API Endpoints to get Spend #### Getting Spend Reports - To Charge Other Teams, Customers Use the `/global/spend/report` endpoint to get daily spend report per - team - customer [this is `user` passed to `/chat/completions` request](#how-to-track-spend-with-litellm) - - - - +- key From 1ac1078080d6685d64e3bf8ee96bf95fff3670ec Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 11:35:57 -0700 Subject: [PATCH 054/124] fix cost tracking img --- docs/my-website/img/response_cost_img.png | Bin 145039 -> 210929 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/my-website/img/response_cost_img.png b/docs/my-website/img/response_cost_img.png index 9f466b3fbbc9f066d39ffcdca01401d375df49fa..2fa9c20095dc6a84c692c6489ee98b26cdb56a43 100644 GIT binary patch literal 210929 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%0D9w>Q)*AI$@L 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 z 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 U16ulU6ZG$w4NQK|JMS3rKbs7{00000 From 8d9352258fd6865369b0ea3ceebe47abfdf02f06 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 11:36:15 -0700 Subject: [PATCH 055/124] =?UTF-8?q?bump:=20version=201.41.0=20=E2=86=92=20?= =?UTF-8?q?1.41.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a3267dfa8..3926ba0bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.41.0" +version = "1.41.1" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -90,7 +90,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.41.0" +version = "1.41.1" version_files = [ "pyproject.toml:^version" ] From bea23ffc8dc73af197fb4aee0638e10c3b169f97 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 11:44:19 -0700 Subject: [PATCH 056/124] docs cost tracking by api key --- docs/my-website/docs/proxy/cost_tracking.md | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/docs/my-website/docs/proxy/cost_tracking.md b/docs/my-website/docs/proxy/cost_tracking.md index 8096cc2a0..e8ae83897 100644 --- a/docs/my-website/docs/proxy/cost_tracking.md +++ b/docs/my-website/docs/proxy/cost_tracking.md @@ -117,6 +117,8 @@ That's IT. Now Verify your spend was tracked +Expect to see `x-litellm-response-cost` in the response headers with calculated cost + @@ -337,6 +339,61 @@ curl -X GET 'http://localhost:4000/global/spend/report?start_date=2024-04-01&end ``` + + + + + +👉 Key Change: Specify `group_by=api_key` + + +```shell +curl -X GET 'http://localhost:4000/global/spend/report?start_date=2024-04-01&end_date=2024-06-30&group_by=api_key' \ + -H 'Authorization: Bearer sk-1234' +``` + +##### Example Response + + +```shell +[ + { + "api_key": "ad64768847d05d978d62f623d872bff0f9616cc14b9c1e651c84d14fe3b9f539", + "total_cost": 0.0002157, + "total_input_tokens": 45.0, + "total_output_tokens": 1375.0, + "model_details": [ + { + "model": "gpt-3.5-turbo", + "total_cost": 0.0001095, + "total_input_tokens": 9, + "total_output_tokens": 70 + }, + { + "model": "llama3-8b-8192", + "total_cost": 0.0001062, + "total_input_tokens": 36, + "total_output_tokens": 1305 + } + ] + }, + { + "api_key": "88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b", + "total_cost": 0.00012924, + "total_input_tokens": 36.0, + "total_output_tokens": 1593.0, + "model_details": [ + { + "model": "llama3-8b-8192", + "total_cost": 0.00012924, + "total_input_tokens": 36, + "total_output_tokens": 1593 + } + ] + } +] +``` + From 3dea5dce86bad52e7066f744172badd274464c9f Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 11:48:45 -0700 Subject: [PATCH 057/124] docs(vertex.md): add 'response_schema' param to vertex docs --- docs/my-website/docs/providers/vertex.md | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index 5e2c72060..664a99e0d 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -123,6 +123,45 @@ print(completion(**data)) ### **JSON Schema** +From v`1.40.1+` LiteLLM supports sending `response_schema` as a param for Gemini-Pro-1.5 on Vertex AI. + +```python +from litellm import completion +import json + +## SETUP ENVIRONMENT +# !gcloud auth application-default login - run this to add vertex credentials to your env + +messages = [ + { + "role": "user", + "content": "List 5 popular cookie recipes." + } +] + +response_schema = { + "type": "array", + "items": { + "type": "object", + "properties": { + "recipe_name": { + "type": "string", + }, + }, + "required": ["recipe_name"], + }, + } + + +completion( + model="vertex_ai_beta/gemini-1.5-pro", + messages=messages, + response_format={"type": "json_object", "response_schema": response_schema} # 👈 KEY CHANGE + ) + +print(json.loads(completion.choices[0].message.content)) +``` + ```python from litellm import completion From ef14d57fce795176ae9265b4a454e0b360ad2ad0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 11:50:45 -0700 Subject: [PATCH 058/124] doc - spend tracking endpoint --- docs/my-website/docs/proxy/cost_tracking.md | 8 ++++---- docs/my-website/docs/proxy/enterprise.md | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/my-website/docs/proxy/cost_tracking.md b/docs/my-website/docs/proxy/cost_tracking.md index e8ae83897..fe3a46250 100644 --- a/docs/my-website/docs/proxy/cost_tracking.md +++ b/docs/my-website/docs/proxy/cost_tracking.md @@ -150,13 +150,13 @@ Navigate to the Usage Tab on the LiteLLM UI (found on https://your-proxy-endpoin -## API Endpoints to get Spend +## ✨ (Enterprise) API Endpoints to get Spend #### Getting Spend Reports - To Charge Other Teams, Customers Use the `/global/spend/report` endpoint to get daily spend report per -- team -- customer [this is `user` passed to `/chat/completions` request](#how-to-track-spend-with-litellm) -- key +- Team +- Customer [this is `user` passed to `/chat/completions` request](#how-to-track-spend-with-litellm) +- [LiteLLM API key](virtual_keys.md) diff --git a/docs/my-website/docs/proxy/enterprise.md b/docs/my-website/docs/proxy/enterprise.md index d580f58b6..e061a917e 100644 --- a/docs/my-website/docs/proxy/enterprise.md +++ b/docs/my-website/docs/proxy/enterprise.md @@ -22,6 +22,7 @@ Features: - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) - **Spend Tracking** - ✅ [Tracking Spend for Custom Tags](#tracking-spend-for-custom-tags) + - ✅ [API Endpoints to get Spend Reports per Team, API Key, Customer](cost_tracking.md#✨-enterprise-api-endpoints-to-get-spend) - **Guardrails, PII Masking, Content Moderation** - ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](#content-moderation) - ✅ [Prompt Injection Detection (with LakeraAI API)](#prompt-injection-detection---lakeraai) From be8a6377f6a699ef3f2af3d93bf5d291136720c5 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 11:51:52 -0700 Subject: [PATCH 059/124] docs(input.md): add vertex ai json mode to mapped input params --- docs/my-website/docs/completion/input.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/docs/completion/input.md b/docs/my-website/docs/completion/input.md index db2931909..5e2bd6079 100644 --- a/docs/my-website/docs/completion/input.md +++ b/docs/my-website/docs/completion/input.md @@ -50,7 +50,7 @@ Use `litellm.get_supported_openai_params()` for an updated list of params for ea |Huggingface| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | |Openrouter| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | ✅ | | | | | |AI21| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | -|VertexAI| ✅ | ✅ | | ✅ | ✅ | | | | | | | | | | ✅ | | | +|VertexAI| ✅ | ✅ | | ✅ | ✅ | | | | | | | | | ✅ | ✅ | | | |Bedrock| ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | | | | | ✅ (for anthropic) | | |Sagemaker| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | |TogetherAI| ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | ✅ | From 46a1e5dcfcef4d1f6a6d9df7cac0da40d02758b5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 11:55:01 -0700 Subject: [PATCH 060/124] docs enterprise spend tracking list --- docs/my-website/docs/enterprise.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/my-website/docs/enterprise.md b/docs/my-website/docs/enterprise.md index cfab07c22..e3758266a 100644 --- a/docs/my-website/docs/enterprise.md +++ b/docs/my-website/docs/enterprise.md @@ -10,22 +10,23 @@ Interested in Enterprise? Schedule a meeting with us here 👉 This covers: - **Enterprise Features** - **Security** - - ✅ [SSO for Admin UI](./ui.md#✨-enterprise-features) - - ✅ [Audit Logs with retention policy](#audit-logs) + - ✅ [SSO for Admin UI](./proxy/ui#✨-enterprise-features) + - ✅ [Audit Logs with retention policy](./proxy/enterprise#audit-logs) - ✅ [JWT-Auth](../docs/proxy/token_auth.md) - - ✅ [Control available public, private routes](#control-available-public-private-routes) - - ✅ [[BETA] AWS Key Manager v2 - Key Decryption](#beta-aws-key-manager---key-decryption) - - ✅ [Use LiteLLM keys/authentication on Pass Through Endpoints](pass_through#✨-enterprise---use-litellm-keysauthentication-on-pass-through-endpoints) - - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) + - ✅ [Control available public, private routes](./proxy/enterprise#control-available-public-private-routes) + - ✅ [[BETA] AWS Key Manager v2 - Key Decryption](./proxy/enterprise#beta-aws-key-manager---key-decryption) + - ✅ [Use LiteLLM keys/authentication on Pass Through Endpoints](./proxy/pass_through#✨-enterprise---use-litellm-keysauthentication-on-pass-through-endpoints) + - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](./proxy/enterprise#enforce-required-params-for-llm-requests) - **Spend Tracking** - - ✅ [Tracking Spend for Custom Tags](#tracking-spend-for-custom-tags) + - ✅ [Tracking Spend for Custom Tags](./proxy/enterprise#tracking-spend-for-custom-tags) + - ✅ [API Endpoints to get Spend Reports per Team, API Key, Customer](./proxy/cost_tracking.md#✨-enterprise-api-endpoints-to-get-spend) - **Guardrails, PII Masking, Content Moderation** - - ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](#content-moderation) - - ✅ [Prompt Injection Detection (with LakeraAI API)](#prompt-injection-detection---lakeraai) + - ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](./proxy/enterprise#content-moderation) + - ✅ [Prompt Injection Detection (with LakeraAI API)](./proxy/enterprise#prompt-injection-detection---lakeraai) - ✅ Reject calls from Blocked User list - ✅ Reject calls (incoming / outgoing) with Banned Keywords (e.g. competitors) - **Custom Branding** - - ✅ [Custom Branding + Routes on Swagger Docs](#swagger-docs---custom-routes--branding) + - ✅ [Custom Branding + Routes on Swagger Docs](./proxy/enterprise#swagger-docs---custom-routes--branding) - ✅ [Public Model Hub](../docs/proxy/enterprise.md#public-model-hub) - ✅ [Custom Email Branding](../docs/proxy/email.md#customizing-email-branding) - ✅ **Feature Prioritization** From 3a5d258e054f528d81bd52849cd96ba806ec70f7 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 12:00:19 -0700 Subject: [PATCH 061/124] raise error on /spend/report endpoint --- litellm/proxy/_types.py | 2 +- litellm/proxy/spend_tracking/spend_management_endpoints.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 640c7695a..1f1aaf0ee 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1622,7 +1622,7 @@ class ProxyException(Exception): } -class CommonProxyErrors(enum.Enum): +class CommonProxyErrors(str, enum.Enum): db_not_connected_error = "DB not connected" no_llm_router = "No models configured on proxy" not_allowed_access = "Admin-only endpoint. Not allowed to access this." diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index aafd14c62..11406b162 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -860,7 +860,7 @@ async def get_global_spend_report( start_date_obj = datetime.strptime(start_date, "%Y-%m-%d") end_date_obj = datetime.strptime(end_date, "%Y-%m-%d") - from litellm.proxy.proxy_server import prisma_client + from litellm.proxy.proxy_server import premium_user, prisma_client try: if prisma_client is None: @@ -868,6 +868,11 @@ async def get_global_spend_report( f"Database not connected. Connect a database to your proxy - https://docs.litellm.ai/docs/simple_proxy#managing-auth---virtual-keys" ) + if premium_user is not True: + raise ValueError( + "/spend/report endpoint" + CommonProxyErrors.not_premium_user.value + ) + if group_by == "team": # first get data from spend logs -> SpendByModelApiKey # then read data from "SpendByModelApiKey" to format the response obj From c7e89a571eb864f53c6e82e9cd5c218ab0a1dde4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 12:15:14 -0700 Subject: [PATCH 062/124] fix error logs store exception in DB --- litellm/proxy/proxy_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index ff4b1e663..cd5670225 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -593,7 +593,7 @@ async def _PROXY_failure_handler( _model_id = _metadata.get("model_info", {}).get("id", "") _model_group = _metadata.get("model_group", "") api_base = litellm.get_api_base(model=_model, optional_params=_litellm_params) - _exception_string = str(_exception)[:500] + _exception_string = str(_exception) error_log = LiteLLM_ErrorLogs( request_id=str(uuid.uuid4()), From 5718d1e2055b521b62d30a8770aa15c016cfd831 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 12:40:29 -0700 Subject: [PATCH 063/124] fix(utils.py): new helper function to check if provider/model supports 'response_schema' param --- ...odel_prices_and_context_window_backup.json | 4 + litellm/tests/test_utils.py | 26 +++ litellm/types/utils.py | 1 + litellm/utils.py | 172 ++++++++---------- model_prices_and_context_window.json | 4 + 5 files changed, 114 insertions(+), 93 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 92f97ebe5..49f2f0c28 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1486,6 +1486,7 @@ "supports_system_messages": true, "supports_function_calling": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.5-pro-001": { @@ -1511,6 +1512,7 @@ "supports_system_messages": true, "supports_function_calling": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.5-pro-preview-0514": { @@ -2007,6 +2009,7 @@ "supports_function_calling": true, "supports_vision": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini/gemini-1.5-pro-latest": { @@ -2023,6 +2026,7 @@ "supports_function_calling": true, "supports_vision": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://ai.google.dev/models/gemini" }, "gemini/gemini-pro-vision": { diff --git a/litellm/tests/test_utils.py b/litellm/tests/test_utils.py index 78b64270c..8225b309d 100644 --- a/litellm/tests/test_utils.py +++ b/litellm/tests/test_utils.py @@ -663,3 +663,29 @@ def test_convert_model_response_object(): e.message == '{"type":"error","error":{"type":"invalid_request_error","message":"Output blocked by content filtering policy"}}' ) + + +@pytest.mark.parametrize( + "model, expected_bool", + [ + ("vertex_ai/gemini-1.5-pro", True), + ("gemini/gemini-1.5-pro", True), + ("predibase/llama3-8b-instruct", True), + ("gpt-4o", False), + ], +) +def test_supports_response_schema(model, expected_bool): + """ + Unit tests for 'supports_response_schema' helper function. + + Should be true for gemini-1.5-pro on google ai studio / vertex ai AND predibase models + Should be false otherwise + """ + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + from litellm.utils import supports_response_schema + + response = supports_response_schema(model=model, custom_llm_provider=None) + + assert expected_bool == response diff --git a/litellm/types/utils.py b/litellm/types/utils.py index a63e34738..51ce08671 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -71,6 +71,7 @@ class ModelInfo(TypedDict, total=False): ] supported_openai_params: Required[Optional[List[str]]] supports_system_messages: Optional[bool] + supports_response_schema: Optional[bool] class GenericStreamingChunk(TypedDict): diff --git a/litellm/utils.py b/litellm/utils.py index dc2bcb25a..227274d3a 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -1847,9 +1847,10 @@ def supports_system_messages(model: str, custom_llm_provider: Optional[str]) -> Parameters: model (str): The model name to be checked. + custom_llm_provider (str): The provider to be checked. Returns: - bool: True if the model supports function calling, False otherwise. + bool: True if the model supports system messages, False otherwise. Raises: Exception: If the given model is not found in model_prices_and_context_window.json. @@ -1867,6 +1868,43 @@ def supports_system_messages(model: str, custom_llm_provider: Optional[str]) -> ) +def supports_response_schema(model: str, custom_llm_provider: Optional[str]) -> bool: + """ + Check if the given model + provider supports 'response_schema' as a param. + + Parameters: + model (str): The model name to be checked. + custom_llm_provider (str): The provider to be checked. + + Returns: + bool: True if the model supports response_schema, False otherwise. + + Raises: + Exception: If the given model is not found in model_prices_and_context_window.json. + """ + try: + ## GET LLM PROVIDER ## + model, custom_llm_provider, _, _ = get_llm_provider( + model=model, custom_llm_provider=custom_llm_provider + ) + + if custom_llm_provider == "predibase": # predibase supports this globally + return True + + ## GET MODEL INFO + model_info = litellm.get_model_info( + model=model, custom_llm_provider=custom_llm_provider + ) + + if model_info.get("supports_response_schema", False) is True: + return True + return False + except Exception: + raise Exception( + f"Model not in model_prices_and_context_window.json. You passed model={model}, custom_llm_provider={custom_llm_provider}." + ) + + def supports_function_calling(model: str) -> bool: """ Check if the given model supports function calling and return a boolean value. @@ -4434,8 +4472,7 @@ def get_max_tokens(model: str) -> Optional[int]: def get_model_info(model: str, custom_llm_provider: Optional[str] = None) -> ModelInfo: """ - Get a dict for the maximum tokens (context window), - input_cost_per_token, output_cost_per_token for a given model. + Get a dict for the maximum tokens (context window), input_cost_per_token, output_cost_per_token for a given model. Parameters: - model (str): The name of the model. @@ -4520,6 +4557,7 @@ def get_model_info(model: str, custom_llm_provider: Optional[str] = None) -> Mod mode="chat", supported_openai_params=supported_openai_params, supports_system_messages=None, + supports_response_schema=None, ) else: """ @@ -4541,36 +4579,6 @@ def get_model_info(model: str, custom_llm_provider: Optional[str] = None) -> Mod pass else: raise Exception - return ModelInfo( - max_tokens=_model_info.get("max_tokens", None), - max_input_tokens=_model_info.get("max_input_tokens", None), - max_output_tokens=_model_info.get("max_output_tokens", None), - input_cost_per_token=_model_info.get("input_cost_per_token", 0), - input_cost_per_character=_model_info.get( - "input_cost_per_character", None - ), - input_cost_per_token_above_128k_tokens=_model_info.get( - "input_cost_per_token_above_128k_tokens", None - ), - output_cost_per_token=_model_info.get("output_cost_per_token", 0), - output_cost_per_character=_model_info.get( - "output_cost_per_character", None - ), - output_cost_per_token_above_128k_tokens=_model_info.get( - "output_cost_per_token_above_128k_tokens", None - ), - output_cost_per_character_above_128k_tokens=_model_info.get( - "output_cost_per_character_above_128k_tokens", None - ), - litellm_provider=_model_info.get( - "litellm_provider", custom_llm_provider - ), - mode=_model_info.get("mode"), - supported_openai_params=supported_openai_params, - supports_system_messages=_model_info.get( - "supports_system_messages", None - ), - ) elif model in litellm.model_cost: _model_info = litellm.model_cost[model] _model_info["supported_openai_params"] = supported_openai_params @@ -4584,36 +4592,6 @@ def get_model_info(model: str, custom_llm_provider: Optional[str] = None) -> Mod pass else: raise Exception - return ModelInfo( - max_tokens=_model_info.get("max_tokens", None), - max_input_tokens=_model_info.get("max_input_tokens", None), - max_output_tokens=_model_info.get("max_output_tokens", None), - input_cost_per_token=_model_info.get("input_cost_per_token", 0), - input_cost_per_character=_model_info.get( - "input_cost_per_character", None - ), - input_cost_per_token_above_128k_tokens=_model_info.get( - "input_cost_per_token_above_128k_tokens", None - ), - output_cost_per_token=_model_info.get("output_cost_per_token", 0), - output_cost_per_character=_model_info.get( - "output_cost_per_character", None - ), - output_cost_per_token_above_128k_tokens=_model_info.get( - "output_cost_per_token_above_128k_tokens", None - ), - output_cost_per_character_above_128k_tokens=_model_info.get( - "output_cost_per_character_above_128k_tokens", None - ), - litellm_provider=_model_info.get( - "litellm_provider", custom_llm_provider - ), - mode=_model_info.get("mode"), - supported_openai_params=supported_openai_params, - supports_system_messages=_model_info.get( - "supports_system_messages", None - ), - ) elif split_model in litellm.model_cost: _model_info = litellm.model_cost[split_model] _model_info["supported_openai_params"] = supported_openai_params @@ -4627,40 +4605,48 @@ def get_model_info(model: str, custom_llm_provider: Optional[str] = None) -> Mod pass else: raise Exception - return ModelInfo( - max_tokens=_model_info.get("max_tokens", None), - max_input_tokens=_model_info.get("max_input_tokens", None), - max_output_tokens=_model_info.get("max_output_tokens", None), - input_cost_per_token=_model_info.get("input_cost_per_token", 0), - input_cost_per_character=_model_info.get( - "input_cost_per_character", None - ), - input_cost_per_token_above_128k_tokens=_model_info.get( - "input_cost_per_token_above_128k_tokens", None - ), - output_cost_per_token=_model_info.get("output_cost_per_token", 0), - output_cost_per_character=_model_info.get( - "output_cost_per_character", None - ), - output_cost_per_token_above_128k_tokens=_model_info.get( - "output_cost_per_token_above_128k_tokens", None - ), - output_cost_per_character_above_128k_tokens=_model_info.get( - "output_cost_per_character_above_128k_tokens", None - ), - litellm_provider=_model_info.get( - "litellm_provider", custom_llm_provider - ), - mode=_model_info.get("mode"), - supported_openai_params=supported_openai_params, - supports_system_messages=_model_info.get( - "supports_system_messages", None - ), - ) else: raise ValueError( "This model isn't mapped yet. Add it here - https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json" ) + + ## PROVIDER-SPECIFIC INFORMATION + if custom_llm_provider == "predibase": + _model_info["supports_response_schema"] = True + + return ModelInfo( + max_tokens=_model_info.get("max_tokens", None), + max_input_tokens=_model_info.get("max_input_tokens", None), + max_output_tokens=_model_info.get("max_output_tokens", None), + input_cost_per_token=_model_info.get("input_cost_per_token", 0), + input_cost_per_character=_model_info.get( + "input_cost_per_character", None + ), + input_cost_per_token_above_128k_tokens=_model_info.get( + "input_cost_per_token_above_128k_tokens", None + ), + output_cost_per_token=_model_info.get("output_cost_per_token", 0), + output_cost_per_character=_model_info.get( + "output_cost_per_character", None + ), + output_cost_per_token_above_128k_tokens=_model_info.get( + "output_cost_per_token_above_128k_tokens", None + ), + output_cost_per_character_above_128k_tokens=_model_info.get( + "output_cost_per_character_above_128k_tokens", None + ), + litellm_provider=_model_info.get( + "litellm_provider", custom_llm_provider + ), + mode=_model_info.get("mode"), + supported_openai_params=supported_openai_params, + supports_system_messages=_model_info.get( + "supports_system_messages", None + ), + supports_response_schema=_model_info.get( + "supports_response_schema", None + ), + ) except Exception: raise Exception( "This model isn't mapped yet. Add it here - https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json" diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 92f97ebe5..49f2f0c28 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1486,6 +1486,7 @@ "supports_system_messages": true, "supports_function_calling": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.5-pro-001": { @@ -1511,6 +1512,7 @@ "supports_system_messages": true, "supports_function_calling": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.5-pro-preview-0514": { @@ -2007,6 +2009,7 @@ "supports_function_calling": true, "supports_vision": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini/gemini-1.5-pro-latest": { @@ -2023,6 +2026,7 @@ "supports_function_calling": true, "supports_vision": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://ai.google.dev/models/gemini" }, "gemini/gemini-pro-vision": { From a6bc878a2a7f1f92d69cb0d54cc1ac4626c591d9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 13:11:55 -0700 Subject: [PATCH 064/124] fix - error str in OpenAI, Azure exception --- .../exception_mapping_utils.py | 40 ++++++++++++++++ litellm/utils.py | 47 ++++++++++++------- 2 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 litellm/litellm_core_utils/exception_mapping_utils.py diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py new file mode 100644 index 000000000..5ac26c7ae --- /dev/null +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -0,0 +1,40 @@ +import json +from typing import Optional + + +def get_error_message(error_obj) -> Optional[str]: + """ + OpenAI Returns Error message that is nested, this extract the message + + Example: + { + 'request': "", + 'message': "Error code: 400 - {\'error\': {\'message\': \"Invalid 'temperature': decimal above maximum value. Expected a value <= 2, but got 200 instead.\", 'type': 'invalid_request_error', 'param': 'temperature', 'code': 'decimal_above_max_value'}}", + 'body': { + 'message': "Invalid 'temperature': decimal above maximum value. Expected a value <= 2, but got 200 instead.", + 'type': 'invalid_request_error', + 'param': 'temperature', + 'code': 'decimal_above_max_value' + }, + 'code': 'decimal_above_max_value', + 'param': 'temperature', + 'type': 'invalid_request_error', + 'response': "", + 'status_code': 400, + 'request_id': 'req_f287898caa6364cd42bc01355f74dd2a' + } + """ + try: + # First, try to access the message directly from the 'body' key + if error_obj is None: + return None + + if hasattr(error_obj, "body"): + _error_obj_body = getattr(error_obj, "body") + if isinstance(_error_obj_body, dict): + return _error_obj_body.get("message") + + # If all else fails, return None + return None + except Exception as e: + return None diff --git a/litellm/utils.py b/litellm/utils.py index fef4976ca..0cd3ed00a 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -50,6 +50,7 @@ import litellm._service_logger # for storing API inputs, outputs, and metadata import litellm.litellm_core_utils from litellm.caching import DualCache from litellm.litellm_core_utils.core_helpers import map_finish_reason +from litellm.litellm_core_utils.exception_mapping_utils import get_error_message from litellm.litellm_core_utils.llm_request_utils import _ensure_extra_body_is_safe from litellm.litellm_core_utils.redact_messages import ( redact_message_input_output_from_logging, @@ -5824,10 +5825,13 @@ def exception_type( or custom_llm_provider in litellm.openai_compatible_providers ): # custom_llm_provider is openai, make it OpenAI - if hasattr(original_exception, "message"): - message = original_exception.message - else: - message = str(original_exception) + message = get_error_message(error_obj=original_exception) + if message is None: + if hasattr(original_exception, "message"): + message = original_exception.message + else: + message = str(original_exception) + if message is not None and isinstance(message, str): message = message.replace("OPENAI", custom_llm_provider.upper()) message = message.replace("openai", custom_llm_provider) @@ -7280,10 +7284,17 @@ def exception_type( request=original_exception.request, ) elif custom_llm_provider == "azure": + message = get_error_message(error_obj=original_exception) + if message is None: + if hasattr(original_exception, "message"): + message = original_exception.message + else: + message = str(original_exception) + if "Internal server error" in error_str: exception_mapping_worked = True raise litellm.InternalServerError( - message=f"AzureException Internal server error - {original_exception.message}", + message=f"AzureException Internal server error - {message}", llm_provider="azure", model=model, litellm_debug_info=extra_information, @@ -7296,7 +7307,7 @@ def exception_type( elif "This model's maximum context length is" in error_str: exception_mapping_worked = True raise ContextWindowExceededError( - message=f"AzureException ContextWindowExceededError - {original_exception.message}", + message=f"AzureException ContextWindowExceededError - {message}", llm_provider="azure", model=model, litellm_debug_info=extra_information, @@ -7305,7 +7316,7 @@ def exception_type( elif "DeploymentNotFound" in error_str: exception_mapping_worked = True raise NotFoundError( - message=f"AzureException NotFoundError - {original_exception.message}", + message=f"AzureException NotFoundError - {message}", llm_provider="azure", model=model, litellm_debug_info=extra_information, @@ -7325,7 +7336,7 @@ def exception_type( ): exception_mapping_worked = True raise ContentPolicyViolationError( - message=f"litellm.ContentPolicyViolationError: AzureException - {original_exception.message}", + message=f"litellm.ContentPolicyViolationError: AzureException - {message}", llm_provider="azure", model=model, litellm_debug_info=extra_information, @@ -7334,7 +7345,7 @@ def exception_type( elif "invalid_request_error" in error_str: exception_mapping_worked = True raise BadRequestError( - message=f"AzureException BadRequestError - {original_exception.message}", + message=f"AzureException BadRequestError - {message}", llm_provider="azure", model=model, litellm_debug_info=extra_information, @@ -7346,7 +7357,7 @@ def exception_type( ): exception_mapping_worked = True raise AuthenticationError( - message=f"{exception_provider} AuthenticationError - {original_exception.message}", + message=f"{exception_provider} AuthenticationError - {message}", llm_provider=custom_llm_provider, model=model, litellm_debug_info=extra_information, @@ -7357,7 +7368,7 @@ def exception_type( if original_exception.status_code == 400: exception_mapping_worked = True raise BadRequestError( - message=f"AzureException - {original_exception.message}", + message=f"AzureException - {message}", llm_provider="azure", model=model, litellm_debug_info=extra_information, @@ -7366,7 +7377,7 @@ def exception_type( elif original_exception.status_code == 401: exception_mapping_worked = True raise AuthenticationError( - message=f"AzureException AuthenticationError - {original_exception.message}", + message=f"AzureException AuthenticationError - {message}", llm_provider="azure", model=model, litellm_debug_info=extra_information, @@ -7375,7 +7386,7 @@ def exception_type( elif original_exception.status_code == 408: exception_mapping_worked = True raise Timeout( - message=f"AzureException Timeout - {original_exception.message}", + message=f"AzureException Timeout - {message}", model=model, litellm_debug_info=extra_information, llm_provider="azure", @@ -7383,7 +7394,7 @@ def exception_type( elif original_exception.status_code == 422: exception_mapping_worked = True raise BadRequestError( - message=f"AzureException BadRequestError - {original_exception.message}", + message=f"AzureException BadRequestError - {message}", model=model, llm_provider="azure", litellm_debug_info=extra_information, @@ -7392,7 +7403,7 @@ def exception_type( elif original_exception.status_code == 429: exception_mapping_worked = True raise RateLimitError( - message=f"AzureException RateLimitError - {original_exception.message}", + message=f"AzureException RateLimitError - {message}", model=model, llm_provider="azure", litellm_debug_info=extra_information, @@ -7401,7 +7412,7 @@ def exception_type( elif original_exception.status_code == 503: exception_mapping_worked = True raise ServiceUnavailableError( - message=f"AzureException ServiceUnavailableError - {original_exception.message}", + message=f"AzureException ServiceUnavailableError - {message}", model=model, llm_provider="azure", litellm_debug_info=extra_information, @@ -7410,7 +7421,7 @@ def exception_type( elif original_exception.status_code == 504: # gateway timeout error exception_mapping_worked = True raise Timeout( - message=f"AzureException Timeout - {original_exception.message}", + message=f"AzureException Timeout - {message}", model=model, litellm_debug_info=extra_information, llm_provider="azure", @@ -7419,7 +7430,7 @@ def exception_type( exception_mapping_worked = True raise APIError( status_code=original_exception.status_code, - message=f"AzureException APIError - {original_exception.message}", + message=f"AzureException APIError - {message}", llm_provider="azure", litellm_debug_info=extra_information, model=model, From d13138056394a5b33b67bc5ababd368660f3cf8d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 13:24:57 -0700 Subject: [PATCH 065/124] fix test router debug logs --- litellm/tests/test_router_debug_logs.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/litellm/tests/test_router_debug_logs.py b/litellm/tests/test_router_debug_logs.py index 09590e5ac..20eccf5de 100644 --- a/litellm/tests/test_router_debug_logs.py +++ b/litellm/tests/test_router_debug_logs.py @@ -1,16 +1,23 @@ -import sys, os, time -import traceback, asyncio +import asyncio +import os +import sys +import time +import traceback + import pytest sys.path.insert( 0, os.path.abspath("../..") ) # Adds the parent directory to the system path -import litellm, asyncio, logging +import asyncio +import logging + +import litellm from litellm import Router # this tests debug logs from litellm router and litellm proxy server -from litellm._logging import verbose_router_logger, verbose_logger, verbose_proxy_logger +from litellm._logging import verbose_logger, verbose_proxy_logger, verbose_router_logger # this tests debug logs from litellm router and litellm proxy server @@ -81,7 +88,7 @@ def test_async_fallbacks(caplog): # Define the expected log messages # - error request, falling back notice, success notice expected_logs = [ - "litellm.acompletion(model=gpt-3.5-turbo)\x1b[31m Exception litellm.AuthenticationError: AuthenticationError: OpenAIException - Error code: 401 - {'error': {'message': 'Incorrect API key provided: bad-key. You can find your API key at https://platform.openai.com/account/api-keys.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_api_key'}}\x1b[0m", + "litellm.acompletion(model=gpt-3.5-turbo)\x1b[31m Exception litellm.AuthenticationError: AuthenticationError: OpenAIException - Incorrect API key provided: bad-key. You can find your API key at https://platform.openai.com/account/api-keys.\x1b[0m", "Falling back to model_group = azure/gpt-3.5-turbo", "litellm.acompletion(model=azure/chatgpt-v-2)\x1b[32m 200 OK\x1b[0m", "Successful fallback b/w models.", From 05dfc63b8848376f7adee7411ba08f9e220cf292 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 13:25:27 -0700 Subject: [PATCH 066/124] feat(vertex_httpx.py): support the 'response_schema' param for older vertex ai models - pass as prompt (user-controlled) if 'response_schema' is not supported for vertex model (e.g. gemini-1.5-flash) pass in prompt --- litellm/__init__.py | 1 + litellm/llms/prompt_templates/factory.py | 44 ++++++ litellm/llms/vertex_ai.py | 147 +++++++++--------- litellm/llms/vertex_httpx.py | 79 ++++++---- ...odel_prices_and_context_window_backup.json | 5 +- .../tests/test_amazing_vertex_completion.py | 37 ++++- litellm/utils.py | 6 +- model_prices_and_context_window.json | 5 +- 8 files changed, 212 insertions(+), 112 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index a8d9a80a2..73c382516 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -749,6 +749,7 @@ from .utils import ( create_pretrained_tokenizer, create_tokenizer, supports_function_calling, + supports_response_schema, supports_parallel_function_calling, supports_vision, supports_system_messages, diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index b35914584..87af2a6bd 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -2033,6 +2033,50 @@ def function_call_prompt(messages: list, functions: list): return messages +def response_schema_prompt(model: str, response_schema: dict) -> str: + """ + Decides if a user-defined custom prompt or default needs to be used + + Returns the prompt str that's passed to the model as a user message + """ + custom_prompt_details: Optional[dict] = None + response_schema_as_message = [ + {"role": "user", "content": "{}".format(response_schema)} + ] + if f"{model}/response_schema_prompt" in litellm.custom_prompt_dict: + + custom_prompt_details = litellm.custom_prompt_dict[ + f"{model}/response_schema_prompt" + ] # allow user to define custom response schema prompt by model + elif "response_schema_prompt" in litellm.custom_prompt_dict: + custom_prompt_details = litellm.custom_prompt_dict["response_schema_prompt"] + + if custom_prompt_details is not None: + return custom_prompt( + role_dict=custom_prompt_details["roles"], + initial_prompt_value=custom_prompt_details["initial_prompt_value"], + final_prompt_value=custom_prompt_details["final_prompt_value"], + messages=response_schema_as_message, + ) + else: + return default_response_schema_prompt(response_schema=response_schema) + + +def default_response_schema_prompt(response_schema: dict) -> str: + """ + Used if provider/model doesn't support 'response_schema' param. + + This is the default prompt. Allow user to override this with a custom_prompt. + """ + prompt_str = """Use this JSON schema: + ```json + {} + ```""".format( + response_schema + ) + return prompt_str + + # Custom prompt template def custom_prompt( role_dict: dict, diff --git a/litellm/llms/vertex_ai.py b/litellm/llms/vertex_ai.py index 4a4abaef4..c1e628d17 100644 --- a/litellm/llms/vertex_ai.py +++ b/litellm/llms/vertex_ai.py @@ -12,6 +12,7 @@ import requests # type: ignore from pydantic import BaseModel import litellm +from litellm._logging import verbose_logger from litellm.litellm_core_utils.core_helpers import map_finish_reason from litellm.llms.prompt_templates.factory import ( convert_to_anthropic_image_obj, @@ -328,80 +329,86 @@ def _gemini_convert_messages_with_history(messages: list) -> List[ContentType]: contents: List[ContentType] = [] msg_i = 0 - while msg_i < len(messages): - user_content: List[PartType] = [] - init_msg_i = msg_i - ## MERGE CONSECUTIVE USER CONTENT ## - while msg_i < len(messages) and messages[msg_i]["role"] in user_message_types: - if isinstance(messages[msg_i]["content"], list): - _parts: List[PartType] = [] - for element in messages[msg_i]["content"]: - if isinstance(element, dict): - if element["type"] == "text" and len(element["text"]) > 0: - _part = PartType(text=element["text"]) - _parts.append(_part) - elif element["type"] == "image_url": - image_url = element["image_url"]["url"] - _part = _process_gemini_image(image_url=image_url) - _parts.append(_part) # type: ignore - user_content.extend(_parts) - elif ( - isinstance(messages[msg_i]["content"], str) - and len(messages[msg_i]["content"]) > 0 + try: + while msg_i < len(messages): + user_content: List[PartType] = [] + init_msg_i = msg_i + ## MERGE CONSECUTIVE USER CONTENT ## + while ( + msg_i < len(messages) and messages[msg_i]["role"] in user_message_types ): - _part = PartType(text=messages[msg_i]["content"]) - user_content.append(_part) + if isinstance(messages[msg_i]["content"], list): + _parts: List[PartType] = [] + for element in messages[msg_i]["content"]: + if isinstance(element, dict): + if element["type"] == "text" and len(element["text"]) > 0: + _part = PartType(text=element["text"]) + _parts.append(_part) + elif element["type"] == "image_url": + image_url = element["image_url"]["url"] + _part = _process_gemini_image(image_url=image_url) + _parts.append(_part) # type: ignore + user_content.extend(_parts) + elif ( + isinstance(messages[msg_i]["content"], str) + and len(messages[msg_i]["content"]) > 0 + ): + _part = PartType(text=messages[msg_i]["content"]) + user_content.append(_part) - msg_i += 1 + msg_i += 1 - if user_content: - contents.append(ContentType(role="user", parts=user_content)) - assistant_content = [] - ## MERGE CONSECUTIVE ASSISTANT CONTENT ## - while msg_i < len(messages) and messages[msg_i]["role"] == "assistant": - if isinstance(messages[msg_i]["content"], list): - _parts = [] - for element in messages[msg_i]["content"]: - if isinstance(element, dict): - if element["type"] == "text": - _part = PartType(text=element["text"]) - _parts.append(_part) - elif element["type"] == "image_url": - image_url = element["image_url"]["url"] - _part = _process_gemini_image(image_url=image_url) - _parts.append(_part) # type: ignore - assistant_content.extend(_parts) - elif messages[msg_i].get( - "tool_calls", [] - ): # support assistant tool invoke conversion - assistant_content.extend( - convert_to_gemini_tool_call_invoke(messages[msg_i]["tool_calls"]) + if user_content: + contents.append(ContentType(role="user", parts=user_content)) + assistant_content = [] + ## MERGE CONSECUTIVE ASSISTANT CONTENT ## + while msg_i < len(messages) and messages[msg_i]["role"] == "assistant": + if isinstance(messages[msg_i]["content"], list): + _parts = [] + for element in messages[msg_i]["content"]: + if isinstance(element, dict): + if element["type"] == "text": + _part = PartType(text=element["text"]) + _parts.append(_part) + elif element["type"] == "image_url": + image_url = element["image_url"]["url"] + _part = _process_gemini_image(image_url=image_url) + _parts.append(_part) # type: ignore + assistant_content.extend(_parts) + elif messages[msg_i].get( + "tool_calls", [] + ): # support assistant tool invoke conversion + assistant_content.extend( + convert_to_gemini_tool_call_invoke( + messages[msg_i]["tool_calls"] + ) + ) + else: + assistant_text = ( + messages[msg_i].get("content") or "" + ) # either string or none + if assistant_text: + assistant_content.append(PartType(text=assistant_text)) + + msg_i += 1 + + if assistant_content: + contents.append(ContentType(role="model", parts=assistant_content)) + + ## APPEND TOOL CALL MESSAGES ## + if msg_i < len(messages) and messages[msg_i]["role"] == "tool": + _part = convert_to_gemini_tool_call_result(messages[msg_i]) + contents.append(ContentType(parts=[_part])) # type: ignore + msg_i += 1 + if msg_i == init_msg_i: # prevent infinite loops + raise Exception( + "Invalid Message passed in - {}. File an issue https://github.com/BerriAI/litellm/issues".format( + messages[msg_i] + ) ) - else: - assistant_text = ( - messages[msg_i].get("content") or "" - ) # either string or none - if assistant_text: - assistant_content.append(PartType(text=assistant_text)) - - msg_i += 1 - - if assistant_content: - contents.append(ContentType(role="model", parts=assistant_content)) - - ## APPEND TOOL CALL MESSAGES ## - if msg_i < len(messages) and messages[msg_i]["role"] == "tool": - _part = convert_to_gemini_tool_call_result(messages[msg_i]) - contents.append(ContentType(parts=[_part])) # type: ignore - msg_i += 1 - if msg_i == init_msg_i: # prevent infinite loops - raise Exception( - "Invalid Message passed in - {}. File an issue https://github.com/BerriAI/litellm/issues".format( - messages[msg_i] - ) - ) - - return contents + return contents + except Exception as e: + raise e def _get_client_cache_key(model: str, vertex_project: str, vertex_location: str): diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index 940016ecb..91a2b0276 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -21,7 +21,10 @@ import litellm.litellm_core_utils.litellm_logging from litellm import verbose_logger from litellm.litellm_core_utils.core_helpers import map_finish_reason from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler -from litellm.llms.prompt_templates.factory import convert_url_to_base64 +from litellm.llms.prompt_templates.factory import ( + convert_url_to_base64, + response_schema_prompt, +) from litellm.llms.vertex_ai import _gemini_convert_messages_with_history from litellm.types.llms.openai import ( ChatCompletionResponseMessage, @@ -1011,35 +1014,53 @@ class VertexLLM(BaseLLM): if len(system_prompt_indices) > 0: for idx in reversed(system_prompt_indices): messages.pop(idx) - content = _gemini_convert_messages_with_history(messages=messages) - tools: Optional[Tools] = optional_params.pop("tools", None) - tool_choice: Optional[ToolConfig] = optional_params.pop("tool_choice", None) - safety_settings: Optional[List[SafetSettingsConfig]] = optional_params.pop( - "safety_settings", None - ) # type: ignore - generation_config: Optional[GenerationConfig] = GenerationConfig( - **optional_params - ) - data = RequestBody(contents=content) - if len(system_content_blocks) > 0: - system_instructions = SystemInstructions(parts=system_content_blocks) - data["system_instruction"] = system_instructions - if tools is not None: - data["tools"] = tools - if tool_choice is not None: - data["toolConfig"] = tool_choice - if safety_settings is not None: - data["safetySettings"] = safety_settings - if generation_config is not None: - data["generationConfig"] = generation_config - headers = { - "Content-Type": "application/json", - } - if auth_header is not None: - headers["Authorization"] = f"Bearer {auth_header}" - if extra_headers is not None: - headers.update(extra_headers) + # Checks for 'response_schema' support - if passed in + if "response_schema" in optional_params: + supports_response_schema = litellm.supports_response_schema( + model=model, custom_llm_provider="vertex_ai" + ) + if supports_response_schema is False: + user_response_schema_message = response_schema_prompt( + model=model, response_schema=optional_params.get("response_schema") # type: ignore + ) + messages.append( + {"role": "user", "content": user_response_schema_message} + ) + optional_params.pop("response_schema") + + try: + content = _gemini_convert_messages_with_history(messages=messages) + tools: Optional[Tools] = optional_params.pop("tools", None) + tool_choice: Optional[ToolConfig] = optional_params.pop("tool_choice", None) + safety_settings: Optional[List[SafetSettingsConfig]] = optional_params.pop( + "safety_settings", None + ) # type: ignore + generation_config: Optional[GenerationConfig] = GenerationConfig( + **optional_params + ) + data = RequestBody(contents=content) + if len(system_content_blocks) > 0: + system_instructions = SystemInstructions(parts=system_content_blocks) + data["system_instruction"] = system_instructions + if tools is not None: + data["tools"] = tools + if tool_choice is not None: + data["toolConfig"] = tool_choice + if safety_settings is not None: + data["safetySettings"] = safety_settings + if generation_config is not None: + data["generationConfig"] = generation_config + + headers = { + "Content-Type": "application/json", + } + if auth_header is not None: + headers["Authorization"] = f"Bearer {auth_header}" + if extra_headers is not None: + headers.update(extra_headers) + except Exception as e: + raise e ## LOGGING logging_obj.pre_call( diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 49f2f0c28..7f08b9eb1 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1538,6 +1538,7 @@ "supports_system_messages": true, "supports_function_calling": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.5-pro-preview-0215": { @@ -1563,6 +1564,7 @@ "supports_system_messages": true, "supports_function_calling": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.5-pro-preview-0409": { @@ -1586,7 +1588,8 @@ "litellm_provider": "vertex_ai-language-models", "mode": "chat", "supports_function_calling": true, - "supports_tool_choice": true, + "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.5-flash": { diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index e6f2634f4..4cb79affa 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -880,10 +880,19 @@ Using this JSON schema: mock_call.assert_called_once() -@pytest.mark.parametrize("provider", ["vertex_ai_beta"]) # "vertex_ai", +@pytest.mark.parametrize( + "model, supports_response_schema", + [ + ("vertex_ai_beta/gemini-1.5-pro-001", True), + ("vertex_ai_beta/gemini-1.5-flash", False), + ], +) # "vertex_ai", @pytest.mark.asyncio -async def test_gemini_pro_json_schema_httpx(provider): +async def test_gemini_pro_json_schema_httpx(model, supports_response_schema): load_vertex_ai_credentials() + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + litellm.set_verbose = True messages = [{"role": "user", "content": "List 5 cookie recipes"}] from litellm.llms.custom_httpx.http_handler import HTTPHandler @@ -905,8 +914,8 @@ async def test_gemini_pro_json_schema_httpx(provider): with patch.object(client, "post", new=MagicMock()) as mock_call: try: - response = completion( - model="vertex_ai_beta/gemini-1.5-pro-001", + _ = completion( + model=model, messages=messages, response_format={ "type": "json_object", @@ -914,15 +923,27 @@ async def test_gemini_pro_json_schema_httpx(provider): }, client=client, ) - except Exception as e: + except Exception: pass mock_call.assert_called_once() print(mock_call.call_args.kwargs) print(mock_call.call_args.kwargs["json"]["generationConfig"]) - assert ( - "response_schema" in mock_call.call_args.kwargs["json"]["generationConfig"] - ) + + if supports_response_schema: + assert ( + "response_schema" + in mock_call.call_args.kwargs["json"]["generationConfig"] + ) + else: + assert ( + "response_schema" + not in mock_call.call_args.kwargs["json"]["generationConfig"] + ) + assert ( + "Use this JSON schema:" + in mock_call.call_args.kwargs["json"]["contents"][0]["parts"][1]["text"] + ) @pytest.mark.parametrize("provider", ["vertex_ai_beta"]) # "vertex_ai", diff --git a/litellm/utils.py b/litellm/utils.py index 227274d3a..91cf75424 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -1879,8 +1879,7 @@ def supports_response_schema(model: str, custom_llm_provider: Optional[str]) -> Returns: bool: True if the model supports response_schema, False otherwise. - Raises: - Exception: If the given model is not found in model_prices_and_context_window.json. + Does not raise error. Defaults to 'False'. Outputs logging.error. """ try: ## GET LLM PROVIDER ## @@ -1900,9 +1899,10 @@ def supports_response_schema(model: str, custom_llm_provider: Optional[str]) -> return True return False except Exception: - raise Exception( + verbose_logger.error( f"Model not in model_prices_and_context_window.json. You passed model={model}, custom_llm_provider={custom_llm_provider}." ) + return False def supports_function_calling(model: str) -> bool: diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 49f2f0c28..7f08b9eb1 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1538,6 +1538,7 @@ "supports_system_messages": true, "supports_function_calling": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.5-pro-preview-0215": { @@ -1563,6 +1564,7 @@ "supports_system_messages": true, "supports_function_calling": true, "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.5-pro-preview-0409": { @@ -1586,7 +1588,8 @@ "litellm_provider": "vertex_ai-language-models", "mode": "chat", "supports_function_calling": true, - "supports_tool_choice": true, + "supports_tool_choice": true, + "supports_response_schema": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.5-flash": { From 2f741bdcff977c5f00959952808dae808cc126c4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 13:29:28 -0700 Subject: [PATCH 067/124] test - correct error str on exceptions --- litellm/tests/test_proxy_exception_mapping.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/litellm/tests/test_proxy_exception_mapping.py b/litellm/tests/test_proxy_exception_mapping.py index 498842661..4fb1e7134 100644 --- a/litellm/tests/test_proxy_exception_mapping.py +++ b/litellm/tests/test_proxy_exception_mapping.py @@ -1,25 +1,31 @@ # test that the proxy actually does exception mapping to the OpenAI format -import sys, os -from unittest import mock import json +import os +import sys +from unittest import mock + from dotenv import load_dotenv load_dotenv() -import os, io, asyncio +import asyncio +import io +import os sys.path.insert( 0, os.path.abspath("../..") ) # Adds the parent directory to the system path +import openai import pytest -import litellm, openai -from fastapi.testclient import TestClient from fastapi import Response -from litellm.proxy.proxy_server import ( +from fastapi.testclient import TestClient + +import litellm +from litellm.proxy.proxy_server import ( # Replace with the actual module where your FastAPI router is defined + initialize, router, save_worker_config, - initialize, -) # Replace with the actual module where your FastAPI router is defined +) invalid_authentication_error_response = Response( status_code=401, @@ -66,6 +72,12 @@ def test_chat_completion_exception(client): json_response = response.json() print("keys in json response", json_response.keys()) assert json_response.keys() == {"error"} + print("ERROR=", json_response["error"]) + assert isinstance(json_response["error"]["message"], str) + assert ( + json_response["error"]["message"] + == "litellm.AuthenticationError: AuthenticationError: OpenAIException - Incorrect API key provided: bad-key. You can find your API key at https://platform.openai.com/account/api-keys." + ) # make an openai client to call _make_status_error_from_response openai_client = openai.OpenAI(api_key="anything") From 37352ba6920fb58e0779be99a099ae24951ab4c8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 14:36:58 -0700 Subject: [PATCH 068/124] fix - use correct user_id when creating key for admin ui --- litellm/proxy/proxy_server.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index ff4b1e663..9f87bcd98 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -7511,7 +7511,9 @@ async def login(request: Request): # Non SSO -> If user is using UI_USERNAME and UI_PASSWORD they are Proxy admin user_role = LitellmUserRoles.PROXY_ADMIN user_id = username - key_user_id = user_id + + # we want the key created to have PROXY_ADMIN_PERMISSIONS + key_user_id = litellm_proxy_admin_name if ( os.getenv("PROXY_ADMIN_ID", None) is not None and os.environ["PROXY_ADMIN_ID"] == user_id @@ -7531,7 +7533,17 @@ async def login(request: Request): if os.getenv("DATABASE_URL") is not None: response = await generate_key_helper_fn( request_type="key", - **{"user_role": LitellmUserRoles.PROXY_ADMIN, "duration": "2hr", "key_max_budget": 5, "models": [], "aliases": {}, "config": {}, "spend": 0, "user_id": key_user_id, "team_id": "litellm-dashboard"}, # type: ignore + **{ + "user_role": LitellmUserRoles.PROXY_ADMIN, + "duration": "2hr", + "key_max_budget": 5, + "models": [], + "aliases": {}, + "config": {}, + "spend": 0, + "user_id": key_user_id, + "team_id": "litellm-dashboard", + }, # type: ignore ) else: raise ProxyException( From b699d9a8b90b4a2bfb891cecb22b721c5d7fcff2 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 15:05:52 -0700 Subject: [PATCH 069/124] fix(utils.py): support json schema validation --- litellm/__init__.py | 1 + litellm/exceptions.py | 21 ++- .../json_validation_rule.py | 23 +++ .../tests/test_amazing_vertex_completion.py | 136 +++++++++++++++++- litellm/utils.py | 47 +++++- 5 files changed, 216 insertions(+), 12 deletions(-) create mode 100644 litellm/litellm_core_utils/json_validation_rule.py diff --git a/litellm/__init__.py b/litellm/__init__.py index 73c382516..05497ee72 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -849,6 +849,7 @@ from .exceptions import ( APIResponseValidationError, UnprocessableEntityError, InternalServerError, + JSONSchemaValidationError, LITELLM_EXCEPTION_TYPES, ) from .budget_manager import BudgetManager diff --git a/litellm/exceptions.py b/litellm/exceptions.py index 98b519278..d85510b1d 100644 --- a/litellm/exceptions.py +++ b/litellm/exceptions.py @@ -551,7 +551,7 @@ class APIError(openai.APIError): # type: ignore message, llm_provider, model, - request: httpx.Request, + request: Optional[httpx.Request] = None, litellm_debug_info: Optional[str] = None, max_retries: Optional[int] = None, num_retries: Optional[int] = None, @@ -563,6 +563,8 @@ class APIError(openai.APIError): # type: ignore self.litellm_debug_info = litellm_debug_info self.max_retries = max_retries self.num_retries = num_retries + if request is None: + request = httpx.Request(method="POST", url="https://api.openai.com/v1") super().__init__(self.message, request=request, body=None) # type: ignore def __str__(self): @@ -664,6 +666,22 @@ class OpenAIError(openai.OpenAIError): # type: ignore self.llm_provider = "openai" +class JSONSchemaValidationError(APIError): + def __init__( + self, model: str, llm_provider: str, raw_response: str, schema: str + ) -> None: + self.raw_response = raw_response + self.schema = schema + self.model = model + message = "litellm.JSONSchemaValidationError: model={}, returned an invalid response={}, for schema={}.\nAccess raw response with `e.raw_response`".format( + model, raw_response, schema + ) + self.message = message + super().__init__( + model=model, message=message, llm_provider=llm_provider, status_code=500 + ) + + LITELLM_EXCEPTION_TYPES = [ AuthenticationError, NotFoundError, @@ -682,6 +700,7 @@ LITELLM_EXCEPTION_TYPES = [ APIResponseValidationError, OpenAIError, InternalServerError, + JSONSchemaValidationError, ] diff --git a/litellm/litellm_core_utils/json_validation_rule.py b/litellm/litellm_core_utils/json_validation_rule.py new file mode 100644 index 000000000..f19144aaf --- /dev/null +++ b/litellm/litellm_core_utils/json_validation_rule.py @@ -0,0 +1,23 @@ +import json + + +def validate_schema(schema: dict, response: str): + """ + Validate if the returned json response follows the schema. + + Params: + - schema - dict: JSON schema + - response - str: Received json response as string. + """ + from jsonschema import ValidationError, validate + + from litellm import JSONSchemaValidationError + + response_dict = json.loads(response) + + try: + validate(response_dict, schema=schema) + except ValidationError: + raise JSONSchemaValidationError( + model="", llm_provider="", raw_response=response, schema=json.dumps(schema) + ) diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index 4cb79affa..ecad16fe6 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -880,15 +880,133 @@ Using this JSON schema: mock_call.assert_called_once() +def vertex_httpx_mock_post_valid_response(*args, **kwargs): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"Content-Type": "application/json"} + mock_response.json.return_value = { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": '[{"recipe_name": "Chocolate Chip Cookies"}, {"recipe_name": "Oatmeal Raisin Cookies"}, {"recipe_name": "Peanut Butter Cookies"}, {"recipe_name": "Sugar Cookies"}, {"recipe_name": "Snickerdoodles"}]\n' + } + ], + }, + "finishReason": "STOP", + "safetyRatings": [ + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE", + "probabilityScore": 0.09790669, + "severity": "HARM_SEVERITY_NEGLIGIBLE", + "severityScore": 0.11736965, + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE", + "probabilityScore": 0.1261379, + "severity": "HARM_SEVERITY_NEGLIGIBLE", + "severityScore": 0.08601588, + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE", + "probabilityScore": 0.083441176, + "severity": "HARM_SEVERITY_NEGLIGIBLE", + "severityScore": 0.0355444, + }, + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE", + "probabilityScore": 0.071981624, + "severity": "HARM_SEVERITY_NEGLIGIBLE", + "severityScore": 0.08108212, + }, + ], + } + ], + "usageMetadata": { + "promptTokenCount": 60, + "candidatesTokenCount": 55, + "totalTokenCount": 115, + }, + } + return mock_response + + +def vertex_httpx_mock_post_invalid_schema_response(*args, **kwargs): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"Content-Type": "application/json"} + mock_response.json.return_value = { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + {"text": '[{"recipe_world": "Chocolate Chip Cookies"}]\n'} + ], + }, + "finishReason": "STOP", + "safetyRatings": [ + { + "category": "HARM_CATEGORY_HATE_SPEECH", + "probability": "NEGLIGIBLE", + "probabilityScore": 0.09790669, + "severity": "HARM_SEVERITY_NEGLIGIBLE", + "severityScore": 0.11736965, + }, + { + "category": "HARM_CATEGORY_DANGEROUS_CONTENT", + "probability": "NEGLIGIBLE", + "probabilityScore": 0.1261379, + "severity": "HARM_SEVERITY_NEGLIGIBLE", + "severityScore": 0.08601588, + }, + { + "category": "HARM_CATEGORY_HARASSMENT", + "probability": "NEGLIGIBLE", + "probabilityScore": 0.083441176, + "severity": "HARM_SEVERITY_NEGLIGIBLE", + "severityScore": 0.0355444, + }, + { + "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "probability": "NEGLIGIBLE", + "probabilityScore": 0.071981624, + "severity": "HARM_SEVERITY_NEGLIGIBLE", + "severityScore": 0.08108212, + }, + ], + } + ], + "usageMetadata": { + "promptTokenCount": 60, + "candidatesTokenCount": 55, + "totalTokenCount": 115, + }, + } + return mock_response + + @pytest.mark.parametrize( "model, supports_response_schema", [ ("vertex_ai_beta/gemini-1.5-pro-001", True), ("vertex_ai_beta/gemini-1.5-flash", False), ], -) # "vertex_ai", +) +@pytest.mark.parametrize( + "invalid_response", + [True, False], +) @pytest.mark.asyncio -async def test_gemini_pro_json_schema_httpx(model, supports_response_schema): +async def test_gemini_pro_json_schema_args_sent_httpx( + model, supports_response_schema, invalid_response +): load_vertex_ai_credentials() os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" litellm.model_cost = litellm.get_model_cost_map(url="") @@ -912,7 +1030,12 @@ async def test_gemini_pro_json_schema_httpx(model, supports_response_schema): client = HTTPHandler() - with patch.object(client, "post", new=MagicMock()) as mock_call: + httpx_response = MagicMock() + if invalid_response is True: + httpx_response.side_effect = vertex_httpx_mock_post_invalid_schema_response + else: + httpx_response.side_effect = vertex_httpx_mock_post_valid_response + with patch.object(client, "post", new=httpx_response) as mock_call: try: _ = completion( model=model, @@ -923,8 +1046,11 @@ async def test_gemini_pro_json_schema_httpx(model, supports_response_schema): }, client=client, ) - except Exception: - pass + if invalid_response is True: + pytest.fail("Expected this to fail") + except litellm.JSONSchemaValidationError as e: + if invalid_response is False: + pytest.fail("Expected this to pass. Got={}".format(e)) mock_call.assert_called_once() print(mock_call.call_args.kwargs) diff --git a/litellm/utils.py b/litellm/utils.py index 91cf75424..e75a5df73 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -48,6 +48,7 @@ from tokenizers import Tokenizer import litellm import litellm._service_logger # for storing API inputs, outputs, and metadata import litellm.litellm_core_utils +import litellm.litellm_core_utils.json_validation_rule from litellm.caching import DualCache from litellm.litellm_core_utils.core_helpers import map_finish_reason from litellm.litellm_core_utils.llm_request_utils import _ensure_extra_body_is_safe @@ -579,7 +580,7 @@ def client(original_function): else: return False - def post_call_processing(original_response, model): + def post_call_processing(original_response, model, optional_params: Optional[dict]): try: if original_response is None: pass @@ -594,11 +595,41 @@ def client(original_function): pass else: if isinstance(original_response, ModelResponse): - model_response = original_response.choices[ + model_response: Optional[str] = original_response.choices[ 0 - ].message.content - ### POST-CALL RULES ### - rules_obj.post_call_rules(input=model_response, model=model) + ].message.content # type: ignore + if model_response is not None: + ### POST-CALL RULES ### + rules_obj.post_call_rules( + input=model_response, model=model + ) + ### JSON SCHEMA VALIDATION ### + if ( + optional_params is not None + and "response_format" in optional_params + and isinstance( + optional_params["response_format"], dict + ) + and "type" in optional_params["response_format"] + and optional_params["response_format"]["type"] + == "json_object" + and "response_schema" + in optional_params["response_format"] + and isinstance( + optional_params["response_format"][ + "response_schema" + ], + dict, + ) + ): + # schema given, json response expected + litellm.litellm_core_utils.json_validation_rule.validate_schema( + schema=optional_params["response_format"][ + "response_schema" + ], + response=model_response, + ) + except Exception as e: raise e @@ -867,7 +898,11 @@ def client(original_function): return result ### POST-CALL RULES ### - post_call_processing(original_response=result, model=model or None) + post_call_processing( + original_response=result, + model=model or None, + optional_params=kwargs, + ) # [OPTIONAL] ADD TO CACHE if ( From 457689c31a8f70d3e59f51a1e089ac5909a1f5d9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 15:51:28 -0700 Subject: [PATCH 070/124] fix image gen connection error --- litellm/llms/custom_httpx/azure_dall_e_2.py | 27 +++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/litellm/llms/custom_httpx/azure_dall_e_2.py b/litellm/llms/custom_httpx/azure_dall_e_2.py index f361ede5b..75ab542a8 100644 --- a/litellm/llms/custom_httpx/azure_dall_e_2.py +++ b/litellm/llms/custom_httpx/azure_dall_e_2.py @@ -1,4 +1,8 @@ -import time, json, httpx, asyncio +import asyncio +import json +import time + +import httpx class AsyncCustomHTTPTransport(httpx.AsyncHTTPTransport): @@ -7,15 +11,18 @@ class AsyncCustomHTTPTransport(httpx.AsyncHTTPTransport): """ async def handle_async_request(self, request: httpx.Request) -> httpx.Response: - if "images/generations" in request.url.path and request.url.params[ - "api-version" - ] in [ # dall-e-3 starts from `2023-12-01-preview` so we should be able to avoid conflict - "2023-06-01-preview", - "2023-07-01-preview", - "2023-08-01-preview", - "2023-09-01-preview", - "2023-10-01-preview", - ]: + _api_version = request.url.params.get("api-version", "") + if ( + "images/generations" in request.url.path + and _api_version + in [ # dall-e-3 starts from `2023-12-01-preview` so we should be able to avoid conflict + "2023-06-01-preview", + "2023-07-01-preview", + "2023-08-01-preview", + "2023-09-01-preview", + "2023-10-01-preview", + ] + ): request.url = request.url.copy_with( path="/openai/images/generations:submit" ) From 8a4cffa06ef180b2632560bd9245bfbc83991856 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 17:25:41 -0700 Subject: [PATCH 071/124] fix _api_version check on CustomHttpTransport --- litellm/llms/custom_httpx/azure_dall_e_2.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/litellm/llms/custom_httpx/azure_dall_e_2.py b/litellm/llms/custom_httpx/azure_dall_e_2.py index 75ab542a8..a6726eb98 100644 --- a/litellm/llms/custom_httpx/azure_dall_e_2.py +++ b/litellm/llms/custom_httpx/azure_dall_e_2.py @@ -84,15 +84,18 @@ class CustomHTTPTransport(httpx.HTTPTransport): self, request: httpx.Request, ) -> httpx.Response: - if "images/generations" in request.url.path and request.url.params[ - "api-version" - ] in [ # dall-e-3 starts from `2023-12-01-preview` so we should be able to avoid conflict - "2023-06-01-preview", - "2023-07-01-preview", - "2023-08-01-preview", - "2023-09-01-preview", - "2023-10-01-preview", - ]: + _api_version = request.url.params.get("api-version", "") + if ( + "images/generations" in request.url.path + and _api_version + in [ # dall-e-3 starts from `2023-12-01-preview` so we should be able to avoid conflict + "2023-06-01-preview", + "2023-07-01-preview", + "2023-08-01-preview", + "2023-09-01-preview", + "2023-10-01-preview", + ] + ): request.url = request.url.copy_with( path="/openai/images/generations:submit" ) From 0bda80ddea8c9a1995141c080476ac37fede387a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 17:28:08 -0700 Subject: [PATCH 072/124] test- router when using openai prefix --- litellm/tests/test_router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/tests/test_router.py b/litellm/tests/test_router.py index 7c59611d7..5f22e88a1 100644 --- a/litellm/tests/test_router.py +++ b/litellm/tests/test_router.py @@ -1081,7 +1081,7 @@ async def test_aimg_gen_on_router(): { "model_name": "dall-e-3", "litellm_params": { - "model": "dall-e-3", + "model": "openai/dall-e-3", }, }, { @@ -1137,7 +1137,7 @@ def test_img_gen_on_router(): try: model_list = [ { - "model_name": "dall-e-3", + "model_name": "openai/dall-e-3", "litellm_params": { "model": "dall-e-3", }, From 9eee36b449850a437451e51df0cf9e4be12f4ad2 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 18:01:25 -0700 Subject: [PATCH 073/124] add better debugging on /spend/report --- litellm/proxy/spend_tracking/spend_management_endpoints.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 11406b162..87bd85078 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -869,8 +869,9 @@ async def get_global_spend_report( ) if premium_user is not True: + verbose_proxy_logger.debug("accessing /spend/report but not a premium user") raise ValueError( - "/spend/report endpoint" + CommonProxyErrors.not_premium_user.value + "/spend/report endpoint " + CommonProxyErrors.not_premium_user.value ) if group_by == "team": From f9ba3cf6685a3ee5b160e191b8fc724b6df4ea42 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 18:46:06 -0700 Subject: [PATCH 074/124] fix bedrock claude test --- litellm/__init__.py | 6 +++++- litellm/llms/bedrock_httpx.py | 16 ++++++++++++---- litellm/main.py | 7 +++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index a8d9a80a2..5bd5d1a16 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -799,7 +799,11 @@ from .llms.sagemaker import SagemakerConfig from .llms.ollama import OllamaConfig from .llms.ollama_chat import OllamaChatConfig from .llms.maritalk import MaritTalkConfig -from .llms.bedrock_httpx import AmazonCohereChatConfig, AmazonConverseConfig +from .llms.bedrock_httpx import ( + AmazonCohereChatConfig, + AmazonConverseConfig, + BEDROCK_CONVERSE_MODELS, +) from .llms.bedrock import ( AmazonTitanConfig, AmazonAI21Config, diff --git a/litellm/llms/bedrock_httpx.py b/litellm/llms/bedrock_httpx.py index d376808b7..3faaf40f1 100644 --- a/litellm/llms/bedrock_httpx.py +++ b/litellm/llms/bedrock_httpx.py @@ -60,6 +60,12 @@ from .prompt_templates.factory import ( prompt_factory, ) +BEDROCK_CONVERSE_MODELS = [ + "anthropic.claude-3-opus-20240229-v1:0", + "anthropic.claude-3-sonnet-20240229-v1:0", + "anthropic.claude-3-haiku-20240307-v1:0", +] + iam_cache = DualCache() @@ -437,14 +443,15 @@ class BedrockLLM(BaseLLM): aws_access_key_id is not None and aws_secret_access_key is not None and aws_session_token is not None - ): ### CHECK FOR AWS SESSION TOKEN ### + ): ### CHECK FOR AWS SESSION TOKEN ### from botocore.credentials import Credentials + credentials = Credentials( access_key=aws_access_key_id, secret_key=aws_secret_access_key, token=aws_session_token, ) - return credentials + return credentials else: session = boto3.Session( aws_access_key_id=aws_access_key_id, @@ -1571,14 +1578,15 @@ class BedrockConverseLLM(BaseLLM): aws_access_key_id is not None and aws_secret_access_key is not None and aws_session_token is not None - ): ### CHECK FOR AWS SESSION TOKEN ### + ): ### CHECK FOR AWS SESSION TOKEN ### from botocore.credentials import Credentials + credentials = Credentials( access_key=aws_access_key_id, secret_key=aws_secret_access_key, token=aws_session_token, ) - return credentials + return credentials else: session = boto3.Session( aws_access_key_id=aws_access_key_id, diff --git a/litellm/main.py b/litellm/main.py index 951a79c45..10bcbe9e3 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -2200,8 +2200,7 @@ def completion( # boto3 reads keys from .env custom_prompt_dict = custom_prompt_dict or litellm.custom_prompt_dict - - if ("aws_bedrock_client" in optional_params): + if "aws_bedrock_client" in optional_params: # Extract credentials for legacy boto3 client and pass thru to httpx aws_bedrock_client = optional_params.pop("aws_bedrock_client") creds = aws_bedrock_client._get_credentials().get_frozen_credentials() @@ -2210,9 +2209,9 @@ def completion( if creds.secret_key: optional_params["aws_secret_access_key"] = creds.secret_key if creds.token: - optional_params["aws_session_token"] = creds.token + optional_params["aws_session_token"] = creds.token - if model.startswith("anthropic"): + if model in litellm.BEDROCK_CONVERSE_MODELS: response = bedrock_converse_chat_completion.completion( model=model, messages=messages, From 588f0e50d2a3eede61c003abf1ad49af321247d1 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 18:48:13 -0700 Subject: [PATCH 075/124] ci/cd run again --- .circleci/config.yml | 1 + litellm/tests/test_completion.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5dfeedcaa..da0738fb7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -212,6 +212,7 @@ jobs: -e AWS_REGION_NAME=$AWS_REGION_NAME \ -e AUTO_INFER_REGION=True \ -e OPENAI_API_KEY=$OPENAI_API_KEY \ + -e LITELLM_LICENSE=$LITELLM_LICENSE \ -e LANGFUSE_PROJECT1_PUBLIC=$LANGFUSE_PROJECT1_PUBLIC \ -e LANGFUSE_PROJECT2_PUBLIC=$LANGFUSE_PROJECT2_PUBLIC \ -e LANGFUSE_PROJECT1_SECRET=$LANGFUSE_PROJECT1_SECRET \ diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 1c10ef461..5138e9b61 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -23,7 +23,7 @@ from litellm import RateLimitError, Timeout, completion, completion_cost, embedd from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.llms.prompt_templates.factory import anthropic_messages_pt -# litellm.num_retries=3 +# litellm.num_retries = 3 litellm.cache = None litellm.success_callback = [] user_message = "Write a short poem about the sky" From 4b1e85f54e06bf8186b87c7872bdb3baaf921b23 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 18:54:10 -0700 Subject: [PATCH 076/124] fix(vertex_ai_anthropic.py): support pre-filling "{" for json mode --- litellm/llms/vertex_ai_anthropic.py | 144 +++++++++++++----- .../tests/test_amazing_vertex_completion.py | 39 +++-- litellm/types/utils.py | 5 + litellm/utils.py | 4 +- 4 files changed, 137 insertions(+), 55 deletions(-) diff --git a/litellm/llms/vertex_ai_anthropic.py b/litellm/llms/vertex_ai_anthropic.py index ee6653afc..6b39716f1 100644 --- a/litellm/llms/vertex_ai_anthropic.py +++ b/litellm/llms/vertex_ai_anthropic.py @@ -1,24 +1,32 @@ # What is this? ## Handler file for calling claude-3 on vertex ai -import os, types +import copy import json +import os +import time +import types +import uuid from enum import Enum -import requests, copy # type: ignore -import time, uuid -from typing import Callable, Optional, List -from litellm.utils import ModelResponse, Usage, CustomStreamWrapper -from litellm.litellm_core_utils.core_helpers import map_finish_reason +from typing import Any, Callable, List, Optional, Tuple + +import httpx # type: ignore +import requests # type: ignore + import litellm +from litellm.litellm_core_utils.core_helpers import map_finish_reason from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler +from litellm.types.utils import ResponseFormatChunk +from litellm.utils import CustomStreamWrapper, ModelResponse, Usage + from .prompt_templates.factory import ( - contains_tag, - prompt_factory, - custom_prompt, construct_tool_use_system_prompt, + contains_tag, + custom_prompt, extract_between_tags, parse_xml_params, + prompt_factory, + response_schema_prompt, ) -import httpx # type: ignore class VertexAIError(Exception): @@ -104,6 +112,7 @@ class VertexAIAnthropicConfig: "stop", "temperature", "top_p", + "response_format", ] def map_openai_params(self, non_default_params: dict, optional_params: dict): @@ -120,6 +129,8 @@ class VertexAIAnthropicConfig: optional_params["temperature"] = value if param == "top_p": optional_params["top_p"] = value + if param == "response_format" and "response_schema" in value: + optional_params["response_format"] = ResponseFormatChunk(**value) # type: ignore return optional_params @@ -129,7 +140,6 @@ class VertexAIAnthropicConfig: """ -# makes headers for API call def refresh_auth( credentials, ) -> str: # used when user passes in credentials as json string @@ -144,6 +154,40 @@ def refresh_auth( return credentials.token +def get_vertex_client( + client: Any, + vertex_project: Optional[str], + vertex_location: Optional[str], + vertex_credentials: Optional[str], +) -> Tuple[Any, Optional[str]]: + args = locals() + from litellm.llms.vertex_httpx import VertexLLM + + try: + from anthropic import AnthropicVertex + except Exception: + raise VertexAIError( + status_code=400, + message="""vertexai import failed please run `pip install -U google-cloud-aiplatform "anthropic[vertex]"`""", + ) + + access_token: Optional[str] = None + + if client is None: + _credentials, cred_project_id = VertexLLM().load_auth( + credentials=vertex_credentials, project_id=vertex_project + ) + vertex_ai_client = AnthropicVertex( + project_id=vertex_project or cred_project_id, + region=vertex_location or "us-central1", + access_token=_credentials.token, + ) + else: + vertex_ai_client = client + + return vertex_ai_client, access_token + + def completion( model: str, messages: list, @@ -151,10 +195,10 @@ def completion( print_verbose: Callable, encoding, logging_obj, + optional_params: dict, vertex_project=None, vertex_location=None, vertex_credentials=None, - optional_params=None, litellm_params=None, logger_fn=None, acompletion: bool = False, @@ -178,6 +222,13 @@ def completion( ) try: + vertex_ai_client, access_token = get_vertex_client( + client=client, + vertex_project=vertex_project, + vertex_location=vertex_location, + vertex_credentials=vertex_credentials, + ) + ## Load Config config = litellm.VertexAIAnthropicConfig.get_config() for k, v in config.items(): @@ -186,6 +237,7 @@ def completion( ## Format Prompt _is_function_call = False + _is_json_schema = False messages = copy.deepcopy(messages) optional_params = copy.deepcopy(optional_params) # Separate system prompt from rest of message @@ -200,6 +252,29 @@ def completion( messages.pop(idx) if len(system_prompt) > 0: optional_params["system"] = system_prompt + # Checks for 'response_schema' support - if passed in + if "response_format" in optional_params: + response_format_chunk = ResponseFormatChunk( + **optional_params["response_format"] # type: ignore + ) + supports_response_schema = litellm.supports_response_schema( + model=model, custom_llm_provider="vertex_ai" + ) + if ( + supports_response_schema is False + and response_format_chunk["type"] == "json_object" + and "response_schema" in response_format_chunk + ): + _is_json_schema = True + user_response_schema_message = response_schema_prompt( + model=model, + response_schema=response_format_chunk["response_schema"], + ) + messages.append( + {"role": "user", "content": user_response_schema_message} + ) + messages.append({"role": "assistant", "content": "{"}) + optional_params.pop("response_format") # Format rest of message according to anthropic guidelines try: messages = prompt_factory( @@ -233,32 +308,6 @@ def completion( print_verbose( f"VERTEX AI: vertex_project={vertex_project}; vertex_location={vertex_location}; vertex_credentials={vertex_credentials}" ) - access_token = None - if client is None: - if vertex_credentials is not None and isinstance(vertex_credentials, str): - import google.oauth2.service_account - - try: - json_obj = json.loads(vertex_credentials) - except json.JSONDecodeError: - json_obj = json.load(open(vertex_credentials)) - - creds = ( - google.oauth2.service_account.Credentials.from_service_account_info( - json_obj, - scopes=["https://www.googleapis.com/auth/cloud-platform"], - ) - ) - ### CHECK IF ACCESS - access_token = refresh_auth(credentials=creds) - - vertex_ai_client = AnthropicVertex( - project_id=vertex_project, - region=vertex_location, - access_token=access_token, - ) - else: - vertex_ai_client = client if acompletion == True: """ @@ -315,7 +364,16 @@ def completion( ) message = vertex_ai_client.messages.create(**data) # type: ignore - text_content = message.content[0].text + + ## LOGGING + logging_obj.post_call( + input=messages, + api_key="", + original_response=message, + additional_args={"complete_input_dict": data}, + ) + + text_content: str = message.content[0].text ## TOOL CALLING - OUTPUT PARSE if text_content is not None and contains_tag("invoke", text_content): function_name = extract_between_tags("tool_name", text_content)[0] @@ -339,7 +397,13 @@ def completion( ) model_response.choices[0].message = _message # type: ignore else: - model_response.choices[0].message.content = text_content # type: ignore + if ( + _is_json_schema + ): # follows https://github.com/anthropics/anthropic-cookbook/blob/main/misc/how_to_enable_json_mode.ipynb + json_response = "{" + text_content[: text_content.rfind("}") + 1] + model_response.choices[0].message.content = json_response # type: ignore + else: + model_response.choices[0].message.content = text_content # type: ignore model_response.choices[0].finish_reason = map_finish_reason(message.stop_reason) ## CALCULATING USAGE diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index ecad16fe6..505933fc2 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -993,10 +993,10 @@ def vertex_httpx_mock_post_invalid_schema_response(*args, **kwargs): @pytest.mark.parametrize( - "model, supports_response_schema", + "model, vertex_location, supports_response_schema", [ - ("vertex_ai_beta/gemini-1.5-pro-001", True), - ("vertex_ai_beta/gemini-1.5-flash", False), + ("vertex_ai_beta/gemini-1.5-pro-001", "us-central1", True), + ("vertex_ai_beta/gemini-1.5-flash", "us-central1", False), ], ) @pytest.mark.parametrize( @@ -1005,7 +1005,7 @@ def vertex_httpx_mock_post_invalid_schema_response(*args, **kwargs): ) @pytest.mark.asyncio async def test_gemini_pro_json_schema_args_sent_httpx( - model, supports_response_schema, invalid_response + model, supports_response_schema, vertex_location, invalid_response ): load_vertex_ai_credentials() os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" @@ -1015,17 +1015,27 @@ async def test_gemini_pro_json_schema_args_sent_httpx( messages = [{"role": "user", "content": "List 5 cookie recipes"}] from litellm.llms.custom_httpx.http_handler import HTTPHandler + # response_schema = { + # "type": "array", + # "items": { + # "type": "object", + # "properties": { + # "recipe_name": { + # "type": "string", + # }, + # }, + # "required": ["recipe_name"], + # }, + # } response_schema = { - "type": "array", - "items": { - "type": "object", - "properties": { - "recipe_name": { - "type": "string", - }, - }, - "required": ["recipe_name"], + "type": "object", + "properties": { + "recipe_name": {"type": "string"}, + "ingredients": {"type": "array", "items": {"type": "string"}}, + "prep_time": {"type": "number"}, + "difficulty": {"type": "string", "enum": ["easy", "medium", "hard"]}, }, + "required": ["recipe_name", "ingredients", "prep_time"], } client = HTTPHandler() @@ -1044,12 +1054,13 @@ async def test_gemini_pro_json_schema_args_sent_httpx( "type": "json_object", "response_schema": response_schema, }, + vertex_location=vertex_location, client=client, ) if invalid_response is True: pytest.fail("Expected this to fail") except litellm.JSONSchemaValidationError as e: - if invalid_response is False: + if invalid_response is False and "claude-3" not in model: pytest.fail("Expected this to pass. Got={}".format(e)) mock_call.assert_called_once() diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 51ce08671..d6b7bf744 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -995,3 +995,8 @@ class GenericImageParsingChunk(TypedDict): type: str media_type: str data: str + + +class ResponseFormatChunk(TypedDict, total=False): + type: Required[Literal["json_object", "text"]] + response_schema: dict diff --git a/litellm/utils.py b/litellm/utils.py index e75a5df73..877685416 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -1351,7 +1351,9 @@ def client(original_function): ).total_seconds() * 1000 # return response latency in ms like openai ### POST-CALL RULES ### - post_call_processing(original_response=result, model=model) + post_call_processing( + original_response=result, model=model, optional_params=kwargs + ) # [OPTIONAL] ADD TO CACHE if ( From 69d06cfbcd77bd9db53a2ec9ac5923bdf04263b1 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 18:58:44 -0700 Subject: [PATCH 077/124] fix(utils.py): fix passing additional param to post-call processing --- litellm/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/litellm/utils.py b/litellm/utils.py index e75a5df73..877685416 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -1351,7 +1351,9 @@ def client(original_function): ).total_seconds() * 1000 # return response latency in ms like openai ### POST-CALL RULES ### - post_call_processing(original_response=result, model=model) + post_call_processing( + original_response=result, model=model, optional_params=kwargs + ) # [OPTIONAL] ADD TO CACHE if ( From 7670c5bd13ff78f092ac75857c8d7d132eb3dd95 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 19:12:00 -0700 Subject: [PATCH 078/124] fix(utils.py): add 'enforce_validation' param --- litellm/tests/test_amazing_vertex_completion.py | 9 +++++++-- litellm/utils.py | 8 +++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index ecad16fe6..5788d331c 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -1003,9 +1003,13 @@ def vertex_httpx_mock_post_invalid_schema_response(*args, **kwargs): "invalid_response", [True, False], ) +@pytest.mark.parametrize( + "enforce_validation", + [True, False], +) @pytest.mark.asyncio async def test_gemini_pro_json_schema_args_sent_httpx( - model, supports_response_schema, invalid_response + model, supports_response_schema, invalid_response, enforce_validation ): load_vertex_ai_credentials() os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" @@ -1043,10 +1047,11 @@ async def test_gemini_pro_json_schema_args_sent_httpx( response_format={ "type": "json_object", "response_schema": response_schema, + "enforce_validation": enforce_validation, }, client=client, ) - if invalid_response is True: + if invalid_response is True and enforce_validation is True: pytest.fail("Expected this to fail") except litellm.JSONSchemaValidationError as e: if invalid_response is False: diff --git a/litellm/utils.py b/litellm/utils.py index 877685416..2598aa46b 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -621,8 +621,14 @@ def client(original_function): ], dict, ) + and "enforce_validation" + in optional_params["response_format"] + and optional_params["response_format"][ + "enforce_validation" + ] + is True ): - # schema given, json response expected + # schema given, json response expected, and validation enforced litellm.litellm_core_utils.json_validation_rule.validate_schema( schema=optional_params["response_format"][ "response_schema" From b78043f904eefe7fcc42d00e4cd029e1cd49a0be Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 19:14:19 -0700 Subject: [PATCH 079/124] test(test_amazing_vertex_completion.py): reduce vertex tests - quota exhaustion --- .../tests/test_amazing_vertex_completion.py | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index 5788d331c..3a48bcb6c 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -1111,48 +1111,6 @@ async def test_gemini_pro_httpx_custom_api_base(provider): assert "hello" in mock_call.call_args.kwargs["headers"] -@pytest.mark.parametrize("sync_mode", [True, False]) -@pytest.mark.parametrize("provider", ["vertex_ai_beta"]) # "vertex_ai", -@pytest.mark.asyncio -async def test_gemini_pro_httpx_custom_api_base_streaming_real_call( - provider, sync_mode -): - load_vertex_ai_credentials() - import random - - litellm.set_verbose = True - messages = [ - { - "role": "user", - "content": "Hey, how's it going?", - } - ] - - vertex_region = random.sample(["asia-southeast1", "us-central1"], k=1)[0] - if sync_mode is True: - response = completion( - model="vertex_ai_beta/gemini-1.5-flash", - messages=messages, - api_base="https://gateway.ai.cloudflare.com/v1/fa4cdcab1f32b95ca3b53fd36043d691/test/google-vertex-ai/v1/projects/adroit-crow-413218/locations/us-central1/publishers/google/models/gemini-1.5-flash", - stream=True, - vertex_region=vertex_region, - ) - - for chunk in response: - print(chunk) - else: - response = await litellm.acompletion( - model="vertex_ai_beta/gemini-1.5-flash", - messages=messages, - api_base="https://gateway.ai.cloudflare.com/v1/fa4cdcab1f32b95ca3b53fd36043d691/test/google-vertex-ai/v1/projects/adroit-crow-413218/locations/us-central1/publishers/google/models/gemini-1.5-flash", - stream=True, - vertex_region=vertex_region, - ) - - async for chunk in response: - print(chunk) - - @pytest.mark.skip(reason="exhausted vertex quota. need to refactor to mock the call") @pytest.mark.parametrize("sync_mode", [True]) @pytest.mark.parametrize("provider", ["vertex_ai"]) From dc0218d34c5ebeae57dca5212a3ebee3531ec07e Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 19:24:00 -0700 Subject: [PATCH 080/124] build(pyproject.toml): remove ijson dep. add jsonschema dep. --- litellm/llms/vertex_httpx.py | 1 - pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index 91a2b0276..9e361d3cc 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -12,7 +12,6 @@ from functools import partial from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union import httpx # type: ignore -import ijson import requests # type: ignore import litellm diff --git a/pyproject.toml b/pyproject.toml index 3926ba0bc..578c50cc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ jinja2 = "^3.1.2" aiohttp = "*" requests = "^2.31.0" pydantic = "^2.0.0" -ijson = "*" +jsonschema = "^4.22.0" uvicorn = {version = "^0.22.0", optional = true} gunicorn = {version = "^22.0.0", optional = true} diff --git a/requirements.txt b/requirements.txt index 00d3802da..e71ab450b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,5 +46,5 @@ aiohttp==3.9.0 # for network calls aioboto3==12.3.0 # for async sagemaker calls tenacity==8.2.3 # for retrying requests, when litellm.num_retries set pydantic==2.7.1 # proxy + openai req. -ijson==3.2.3 # for google ai studio streaming +jsonschema==4.22.0 # validating json schema #### \ No newline at end of file From b3944529e4c2744768597cdda46c2ab00d7ea703 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 19:25:16 -0700 Subject: [PATCH 081/124] ci(config.yml): add jsonschema to ci/cd --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5dfeedcaa..4cfb68f64 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,7 +66,7 @@ jobs: pip install "pydantic==2.7.1" pip install "diskcache==5.6.1" pip install "Pillow==10.3.0" - pip install "ijson==3.2.3" + pip install "jsonschema==4.22.0" - save_cache: paths: - ./venv @@ -128,7 +128,7 @@ jobs: pip install jinja2 pip install tokenizers pip install openai - pip install ijson + pip install jsonschema - run: name: Run tests command: | @@ -183,7 +183,7 @@ jobs: pip install numpydoc pip install prisma pip install fastapi - pip install ijson + pip install jsonschema pip install "httpx==0.24.1" pip install "gunicorn==21.2.0" pip install "anyio==3.7.1" From 25b1463920a9a6d6e6f451670a43e79142bbdbbd Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 19:26:21 -0700 Subject: [PATCH 082/124] style: trigger new build --- litellm/tests/test_jwt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/litellm/tests/test_jwt.py b/litellm/tests/test_jwt.py index 72d4d7b1b..e287946ae 100644 --- a/litellm/tests/test_jwt.py +++ b/litellm/tests/test_jwt.py @@ -61,7 +61,6 @@ async def test_token_single_public_key(): import jwt jwt_handler = JWTHandler() - backend_keys = { "keys": [ { From 46698ae01f642e1b0416f133581e21ff8bb43cfe Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 19:33:26 -0700 Subject: [PATCH 083/124] fix param mapping for bedrock claude --- litellm/llms/bedrock_httpx.py | 4 ++++ litellm/utils.py | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/litellm/llms/bedrock_httpx.py b/litellm/llms/bedrock_httpx.py index 3faaf40f1..8aefc63cf 100644 --- a/litellm/llms/bedrock_httpx.py +++ b/litellm/llms/bedrock_httpx.py @@ -64,6 +64,10 @@ BEDROCK_CONVERSE_MODELS = [ "anthropic.claude-3-opus-20240229-v1:0", "anthropic.claude-3-sonnet-20240229-v1:0", "anthropic.claude-3-haiku-20240307-v1:0", + "anthropic.claude-v2", + "anthropic.claude-v2:1", + "anthropic.claude-v1", + "anthropic.claude-instant-v1", ] iam_cache = DualCache() diff --git a/litellm/utils.py b/litellm/utils.py index f6e612e9c..08a5eb40d 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2868,12 +2868,7 @@ def get_optional_params( optional_params=optional_params, ) ) - else: - optional_params = litellm.AmazonAnthropicConfig().map_openai_params( - non_default_params=non_default_params, - optional_params=optional_params, - ) - else: # bedrock httpx route + elif model in litellm.BEDROCK_CONVERSE_MODELS: optional_params = litellm.AmazonConverseConfig().map_openai_params( model=model, non_default_params=non_default_params, @@ -2884,6 +2879,11 @@ def get_optional_params( else False ), ) + else: + optional_params = litellm.AmazonAnthropicConfig().map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + ) elif "amazon" in model: # amazon titan llms _check_valid_arg(supported_params=supported_params) # see https://us-west-2.console.aws.amazon.com/bedrock/home?region=us-west-2#/providers?model=titan-large From bad49a270dcaf2a4b3c28eb72c6ea8ae66037993 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 19:41:05 -0700 Subject: [PATCH 084/124] fix test test_provisioned_throughput --- litellm/tests/test_bedrock_completion.py | 96 ++++++------------------ 1 file changed, 21 insertions(+), 75 deletions(-) diff --git a/litellm/tests/test_bedrock_completion.py b/litellm/tests/test_bedrock_completion.py index 7e61b9a14..6e39c30b3 100644 --- a/litellm/tests/test_bedrock_completion.py +++ b/litellm/tests/test_bedrock_completion.py @@ -25,9 +25,8 @@ from litellm import ( completion_cost, embedding, ) -from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler - from litellm.llms.bedrock_httpx import BedrockLLM +from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler # litellm.num_retries = 3 litellm.cache = None @@ -218,6 +217,7 @@ def test_completion_bedrock_claude_sts_client_auth(): except Exception as e: pytest.fail(f"Error occurred: {e}") + @pytest.fixture() def bedrock_session_token_creds(): print("\ncalling oidc auto to get aws_session_token credentials") @@ -231,15 +231,17 @@ def bedrock_session_token_creds(): # For local testing creds = bllm.get_credentials( aws_region_name=aws_region_name, - aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'], - aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'], - aws_session_token=aws_session_token + aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"], + aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"], + aws_session_token=aws_session_token, ) else: # For circle-ci testing # aws_role_name = os.environ["AWS_TEMP_ROLE_NAME"] # TODO: This is using ai.moda's IAM role, we should use LiteLLM's IAM role eventually - aws_role_name = "arn:aws:iam::335785316107:role/litellm-github-unit-tests-circleci" + aws_role_name = ( + "arn:aws:iam::335785316107:role/litellm-github-unit-tests-circleci" + ) aws_web_identity_token = "oidc/circleci_v2/" creds = bllm.get_credentials( @@ -250,8 +252,10 @@ def bedrock_session_token_creds(): ) return creds + def process_stream_response(res, messages): import types + if isinstance(res, litellm.utils.CustomStreamWrapper): chunks = [] for part in res: @@ -261,17 +265,19 @@ def process_stream_response(res, messages): res = litellm.stream_chunk_builder(chunks, messages=messages) else: raise ValueError("Response object is not a streaming response") - + return res + @pytest.mark.skipif( os.environ.get("CIRCLE_OIDC_TOKEN_V2") is None, reason="Cannot run without being in CircleCI Runner", ) def test_completion_bedrock_claude_aws_session_token(bedrock_session_token_creds): print("\ncalling bedrock claude with aws_session_token auth") - + import os + aws_region_name = os.environ["AWS_REGION_NAME"] aws_access_key_id = bedrock_session_token_creds.access_key aws_secret_access_key = bedrock_session_token_creds.secret_key @@ -334,7 +340,7 @@ def test_completion_bedrock_claude_aws_session_token(bedrock_session_token_creds aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, aws_session_token=aws_session_token, - stream=True + stream=True, ) response_4 = process_stream_response(response_4, messages) print(response_4) @@ -346,14 +352,16 @@ def test_completion_bedrock_claude_aws_session_token(bedrock_session_token_creds except Exception as e: pytest.fail(f"Error occurred: {e}") + @pytest.mark.skipif( os.environ.get("CIRCLE_OIDC_TOKEN_V2") is None, reason="Cannot run without being in CircleCI Runner", ) def test_completion_bedrock_claude_aws_bedrock_client(bedrock_session_token_creds): print("\ncalling bedrock claude with aws_session_token auth") - + import os + import boto3 from botocore.client import Config @@ -368,12 +376,9 @@ def test_completion_bedrock_claude_aws_bedrock_client(bedrock_session_token_cred aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, aws_session_token=aws_session_token, - config= Config( - read_timeout=600 - ) + config=Config(read_timeout=600), ) - try: litellm.set_verbose = True @@ -407,9 +412,7 @@ def test_completion_bedrock_claude_aws_bedrock_client(bedrock_session_token_cred aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, aws_session_token=aws_session_token, - config= Config( - read_timeout=600 - ) + config=Config(read_timeout=600), ) response_3 = completion( @@ -430,21 +433,19 @@ def test_completion_bedrock_claude_aws_bedrock_client(bedrock_session_token_cred max_tokens=6, temperature=0.3, aws_bedrock_client=aws_bedrock_client_east, - stream=True + stream=True, ) response_4 = process_stream_response(response_4, messages) print(response_4) assert len(response_4.choices) > 0 assert len(response_4.choices[0].message.content) > 0 - except RateLimitError: pass except Exception as e: pytest.fail(f"Error occurred: {e}") - # test_completion_bedrock_claude_sts_client_auth() @@ -717,61 +718,6 @@ def test_completion_claude_3_base64(): pytest.fail(f"An exception occurred - {str(e)}") -def test_provisioned_throughput(): - try: - litellm.set_verbose = True - import io - import json - - import botocore - import botocore.session - from botocore.stub import Stubber - - bedrock_client = botocore.session.get_session().create_client( - "bedrock-runtime", region_name="us-east-1" - ) - - expected_params = { - "accept": "application/json", - "body": '{"prompt": "\\n\\nHuman: Hello, how are you?\\n\\nAssistant: ", ' - '"max_tokens_to_sample": 256}', - "contentType": "application/json", - "modelId": "provisioned-model-arn", - } - response_from_bedrock = { - "body": io.StringIO( - json.dumps( - { - "completion": " Here is a short poem about the sky:", - "stop_reason": "max_tokens", - "stop": None, - } - ) - ), - "contentType": "contentType", - "ResponseMetadata": {"HTTPStatusCode": 200}, - } - - with Stubber(bedrock_client) as stubber: - stubber.add_response( - "invoke_model", - service_response=response_from_bedrock, - expected_params=expected_params, - ) - response = litellm.completion( - model="bedrock/anthropic.claude-instant-v1", - model_id="provisioned-model-arn", - messages=[{"content": "Hello, how are you?", "role": "user"}], - aws_bedrock_client=bedrock_client, - ) - print("response stubbed", response) - except Exception as e: - pytest.fail(f"Error occurred: {e}") - - -# test_provisioned_throughput() - - def test_completion_bedrock_mistral_completion_auth(): print("calling bedrock mistral completion params auth") import os From 03dbc29c853c9be069452f449584f73a627d5224 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 19:51:07 -0700 Subject: [PATCH 085/124] fix test_completion_replicate_llama3_streaming --- litellm/tests/test_streaming.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/litellm/tests/test_streaming.py b/litellm/tests/test_streaming.py index fa9e49f87..1f1b253a0 100644 --- a/litellm/tests/test_streaming.py +++ b/litellm/tests/test_streaming.py @@ -1226,6 +1226,7 @@ async def test_completion_replicate_llama3_streaming(sync_mode): messages=messages, max_tokens=10, # type: ignore stream=True, + num_retries=3, ) complete_response = "" # Add any assertions here to check the response @@ -1247,6 +1248,7 @@ async def test_completion_replicate_llama3_streaming(sync_mode): messages=messages, max_tokens=100, # type: ignore stream=True, + num_retries=3, ) complete_response = "" # Add any assertions here to check the response From 3f9859e638e4ea92a22293f0d8669854bb24d9b4 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 19:57:41 -0700 Subject: [PATCH 086/124] fix(utils.py): add 'enforce_validation' param --- .../tests/test_amazing_vertex_completion.py | 41 +++++++++---------- litellm/utils.py | 8 +++- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index 505933fc2..807fb3131 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -1003,9 +1003,17 @@ def vertex_httpx_mock_post_invalid_schema_response(*args, **kwargs): "invalid_response", [True, False], ) +@pytest.mark.parametrize( + "enforce_validation", + [True, False], +) @pytest.mark.asyncio async def test_gemini_pro_json_schema_args_sent_httpx( - model, supports_response_schema, vertex_location, invalid_response + model, + supports_response_schema, + vertex_location, + invalid_response, + enforce_validation, ): load_vertex_ai_credentials() os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" @@ -1015,27 +1023,17 @@ async def test_gemini_pro_json_schema_args_sent_httpx( messages = [{"role": "user", "content": "List 5 cookie recipes"}] from litellm.llms.custom_httpx.http_handler import HTTPHandler - # response_schema = { - # "type": "array", - # "items": { - # "type": "object", - # "properties": { - # "recipe_name": { - # "type": "string", - # }, - # }, - # "required": ["recipe_name"], - # }, - # } response_schema = { - "type": "object", - "properties": { - "recipe_name": {"type": "string"}, - "ingredients": {"type": "array", "items": {"type": "string"}}, - "prep_time": {"type": "number"}, - "difficulty": {"type": "string", "enum": ["easy", "medium", "hard"]}, + "type": "array", + "items": { + "type": "object", + "properties": { + "recipe_name": { + "type": "string", + }, + }, + "required": ["recipe_name"], }, - "required": ["recipe_name", "ingredients", "prep_time"], } client = HTTPHandler() @@ -1053,11 +1051,12 @@ async def test_gemini_pro_json_schema_args_sent_httpx( response_format={ "type": "json_object", "response_schema": response_schema, + "enforce_validation": enforce_validation, }, vertex_location=vertex_location, client=client, ) - if invalid_response is True: + if invalid_response is True and enforce_validation is True: pytest.fail("Expected this to fail") except litellm.JSONSchemaValidationError as e: if invalid_response is False and "claude-3" not in model: diff --git a/litellm/utils.py b/litellm/utils.py index 877685416..2598aa46b 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -621,8 +621,14 @@ def client(original_function): ], dict, ) + and "enforce_validation" + in optional_params["response_format"] + and optional_params["response_format"][ + "enforce_validation" + ] + is True ): - # schema given, json response expected + # schema given, json response expected, and validation enforced litellm.litellm_core_utils.json_validation_rule.validate_schema( schema=optional_params["response_format"][ "response_schema" From 5df940c54d4e96a2443ce81ff82cfc03a59a5383 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 19:14:19 -0700 Subject: [PATCH 087/124] test(test_amazing_vertex_completion.py): reduce vertex tests - quota exhaustion --- .../tests/test_amazing_vertex_completion.py | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index 807fb3131..c4705325b 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -1116,48 +1116,6 @@ async def test_gemini_pro_httpx_custom_api_base(provider): assert "hello" in mock_call.call_args.kwargs["headers"] -@pytest.mark.parametrize("sync_mode", [True, False]) -@pytest.mark.parametrize("provider", ["vertex_ai_beta"]) # "vertex_ai", -@pytest.mark.asyncio -async def test_gemini_pro_httpx_custom_api_base_streaming_real_call( - provider, sync_mode -): - load_vertex_ai_credentials() - import random - - litellm.set_verbose = True - messages = [ - { - "role": "user", - "content": "Hey, how's it going?", - } - ] - - vertex_region = random.sample(["asia-southeast1", "us-central1"], k=1)[0] - if sync_mode is True: - response = completion( - model="vertex_ai_beta/gemini-1.5-flash", - messages=messages, - api_base="https://gateway.ai.cloudflare.com/v1/fa4cdcab1f32b95ca3b53fd36043d691/test/google-vertex-ai/v1/projects/adroit-crow-413218/locations/us-central1/publishers/google/models/gemini-1.5-flash", - stream=True, - vertex_region=vertex_region, - ) - - for chunk in response: - print(chunk) - else: - response = await litellm.acompletion( - model="vertex_ai_beta/gemini-1.5-flash", - messages=messages, - api_base="https://gateway.ai.cloudflare.com/v1/fa4cdcab1f32b95ca3b53fd36043d691/test/google-vertex-ai/v1/projects/adroit-crow-413218/locations/us-central1/publishers/google/models/gemini-1.5-flash", - stream=True, - vertex_region=vertex_region, - ) - - async for chunk in response: - print(chunk) - - @pytest.mark.skip(reason="exhausted vertex quota. need to refactor to mock the call") @pytest.mark.parametrize("sync_mode", [True]) @pytest.mark.parametrize("provider", ["vertex_ai"]) From 824e0c5e39c013f82c91c8991cc99c56df49c9f7 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 19:24:00 -0700 Subject: [PATCH 088/124] build(pyproject.toml): remove ijson dep. add jsonschema dep. --- litellm/llms/vertex_httpx.py | 1 - pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index 91a2b0276..9e361d3cc 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -12,7 +12,6 @@ from functools import partial from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union import httpx # type: ignore -import ijson import requests # type: ignore import litellm diff --git a/pyproject.toml b/pyproject.toml index 3926ba0bc..578c50cc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ jinja2 = "^3.1.2" aiohttp = "*" requests = "^2.31.0" pydantic = "^2.0.0" -ijson = "*" +jsonschema = "^4.22.0" uvicorn = {version = "^0.22.0", optional = true} gunicorn = {version = "^22.0.0", optional = true} diff --git a/requirements.txt b/requirements.txt index 00d3802da..e71ab450b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,5 +46,5 @@ aiohttp==3.9.0 # for network calls aioboto3==12.3.0 # for async sagemaker calls tenacity==8.2.3 # for retrying requests, when litellm.num_retries set pydantic==2.7.1 # proxy + openai req. -ijson==3.2.3 # for google ai studio streaming +jsonschema==4.22.0 # validating json schema #### \ No newline at end of file From 1ec0c71a14f53aafc5d156f8d7efa23b9a18396e Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 19:25:16 -0700 Subject: [PATCH 089/124] ci(config.yml): add jsonschema to ci/cd --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5dfeedcaa..4cfb68f64 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,7 +66,7 @@ jobs: pip install "pydantic==2.7.1" pip install "diskcache==5.6.1" pip install "Pillow==10.3.0" - pip install "ijson==3.2.3" + pip install "jsonschema==4.22.0" - save_cache: paths: - ./venv @@ -128,7 +128,7 @@ jobs: pip install jinja2 pip install tokenizers pip install openai - pip install ijson + pip install jsonschema - run: name: Run tests command: | @@ -183,7 +183,7 @@ jobs: pip install numpydoc pip install prisma pip install fastapi - pip install ijson + pip install jsonschema pip install "httpx==0.24.1" pip install "gunicorn==21.2.0" pip install "anyio==3.7.1" From 2ab6f2be633fd632c1bb97b4693ea7758e3076f5 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 29 Jun 2024 19:26:21 -0700 Subject: [PATCH 090/124] style: trigger new build --- litellm/tests/test_jwt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/litellm/tests/test_jwt.py b/litellm/tests/test_jwt.py index 72d4d7b1b..e287946ae 100644 --- a/litellm/tests/test_jwt.py +++ b/litellm/tests/test_jwt.py @@ -61,7 +61,6 @@ async def test_token_single_public_key(): import jwt jwt_handler = JWTHandler() - backend_keys = { "keys": [ { From bde34c79d72f882f7ff67c8bfe2df346dde9cf89 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 20:03:12 -0700 Subject: [PATCH 091/124] =?UTF-8?q?bump:=20version=201.41.1=20=E2=86=92=20?= =?UTF-8?q?1.41.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3926ba0bc..95aa18c08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.41.1" +version = "1.41.2" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -90,7 +90,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.41.1" +version = "1.41.2" version_files = [ "pyproject.toml:^version" ] From eac42bb47363dc4681d7c7c81fd8a9f5594fee50 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 20:13:32 -0700 Subject: [PATCH 092/124] ci/cd run again --- litellm/tests/test_completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 5138e9b61..1c10ef461 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -23,7 +23,7 @@ from litellm import RateLimitError, Timeout, completion, completion_cost, embedd from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.llms.prompt_templates.factory import anthropic_messages_pt -# litellm.num_retries = 3 +# litellm.num_retries=3 litellm.cache = None litellm.success_callback = [] user_message = "Write a short poem about the sky" From d25b079caf3c7ecb11d7c1ea498bd3368a9906c2 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 20:54:22 -0700 Subject: [PATCH 093/124] fix img gen test --- litellm/tests/test_router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/tests/test_router.py b/litellm/tests/test_router.py index 5f22e88a1..7c59611d7 100644 --- a/litellm/tests/test_router.py +++ b/litellm/tests/test_router.py @@ -1081,7 +1081,7 @@ async def test_aimg_gen_on_router(): { "model_name": "dall-e-3", "litellm_params": { - "model": "openai/dall-e-3", + "model": "dall-e-3", }, }, { @@ -1137,7 +1137,7 @@ def test_img_gen_on_router(): try: model_list = [ { - "model_name": "openai/dall-e-3", + "model_name": "dall-e-3", "litellm_params": { "model": "dall-e-3", }, From 8b1b79e15b42756df483400433d3aa358b726f7c Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 29 Jun 2024 20:56:29 -0700 Subject: [PATCH 094/124] ci/cd run again --- litellm/tests/test_completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 1c10ef461..5138e9b61 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -23,7 +23,7 @@ from litellm import RateLimitError, Timeout, completion, completion_cost, embedd from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.llms.prompt_templates.factory import anthropic_messages_pt -# litellm.num_retries=3 +# litellm.num_retries = 3 litellm.cache = None litellm.success_callback = [] user_message = "Write a short poem about the sky" From 46fe08920409816e631956cf0ad88021d132ca6a Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sun, 30 Jun 2024 09:14:43 -0700 Subject: [PATCH 095/124] docs(vertex.md): add vertex json schema details to docs --- docs/my-website/docs/providers/vertex.md | 139 ++++++++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index 664a99e0d..ce9e73bab 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -123,7 +123,11 @@ print(completion(**data)) ### **JSON Schema** -From v`1.40.1+` LiteLLM supports sending `response_schema` as a param for Gemini-Pro-1.5 on Vertex AI. +From v`1.40.1+` LiteLLM supports sending `response_schema` as a param for Gemini-1.5-Pro on Vertex AI. For other models (e.g. `gemini-1.5-flash` or `claude-3-5-sonnet`), LiteLLM adds the schema to the message list with a user-controlled prompt. + +**Response Schema** + + ```python from litellm import completion @@ -162,6 +166,139 @@ completion( print(json.loads(completion.choices[0].message.content)) ``` + + + +1. Add model to config.yaml +```yaml +model_list: + - model_name: gemini-pro + litellm_params: + model: vertex_ai_beta/gemini-1.5-pro + vertex_project: "project-id" + vertex_location: "us-central1" + vertex_credentials: "/path/to/service_account.json" # [OPTIONAL] Do this OR `!gcloud auth application-default login` - run this to add vertex credentials to your env +``` + +2. Start Proxy + +``` +$ litellm --config /path/to/config.yaml +``` + +3. Make Request! + +```bash +curl -X POST 'http://0.0.0.0:4000/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-D '{ + "model": "gemini-pro", + "messages": [ + {"role": "user", "content": "List 5 popular cookie recipes."} + ], + "response_format": {"type": "json_object", "response_schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "recipe_name": { + "type": "string", + }, + }, + "required": ["recipe_name"], + }, + }} +} +' +``` + + + + +**Validate Schema** + +To validate the response_schema, set `enforce_validation: true`. + + + + +```python +from litellm import completion, JSONSchemaValidationError +try: + completion( + model="vertex_ai_beta/gemini-1.5-pro", + messages=messages, + response_format={ + "type": "json_object", + "response_schema": response_schema, + "enforce_validation": true # 👈 KEY CHANGE + } + ) +except JSONSchemaValidationError as e: + print("Raw Response: {}".format(e.raw_response)) + raise e +``` + + + +1. Add model to config.yaml +```yaml +model_list: + - model_name: gemini-pro + litellm_params: + model: vertex_ai_beta/gemini-1.5-pro + vertex_project: "project-id" + vertex_location: "us-central1" + vertex_credentials: "/path/to/service_account.json" # [OPTIONAL] Do this OR `!gcloud auth application-default login` - run this to add vertex credentials to your env +``` + +2. Start Proxy + +``` +$ litellm --config /path/to/config.yaml +``` + +3. Make Request! + +```bash +curl -X POST 'http://0.0.0.0:4000/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-D '{ + "model": "gemini-pro", + "messages": [ + {"role": "user", "content": "List 5 popular cookie recipes."} + ], + "response_format": {"type": "json_object", "response_schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "recipe_name": { + "type": "string", + }, + }, + "required": ["recipe_name"], + }, + }, + "enforce_validation": true + } +} +' +``` + + + + +LiteLLM will validate the response against the schema, and raise a `JSONSchemaValidationError` if the response does not match the schema. + +JSONSchemaValidationError inherits from `openai.APIError` + +Access the raw response with `e.raw_response` + +**Add to prompt yourself** + ```python from litellm import completion From afb615b5c910795abf86782411c4afb87b920ca4 Mon Sep 17 00:00:00 2001 From: Saurav Panda Date: Mon, 1 Jul 2024 00:41:51 -0400 Subject: [PATCH 096/124] Update azure_ai.md Invalid key name for azure openai --- docs/my-website/docs/providers/azure_ai.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/my-website/docs/providers/azure_ai.md b/docs/my-website/docs/providers/azure_ai.md index 87b8041ef..26c965a0c 100644 --- a/docs/my-website/docs/providers/azure_ai.md +++ b/docs/my-website/docs/providers/azure_ai.md @@ -14,7 +14,7 @@ LiteLLM supports all models on Azure AI Studio ### ENV VAR ```python import os -os.environ["AZURE_API_API_KEY"] = "" +os.environ["AZURE_AI_API_KEY"] = "" os.environ["AZURE_AI_API_BASE"] = "" ``` @@ -24,7 +24,7 @@ os.environ["AZURE_AI_API_BASE"] = "" from litellm import completion import os ## set ENV variables -os.environ["AZURE_API_API_KEY"] = "azure ai key" +os.environ["AZURE_AI_API_KEY"] = "azure ai key" os.environ["AZURE_AI_API_BASE"] = "azure ai base url" # e.g.: https://Mistral-large-dfgfj-serverless.eastus2.inference.ai.azure.com/ # predibase llama-3 call From d17816dd209e5316ac16e1c04de111da7f308004 Mon Sep 17 00:00:00 2001 From: Indigo <119925802+ubirdio@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:54:02 +1000 Subject: [PATCH 097/124] Fix usage of parameter-based credentials when using vertex_ai_beta route --- litellm/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/utils.py b/litellm/utils.py index 4b80d203b..7302bea75 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2406,7 +2406,7 @@ def get_optional_params( elif k == "hf_model_name" and custom_llm_provider != "sagemaker": continue elif ( - k.startswith("vertex_") and custom_llm_provider != "vertex_ai" + k.startswith("vertex_") and custom_llm_provider != "vertex_ai" and custom_llm_provider != "vertex_ai_beta" ): # allow dynamically setting vertex ai init logic continue passed_params[k] = v From 9a74364baa035aa3d05c149e7633e8c101a084a2 Mon Sep 17 00:00:00 2001 From: David Manouchehri Date: Mon, 1 Jul 2024 14:17:20 +0000 Subject: [PATCH 098/124] fix(bedrock_httpx.py): Add anthropic.claude-3-5-sonnet-20240620-v1:0 to the converse list. --- litellm/llms/bedrock_httpx.py | 1 + 1 file changed, 1 insertion(+) diff --git a/litellm/llms/bedrock_httpx.py b/litellm/llms/bedrock_httpx.py index 8aefc63cf..7b4628a76 100644 --- a/litellm/llms/bedrock_httpx.py +++ b/litellm/llms/bedrock_httpx.py @@ -61,6 +61,7 @@ from .prompt_templates.factory import ( ) BEDROCK_CONVERSE_MODELS = [ + "anthropic.claude-3-5-sonnet-20240620-v1:0", "anthropic.claude-3-opus-20240229-v1:0", "anthropic.claude-3-sonnet-20240229-v1:0", "anthropic.claude-3-haiku-20240307-v1:0", From 19464b2521cf330d729cc1f6d0f71e5cf1e5ea32 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 07:36:06 -0700 Subject: [PATCH 099/124] fix openai compatible doc quote --- docs/my-website/docs/providers/openai_compatible.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/docs/providers/openai_compatible.md b/docs/my-website/docs/providers/openai_compatible.md index f02149024..33ab8fb41 100644 --- a/docs/my-website/docs/providers/openai_compatible.md +++ b/docs/my-website/docs/providers/openai_compatible.md @@ -18,7 +18,7 @@ import litellm import os response = litellm.completion( - model="openai/mistral, # add `openai/` prefix to model so litellm knows to route to OpenAI + model="openai/mistral", # add `openai/` prefix to model so litellm knows to route to OpenAI api_key="sk-1234", # api key to your openai compatible endpoint api_base="http://0.0.0.0:4000", # set API Base of your Custom OpenAI Endpoint messages=[ From be3c98bc162bdeea8aaae2a889058203f28b40e8 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Jul 2024 08:14:10 -0700 Subject: [PATCH 100/124] fix(main.py): copy messages - prevent modifying user input Fixes https://github.com/BerriAI/litellm/discussions/4489 --- litellm/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/litellm/main.py b/litellm/main.py index 10bcbe9e3..48d430d52 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -676,6 +676,8 @@ def completion( client = kwargs.get("client", None) ### Admin Controls ### no_log = kwargs.get("no-log", False) + ### COPY MESSAGES ### - related issue https://github.com/BerriAI/litellm/discussions/4489 + messages = deepcopy(messages) ######## end of unpacking kwargs ########### openai_params = [ "functions", From 5e521bd36ec4ef56dfaea922fd654e863f36a6d9 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Jul 2024 08:23:46 -0700 Subject: [PATCH 101/124] fix(utils.py): fix openrouter params Fixes https://github.com/BerriAI/litellm/issues/4488 --- litellm/utils.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/litellm/utils.py b/litellm/utils.py index 7302bea75..66df7c503 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -3842,23 +3842,18 @@ def get_supported_openai_params( return litellm.AzureOpenAIConfig().get_supported_openai_params() elif custom_llm_provider == "openrouter": return [ - "functions", - "function_call", "temperature", "top_p", - "n", - "stream", - "stop", - "max_tokens", - "presence_penalty", "frequency_penalty", - "logit_bias", - "user", - "response_format", + "presence_penalty", + "repetition_penalty", "seed", - "tools", - "tool_choice", - "max_retries", + "max_tokens", + "logit_bias", + "logprobs", + "top_logprobs", + "response_format", + "stop", ] elif custom_llm_provider == "mistral" or custom_llm_provider == "codestral": # mistal and codestral api have the exact same params From 4c95782f746b62a69f6d81a51c9c275fdfd096fe Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Jul 2024 08:25:29 -0700 Subject: [PATCH 102/124] fix(utils.py): fix openrouter tool calling params --- litellm/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/litellm/utils.py b/litellm/utils.py index 66df7c503..103f854b6 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2406,7 +2406,9 @@ def get_optional_params( elif k == "hf_model_name" and custom_llm_provider != "sagemaker": continue elif ( - k.startswith("vertex_") and custom_llm_provider != "vertex_ai" and custom_llm_provider != "vertex_ai_beta" + k.startswith("vertex_") + and custom_llm_provider != "vertex_ai" + and custom_llm_provider != "vertex_ai_beta" ): # allow dynamically setting vertex ai init logic continue passed_params[k] = v @@ -3871,6 +3873,10 @@ def get_supported_openai_params( "top_p", "stop", "seed", + "tools", + "tool_choice", + "functions", + "function_call", ] elif custom_llm_provider == "huggingface": return litellm.HuggingfaceConfig().get_supported_openai_params() From a389f6bff4db6b0c331960642cb143241fc86df9 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Jul 2024 12:48:56 -0700 Subject: [PATCH 103/124] fix(core_helpers.py): map vertex ai 'RECITATION' finish reason to 'content_filter' Fixes https://github.com/BerriAI/litellm/issues/4491 --- litellm/litellm_core_utils/core_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/core_helpers.py b/litellm/litellm_core_utils/core_helpers.py index a325a6885..d8d551048 100644 --- a/litellm/litellm_core_utils/core_helpers.py +++ b/litellm/litellm_core_utils/core_helpers.py @@ -26,7 +26,7 @@ def map_finish_reason( finish_reason == "FINISH_REASON_UNSPECIFIED" or finish_reason == "STOP" ): # vertex ai - got from running `print(dir(response_obj.candidates[0].finish_reason))`: ['FINISH_REASON_UNSPECIFIED', 'MAX_TOKENS', 'OTHER', 'RECITATION', 'SAFETY', 'STOP',] return "stop" - elif finish_reason == "SAFETY": # vertex ai + elif finish_reason == "SAFETY" or finish_reason == "RECITATION": # vertex ai return "content_filter" elif finish_reason == "STOP": # vertex ai return "stop" From 223494a0e006311576a131a5f56548c2bcaf141c Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Jul 2024 13:46:20 -0700 Subject: [PATCH 104/124] fix(aws_secret_manager.py): accept 'aws_kms' being in the key name --- litellm/proxy/secret_managers/aws_secret_manager.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/secret_managers/aws_secret_manager.py b/litellm/proxy/secret_managers/aws_secret_manager.py index b737640b3..8895717c6 100644 --- a/litellm/proxy/secret_managers/aws_secret_manager.py +++ b/litellm/proxy/secret_managers/aws_secret_manager.py @@ -11,6 +11,7 @@ Requires: import ast import base64 import os +import re from typing import Any, Dict, Optional import litellm @@ -145,9 +146,14 @@ def decrypt_env_var() -> Dict[str, Any]: # iterate through env - for `aws_kms/` new_values = {} for k, v in os.environ.items(): - if v is not None and isinstance(v, str) and v.startswith("aws_kms/"): + if ( + k is not None + and isinstance(k, str) + and k.lower().startswith("litellm_secret_aws_kms") + ) or (v is not None and isinstance(v, str) and v.startswith("aws_kms/")): decrypted_value = aws_kms.decrypt_value(secret_name=k) # reset env var + k = re.sub("litellm_secret_aws_kms", "", k, flags=re.IGNORECASE) new_values[k] = decrypted_value return new_values From bfab76a810ae1d64588bc68522d9eec799833b4b Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Jul 2024 14:11:45 -0700 Subject: [PATCH 105/124] docs(configs.md): add wildcard model name to docs --- docs/my-website/docs/proxy/configs.md | 48 ++++++++++++++++++++++++ litellm/proxy/_new_secret_config.yaml | 54 +-------------------------- 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/docs/my-website/docs/proxy/configs.md b/docs/my-website/docs/proxy/configs.md index 3ab644855..00457dbc4 100644 --- a/docs/my-website/docs/proxy/configs.md +++ b/docs/my-website/docs/proxy/configs.md @@ -277,6 +277,54 @@ curl --location 'http://0.0.0.0:4000/v1/model/info' \ --data '' ``` +## Wildcard Model Name (Add ALL MODELS from env) + +Dynamically call any model from any given provider without the need to predefine it in the config YAML file. As long as the relevant keys are in the environment (see [providers list](../providers/)), LiteLLM will make the call correctly. + + + +1. Setup config.yaml +``` +model_list: + - model_name: "*" # all requests where model not in your config go to this deployment + litellm_params: + model: "openai/*" # passes our validation check that a real provider is given +``` + +2. Start LiteLLM proxy + +``` +litellm --config /path/to/config.yaml +``` + +3. Try claude 3-5 sonnet from anthropic + +```bash +curl -X POST 'http://0.0.0.0:4000/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-D '{ + "model": "claude-3-5-sonnet-20240620", + "messages": [ + {"role": "user", "content": "Hey, how'\''s it going?"}, + { + "role": "assistant", + "content": "I'\''m doing well. Would like to hear the rest of the story?" + }, + {"role": "user", "content": "Na"}, + { + "role": "assistant", + "content": "No problem, is there anything else i can help you with today?" + }, + { + "role": "user", + "content": "I think you'\''re getting cut off sometimes" + } + ] +} +' +``` + ## Load Balancing :::info diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 938e74b5e..2693ce3d3 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -1,54 +1,4 @@ -# model_list: -# - model_name: my-fake-model -# litellm_params: -# model: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 -# api_key: my-fake-key -# aws_bedrock_runtime_endpoint: http://127.0.0.1:8000 -# mock_response: "Hello world 1" -# model_info: -# max_input_tokens: 0 # trigger context window fallback -# - model_name: my-fake-model -# litellm_params: -# model: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 -# api_key: my-fake-key -# aws_bedrock_runtime_endpoint: http://127.0.0.1:8000 -# mock_response: "Hello world 2" -# model_info: -# max_input_tokens: 0 - -# router_settings: -# enable_pre_call_checks: True - - -# litellm_settings: -# failure_callback: ["langfuse"] - model_list: - - model_name: summarize + - model_name: "*" # all requests where model not in your config go to this deployment litellm_params: - model: openai/gpt-4o - rpm: 10000 - tpm: 12000000 - api_key: os.environ/OPENAI_API_KEY - mock_response: Hello world 1 - - - model_name: summarize-l - litellm_params: - model: claude-3-5-sonnet-20240620 - rpm: 4000 - tpm: 400000 - api_key: os.environ/ANTHROPIC_API_KEY - mock_response: Hello world 2 - -litellm_settings: - num_retries: 3 - request_timeout: 120 - allowed_fails: 3 - # fallbacks: [{"summarize": ["summarize-l", "summarize-xl"]}, {"summarize-l": ["summarize-xl"]}] - # context_window_fallbacks: [{"summarize": ["summarize-l", "summarize-xl"]}, {"summarize-l": ["summarize-xl"]}] - - - -router_settings: - routing_strategy: simple-shuffle - enable_pre_call_checks: true. + model: "openai/*" From ea74e0181310679f4bfc1d30cbbab0d6a257b90d Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Jul 2024 15:03:10 -0700 Subject: [PATCH 106/124] fix(router.py): disable cooldowns allow admin to disable model cooldowns --- litellm/main.py | 9 +++ litellm/proxy/_new_secret_config.yaml | 5 +- litellm/router.py | 87 +++++++++++++++++++++++---- 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/litellm/main.py b/litellm/main.py index 48d430d52..c48c242ce 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -476,6 +476,15 @@ def mock_completion( model=model, # type: ignore request=httpx.Request(method="POST", url="https://api.openai.com/v1/"), ) + elif ( + isinstance(mock_response, str) and mock_response == "litellm.RateLimitError" + ): + raise litellm.RateLimitError( + message="this is a mock rate limit error", + status_code=getattr(mock_response, "status_code", 429), # type: ignore + llm_provider=getattr(mock_response, "llm_provider", custom_llm_provider or "openai"), # type: ignore + model=model, + ) time_delay = kwargs.get("mock_delay", None) if time_delay is not None: time.sleep(time_delay) diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 2693ce3d3..c62fc9944 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -1,4 +1,5 @@ model_list: - - model_name: "*" # all requests where model not in your config go to this deployment + - model_name: claude-3-5-sonnet # all requests where model not in your config go to this deployment litellm_params: - model: "openai/*" + model: "openai/*" + mock_response: "litellm.RateLimitError" \ No newline at end of file diff --git a/litellm/router.py b/litellm/router.py index ba3f13b8e..0d082de5d 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -156,6 +156,7 @@ class Router: cooldown_time: Optional[ float ] = None, # (seconds) time to cooldown a deployment after failure + disable_cooldowns: Optional[bool] = None, routing_strategy: Literal[ "simple-shuffle", "least-busy", @@ -307,6 +308,7 @@ class Router: self.allowed_fails = allowed_fails or litellm.allowed_fails self.cooldown_time = cooldown_time or 60 + self.disable_cooldowns = disable_cooldowns self.failed_calls = ( InMemoryCache() ) # cache to track failed call per deployment, if num failed calls within 1 minute > allowed fails, then add it to cooldown @@ -2990,6 +2992,8 @@ class Router: the exception is not one that should be immediately retried (e.g. 401) """ + if self.disable_cooldowns is True: + return if deployment is None: return @@ -3030,24 +3034,50 @@ class Router: exception_status = 500 _should_retry = litellm._should_retry(status_code=exception_status) - if updated_fails > allowed_fails or _should_retry == False: + if updated_fails > allowed_fails or _should_retry is False: # get the current cooldown list for that minute cooldown_key = f"{current_minute}:cooldown_models" # group cooldown models by minute to reduce number of redis calls - cached_value = self.cache.get_cache(key=cooldown_key) + cached_value = self.cache.get_cache( + key=cooldown_key + ) # [(deployment_id, {last_error_str, last_error_status_code})] + cached_value_deployment_ids = [] + if ( + cached_value is not None + and isinstance(cached_value, list) + and len(cached_value) > 0 + and isinstance(cached_value[0], tuple) + ): + cached_value_deployment_ids = [cv[0] for cv in cached_value] verbose_router_logger.debug(f"adding {deployment} to cooldown models") # update value - try: - if deployment in cached_value: + if cached_value is not None and len(cached_value_deployment_ids) > 0: + if deployment in cached_value_deployment_ids: pass else: - cached_value = cached_value + [deployment] + cached_value = cached_value + [ + ( + deployment, + { + "Exception Received": str(original_exception), + "Status Code": str(exception_status), + }, + ) + ] # save updated value self.cache.set_cache( value=cached_value, key=cooldown_key, ttl=cooldown_time ) - except: - cached_value = [deployment] + else: + cached_value = [ + ( + deployment, + { + "Exception Received": str(original_exception), + "Status Code": str(exception_status), + }, + ) + ] # save updated value self.cache.set_cache( value=cached_value, key=cooldown_key, ttl=cooldown_time @@ -3063,7 +3093,33 @@ class Router: key=deployment, value=updated_fails, ttl=cooldown_time ) - async def _async_get_cooldown_deployments(self): + async def _async_get_cooldown_deployments(self) -> List[str]: + """ + Async implementation of '_get_cooldown_deployments' + """ + dt = get_utc_datetime() + current_minute = dt.strftime("%H-%M") + # get the current cooldown list for that minute + cooldown_key = f"{current_minute}:cooldown_models" + + # ---------------------- + # Return cooldown models + # ---------------------- + cooldown_models = await self.cache.async_get_cache(key=cooldown_key) or [] + + cached_value_deployment_ids = [] + if ( + cooldown_models is not None + and isinstance(cooldown_models, list) + and len(cooldown_models) > 0 + and isinstance(cooldown_models[0], tuple) + ): + cached_value_deployment_ids = [cv[0] for cv in cooldown_models] + + verbose_router_logger.debug(f"retrieve cooldown models: {cooldown_models}") + return cached_value_deployment_ids + + async def _async_get_cooldown_deployments_with_debug_info(self) -> List[tuple]: """ Async implementation of '_get_cooldown_deployments' """ @@ -3080,7 +3136,7 @@ class Router: verbose_router_logger.debug(f"retrieve cooldown models: {cooldown_models}") return cooldown_models - def _get_cooldown_deployments(self): + def _get_cooldown_deployments(self) -> List[str]: """ Get the list of models being cooled down for this minute """ @@ -3094,8 +3150,17 @@ class Router: # ---------------------- cooldown_models = self.cache.get_cache(key=cooldown_key) or [] + cached_value_deployment_ids = [] + if ( + cooldown_models is not None + and isinstance(cooldown_models, list) + and len(cooldown_models) > 0 + and isinstance(cooldown_models[0], tuple) + ): + cached_value_deployment_ids = [cv[0] for cv in cooldown_models] + verbose_router_logger.debug(f"retrieve cooldown models: {cooldown_models}") - return cooldown_models + return cached_value_deployment_ids def _get_healthy_deployments(self, model: str): _all_deployments: list = [] @@ -4713,7 +4778,7 @@ class Router: if _allowed_model_region is None: _allowed_model_region = "n/a" raise ValueError( - f"{RouterErrors.no_deployments_available.value}, Try again in {self.cooldown_time} seconds. Passed model={model}. pre-call-checks={self.enable_pre_call_checks}, allowed_model_region={_allowed_model_region}" + f"{RouterErrors.no_deployments_available.value}, Try again in {self.cooldown_time} seconds. Passed model={model}. pre-call-checks={self.enable_pre_call_checks}, allowed_model_region={_allowed_model_region}, cooldown_list={await self._async_get_cooldown_deployments_with_debug_info()}" ) if ( From 051dfee421930b6f833da3636634213f234524f9 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Jul 2024 15:05:38 -0700 Subject: [PATCH 107/124] docs(routing.md): add docs on how to disable cooldowns --- docs/my-website/docs/routing.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/my-website/docs/routing.md b/docs/my-website/docs/routing.md index 240e6c8e0..905954e97 100644 --- a/docs/my-website/docs/routing.md +++ b/docs/my-website/docs/routing.md @@ -815,6 +815,35 @@ model_list: +**Expected Response** + +``` +No deployments available for selected model, Try again in 60 seconds. Passed model=claude-3-5-sonnet. pre-call-checks=False, allowed_model_region=n/a. +``` + +#### **Disable cooldowns** + + + + + +```python +from litellm import Router + + +router = Router(..., disable_cooldowns=True) +``` + + + +```yaml +router_settings: + disable_cooldowns: True +``` + + + + ### Retries For both async + sync functions, we support retrying failed requests. From a5439a621bc5363ae5a4e1454fec527294a49726 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Jul 2024 15:13:19 -0700 Subject: [PATCH 108/124] docs(debugging.md): add docs on debugging common errors --- docs/my-website/docs/proxy/debugging.md | 29 ++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/my-website/docs/proxy/debugging.md b/docs/my-website/docs/proxy/debugging.md index 571a97c0e..38680982a 100644 --- a/docs/my-website/docs/proxy/debugging.md +++ b/docs/my-website/docs/proxy/debugging.md @@ -88,4 +88,31 @@ Expected Output: ```bash # no info statements -``` \ No newline at end of file +``` + +## Common Errors + +1. "No available deployments..." + +``` +No deployments available for selected model, Try again in 60 seconds. Passed model=claude-3-5-sonnet. pre-call-checks=False, allowed_model_region=n/a. +``` + +This can be caused due to all your models hitting rate limit errors, causing the cooldown to kick in. + +How to control this? +- Adjust the cooldown time + +```yaml +router_settings: + cooldown_time: 0 # 👈 KEY CHANGE +``` + +- Disable Cooldowns [NOT RECOMMENDED] + +```yaml +router_settings: + disable_cooldowns: True +``` + +This is not recommended, as it will lead to requests being routed to deployments over their tpm/rpm limit. \ No newline at end of file From 4b7feb3261e947ebd7a0ef572b5538253f3b401d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 17:01:42 -0700 Subject: [PATCH 109/124] feat - return response headers for async openai requests --- litellm/__init__.py | 3 +++ litellm/llms/openai.py | 33 +++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 0fa822a98..a9e6b69ae 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -125,6 +125,9 @@ llm_guard_mode: Literal["all", "key-specific", "request-specific"] = "all" ################## ### PREVIEW FEATURES ### enable_preview_features: bool = False +return_response_headers: bool = ( + False # get response headers from LLM Api providers - example x-remaining-requests, +) ################## logging: bool = True caching: bool = ( diff --git a/litellm/llms/openai.py b/litellm/llms/openai.py index 32e63b957..d98dd9d8c 100644 --- a/litellm/llms/openai.py +++ b/litellm/llms/openai.py @@ -652,6 +652,31 @@ class OpenAIChatCompletion(BaseLLM): else: return client + async def make_openai_chat_completion_request( + self, + openai_aclient: AsyncOpenAI, + data: dict, + timeout: Union[float, httpx.Timeout], + ): + try: + if litellm.return_response_headers is True: + raw_response = ( + await openai_aclient.chat.completions.with_raw_response.create( + **data, timeout=timeout + ) + ) + + headers = dict(raw_response.headers) + response = raw_response.parse() + return headers, response + else: + response = await openai_aclient.chat.completions.create( + **data, timeout=timeout + ) + return None, response + except Exception as e: + raise e + def completion( self, model_response: ModelResponse, @@ -869,8 +894,8 @@ class OpenAIChatCompletion(BaseLLM): }, ) - response = await openai_aclient.chat.completions.create( - **data, timeout=timeout + headers, response = await self.make_openai_chat_completion_request( + openai_aclient=openai_aclient, data=data, timeout=timeout ) stringified_response = response.model_dump() logging_obj.post_call( @@ -965,8 +990,8 @@ class OpenAIChatCompletion(BaseLLM): }, ) - response = await openai_aclient.chat.completions.create( - **data, timeout=timeout + headers, response = await self.make_openai_chat_completion_request( + openai_aclient=openai_aclient, data=data, timeout=timeout ) streamwrapper = CustomStreamWrapper( completion_stream=response, From 48946f7528983fbce3f4297fa403ad78b7ba1ce0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 17:02:15 -0700 Subject: [PATCH 110/124] fix config --- litellm/proxy/proxy_config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 88b778a6d..9f2324e51 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -36,6 +36,7 @@ general_settings: LANGFUSE_SECRET_KEY: "os.environ/LANGFUSE_DEV_SK_KEY" litellm_settings: + return_response_headers: true success_callback: ["prometheus"] callbacks: ["otel", "hide_secrets"] failure_callback: ["prometheus"] From 140f7fe2544536e4d4b8606e1d78fd6fdcf8f8e4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 17:09:06 -0700 Subject: [PATCH 111/124] return azure response headers --- litellm/llms/azure.py | 45 ++++++++++++++++++++++++++++++++++++++---- litellm/llms/openai.py | 5 +++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/litellm/llms/azure.py b/litellm/llms/azure.py index e127ecea6..4b36edff2 100644 --- a/litellm/llms/azure.py +++ b/litellm/llms/azure.py @@ -458,6 +458,36 @@ class AzureChatCompletion(BaseLLM): return azure_client + async def make_azure_openai_chat_completion_request( + self, + azure_client: AsyncAzureOpenAI, + data: dict, + timeout: Union[float, httpx.Timeout], + ): + """ + Helper to: + - call chat.completions.create.with_raw_response when litellm.return_response_headers is True + - call chat.completions.create by default + """ + try: + if litellm.return_response_headers is True: + raw_response = ( + await azure_client.chat.completions.with_raw_response.create( + **data, timeout=timeout + ) + ) + + headers = dict(raw_response.headers) + response = raw_response.parse() + return headers, response + else: + response = await azure_client.chat.completions.create( + **data, timeout=timeout + ) + return None, response + except Exception as e: + raise e + def completion( self, model: str, @@ -701,8 +731,11 @@ class AzureChatCompletion(BaseLLM): "complete_input_dict": data, }, ) - response = await azure_client.chat.completions.create( - **data, timeout=timeout + + headers, response = await self.make_azure_openai_chat_completion_request( + azure_client=azure_client, + data=data, + timeout=timeout, ) stringified_response = response.model_dump() @@ -861,9 +894,13 @@ class AzureChatCompletion(BaseLLM): "complete_input_dict": data, }, ) - response = await azure_client.chat.completions.create( - **data, timeout=timeout + + headers, response = await self.make_azure_openai_chat_completion_request( + azure_client=azure_client, + data=data, + timeout=timeout, ) + # return response streamwrapper = CustomStreamWrapper( completion_stream=response, diff --git a/litellm/llms/openai.py b/litellm/llms/openai.py index d98dd9d8c..c7bb0e353 100644 --- a/litellm/llms/openai.py +++ b/litellm/llms/openai.py @@ -658,6 +658,11 @@ class OpenAIChatCompletion(BaseLLM): data: dict, timeout: Union[float, httpx.Timeout], ): + """ + Helper to: + - call chat.completions.create.with_raw_response when litellm.return_response_headers is True + - call chat.completions.create by default + """ try: if litellm.return_response_headers is True: raw_response = ( From b761ce9dd17960c9a565327a30e30f6db5909bd9 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 1 Jul 2024 17:22:31 -0700 Subject: [PATCH 112/124] fix(s3.py): fix s3 path logging on proxy --- docs/my-website/docs/proxy/logging.md | 1 + litellm/integrations/s3.py | 9 +++++++-- litellm/proxy/_new_secret_config.yaml | 10 ++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/my-website/docs/proxy/logging.md b/docs/my-website/docs/proxy/logging.md index f9ed5db3d..83bf8ee95 100644 --- a/docs/my-website/docs/proxy/logging.md +++ b/docs/my-website/docs/proxy/logging.md @@ -1188,6 +1188,7 @@ litellm_settings: s3_region_name: us-west-2 # AWS Region Name for S3 s3_aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID # us os.environ/ to pass environment variables. This is AWS Access Key ID for S3 s3_aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY # AWS Secret Access Key for S3 + s3_path: my-test-path # [OPTIONAL] set path in bucket you want to write logs to s3_endpoint_url: https://s3.amazonaws.com # [OPTIONAL] S3 endpoint URL, if you want to use Backblaze/cloudflare s3 buckets ``` diff --git a/litellm/integrations/s3.py b/litellm/integrations/s3.py index 0796d1048..6e8c4a4e4 100644 --- a/litellm/integrations/s3.py +++ b/litellm/integrations/s3.py @@ -1,10 +1,14 @@ #### What this does #### # On success + failure, log events to Supabase +import datetime import os +import subprocess +import sys import traceback -import datetime, subprocess, sys -import litellm, uuid +import uuid + +import litellm from litellm._logging import print_verbose, verbose_logger @@ -54,6 +58,7 @@ class S3Logger: "s3_aws_session_token" ) s3_config = litellm.s3_callback_params.get("s3_config") + s3_path = litellm.s3_callback_params.get("s3_path") # done reading litellm.s3_callback_params self.bucket_name = s3_bucket_name diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 2693ce3d3..f21a9e832 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -2,3 +2,13 @@ model_list: - model_name: "*" # all requests where model not in your config go to this deployment litellm_params: model: "openai/*" + +litellm_settings: + success_callback: ["s3"] + s3_callback_params: + s3_bucket_name: my-test-bucket-22-litellm # AWS Bucket Name for S3 + s3_region_name: us-west-2 # AWS Region Name for S3 + s3_aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID # us os.environ/ to pass environment variables. This is AWS Access Key ID for S3 + s3_aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY # AWS Secret Access Key for S3 + s3_path: my-test-path + From 04a975d486bed4474fc4d5e2abc78cb412a2a492 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 17:25:15 -0700 Subject: [PATCH 113/124] feat - add response_headers in litellm_logging_obj --- litellm/llms/openai.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/litellm/llms/openai.py b/litellm/llms/openai.py index c7bb0e353..357bda799 100644 --- a/litellm/llms/openai.py +++ b/litellm/llms/openai.py @@ -21,6 +21,7 @@ from pydantic import BaseModel from typing_extensions import overload, override import litellm +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.types.utils import ProviderField from litellm.utils import ( Choices, @@ -866,13 +867,13 @@ class OpenAIChatCompletion(BaseLLM): self, data: dict, model_response: ModelResponse, + logging_obj: LiteLLMLoggingObj, timeout: Union[float, httpx.Timeout], api_key: Optional[str] = None, api_base: Optional[str] = None, organization: Optional[str] = None, client=None, max_retries=None, - logging_obj=None, headers=None, ): response = None @@ -909,9 +910,11 @@ class OpenAIChatCompletion(BaseLLM): original_response=stringified_response, additional_args={"complete_input_dict": data}, ) + logging_obj.model_call_details["response_headers"] = headers return convert_to_model_response_object( response_object=stringified_response, model_response_object=model_response, + hidden_params={"headers": headers}, ) except Exception as e: raise e @@ -961,10 +964,10 @@ class OpenAIChatCompletion(BaseLLM): async def async_streaming( self, - logging_obj, timeout: Union[float, httpx.Timeout], data: dict, model: str, + logging_obj: LiteLLMLoggingObj, api_key: Optional[str] = None, api_base: Optional[str] = None, organization: Optional[str] = None, @@ -998,6 +1001,7 @@ class OpenAIChatCompletion(BaseLLM): headers, response = await self.make_openai_chat_completion_request( openai_aclient=openai_aclient, data=data, timeout=timeout ) + logging_obj.model_call_details["response_headers"] = headers streamwrapper = CustomStreamWrapper( completion_stream=response, model=model, @@ -1527,9 +1531,9 @@ class OpenAITextCompletion(BaseLLM): model: str, messages: list, timeout: float, + logging_obj: LiteLLMLoggingObj, print_verbose: Optional[Callable] = None, api_base: Optional[str] = None, - logging_obj=None, acompletion: bool = False, optional_params=None, litellm_params=None, From c37d45d556e0ce2e4d128a1bd5af27209a0ef1a8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 20:00:47 -0700 Subject: [PATCH 114/124] feat - prometheus log remaining headers --- litellm/integrations/prometheus.py | 101 +++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 5 deletions(-) diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index 4f0ffa387..6cd746907 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -2,14 +2,20 @@ #### What this does #### # On success, log events to Prometheus -import dotenv, os -import requests # type: ignore +import datetime +import os +import subprocess +import sys import traceback -import datetime, subprocess, sys -import litellm, uuid -from litellm._logging import print_verbose, verbose_logger +import uuid from typing import Optional, Union +import dotenv +import requests # type: ignore + +import litellm +from litellm._logging import print_verbose, verbose_logger + class PrometheusLogger: # Class variables or attributes @@ -20,6 +26,8 @@ class PrometheusLogger: try: from prometheus_client import Counter, Gauge + from litellm.proxy.proxy_server import premium_user + self.litellm_llm_api_failed_requests_metric = Counter( name="litellm_llm_api_failed_requests_metric", documentation="Total number of failed LLM API calls via litellm", @@ -88,6 +96,31 @@ class PrometheusLogger: labelnames=["hashed_api_key", "api_key_alias"], ) + # Litellm-Enterprise Metrics + if premium_user is True: + # Remaining Rate Limit for model + self.litellm_remaining_requests_metric = Gauge( + "litellm_remaining_requests", + "remaining requests for model, returned from LLM API Provider", + labelnames=[ + "model_group", + "api_provider", + "api_base", + "litellm_model_name", + ], + ) + + self.litellm_remaining_tokens_metric = Gauge( + "litellm_remaining_tokens", + "remaining tokens for model, returned from LLM API Provider", + labelnames=[ + "model_group", + "api_provider", + "api_base", + "litellm_model_name", + ], + ) + except Exception as e: print_verbose(f"Got exception on init prometheus client {str(e)}") raise e @@ -104,6 +137,8 @@ class PrometheusLogger: ): try: # Define prometheus client + from litellm.proxy.proxy_server import premium_user + verbose_logger.debug( f"prometheus Logging - Enters logging function for model {kwargs}" ) @@ -199,6 +234,10 @@ class PrometheusLogger: user_api_key, user_api_key_alias ).set(_remaining_api_key_budget) + # set x-ratelimit headers + if premium_user is True: + self.set_remaining_tokens_requests_metric(kwargs) + ### FAILURE INCREMENT ### if "exception" in kwargs: self.litellm_llm_api_failed_requests_metric.labels( @@ -216,6 +255,58 @@ class PrometheusLogger: verbose_logger.debug(traceback.format_exc()) pass + def set_remaining_tokens_requests_metric(self, request_kwargs: dict): + try: + verbose_logger.debug("setting remaining tokens requests metric") + _response_headers = request_kwargs.get("response_headers") + _litellm_params = request_kwargs.get("litellm_params", {}) or {} + _metadata = _litellm_params.get("metadata", {}) + litellm_model_name = request_kwargs.get("model", None) + model_group = _metadata.get("model_group", None) + api_base = _metadata.get("api_base", None) + llm_provider = _litellm_params.get("custom_llm_provider", None) + + remaining_requests = None + remaining_tokens = None + # OpenAI / OpenAI Compatible headers + if ( + _response_headers + and "x-ratelimit-remaining-requests" in _response_headers + ): + remaining_requests = _response_headers["x-ratelimit-remaining-requests"] + if ( + _response_headers + and "x-ratelimit-remaining-tokens" in _response_headers + ): + remaining_tokens = _response_headers["x-ratelimit-remaining-tokens"] + verbose_logger.debug( + f"remaining requests: {remaining_requests}, remaining tokens: {remaining_tokens}" + ) + + if remaining_requests: + """ + "model_group", + "api_provider", + "api_base", + "litellm_model_name" + """ + self.litellm_remaining_requests_metric.labels( + model_group, llm_provider, api_base, litellm_model_name + ).set(remaining_requests) + + if remaining_tokens: + self.litellm_remaining_tokens_metric.labels( + model_group, llm_provider, api_base, litellm_model_name + ).set(remaining_tokens) + + except Exception as e: + verbose_logger.error( + "Prometheus Error: set_remaining_tokens_requests_metric. Exception occured - {}".format( + str(e) + ) + ) + return + def safe_get_remaining_budget( max_budget: Optional[float], spend: Optional[float] From 568245b5c00209876ff17ce29539a1cea21e8041 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 20:12:39 -0700 Subject: [PATCH 115/124] feat - set response headers in azure requests --- litellm/llms/azure.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/litellm/llms/azure.py b/litellm/llms/azure.py index 4b36edff2..000feed44 100644 --- a/litellm/llms/azure.py +++ b/litellm/llms/azure.py @@ -23,6 +23,7 @@ from typing_extensions import overload import litellm from litellm import OpenAIConfig from litellm.caching import DualCache +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.utils import ( Choices, CustomStreamWrapper, @@ -500,7 +501,7 @@ class AzureChatCompletion(BaseLLM): azure_ad_token: str, print_verbose: Callable, timeout: Union[float, httpx.Timeout], - logging_obj, + logging_obj: LiteLLMLoggingObj, optional_params, litellm_params, logger_fn, @@ -679,9 +680,9 @@ class AzureChatCompletion(BaseLLM): data: dict, timeout: Any, model_response: ModelResponse, + logging_obj: LiteLLMLoggingObj, azure_ad_token: Optional[str] = None, client=None, # this is the AsyncAzureOpenAI - logging_obj=None, ): response = None try: @@ -737,6 +738,7 @@ class AzureChatCompletion(BaseLLM): data=data, timeout=timeout, ) + logging_obj.model_call_details["response_headers"] = headers stringified_response = response.model_dump() logging_obj.post_call( @@ -845,7 +847,7 @@ class AzureChatCompletion(BaseLLM): async def async_streaming( self, - logging_obj, + logging_obj: LiteLLMLoggingObj, api_base: str, api_key: str, api_version: str, @@ -900,6 +902,7 @@ class AzureChatCompletion(BaseLLM): data=data, timeout=timeout, ) + logging_obj.model_call_details["response_headers"] = headers # return response streamwrapper = CustomStreamWrapper( From 403330265602ce5eb19eaf0dd699eece3af1be8f Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 20:27:27 -0700 Subject: [PATCH 116/124] feat - return headers for openai audio transcriptions --- litellm/llms/openai.py | 72 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/litellm/llms/openai.py b/litellm/llms/openai.py index 357bda799..990ef2fae 100644 --- a/litellm/llms/openai.py +++ b/litellm/llms/openai.py @@ -1026,17 +1026,43 @@ class OpenAIChatCompletion(BaseLLM): else: raise OpenAIError(status_code=500, message=f"{str(e)}") + # Embedding + async def make_openai_embedding_request( + self, + openai_aclient: AsyncOpenAI, + data: dict, + timeout: Union[float, httpx.Timeout], + ): + """ + Helper to: + - call embeddings.create.with_raw_response when litellm.return_response_headers is True + - call embeddings.create by default + """ + try: + if litellm.return_response_headers is True: + raw_response = await openai_aclient.embeddings.with_raw_response.create( + **data, timeout=timeout + ) # type: ignore + headers = dict(raw_response.headers) + response = raw_response.parse() + return headers, response + else: + response = await openai_aclient.embeddings.create(**data, timeout=timeout) # type: ignore + return None, response + except Exception as e: + raise e + async def aembedding( self, input: list, data: dict, model_response: litellm.utils.EmbeddingResponse, timeout: float, + logging_obj: LiteLLMLoggingObj, api_key: Optional[str] = None, api_base: Optional[str] = None, client: Optional[AsyncOpenAI] = None, max_retries=None, - logging_obj=None, ): response = None try: @@ -1048,7 +1074,10 @@ class OpenAIChatCompletion(BaseLLM): max_retries=max_retries, client=client, ) - response = await openai_aclient.embeddings.create(**data, timeout=timeout) # type: ignore + headers, response = await self.make_openai_embedding_request( + openai_aclient=openai_aclient, data=data, timeout=timeout + ) + logging_obj.model_call_details["response_headers"] = headers stringified_response = response.model_dump() ## LOGGING logging_obj.post_call( @@ -1263,6 +1292,34 @@ class OpenAIChatCompletion(BaseLLM): else: raise OpenAIError(status_code=500, message=str(e)) + # Audio Transcriptions + async def make_openai_audio_transcriptions_request( + self, + openai_aclient: AsyncOpenAI, + data: dict, + timeout: Union[float, httpx.Timeout], + ): + """ + Helper to: + - call openai_aclient.audio.transcriptions.with_raw_response when litellm.return_response_headers is True + - call openai_aclient.audio.transcriptions.create by default + """ + try: + if litellm.return_response_headers is True: + raw_response = ( + await openai_aclient.audio.transcriptions.with_raw_response.create( + **data, timeout=timeout + ) + ) # type: ignore + headers = dict(raw_response.headers) + response = raw_response.parse() + return headers, response + else: + response = await openai_aclient.audio.transcriptions.create(**data, timeout=timeout) # type: ignore + return None, response + except Exception as e: + raise e + def audio_transcriptions( self, model: str, @@ -1320,11 +1377,11 @@ class OpenAIChatCompletion(BaseLLM): data: dict, model_response: TranscriptionResponse, timeout: float, + logging_obj: LiteLLMLoggingObj, api_key: Optional[str] = None, api_base: Optional[str] = None, client=None, max_retries=None, - logging_obj=None, ): try: openai_aclient = self._get_openai_client( @@ -1336,9 +1393,12 @@ class OpenAIChatCompletion(BaseLLM): client=client, ) - response = await openai_aclient.audio.transcriptions.create( - **data, timeout=timeout - ) # type: ignore + headers, response = await self.make_openai_audio_transcriptions_request( + openai_aclient=openai_aclient, + data=data, + timeout=timeout, + ) + logging_obj.model_call_details["response_headers"] = headers stringified_response = response.model_dump() ## LOGGING logging_obj.post_call( From fcf65d5215970a579dd8175eacab8b32c0adfceb Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 21:05:37 -0700 Subject: [PATCH 117/124] fix exception provider not known --- litellm/utils.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/litellm/utils.py b/litellm/utils.py index 103f854b6..f8e8566f8 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5810,6 +5810,18 @@ def exception_type( _model_group = _metadata.get("model_group") _deployment = _metadata.get("deployment") extra_information = f"\nModel: {model}" + + exception_provider = "Unknown" + if ( + isinstance(custom_llm_provider, str) + and len(custom_llm_provider) > 0 + ): + exception_provider = ( + custom_llm_provider[0].upper() + + custom_llm_provider[1:] + + "Exception" + ) + if _api_base: extra_information += f"\nAPI Base: `{_api_base}`" if ( From 665d8fb250c8f9a464334cbcef5e6841d8cf4c53 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 21:19:47 -0700 Subject: [PATCH 118/124] test - test_azure_embedding_exceptions --- litellm/tests/test_exceptions.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/litellm/tests/test_exceptions.py b/litellm/tests/test_exceptions.py index 3d8cb3c2a..fb390bb48 100644 --- a/litellm/tests/test_exceptions.py +++ b/litellm/tests/test_exceptions.py @@ -249,6 +249,25 @@ def test_completion_azure_exception(): # test_completion_azure_exception() +def test_azure_embedding_exceptions(): + try: + + response = litellm.embedding( + model="azure/azure-embedding-model", + input="hello", + messages="hello", + ) + pytest.fail(f"Bad request this should have failed but got {response}") + + except Exception as e: + print(vars(e)) + # CRUCIAL Test - Ensures our exceptions are readable and not overly complicated. some users have complained exceptions will randomly have another exception raised in our exception mapping + assert ( + e.message + == "litellm.APIError: AzureException APIError - Embeddings.create() got an unexpected keyword argument 'messages'" + ) + + async def asynctest_completion_azure_exception(): try: import openai From 4cb098661a281c6526abf4fff0486df32e829140 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 21:35:52 -0700 Subject: [PATCH 119/124] =?UTF-8?q?bump:=20version=201.41.2=20=E2=86=92=20?= =?UTF-8?q?1.41.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2519c167f..c698a18e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.41.2" +version = "1.41.3" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -90,7 +90,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.41.2" +version = "1.41.3" version_files = [ "pyproject.toml:^version" ] From e2a2c2bde1ac45e6e6071456e06eb06beee5fa45 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 21:36:30 -0700 Subject: [PATCH 120/124] ci/cd run again --- litellm/tests/test_completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 5138e9b61..1c10ef461 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -23,7 +23,7 @@ from litellm import RateLimitError, Timeout, completion, completion_cost, embedd from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.llms.prompt_templates.factory import anthropic_messages_pt -# litellm.num_retries = 3 +# litellm.num_retries=3 litellm.cache = None litellm.success_callback = [] user_message = "Write a short poem about the sky" From 5f04ef14a60307a12b2f965b1972675a613dd57a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 22:41:52 -0700 Subject: [PATCH 121/124] doc prometheus tracking --- docs/my-website/docs/enterprise.md | 2 ++ docs/my-website/docs/proxy/enterprise.md | 2 ++ docs/my-website/docs/proxy/prometheus.md | 26 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/docs/my-website/docs/enterprise.md b/docs/my-website/docs/enterprise.md index e3758266a..5bd09ec15 100644 --- a/docs/my-website/docs/enterprise.md +++ b/docs/my-website/docs/enterprise.md @@ -20,6 +20,8 @@ This covers: - **Spend Tracking** - ✅ [Tracking Spend for Custom Tags](./proxy/enterprise#tracking-spend-for-custom-tags) - ✅ [API Endpoints to get Spend Reports per Team, API Key, Customer](./proxy/cost_tracking.md#✨-enterprise-api-endpoints-to-get-spend) + - **Advanced Metrics** + - ✅ [`x-ratelimit-remaining-requests`, `x-ratelimit-remaining-tokens` for LLM APIs on Prometheus](./proxy/prometheus#✨-enterprise-llm-remaining-requests-and-remaining-tokens) - **Guardrails, PII Masking, Content Moderation** - ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](./proxy/enterprise#content-moderation) - ✅ [Prompt Injection Detection (with LakeraAI API)](./proxy/enterprise#prompt-injection-detection---lakeraai) diff --git a/docs/my-website/docs/proxy/enterprise.md b/docs/my-website/docs/proxy/enterprise.md index e061a917e..5dabba5ed 100644 --- a/docs/my-website/docs/proxy/enterprise.md +++ b/docs/my-website/docs/proxy/enterprise.md @@ -23,6 +23,8 @@ Features: - **Spend Tracking** - ✅ [Tracking Spend for Custom Tags](#tracking-spend-for-custom-tags) - ✅ [API Endpoints to get Spend Reports per Team, API Key, Customer](cost_tracking.md#✨-enterprise-api-endpoints-to-get-spend) +- **Advanced Metrics** + - ✅ [`x-ratelimit-remaining-requests`, `x-ratelimit-remaining-tokens` for LLM APIs on Prometheus](prometheus#✨-enterprise-llm-remaining-requests-and-remaining-tokens) - **Guardrails, PII Masking, Content Moderation** - ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](#content-moderation) - ✅ [Prompt Injection Detection (with LakeraAI API)](#prompt-injection-detection---lakeraai) diff --git a/docs/my-website/docs/proxy/prometheus.md b/docs/my-website/docs/proxy/prometheus.md index 2c7481f4c..974a081a9 100644 --- a/docs/my-website/docs/proxy/prometheus.md +++ b/docs/my-website/docs/proxy/prometheus.md @@ -61,6 +61,32 @@ http://localhost:4000/metrics | `litellm_remaining_api_key_budget_metric` | Remaining Budget for API Key (A key Created on LiteLLM)| +### ✨ (Enterprise) LLM Remaining Requests and Remaining Tokens +Set this on your config.yaml to allow you to track how close you are to hitting your TPM / RPM limits on each model group + +```yaml +litellm_settings: + success_callback: ["prometheus"] + failure_callback: ["prometheus"] + return_response_headers: true # ensures the LLM API calls track the response headers +``` + +| Metric Name | Description | +|----------------------|--------------------------------------| +| `litellm_remaining_requests_metric` | Track `x-ratelimit-remaining-requests` returned from LLM API Deployment | +| `litellm_remaining_tokens` | Track `x-ratelimit-remaining-tokens` return from LLM API Deployment | + +Example Metric +```shell +litellm_remaining_tokens +{ + api_base="https://api.openai.com/v1", + api_provider="openai", + litellm_model_name="gpt-3.5-turbo", + model_group="gpt-3.5-turbo" +} +999981.0 +``` ## Monitor System Health To monitor the health of litellm adjacent services (redis / postgres), do: From 38770da2f6c6102e5a31fe994262650f7a95b4b0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 1 Jul 2024 22:45:33 -0700 Subject: [PATCH 122/124] docs prometheus tracking x-remaining tokens --- docs/my-website/docs/proxy/prometheus.md | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/my-website/docs/proxy/prometheus.md b/docs/my-website/docs/proxy/prometheus.md index 974a081a9..6790b25b0 100644 --- a/docs/my-website/docs/proxy/prometheus.md +++ b/docs/my-website/docs/proxy/prometheus.md @@ -1,3 +1,6 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + # 📈 Prometheus metrics [BETA] LiteLLM Exposes a `/metrics` endpoint for Prometheus to Poll @@ -77,6 +80,25 @@ litellm_settings: | `litellm_remaining_tokens` | Track `x-ratelimit-remaining-tokens` return from LLM API Deployment | Example Metric + + + + +```shell +litellm_remaining_requests +{ + api_base="https://api.openai.com/v1", + api_provider="openai", + litellm_model_name="gpt-3.5-turbo", + model_group="gpt-3.5-turbo" +} +8998.0 +``` + + + + + ```shell litellm_remaining_tokens { @@ -87,6 +109,11 @@ litellm_remaining_tokens } 999981.0 ``` + + + + + ## Monitor System Health To monitor the health of litellm adjacent services (redis / postgres), do: From 5aae2313f3fa9bc099eaa82ead51144205e5f203 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 2 Jul 2024 00:41:54 -0700 Subject: [PATCH 123/124] fix(aws_secret_manager.py): fix string replace --- litellm/proxy/secret_managers/aws_secret_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/secret_managers/aws_secret_manager.py b/litellm/proxy/secret_managers/aws_secret_manager.py index 8895717c6..c4afaedc2 100644 --- a/litellm/proxy/secret_managers/aws_secret_manager.py +++ b/litellm/proxy/secret_managers/aws_secret_manager.py @@ -153,7 +153,7 @@ def decrypt_env_var() -> Dict[str, Any]: ) or (v is not None and isinstance(v, str) and v.startswith("aws_kms/")): decrypted_value = aws_kms.decrypt_value(secret_name=k) # reset env var - k = re.sub("litellm_secret_aws_kms", "", k, flags=re.IGNORECASE) + k = re.sub("litellm_secret_aws_kms_", "", k, flags=re.IGNORECASE) new_values[k] = decrypted_value return new_values From 79670ab82eece55504c1cacbe9b6cb04da55eea6 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 2 Jul 2024 09:24:07 -0700 Subject: [PATCH 124/124] fix(main.py): get the region name from boto3 client if dynamic var not set --- litellm/main.py | 11 +++++ litellm/tests/test_bedrock_completion.py | 53 ++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/litellm/main.py b/litellm/main.py index c48c242ce..55ac01935 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -2212,15 +2212,26 @@ def completion( custom_prompt_dict = custom_prompt_dict or litellm.custom_prompt_dict if "aws_bedrock_client" in optional_params: + verbose_logger.warning( + "'aws_bedrock_client' is a deprecated param. Please move to another auth method - https://docs.litellm.ai/docs/providers/bedrock#boto3---authentication." + ) # Extract credentials for legacy boto3 client and pass thru to httpx aws_bedrock_client = optional_params.pop("aws_bedrock_client") creds = aws_bedrock_client._get_credentials().get_frozen_credentials() + if creds.access_key: optional_params["aws_access_key_id"] = creds.access_key if creds.secret_key: optional_params["aws_secret_access_key"] = creds.secret_key if creds.token: optional_params["aws_session_token"] = creds.token + if ( + "aws_region_name" not in optional_params + or optional_params["aws_region_name"] is None + ): + optional_params["aws_region_name"] = ( + aws_bedrock_client.meta.region_name + ) if model in litellm.BEDROCK_CONVERSE_MODELS: response = bedrock_converse_chat_completion.completion( diff --git a/litellm/tests/test_bedrock_completion.py b/litellm/tests/test_bedrock_completion.py index 6e39c30b3..fb4ba7556 100644 --- a/litellm/tests/test_bedrock_completion.py +++ b/litellm/tests/test_bedrock_completion.py @@ -856,3 +856,56 @@ async def test_bedrock_custom_prompt_template(): prompt = json.loads(mock_client_post.call_args.kwargs["data"])["prompt"] assert prompt == "<|im_start|>user\nWhat's AWS?<|im_end|>" mock_client_post.assert_called_once() + + +def test_completion_bedrock_external_client_region(): + print("\ncalling bedrock claude external client auth") + import os + + aws_access_key_id = os.environ["AWS_ACCESS_KEY_ID"] + aws_secret_access_key = os.environ["AWS_SECRET_ACCESS_KEY"] + aws_region_name = "us-east-1" + + os.environ.pop("AWS_ACCESS_KEY_ID", None) + os.environ.pop("AWS_SECRET_ACCESS_KEY", None) + + client = HTTPHandler() + + try: + import boto3 + + litellm.set_verbose = True + + bedrock = boto3.client( + service_name="bedrock-runtime", + region_name=aws_region_name, + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + endpoint_url=f"https://bedrock-runtime.{aws_region_name}.amazonaws.com", + ) + with patch.object(client, "post", new=Mock()) as mock_client_post: + try: + response = completion( + model="bedrock/anthropic.claude-instant-v1", + messages=messages, + max_tokens=10, + temperature=0.1, + aws_bedrock_client=bedrock, + client=client, + ) + # Add any assertions here to check the response + print(response) + except Exception as e: + pass + + print(f"mock_client_post.call_args: {mock_client_post.call_args}") + assert "us-east-1" in mock_client_post.call_args.kwargs["url"] + + mock_client_post.assert_called_once() + + os.environ["AWS_ACCESS_KEY_ID"] = aws_access_key_id + os.environ["AWS_SECRET_ACCESS_KEY"] = aws_secret_access_key + except RateLimitError: + pass + except Exception as e: + pytest.fail(f"Error occurred: {e}")