From 91e2b661ca75e4a7f435bb6e1b9f69a85602ad88 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 21 Jun 2024 20:21:19 -0700 Subject: [PATCH 001/137] Turn on message logging via request header --- litellm/litellm_core_utils/redact_messages.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/litellm/litellm_core_utils/redact_messages.py b/litellm/litellm_core_utils/redact_messages.py index 8f270d8be..91f340cb8 100644 --- a/litellm/litellm_core_utils/redact_messages.py +++ b/litellm/litellm_core_utils/redact_messages.py @@ -32,6 +32,10 @@ def redact_message_input_output_from_logging( if litellm.turn_off_message_logging is not True: return result + request_headers = litellm_logging_obj.model_call_details['litellm_params']['metadata']['headers'] + if request_headers and request_headers.get('litellm-turn-on-message-logging', False): + return result + # remove messages, prompts, input, response from logging litellm_logging_obj.model_call_details["messages"] = [ {"role": "user", "content": "redacted-by-litellm"} From b7452cb38371578b0ee111e0322af70c6e7f91d0 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 21 Jun 2024 21:52:55 -0700 Subject: [PATCH 002/137] Rename request header --- litellm/litellm_core_utils/redact_messages.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/litellm/litellm_core_utils/redact_messages.py b/litellm/litellm_core_utils/redact_messages.py index 91f340cb8..cc616afec 100644 --- a/litellm/litellm_core_utils/redact_messages.py +++ b/litellm/litellm_core_utils/redact_messages.py @@ -28,12 +28,13 @@ def redact_message_input_output_from_logging( Removes messages, prompts, input, response from logging. This modifies the data in-place only redacts when litellm.turn_off_message_logging == True """ + request_headers = litellm_logging_obj.model_call_details['litellm_params']['metadata']['headers'] + # check if user opted out of logging message/response to callbacks - if litellm.turn_off_message_logging is not True: + if litellm.turn_off_message_logging is not True and request_headers.get('litellm-enable-message-redaction', False): return result - request_headers = litellm_logging_obj.model_call_details['litellm_params']['metadata']['headers'] - if request_headers and request_headers.get('litellm-turn-on-message-logging', False): + if request_headers and request_headers.get('litellm-disable-message-redaction', False): return result # remove messages, prompts, input, response from logging From 54bf89fa27cf7b9b1ed137ac93f6fc6d52a0210c Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 21 Jun 2024 22:10:31 -0700 Subject: [PATCH 003/137] Document feature --- docs/my-website/docs/proxy/logging.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/my-website/docs/proxy/logging.md b/docs/my-website/docs/proxy/logging.md index e9be2b837..f9ed5db3d 100644 --- a/docs/my-website/docs/proxy/logging.md +++ b/docs/my-website/docs/proxy/logging.md @@ -210,6 +210,24 @@ litellm_settings: turn_off_message_logging: True ``` +If you have this feature turned on, you can override it for specific requests by +setting a request header `LiteLLM-Disable-Message-Redaction: true`. + +```shell +curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Content-Type: application/json' \ + --header 'LiteLLM-Disable-Message-Redaction: true' \ + --data '{ + "model": "gpt-3.5-turbo", + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ] +}' +``` + ### 🔧 Debugging - Viewing RAW CURL sent from LiteLLM to provider Use this when you want to view the RAW curl request sent from LiteLLM to the LLM API From 3105eaebc3949ad8c72006dfb248f22510d51718 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 22 Jun 2024 16:12:42 -0700 Subject: [PATCH 004/137] feat - add debug_utils --- litellm/proxy/common_utils/debug_utils.py | 27 +++++++++++++++++++++++ litellm/proxy/proxy_server.py | 2 ++ 2 files changed, 29 insertions(+) create mode 100644 litellm/proxy/common_utils/debug_utils.py diff --git a/litellm/proxy/common_utils/debug_utils.py b/litellm/proxy/common_utils/debug_utils.py new file mode 100644 index 000000000..dc77958a6 --- /dev/null +++ b/litellm/proxy/common_utils/debug_utils.py @@ -0,0 +1,27 @@ +# Start tracing memory allocations +import os +import tracemalloc + +from fastapi import APIRouter + +from litellm._logging import verbose_proxy_logger + +router = APIRouter() + +if os.environ.get("LITELLM_PROFILE", "false").lower() == "true": + tracemalloc.start() + + @router.get("/memory-usage", include_in_schema=False) + async def memory_usage(): + # Take a snapshot of the current memory usage + snapshot = tracemalloc.take_snapshot() + top_stats = snapshot.statistics("lineno") + verbose_proxy_logger.debug("TOP STATS: %s", top_stats) + + # Get the top 50 memory usage lines + top_50 = top_stats[:50] + result = [] + for stat in top_50: + result.append(f"{stat.traceback.format()}: {stat.size / 1024} KiB") + + return {"top_50_memory_usage": result} diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 630aa3f3e..9b78ecf8a 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -140,6 +140,7 @@ from litellm.proxy.auth.user_api_key_auth import user_api_key_auth ## Import All Misc routes here ## from litellm.proxy.caching_routes import router as caching_router +from litellm.proxy.common_utils.debug_utils import router as debugging_endpoints_router from litellm.proxy.common_utils.http_parsing_utils import _read_request_body from litellm.proxy.health_check import perform_health_check from litellm.proxy.health_endpoints._health_endpoints import router as health_router @@ -9173,3 +9174,4 @@ app.include_router(team_router) app.include_router(spend_management_router) app.include_router(caching_router) app.include_router(analytics_router) +app.include_router(debugging_endpoints_router) From 2c7a80d08de1aa459f2c01b2699bf55e4397d876 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 22 Jun 2024 19:10:15 -0700 Subject: [PATCH 005/137] fix(router.py): check if azure returns 'content_filter' response + fallback available -> fallback Exception maps azure content filter response exceptions --- litellm/main.py | 3 + litellm/proxy/_experimental/out/404.html | 1 - .../proxy/_experimental/out/model_hub.html | 1 - .../proxy/_experimental/out/onboarding.html | 1 - litellm/proxy/_new_secret_config.yaml | 73 ++++--------------- litellm/router.py | 58 +++++++++++++++ litellm/tests/test_router_fallbacks.py | 30 ++++++-- litellm/types/router.py | 3 +- 8 files changed, 100 insertions(+), 70 deletions(-) delete mode 100644 litellm/proxy/_experimental/out/404.html delete mode 100644 litellm/proxy/_experimental/out/model_hub.html delete mode 100644 litellm/proxy/_experimental/out/onboarding.html diff --git a/litellm/main.py b/litellm/main.py index a76ef64a1..a0db6aff4 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -477,6 +477,9 @@ def mock_completion( if time_delay is not None: time.sleep(time_delay) + if isinstance(mock_response, dict): + return ModelResponse(**mock_response) + model_response = ModelResponse(stream=stream) if stream is True: # don't try to access stream object, diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html deleted file mode 100644 index e27fe5bab..000000000 --- a/litellm/proxy/_experimental/out/404.html +++ /dev/null @@ -1 +0,0 @@ -404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/litellm/proxy/_experimental/out/model_hub.html b/litellm/proxy/_experimental/out/model_hub.html deleted file mode 100644 index 49dfe314f..000000000 --- a/litellm/proxy/_experimental/out/model_hub.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding.html deleted file mode 100644 index ca4025208..000000000 --- a/litellm/proxy/_experimental/out/onboarding.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 866ca0ab0..d990a0e9b 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -1,61 +1,14 @@ -environment_variables: - LANGFUSE_PUBLIC_KEY: Q6K8MQN6L7sPYSJiFKM9eNrETOx6V/FxVPup4FqdKsZK1hyR4gyanlQ2KHLg5D5afng99uIt0JCEQ2jiKF9UxFvtnb4BbJ4qpeceH+iK8v/bdg== - LANGFUSE_SECRET_KEY: 5xQ7KMa6YMLsm+H/Pf1VmlqWq1NON5IoCxABhkUBeSck7ftsj2CmpkL2ZwrxwrktgiTUBH+3gJYBX+XBk7lqOOUpvmiLjol/E5lCqq0M1CqLWA== - SLACK_WEBHOOK_URL: RJjhS0Hhz0/s07sCIf1OTXmTGodpK9L2K9p953Z+fOX0l2SkPFT6mB9+yIrLufmlwEaku5NNEBKy//+AG01yOd+7wV1GhK65vfj3B/gTN8t5cuVnR4vFxKY5Rx4eSGLtzyAs+aIBTp4GoNXDIjroCqfCjPkItEZWCg== -general_settings: - alerting: - - slack - alerting_threshold: 300 - database_connection_pool_limit: 100 - database_connection_timeout: 60 - disable_master_key_return: true - health_check_interval: 300 - proxy_batch_write_at: 60 - ui_access_mode: all - # master_key: sk-1234 +model_list: + - model_name: my-fake-model + litellm_params: + model: gpt-3.5-turbo + api_key: my-fake-key + mock_response: hello-world + - model_name: gpt-4o + litellm_params: + model: azure/gpt-4o + api_base: https://litellm8397336933.openai.azure.com/ + api_key: 610f806211ab47f2a694493000045858 + litellm_settings: - allowed_fails: 3 - failure_callback: - - prometheus - num_retries: 3 - service_callback: - - prometheus_system - success_callback: - - langfuse - - prometheus - - langsmith -model_list: -- litellm_params: - model: gpt-3.5-turbo - model_name: gpt-3.5-turbo -- litellm_params: - api_base: https://openai-function-calling-workers.tasslexyz.workers.dev/ - api_key: my-fake-key - model: openai/my-fake-model - stream_timeout: 0.001 - model_name: fake-openai-endpoint -- litellm_params: - api_base: https://openai-function-calling-workers.tasslexyz.workers.dev/ - api_key: my-fake-key - model: openai/my-fake-model-2 - stream_timeout: 0.001 - model_name: fake-openai-endpoint -- litellm_params: - api_base: os.environ/AZURE_API_BASE - api_key: os.environ/AZURE_API_KEY - api_version: 2023-07-01-preview - model: azure/chatgpt-v-2 - stream_timeout: 0.001 - model_name: azure-gpt-3.5 -- litellm_params: - api_key: os.environ/OPENAI_API_KEY - model: text-embedding-ada-002 - model_name: text-embedding-ada-002 -- litellm_params: - model: text-completion-openai/gpt-3.5-turbo-instruct - model_name: gpt-instruct -router_settings: - enable_pre_call_checks: true - redis_host: os.environ/REDIS_HOST - redis_password: os.environ/REDIS_PASSWORD - redis_port: os.environ/REDIS_PORT + content_policy_fallbacks: [{"gpt-4o": ["my-fake-model"]}] \ No newline at end of file diff --git a/litellm/router.py b/litellm/router.py index 69000d604..a726e7f44 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -562,6 +562,18 @@ class Router: f"litellm.completion(model={model_name})\033[32m 200 OK\033[0m" ) + ## CHECK CONTENT FILTER ERROR ## + if isinstance(response, ModelResponse): + _should_raise = self._should_raise_content_policy_error( + model=model, response=response, kwargs=kwargs + ) + if _should_raise: + raise litellm.ContentPolicyViolationError( + message="Response output was blocked.", + model=model, + llm_provider="", + ) + return response except Exception as e: verbose_router_logger.info( @@ -721,6 +733,18 @@ class Router: await self.async_routing_strategy_pre_call_checks(deployment=deployment) response = await _response + ## CHECK CONTENT FILTER ERROR ## + if isinstance(response, ModelResponse): + _should_raise = self._should_raise_content_policy_error( + model=model, response=response, kwargs=kwargs + ) + if _should_raise: + raise litellm.ContentPolicyViolationError( + message="Response output was blocked.", + model=model, + llm_provider="", + ) + self.success_calls[model_name] += 1 verbose_router_logger.info( f"litellm.acompletion(model={model_name})\033[32m 200 OK\033[0m" @@ -2801,6 +2825,40 @@ class Router: # Catch all - if any exceptions default to cooling down return True + def _should_raise_content_policy_error( + self, model: str, response: ModelResponse, kwargs: dict + ) -> bool: + """ + Determines if a content policy error should be raised. + + Only raised if a fallback is available. + + Else, original response is returned. + """ + if response.choices[0].finish_reason != "content_filter": + return False + + content_policy_fallbacks = kwargs.get( + "content_policy_fallbacks", self.content_policy_fallbacks + ) + ### ONLY RAISE ERROR IF CP FALLBACK AVAILABLE ### + if content_policy_fallbacks is not None: + fallback_model_group = None + for item in content_policy_fallbacks: # [{"gpt-3.5-turbo": ["gpt-4"]}] + if list(item.keys())[0] == model: + fallback_model_group = item[model] + break + + if fallback_model_group is not None: + return True + + verbose_router_logger.info( + "Content Policy Error occurred. No available fallbacks. Returning original response. model={}, content_policy_fallbacks={}".format( + model, content_policy_fallbacks + ) + ) + return False + def _set_cooldown_deployments( self, original_exception: Any, diff --git a/litellm/tests/test_router_fallbacks.py b/litellm/tests/test_router_fallbacks.py index 545eb23db..99d2a600c 100644 --- a/litellm/tests/test_router_fallbacks.py +++ b/litellm/tests/test_router_fallbacks.py @@ -1,8 +1,12 @@ #### What this tests #### # This tests calling router with fallback models -import sys, os, time -import traceback, asyncio +import asyncio +import os +import sys +import time +import traceback + import pytest sys.path.insert( @@ -762,9 +766,11 @@ def test_ausage_based_routing_fallbacks(): # The Request should fail azure/gpt-4-fast. Then fallback -> "azure/gpt-4-basic" -> "openai-gpt-4" # It should work with "openai-gpt-4" import os + + from dotenv import load_dotenv + import litellm from litellm import Router - from dotenv import load_dotenv load_dotenv() @@ -1112,9 +1118,19 @@ async def test_client_side_fallbacks_list(sync_mode): @pytest.mark.parametrize("sync_mode", [True, False]) +@pytest.mark.parametrize("content_filter_response_exception", [True, False]) @pytest.mark.asyncio -async def test_router_content_policy_fallbacks(sync_mode): +async def test_router_content_policy_fallbacks( + sync_mode, content_filter_response_exception +): os.environ["LITELLM_LOG"] = "DEBUG" + + if content_filter_response_exception: + mock_response = Exception("content filtering policy") + else: + mock_response = litellm.ModelResponse( + choices=[litellm.Choices(finish_reason="content_filter")] + ) router = Router( model_list=[ { @@ -1122,13 +1138,13 @@ async def test_router_content_policy_fallbacks(sync_mode): "litellm_params": { "model": "claude-2", "api_key": "", - "mock_response": Exception("content filtering policy"), + "mock_response": mock_response, }, }, { "model_name": "my-fallback-model", "litellm_params": { - "model": "claude-2", + "model": "openai/my-fake-model", "api_key": "", "mock_response": "This works!", }, @@ -1165,3 +1181,5 @@ async def test_router_content_policy_fallbacks(sync_mode): model="claude-2", messages=[{"role": "user", "content": "Hey, how's it going?"}], ) + + assert response.model == "my-fake-model" diff --git a/litellm/types/router.py b/litellm/types/router.py index 206216ef0..83c21e5d9 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -12,6 +12,7 @@ from pydantic import BaseModel, ConfigDict, Field from .completion import CompletionRequest from .embedding import EmbeddingRequest +from .utils import ModelResponse class ModelConfig(BaseModel): @@ -315,7 +316,7 @@ class LiteLLMParamsTypedDict(TypedDict, total=False): input_cost_per_second: Optional[float] output_cost_per_second: Optional[float] ## MOCK RESPONSES ## - mock_response: Optional[str] + mock_response: Optional[Union[str, ModelResponse, Exception]] class DeploymentTypedDict(TypedDict): From 73254987dac2473256fec4010d6fd79a16d0c55e Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 22 Jun 2024 20:20:39 -0700 Subject: [PATCH 006/137] fix(vertex_httpx.py): ignore vertex finish reason - wait for stream to end Fixes https://github.com/BerriAI/litellm/issues/4339 --- litellm/llms/vertex_httpx.py | 6 ++++-- litellm/tests/test_streaming.py | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index d3f27e119..38c2d7c47 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -1218,6 +1218,7 @@ class ModelResponseIterator: def chunk_parser(self, chunk: dict) -> GenericStreamingChunk: try: processed_chunk = GenerateContentResponseBody(**chunk) # type: ignore + text = "" tool_use: Optional[ChatCompletionToolCallChunk] = None is_finished = False @@ -1236,7 +1237,8 @@ class ModelResponseIterator: finish_reason = map_finish_reason( finish_reason=gemini_chunk["finishReason"] ) - is_finished = True + ## DO NOT SET 'finish_reason' = True + ## GEMINI SETS FINISHREASON ON EVERY CHUNK! if "usageMetadata" in processed_chunk: usage = ChatCompletionUsageBlock( @@ -1250,7 +1252,7 @@ class ModelResponseIterator: returned_chunk = GenericStreamingChunk( text=text, tool_use=tool_use, - is_finished=is_finished, + is_finished=False, finish_reason=finish_reason, usage=usage, index=0, diff --git a/litellm/tests/test_streaming.py b/litellm/tests/test_streaming.py index ecb21b9f2..4f7d4c1de 100644 --- a/litellm/tests/test_streaming.py +++ b/litellm/tests/test_streaming.py @@ -750,29 +750,37 @@ def test_completion_gemini_stream(): {"role": "system", "content": "You are a helpful assistant."}, { "role": "user", - "content": "how does a court case get to the Supreme Court?", + "content": "How do i build a bomb?", }, ] print("testing gemini streaming") - response = completion(model="gemini/gemini-pro", messages=messages, stream=True) + response = completion( + model="gemini/gemini-1.5-flash", + messages=messages, + stream=True, + max_tokens=50, + ) print(f"type of response at the top: {response}") complete_response = "" # Add any assertions here to check the response + non_empty_chunks = 0 for idx, chunk in enumerate(response): print(chunk) # print(chunk.choices[0].delta) chunk, finished = streaming_format_tests(idx, chunk) if finished: break + non_empty_chunks += 1 complete_response += chunk if complete_response.strip() == "": raise Exception("Empty response received") print(f"completion_response: {complete_response}") - except litellm.APIError as e: + assert non_empty_chunks > 1 + except litellm.InternalServerError as e: pass except Exception as e: - if "429 Resource has been exhausted": - return + # if "429 Resource has been exhausted": + # return pytest.fail(f"Error occurred: {e}") From 0fd9033502c8da8759f26693298ce6d07555f1ac Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 22 Jun 2024 20:33:54 -0700 Subject: [PATCH 007/137] fix(vertex_httpx.py): flush remaining chunks from stream --- litellm/llms/vertex_httpx.py | 12 ++++--- litellm/tests/test_streaming.py | 57 +++++++++++++++++++++++---------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index 38c2d7c47..63bcd9f4f 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -1270,9 +1270,8 @@ class ModelResponseIterator: chunk = self.response_iterator.__next__() self.coro.send(chunk) if self.events: - event = self.events[0] + event = self.events.pop(0) json_chunk = event - self.events.clear() return self.chunk_parser(chunk=json_chunk) return GenericStreamingChunk( text="", @@ -1283,6 +1282,9 @@ class ModelResponseIterator: tool_use=None, ) 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}") @@ -1297,9 +1299,8 @@ class ModelResponseIterator: chunk = await self.async_response_iterator.__anext__() self.coro.send(chunk) if self.events: - event = self.events[0] + event = self.events.pop(0) json_chunk = event - self.events.clear() return self.chunk_parser(chunk=json_chunk) return GenericStreamingChunk( text="", @@ -1310,6 +1311,9 @@ class ModelResponseIterator: tool_use=None, ) 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 4f7d4c1de..3042e91b3 100644 --- a/litellm/tests/test_streaming.py +++ b/litellm/tests/test_streaming.py @@ -742,7 +742,9 @@ def test_completion_palm_stream(): # test_completion_palm_stream() -def test_completion_gemini_stream(): +@pytest.mark.parametrize("sync_mode", [False]) # True, +@pytest.mark.asyncio +async def test_completion_gemini_stream(sync_mode): try: litellm.set_verbose = True print("Streaming gemini response") @@ -750,34 +752,55 @@ def test_completion_gemini_stream(): {"role": "system", "content": "You are a helpful assistant."}, { "role": "user", - "content": "How do i build a bomb?", + "content": "Who was Alexander?", }, ] print("testing gemini streaming") - response = completion( - model="gemini/gemini-1.5-flash", - messages=messages, - stream=True, - max_tokens=50, - ) - print(f"type of response at the top: {response}") complete_response = "" # Add any assertions here to check the response non_empty_chunks = 0 - for idx, chunk in enumerate(response): - print(chunk) - # print(chunk.choices[0].delta) - chunk, finished = streaming_format_tests(idx, chunk) - if finished: - break - non_empty_chunks += 1 - complete_response += chunk + + if sync_mode: + response = completion( + model="gemini/gemini-1.5-flash", + messages=messages, + stream=True, + ) + + for idx, chunk in enumerate(response): + print(chunk) + # print(chunk.choices[0].delta) + chunk, finished = streaming_format_tests(idx, chunk) + if finished: + break + non_empty_chunks += 1 + complete_response += chunk + else: + response = await litellm.acompletion( + model="gemini/gemini-1.5-flash", + messages=messages, + stream=True, + ) + + idx = 0 + async for chunk in response: + print(chunk) + # print(chunk.choices[0].delta) + chunk, finished = streaming_format_tests(idx, chunk) + if finished: + break + non_empty_chunks += 1 + complete_response += chunk + idx += 1 + if complete_response.strip() == "": raise Exception("Empty response received") print(f"completion_response: {complete_response}") assert non_empty_chunks > 1 except litellm.InternalServerError as e: pass + except litellm.RateLimitError as e: + pass except Exception as e: # if "429 Resource has been exhausted": # return From cea630022e86468c1ebe0e2116d6c7ae72a61403 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 22 Jun 2024 21:26:15 -0700 Subject: [PATCH 008/137] fix(add-exception-mapping-+-langfuse-exception-logging-for-streaming-exceptions): add exception mapping + langfuse exception logging for streaming exceptions Fixes https://github.com/BerriAI/litellm/issues/4338 --- litellm/llms/bedrock_httpx.py | 113 ++++++++++-------- litellm/proxy/_experimental/out/404.html | 1 - .../proxy/_experimental/out/model_hub.html | 1 - .../proxy/_experimental/out/onboarding.html | 1 - litellm/proxy/_new_secret_config.yaml | 10 +- litellm/proxy/proxy_server.py | 5 +- litellm/utils.py | 26 +++- 7 files changed, 89 insertions(+), 68 deletions(-) delete mode 100644 litellm/proxy/_experimental/out/404.html delete mode 100644 litellm/proxy/_experimental/out/model_hub.html delete mode 100644 litellm/proxy/_experimental/out/onboarding.html diff --git a/litellm/llms/bedrock_httpx.py b/litellm/llms/bedrock_httpx.py index 510bf7c7c..84ab10907 100644 --- a/litellm/llms/bedrock_httpx.py +++ b/litellm/llms/bedrock_httpx.py @@ -1,63 +1,64 @@ # What is this? ## Initial implementation of calling bedrock via httpx client (allows for async calls). ## V1 - covers cohere + anthropic claude-3 support -from functools import partial -import os, types +import copy import json -from enum import Enum -import requests, copy # type: ignore +import os import time +import types +import urllib.parse +import uuid +from enum import Enum +from functools import partial from typing import ( + Any, + AsyncIterator, Callable, - Optional, + Iterator, List, Literal, - Union, - Any, - TypedDict, + Optional, Tuple, - Iterator, - AsyncIterator, -) -from litellm.utils import ( - ModelResponse, - Usage, - CustomStreamWrapper, - get_secret, + TypedDict, + Union, ) + +import httpx # type: ignore +import requests # type: ignore + +import litellm +from litellm.caching import DualCache from litellm.litellm_core_utils.core_helpers import map_finish_reason from litellm.litellm_core_utils.litellm_logging import Logging -from litellm.types.utils import Message, Choices -import litellm, uuid -from .prompt_templates.factory import ( - prompt_factory, - custom_prompt, - cohere_message_pt, - construct_tool_use_system_prompt, - extract_between_tags, - parse_xml_params, - contains_tag, - _bedrock_converse_messages_pt, - _bedrock_tools_pt, -) from litellm.llms.custom_httpx.http_handler import ( AsyncHTTPHandler, HTTPHandler, _get_async_httpx_client, _get_httpx_client, ) -from .base import BaseLLM -import httpx # type: ignore -from .bedrock import BedrockError, convert_messages_to_prompt, ModelResponseIterator from litellm.types.llms.bedrock import * -import urllib.parse from litellm.types.llms.openai import ( + ChatCompletionDeltaChunk, ChatCompletionResponseMessage, ChatCompletionToolCallChunk, ChatCompletionToolCallFunctionChunk, - ChatCompletionDeltaChunk, ) -from litellm.caching import DualCache +from litellm.types.utils import Choices, Message +from litellm.utils import CustomStreamWrapper, ModelResponse, Usage, get_secret + +from .base import BaseLLM +from .bedrock import BedrockError, ModelResponseIterator, convert_messages_to_prompt +from .prompt_templates.factory import ( + _bedrock_converse_messages_pt, + _bedrock_tools_pt, + cohere_message_pt, + construct_tool_use_system_prompt, + contains_tag, + custom_prompt, + extract_between_tags, + parse_xml_params, + prompt_factory, +) iam_cache = DualCache() @@ -171,26 +172,34 @@ async def make_call( messages: list, logging_obj, ): - if client is None: - client = _get_async_httpx_client() # Create a new client if none provided + try: + 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) + response = await client.post(api_base, headers=headers, data=data, stream=True) - if response.status_code != 200: - raise BedrockError(status_code=response.status_code, message=response.text) + if response.status_code != 200: + raise BedrockError(status_code=response.status_code, message=response.text) - decoder = AWSEventStreamDecoder(model=model) - completion_stream = decoder.aiter_bytes(response.aiter_bytes(chunk_size=1024)) + decoder = AWSEventStreamDecoder(model=model) + completion_stream = decoder.aiter_bytes(response.aiter_bytes(chunk_size=1024)) - # LOGGING - logging_obj.post_call( - input=messages, - api_key="", - original_response="first stream response received", - additional_args={"complete_input_dict": data}, - ) + # LOGGING + logging_obj.post_call( + input=messages, + api_key="", + original_response="first stream response received", + additional_args={"complete_input_dict": data}, + ) - return completion_stream + return completion_stream + except httpx.HTTPStatusError as err: + error_code = err.response.status_code + raise BedrockError(status_code=error_code, message=str(err)) + except httpx.TimeoutException as e: + raise BedrockError(status_code=408, message="Timeout error occurred.") + except Exception as e: + raise BedrockError(status_code=500, message=str(e)) def make_sync_call( @@ -704,7 +713,6 @@ class BedrockLLM(BaseLLM): ) -> Union[ModelResponse, CustomStreamWrapper]: try: import boto3 - from botocore.auth import SigV4Auth from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials @@ -1650,7 +1658,6 @@ class BedrockConverseLLM(BaseLLM): ): try: import boto3 - from botocore.auth import SigV4Auth from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials @@ -1904,8 +1911,8 @@ class BedrockConverseLLM(BaseLLM): def get_response_stream_shape(): - from botocore.model import ServiceModel from botocore.loaders import Loader + from botocore.model import ServiceModel loader = Loader() bedrock_service_dict = loader.load_service_model("bedrock-runtime", "service-2") diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html deleted file mode 100644 index 909f71542..000000000 --- a/litellm/proxy/_experimental/out/404.html +++ /dev/null @@ -1 +0,0 @@ -404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/litellm/proxy/_experimental/out/model_hub.html b/litellm/proxy/_experimental/out/model_hub.html deleted file mode 100644 index ef01db585..000000000 --- a/litellm/proxy/_experimental/out/model_hub.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding.html deleted file mode 100644 index ff88e53c9..000000000 --- a/litellm/proxy/_experimental/out/onboarding.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 01f09ca02..a90a79dbd 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -1,10 +1,10 @@ model_list: - model_name: my-fake-model litellm_params: - model: gpt-3.5-turbo + model: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 api_key: my-fake-key - mock_response: hello-world - tpm: 60 + aws_bedrock_runtime_endpoint: http://127.0.0.1:8000 -litellm_settings: - callbacks: ["dynamic_rate_limiter"] \ No newline at end of file +litellm_settings: + success_callback: ["langfuse"] + failure_callback: ["langfuse"] \ No newline at end of file diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 4cac93b24..30b90abe6 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -2526,11 +2526,10 @@ async def async_data_generator( yield f"data: {done_message}\n\n" except Exception as e: verbose_proxy_logger.error( - "litellm.proxy.proxy_server.async_data_generator(): Exception occured - {}".format( - str(e) + "litellm.proxy.proxy_server.async_data_generator(): Exception occured - {}\n{}".format( + str(e), traceback.format_exc() ) ) - verbose_proxy_logger.debug(traceback.format_exc()) await proxy_logging_obj.post_call_failure_hook( user_api_key_dict=user_api_key_dict, original_exception=e, diff --git a/litellm/utils.py b/litellm/utils.py index 19d99ff59..0849ba3a2 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -9595,6 +9595,11 @@ class CustomStreamWrapper: litellm.request_timeout ) if self.logging_obj is not None: + ## LOGGING + threading.Thread( + target=self.logging_obj.failure_handler, + args=(e, traceback_exception), + ).start() # log response # Handle any exceptions that might occur during streaming asyncio.create_task( self.logging_obj.async_failure_handler(e, traceback_exception) @@ -9602,11 +9607,24 @@ class CustomStreamWrapper: raise e except Exception as e: traceback_exception = traceback.format_exc() - # Handle any exceptions that might occur during streaming - asyncio.create_task( - self.logging_obj.async_failure_handler(e, traceback_exception) # type: ignore + if self.logging_obj is not None: + ## LOGGING + threading.Thread( + target=self.logging_obj.failure_handler, + args=(e, traceback_exception), + ).start() # log response + # Handle any exceptions that might occur during streaming + asyncio.create_task( + self.logging_obj.async_failure_handler(e, traceback_exception) # type: ignore + ) + ## Map to OpenAI Exception + raise exception_type( + model=self.model, + custom_llm_provider=self.custom_llm_provider, + original_exception=e, + completion_kwargs={}, + extra_kwargs={}, ) - raise e class TextCompletionStreamWrapper: From bf12536fdbf3577cb1e54f35c4061df37b039f5b Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 22 Jun 2024 21:34:55 -0700 Subject: [PATCH 009/137] =?UTF-8?q?bump:=20version=201.40.24=20=E2=86=92?= =?UTF-8?q?=201.40.25?= 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 3254ae2e2..fc3526dcc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.40.24" +version = "1.40.25" 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.24" +version = "1.40.25" version_files = [ "pyproject.toml:^version" ] From 4d9a0d45b2b6a3500705c9c31d366c09cb222ed0 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 22 Jun 2024 21:38:01 -0700 Subject: [PATCH 010/137] docs(team_budgets.md): cleanup docs --- docs/my-website/docs/proxy/team_budgets.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/my-website/docs/proxy/team_budgets.md b/docs/my-website/docs/proxy/team_budgets.md index 9ab0c0786..7d5284de7 100644 --- a/docs/my-website/docs/proxy/team_budgets.md +++ b/docs/my-website/docs/proxy/team_budgets.md @@ -156,7 +156,7 @@ litellm_remaining_team_budget_metric{team_alias="QA Prod Bot",team_id="de35b29e- Prevent projects from gobbling too much quota. -Dynamically allocate TPM quota to api keys, based on active keys in that minute. +Dynamically allocate TPM quota to api keys, based on active keys in that minute. [**See Code**](https://github.com/BerriAI/litellm/blob/9bffa9a48e610cc6886fc2dce5c1815aeae2ad46/litellm/proxy/hooks/dynamic_rate_limiter.py#L125) 1. Setup config.yaml @@ -192,12 +192,7 @@ litellm --config /path/to/config.yaml - Mock response returns 30 total tokens / request - Each team will only be able to make 1 request per minute """ -""" -- Run 2 concurrent teams calling same model -- model has 60 TPM -- Mock response returns 30 total tokens / request -- Each team will only be able to make 1 request per minute -""" + import requests from openai import OpenAI, RateLimitError From dc43ffb77148bc3227e473040ef795c046b87b7d Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 22 Jun 2024 21:57:54 -0700 Subject: [PATCH 011/137] fix(redact_messages.py): fix get --- litellm/litellm_core_utils/redact_messages.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/litellm/litellm_core_utils/redact_messages.py b/litellm/litellm_core_utils/redact_messages.py index cc616afec..fa4308da9 100644 --- a/litellm/litellm_core_utils/redact_messages.py +++ b/litellm/litellm_core_utils/redact_messages.py @@ -9,6 +9,7 @@ import copy from typing import TYPE_CHECKING, Any + import litellm if TYPE_CHECKING: @@ -28,13 +29,24 @@ def redact_message_input_output_from_logging( Removes messages, prompts, input, response from logging. This modifies the data in-place only redacts when litellm.turn_off_message_logging == True """ - request_headers = litellm_logging_obj.model_call_details['litellm_params']['metadata']['headers'] + _request_headers = ( + litellm_logging_obj.model_call_details.get("litellm_params", {}).get( + "metadata", {} + ) + or {} + ) + + request_headers = _request_headers.get("headers", {}) # check if user opted out of logging message/response to callbacks - if litellm.turn_off_message_logging is not True and request_headers.get('litellm-enable-message-redaction', False): + if litellm.turn_off_message_logging is not True and request_headers.get( + "litellm-enable-message-redaction", False + ): return result - if request_headers and request_headers.get('litellm-disable-message-redaction', False): + if request_headers and request_headers.get( + "litellm-disable-message-redaction", False + ): return result # remove messages, prompts, input, response from logging From 9450d2cf778a232a252b5ece5d432d6e3efe9753 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 22 Jun 2024 22:43:56 -0700 Subject: [PATCH 012/137] fix(test_dynamic_rate_limit_handler.py): cleanup --- litellm/tests/test_dynamic_rate_limit_handler.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/litellm/tests/test_dynamic_rate_limit_handler.py b/litellm/tests/test_dynamic_rate_limit_handler.py index c3fcca6a6..6e1b55d18 100644 --- a/litellm/tests/test_dynamic_rate_limit_handler.py +++ b/litellm/tests/test_dynamic_rate_limit_handler.py @@ -214,23 +214,23 @@ async def test_base_case(dynamic_rate_limit_handler, mock_response): prev_availability: Optional[int] = None allowed_fails = 1 - for _ in range(5): + for _ in range(2): try: # check availability availability, _, _ = await dynamic_rate_limit_handler.check_available_tpm( model=model ) - ## assert availability updated - if prev_availability is not None and availability is not None: - assert availability == prev_availability - 10 - print( "prev_availability={}, availability={}".format( prev_availability, availability ) ) + ## assert availability updated + if prev_availability is not None and availability is not None: + assert availability == prev_availability - 10 + prev_availability = availability # make call From d857d82b0e82a2e2f7a4303c68626ec9a29a17c0 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 22 Jun 2024 23:27:13 -0700 Subject: [PATCH 013/137] fix(redact_messages.py): fix pr --- litellm/litellm_core_utils/redact_messages.py | 5 +- litellm/tests/langfuse.log | 206 +++++++++++++----- 2 files changed, 151 insertions(+), 60 deletions(-) diff --git a/litellm/litellm_core_utils/redact_messages.py b/litellm/litellm_core_utils/redact_messages.py index fa4308da9..378c46ba0 100644 --- a/litellm/litellm_core_utils/redact_messages.py +++ b/litellm/litellm_core_utils/redact_messages.py @@ -39,8 +39,9 @@ def redact_message_input_output_from_logging( request_headers = _request_headers.get("headers", {}) # check if user opted out of logging message/response to callbacks - if litellm.turn_off_message_logging is not True and request_headers.get( - "litellm-enable-message-redaction", False + if ( + litellm.turn_off_message_logging is not True + and request_headers.get("litellm-enable-message-redaction", False) is not True ): return result diff --git a/litellm/tests/langfuse.log b/litellm/tests/langfuse.log index 61bc6ada5..1921f3136 100644 --- a/litellm/tests/langfuse.log +++ b/litellm/tests/langfuse.log @@ -1,77 +1,167 @@ +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +Creating trace id='52a58bac-492b-433e-9228-2759b73303a6' timestamp=datetime.datetime(2024, 6, 23, 6, 26, 45, 565911, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id='langfuse_latency_test_user' input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': "It's simple to use and easy to get started", 'role': 'assistant'} session_id=None release=None version=None metadata=None tags=[] public=None +Creating trace id='28bc21fe-5955-4ec5-ba39-27325718af5a' timestamp=datetime.datetime(2024, 6, 23, 6, 26, 45, 566213, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id='langfuse_latency_test_user' input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': "It's simple to use and easy to get started", 'role': 'assistant'} session_id=None release=None version=None metadata=None tags=[] public=None +Creating generation trace_id='52a58bac-492b-433e-9228-2759b73303a6' name='litellm-acompletion' start_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 561383) metadata={'litellm_response_cost': None, 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': "It's simple to use and easy to get started", 'role': 'assistant'} level= status_message=None parent_observation_id=None version=None id='time-23-26-45-561383_chatcmpl-193fd5b6-87ce-4b8f-90bb-e2c2608f0f73' end_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 564028) completion_start_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 564028) model='chatgpt-v-2' model_parameters={'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=None) prompt_name=None prompt_version=None... +Creating generation trace_id='28bc21fe-5955-4ec5-ba39-27325718af5a' name='litellm-acompletion' start_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 562146) metadata={'litellm_response_cost': None, 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': "It's simple to use and easy to get started", 'role': 'assistant'} level= status_message=None parent_observation_id=None version=None id='time-23-26-45-562146_chatcmpl-2dc26df5-d4e4-46f5-868e-138aac85dd95' end_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 564312) completion_start_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 564312) model='chatgpt-v-2' model_parameters={'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=None) prompt_name=None prompt_version=None... +item size 459 +Creating trace id='f545a5c8-dfdf-4226-a30c-f24ff8d75144' timestamp=datetime.datetime(2024, 6, 23, 6, 26, 45, 567765, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id='langfuse_latency_test_user' input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': "It's simple to use and easy to get started", 'role': 'assistant'} session_id=None release=None version=None metadata=None tags=[] public=None +item size 459 +Creating trace id='c8d266ca-c370-439e-9d14-f011e5cfa254' timestamp=datetime.datetime(2024, 6, 23, 6, 26, 45, 568137, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id='langfuse_latency_test_user' input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': "It's simple to use and easy to get started", 'role': 'assistant'} session_id=None release=None version=None metadata=None tags=[] public=None +Creating generation trace_id='f545a5c8-dfdf-4226-a30c-f24ff8d75144' name='litellm-acompletion' start_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 562753) metadata={'litellm_response_cost': None, 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': "It's simple to use and easy to get started", 'role': 'assistant'} level= status_message=None parent_observation_id=None version=None id='time-23-26-45-562753_chatcmpl-33ae3e6d-d66a-4447-82d9-c8f5d5be43e5' end_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 564869) completion_start_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 564869) model='chatgpt-v-2' model_parameters={'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=None) prompt_name=None prompt_version=None... +item size 887 +Creating generation trace_id='c8d266ca-c370-439e-9d14-f011e5cfa254' name='litellm-acompletion' start_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 563300) metadata={'litellm_response_cost': None, 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': "It's simple to use and easy to get started", 'role': 'assistant'} level= status_message=None parent_observation_id=None version=None id='time-23-26-45-563300_chatcmpl-56c11246-4c9c-43c0-bb4e-0be309907acd' end_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 565142) completion_start_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 565142) model='chatgpt-v-2' model_parameters={'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=None) prompt_name=None prompt_version=None... +item size 887 +item size 459 +item size 459 +item size 887 +item size 887 +Creating trace id='7c6fec55-def1-4838-8ea1-86960a1ccb19' timestamp=datetime.datetime(2024, 6, 23, 6, 26, 45, 570331, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id='langfuse_latency_test_user' input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': "It's simple to use and easy to get started", 'role': 'assistant'} session_id=None release=None version=None metadata=None tags=[] public=None +Creating generation trace_id='7c6fec55-def1-4838-8ea1-86960a1ccb19' name='litellm-acompletion' start_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 563792) metadata={'litellm_response_cost': None, 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': "It's simple to use and easy to get started", 'role': 'assistant'} level= status_message=None parent_observation_id=None version=None id='time-23-26-45-563792_chatcmpl-c159069a-bc65-43a0-bef5-e2d42688cead' end_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 569384) completion_start_time=datetime.datetime(2024, 6, 22, 23, 26, 45, 569384) model='chatgpt-v-2' model_parameters={'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=None) prompt_name=None prompt_version=None... +item size 459 +item size 887 +~0 items in the Langfuse queue +uploading batch of 10 items +uploading data: {'batch': [{'id': 'cd6c78ba-81aa-4106-bc92-48adbda0ef1b', 'type': 'trace-create', 'body': {'id': '52a58bac-492b-433e-9228-2759b73303a6', 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 565911, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'userId': 'langfuse_latency_test_user', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'tags': []}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 566569, tzinfo=datetime.timezone.utc)}, {'id': '57b678c1-d620-4aad-8052-1722a498972e', 'type': 'trace-create', 'body': {'id': '28bc21fe-5955-4ec5-ba39-27325718af5a', 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 566213, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'userId': 'langfuse_latency_test_user', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'tags': []}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 566947, tzinfo=datetime.timezone.utc)}, {'id': '831370be-b2bd-48d8-b32b-bfcaf103712b', 'type': 'generation-create', 'body': {'traceId': '52a58bac-492b-433e-9228-2759b73303a6', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 561383), 'metadata': {'litellm_response_cost': None, 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'level': , 'id': 'time-23-26-45-561383_chatcmpl-193fd5b6-87ce-4b8f-90bb-e2c2608f0f73', 'endTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 564028), 'completionStartTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 564028), 'model': 'chatgpt-v-2', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': }}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 567294, tzinfo=datetime.timezone.utc)}, {'id': '571fe93d-34b4-405e-98b4-e47b538b884a', 'type': 'generation-create', 'body': {'traceId': '28bc21fe-5955-4ec5-ba39-27325718af5a', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 562146), 'metadata': {'litellm_response_cost': None, 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'level': , 'id': 'time-23-26-45-562146_chatcmpl-2dc26df5-d4e4-46f5-868e-138aac85dd95', 'endTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 564312), 'completionStartTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 564312), 'model': 'chatgpt-v-2', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': }}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 567688, tzinfo=datetime.timezone.utc)}, {'id': '13ae52b9-7480-4b2e-977c-e85f422f9a16', 'type': 'trace-create', 'body': {'id': 'f545a5c8-dfdf-4226-a30c-f24ff8d75144', 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 567765, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'userId': 'langfuse_latency_test_user', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'tags': []}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 568357, tzinfo=datetime.timezone.utc)}, {'id': '7498e67e-0b2b-451c-8533-a35de0aed092', 'type': 'trace-create', 'body': {'id': 'c8d266ca-c370-439e-9d14-f011e5cfa254', 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 568137, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'userId': 'langfuse_latency_test_user', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'tags': []}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 568812, tzinfo=datetime.timezone.utc)}, {'id': '2656f364-b367-442a-a694-19dd159a0769', 'type': 'generation-create', 'body': {'traceId': 'f545a5c8-dfdf-4226-a30c-f24ff8d75144', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 562753), 'metadata': {'litellm_response_cost': None, 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'level': , 'id': 'time-23-26-45-562753_chatcmpl-33ae3e6d-d66a-4447-82d9-c8f5d5be43e5', 'endTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 564869), 'completionStartTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 564869), 'model': 'chatgpt-v-2', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': }}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 569165, tzinfo=datetime.timezone.utc)}, {'id': '8c42f89e-be59-4226-812e-bc849d35ab59', 'type': 'generation-create', 'body': {'traceId': 'c8d266ca-c370-439e-9d14-f011e5cfa254', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 563300), 'metadata': {'litellm_response_cost': None, 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'level': , 'id': 'time-23-26-45-563300_chatcmpl-56c11246-4c9c-43c0-bb4e-0be309907acd', 'endTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 565142), 'completionStartTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 565142), 'model': 'chatgpt-v-2', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': }}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 569494, tzinfo=datetime.timezone.utc)}, {'id': 'a926d1eb-68ed-484c-a9b9-3d82938a7d28', 'type': 'trace-create', 'body': {'id': '7c6fec55-def1-4838-8ea1-86960a1ccb19', 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 570331, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'userId': 'langfuse_latency_test_user', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'tags': []}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 570495, tzinfo=datetime.timezone.utc)}, {'id': '97b5dee7-a3b2-4526-91cb-75dac909c78f', 'type': 'generation-create', 'body': {'traceId': '7c6fec55-def1-4838-8ea1-86960a1ccb19', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 563792), 'metadata': {'litellm_response_cost': None, 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'level': , 'id': 'time-23-26-45-563792_chatcmpl-c159069a-bc65-43a0-bef5-e2d42688cead', 'endTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 569384), 'completionStartTime': datetime.datetime(2024, 6, 22, 23, 26, 45, 569384), 'model': 'chatgpt-v-2', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': }}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 45, 570858, tzinfo=datetime.timezone.utc)}], 'metadata': {'batch_size': 10, 'sdk_integration': 'default', 'sdk_name': 'python', 'sdk_version': '2.32.0', 'public_key': 'pk-lf-b3db7e8e-c2f6-4fc7-825c-a541a8fbe003'}} +making request: {"batch": [{"id": "cd6c78ba-81aa-4106-bc92-48adbda0ef1b", "type": "trace-create", "body": {"id": "52a58bac-492b-433e-9228-2759b73303a6", "timestamp": "2024-06-23T06:26:45.565911Z", "name": "litellm-acompletion", "userId": "langfuse_latency_test_user", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "tags": []}, "timestamp": "2024-06-23T06:26:45.566569Z"}, {"id": "57b678c1-d620-4aad-8052-1722a498972e", "type": "trace-create", "body": {"id": "28bc21fe-5955-4ec5-ba39-27325718af5a", "timestamp": "2024-06-23T06:26:45.566213Z", "name": "litellm-acompletion", "userId": "langfuse_latency_test_user", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "tags": []}, "timestamp": "2024-06-23T06:26:45.566947Z"}, {"id": "831370be-b2bd-48d8-b32b-bfcaf103712b", "type": "generation-create", "body": {"traceId": "52a58bac-492b-433e-9228-2759b73303a6", "name": "litellm-acompletion", "startTime": "2024-06-22T23:26:45.561383-07:00", "metadata": {"litellm_response_cost": null, "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "level": "DEFAULT", "id": "time-23-26-45-561383_chatcmpl-193fd5b6-87ce-4b8f-90bb-e2c2608f0f73", "endTime": "2024-06-22T23:26:45.564028-07:00", "completionStartTime": "2024-06-22T23:26:45.564028-07:00", "model": "chatgpt-v-2", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS"}}, "timestamp": "2024-06-23T06:26:45.567294Z"}, {"id": "571fe93d-34b4-405e-98b4-e47b538b884a", "type": "generation-create", "body": {"traceId": "28bc21fe-5955-4ec5-ba39-27325718af5a", "name": "litellm-acompletion", "startTime": "2024-06-22T23:26:45.562146-07:00", "metadata": {"litellm_response_cost": null, "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "level": "DEFAULT", "id": "time-23-26-45-562146_chatcmpl-2dc26df5-d4e4-46f5-868e-138aac85dd95", "endTime": "2024-06-22T23:26:45.564312-07:00", "completionStartTime": "2024-06-22T23:26:45.564312-07:00", "model": "chatgpt-v-2", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS"}}, "timestamp": "2024-06-23T06:26:45.567688Z"}, {"id": "13ae52b9-7480-4b2e-977c-e85f422f9a16", "type": "trace-create", "body": {"id": "f545a5c8-dfdf-4226-a30c-f24ff8d75144", "timestamp": "2024-06-23T06:26:45.567765Z", "name": "litellm-acompletion", "userId": "langfuse_latency_test_user", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "tags": []}, "timestamp": "2024-06-23T06:26:45.568357Z"}, {"id": "7498e67e-0b2b-451c-8533-a35de0aed092", "type": "trace-create", "body": {"id": "c8d266ca-c370-439e-9d14-f011e5cfa254", "timestamp": "2024-06-23T06:26:45.568137Z", "name": "litellm-acompletion", "userId": "langfuse_latency_test_user", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "tags": []}, "timestamp": "2024-06-23T06:26:45.568812Z"}, {"id": "2656f364-b367-442a-a694-19dd159a0769", "type": "generation-create", "body": {"traceId": "f545a5c8-dfdf-4226-a30c-f24ff8d75144", "name": "litellm-acompletion", "startTime": "2024-06-22T23:26:45.562753-07:00", "metadata": {"litellm_response_cost": null, "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "level": "DEFAULT", "id": "time-23-26-45-562753_chatcmpl-33ae3e6d-d66a-4447-82d9-c8f5d5be43e5", "endTime": "2024-06-22T23:26:45.564869-07:00", "completionStartTime": "2024-06-22T23:26:45.564869-07:00", "model": "chatgpt-v-2", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS"}}, "timestamp": "2024-06-23T06:26:45.569165Z"}, {"id": "8c42f89e-be59-4226-812e-bc849d35ab59", "type": "generation-create", "body": {"traceId": "c8d266ca-c370-439e-9d14-f011e5cfa254", "name": "litellm-acompletion", "startTime": "2024-06-22T23:26:45.563300-07:00", "metadata": {"litellm_response_cost": null, "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "level": "DEFAULT", "id": "time-23-26-45-563300_chatcmpl-56c11246-4c9c-43c0-bb4e-0be309907acd", "endTime": "2024-06-22T23:26:45.565142-07:00", "completionStartTime": "2024-06-22T23:26:45.565142-07:00", "model": "chatgpt-v-2", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS"}}, "timestamp": "2024-06-23T06:26:45.569494Z"}, {"id": "a926d1eb-68ed-484c-a9b9-3d82938a7d28", "type": "trace-create", "body": {"id": "7c6fec55-def1-4838-8ea1-86960a1ccb19", "timestamp": "2024-06-23T06:26:45.570331Z", "name": "litellm-acompletion", "userId": "langfuse_latency_test_user", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "tags": []}, "timestamp": "2024-06-23T06:26:45.570495Z"}, {"id": "97b5dee7-a3b2-4526-91cb-75dac909c78f", "type": "generation-create", "body": {"traceId": "7c6fec55-def1-4838-8ea1-86960a1ccb19", "name": "litellm-acompletion", "startTime": "2024-06-22T23:26:45.563792-07:00", "metadata": {"litellm_response_cost": null, "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "level": "DEFAULT", "id": "time-23-26-45-563792_chatcmpl-c159069a-bc65-43a0-bef5-e2d42688cead", "endTime": "2024-06-22T23:26:45.569384-07:00", "completionStartTime": "2024-06-22T23:26:45.569384-07:00", "model": "chatgpt-v-2", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS"}}, "timestamp": "2024-06-23T06:26:45.570858Z"}], "metadata": {"batch_size": 10, "sdk_integration": "default", "sdk_name": "python", "sdk_version": "2.32.0", "public_key": "pk-lf-b3db7e8e-c2f6-4fc7-825c-a541a8fbe003"}} to https://us.cloud.langfuse.com/api/public/ingestion +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +received response: {"errors":[],"successes":[{"id":"cd6c78ba-81aa-4106-bc92-48adbda0ef1b","status":201},{"id":"57b678c1-d620-4aad-8052-1722a498972e","status":201},{"id":"831370be-b2bd-48d8-b32b-bfcaf103712b","status":201},{"id":"571fe93d-34b4-405e-98b4-e47b538b884a","status":201},{"id":"13ae52b9-7480-4b2e-977c-e85f422f9a16","status":201},{"id":"7498e67e-0b2b-451c-8533-a35de0aed092","status":201},{"id":"2656f364-b367-442a-a694-19dd159a0769","status":201},{"id":"8c42f89e-be59-4226-812e-bc849d35ab59","status":201},{"id":"a926d1eb-68ed-484c-a9b9-3d82938a7d28","status":201},{"id":"97b5dee7-a3b2-4526-91cb-75dac909c78f","status":201}]} +successfully uploaded batch of 10 items +~0 items in the Langfuse queue consumer is running... -Creating trace id='litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' timestamp=datetime.datetime(2024, 5, 7, 20, 11, 22, 420643, tzinfo=datetime.timezone.utc) name='litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' user_id='litellm-test-user1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' input={'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]} output={'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'} session_id='litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' release='litellm-test-release' version='litellm-test-version' metadata={'trace_actual_metadata_key': 'trace_actual_metadata_value', 'generation_id': 'litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'} tags=['litellm-test-tag1', 'litellm-test-tag2', 'cache_hit:False'] public=None -adding task {'id': '9d380abe-bb42-480b-b48f-952ed6776e1c', 'type': 'trace-create', 'body': {'id': 'litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 420643, tzinfo=datetime.timezone.utc), 'name': 'litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'userId': 'litellm-test-user1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'input': {'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]}, 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'sessionId': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'release': 'litellm-test-release', 'version': 'litellm-test-version', 'metadata': {'trace_actual_metadata_key': 'trace_actual_metadata_value', 'generation_id': 'litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}, 'tags': ['litellm-test-tag1', 'litellm-test-tag2', 'cache_hit:False']}} -Creating generation trace_id='litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' name='litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' start_time=datetime.datetime(2024, 5, 7, 13, 11, 22, 419075) metadata={'gen_metadata_key': 'gen_metadata_value', 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]} output={'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'} level= status_message=None parent_observation_id=None version='litellm-test-version' id='litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' end_time=datetime.datetime(2024, 5, 7, 13, 11, 22, 419879) completion_start_time=datetime.datetime(2024, 5, 7, 13, 11, 22, 419879) model='gpt-3.5-turbo' model_parameters={'temperature': '0.2', 'max_tokens': 100, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=5.4999999999999995e-05) prompt_name=None prompt_version=None... -item size 1224 -adding task {'id': '0d3ae4f8-e352-4acd-98ec-d21be7e8f5eb', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'name': 'litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 419075), 'metadata': {'gen_metadata_key': 'gen_metadata_value', 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]}, 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'level': , 'version': 'litellm-test-version', 'id': 'litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 419879), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 419879), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.2', 'max_tokens': 100, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}} -item size 1359 -Creating trace id='litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' timestamp=datetime.datetime(2024, 5, 7, 20, 11, 22, 423093, tzinfo=datetime.timezone.utc) name='litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' user_id='litellm-test-user1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' input={'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]} output={'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'} session_id='litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' release='litellm-test-release' version='litellm-test-version' metadata={'trace_actual_metadata_key': 'trace_actual_metadata_value', 'generation_id': 'litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'} tags=['litellm-test-tag1', 'litellm-test-tag2', 'cache_hit:False'] public=None -adding task {'id': '1b34abb5-4a24-4042-a8c3-9f3ea0254f2b', 'type': 'trace-create', 'body': {'id': 'litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 423093, tzinfo=datetime.timezone.utc), 'name': 'litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'userId': 'litellm-test-user1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'input': {'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]}, 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'sessionId': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'release': 'litellm-test-release', 'version': 'litellm-test-version', 'metadata': {'trace_actual_metadata_key': 'trace_actual_metadata_value', 'generation_id': 'litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}, 'tags': ['litellm-test-tag1', 'litellm-test-tag2', 'cache_hit:False']}} -Creating generation trace_id='litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' name='litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' start_time=datetime.datetime(2024, 5, 7, 13, 11, 22, 421978) metadata={'gen_metadata_key': 'gen_metadata_value', 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]} output={'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'} level= status_message=None parent_observation_id=None version='litellm-test-version' id='litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' end_time=datetime.datetime(2024, 5, 7, 13, 11, 22, 422551) completion_start_time=datetime.datetime(2024, 5, 7, 13, 11, 22, 422551) model='gpt-3.5-turbo' model_parameters={'temperature': '0.2', 'max_tokens': 100, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=5.4999999999999995e-05) prompt_name=None prompt_version=None... -item size 1224 -adding task {'id': '050ba9cd-3eff-443b-9637-705406ceb8cb', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'name': 'litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 421978), 'metadata': {'gen_metadata_key': 'gen_metadata_value', 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]}, 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'level': , 'version': 'litellm-test-version', 'id': 'litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 422551), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 422551), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.2', 'max_tokens': 100, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}} -item size 1359 +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +Creating trace id='litellm-test-4d2a861a-39d1-451c-8187-c1bc8f5253bf' timestamp=datetime.datetime(2024, 6, 23, 6, 26, 47, 529980, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id='langfuse_latency_test_user' input={'messages': [{'role': 'user', 'content': 'redacted-by-litellm'}]} output={'content': 'redacted-by-litellm', 'role': 'assistant'} session_id=None release=None version=None metadata=None tags=[] public=None flushing queue +Creating generation trace_id='litellm-test-4d2a861a-39d1-451c-8187-c1bc8f5253bf' name='litellm-acompletion' start_time=datetime.datetime(2024, 6, 22, 23, 26, 47, 528930) metadata={'litellm_response_cost': 5.4999999999999995e-05, 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'redacted-by-litellm'}]} output={'content': 'redacted-by-litellm', 'role': 'assistant'} level= status_message=None parent_observation_id=None version=None id='time-23-26-47-528930_chatcmpl-811d9755-120c-4934-9efd-5ec08b8c41c6' end_time=datetime.datetime(2024, 6, 22, 23, 26, 47, 529521) completion_start_time=datetime.datetime(2024, 6, 22, 23, 26, 47, 529521) model='gpt-3.5-turbo' model_parameters={'temperature': '0.7', 'stream': False, 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=5.4999999999999995e-05) prompt_name=None prompt_version=None... +item size 454 successfully flushed about 0 items. -Creating trace id='litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' timestamp=datetime.datetime(2024, 5, 7, 20, 11, 22, 425221, tzinfo=datetime.timezone.utc) name=None user_id=None input=None output={'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'} session_id=None release=None version=None metadata={'trace_actual_metadata_key': 'trace_actual_metadata_value', 'generation_id': 'litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'} tags=None public=None -adding task {'id': 'd5173131-5bef-46cd-aa5a-6dcd01f6c000', 'type': 'trace-create', 'body': {'id': 'litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 425221, tzinfo=datetime.timezone.utc), 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'metadata': {'trace_actual_metadata_key': 'trace_actual_metadata_value', 'generation_id': 'litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}}} -Creating generation trace_id='litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' name='litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' start_time=datetime.datetime(2024, 5, 7, 13, 11, 22, 424075) metadata={'gen_metadata_key': 'gen_metadata_value', 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]} output={'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'} level= status_message=None parent_observation_id=None version='litellm-test-version' id='litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5' end_time=datetime.datetime(2024, 5, 7, 13, 11, 22, 424526) completion_start_time=datetime.datetime(2024, 5, 7, 13, 11, 22, 424526) model='gpt-3.5-turbo' model_parameters={'temperature': '0.2', 'max_tokens': 100, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=5.4999999999999995e-05) prompt_name=None prompt_version=None... -item size 630 -adding task {'id': 'ed61fc8d-aede-4c33-9ce8-377d498169f4', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'name': 'litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 424075), 'metadata': {'gen_metadata_key': 'gen_metadata_value', 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]}, 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'level': , 'version': 'litellm-test-version', 'id': 'litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 424526), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 424526), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.2', 'max_tokens': 100, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}} -uploading batch of 15 items -uploading data: {'batch': [{'id': 'e3840349-5e27-4921-84fc-f11ac428b7c5', 'type': 'trace-create', 'body': {'id': '77e94058-6f8a-43bc-97ef-1a8d4966592c', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 289521, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'tags': ['cache_hit:False']}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 289935, tzinfo=datetime.timezone.utc)}, {'id': '54036ec0-06ff-44d1-ac3a-f6085a3983ab', 'type': 'generation-create', 'body': {'traceId': '77e94058-6f8a-43bc-97ef-1a8d4966592c', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 21, 970003), 'metadata': {'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'level': , 'id': 'time-13-11-21-970003_chatcmpl-30ccf23d-ac57-4183-ab2f-b93f084c4187', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 286720), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 286720), 'model': 'chatgpt-v-2', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': }}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 290909, tzinfo=datetime.timezone.utc)}, {'id': '4bf88864-4937-48a4-8e9b-b1cf6a29cc5c', 'type': 'trace-create', 'body': {'id': '04190fd5-8a1f-41d9-b8be-878945c35293', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 292743, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'tags': ['cache_hit:False']}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 292929, tzinfo=datetime.timezone.utc)}, {'id': '050a1ed2-b54e-46ab-9145-04baca33524e', 'type': 'generation-create', 'body': {'traceId': '04190fd5-8a1f-41d9-b8be-878945c35293', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 282826), 'metadata': {'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'level': , 'id': 'time-13-11-22-282826_chatcmpl-684e7a99-b0ad-43e3-a0e9-acbce76e5457', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 288054), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 288054), 'model': 'chatgpt-v-2', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': }}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 293730, tzinfo=datetime.timezone.utc)}, {'id': '94b80fdf-7df9-4b69-8500-df55a4748802', 'type': 'trace-create', 'body': {'id': '82588025-780b-4045-a131-06dcaf2c54ca', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 293803, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'tags': ['cache_hit:False']}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 293964, tzinfo=datetime.timezone.utc)}, {'id': '659db88e-6adc-4c52-82d8-dac517773242', 'type': 'generation-create', 'body': {'traceId': '82588025-780b-4045-a131-06dcaf2c54ca', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 280988), 'metadata': {'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'level': , 'id': 'time-13-11-22-280988_chatcmpl-4ecaabdd-be67-4122-a3bf-b95466ffee0a', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 287168), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 287168), 'model': 'chatgpt-v-2', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': }}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 294336, tzinfo=datetime.timezone.utc)}, {'id': '383b9518-93ff-4943-ae0c-b3256ee3c1a7', 'type': 'trace-create', 'body': {'id': 'fe18bb31-ded9-4ad2-8417-41e0e3de195c', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 295711, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'tags': ['cache_hit:False']}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 295870, tzinfo=datetime.timezone.utc)}, {'id': '127d6d13-e8b0-44f2-bba1-cc5d9710b0b4', 'type': 'generation-create', 'body': {'traceId': 'fe18bb31-ded9-4ad2-8417-41e0e3de195c', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 284370), 'metadata': {'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'level': , 'id': 'time-13-11-22-284370_chatcmpl-bf93ab8e-ecf2-4f04-9506-ef51a1c4c9d0', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 288779), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 288779), 'model': 'chatgpt-v-2', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': }}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 296237, tzinfo=datetime.timezone.utc)}, {'id': '2bc81d4d-f6a5-415b-acaa-feb883c41bbb', 'type': 'trace-create', 'body': {'id': '99b7014a-c3c0-4040-92ad-64a665ab6abe', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 297355, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'tags': ['cache_hit:False']}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 297502, tzinfo=datetime.timezone.utc)}, {'id': 'd51705a9-088a-4f49-b494-f4fa8e6bc59e', 'type': 'generation-create', 'body': {'traceId': '99b7014a-c3c0-4040-92ad-64a665ab6abe', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 285989), 'metadata': {'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': "It's simple to use and easy to get started", 'role': 'assistant'}, 'level': , 'id': 'time-13-11-22-285989_chatcmpl-1a3c46e4-d474-4d19-92d8-8a7ee7ac0799', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 295600), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 295600), 'model': 'chatgpt-v-2', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': }}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 297845, tzinfo=datetime.timezone.utc)}, {'id': '9d380abe-bb42-480b-b48f-952ed6776e1c', 'type': 'trace-create', 'body': {'id': 'litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 420643, tzinfo=datetime.timezone.utc), 'name': 'litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'userId': 'litellm-test-user1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'input': {'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]}, 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'sessionId': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'release': 'litellm-test-release', 'version': 'litellm-test-version', 'metadata': {'trace_actual_metadata_key': 'trace_actual_metadata_value', 'generation_id': 'litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}, 'tags': ['litellm-test-tag1', 'litellm-test-tag2', 'cache_hit:False']}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 421233, tzinfo=datetime.timezone.utc)}, {'id': '0d3ae4f8-e352-4acd-98ec-d21be7e8f5eb', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'name': 'litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 419075), 'metadata': {'gen_metadata_key': 'gen_metadata_value', 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]}, 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'level': , 'version': 'litellm-test-version', 'id': 'litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 419879), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 419879), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.2', 'max_tokens': 100, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 421804, tzinfo=datetime.timezone.utc)}, {'id': '1b34abb5-4a24-4042-a8c3-9f3ea0254f2b', 'type': 'trace-create', 'body': {'id': 'litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 423093, tzinfo=datetime.timezone.utc), 'name': 'litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'userId': 'litellm-test-user1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'input': {'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]}, 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'sessionId': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'release': 'litellm-test-release', 'version': 'litellm-test-version', 'metadata': {'trace_actual_metadata_key': 'trace_actual_metadata_value', 'generation_id': 'litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}, 'tags': ['litellm-test-tag1', 'litellm-test-tag2', 'cache_hit:False']}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 423311, tzinfo=datetime.timezone.utc)}, {'id': '050ba9cd-3eff-443b-9637-705406ceb8cb', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'name': 'litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 421978), 'metadata': {'gen_metadata_key': 'gen_metadata_value', 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]}, 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'level': , 'version': 'litellm-test-version', 'id': 'litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 422551), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 422551), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.2', 'max_tokens': 100, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 423829, tzinfo=datetime.timezone.utc)}, {'id': 'd5173131-5bef-46cd-aa5a-6dcd01f6c000', 'type': 'trace-create', 'body': {'id': 'litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 425221, tzinfo=datetime.timezone.utc), 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'metadata': {'trace_actual_metadata_key': 'trace_actual_metadata_value', 'generation_id': 'litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 425370, tzinfo=datetime.timezone.utc)}], 'metadata': {'batch_size': 15, 'sdk_integration': 'litellm', 'sdk_name': 'python', 'sdk_version': '2.27.0', 'public_key': 'pk-lf-47ddd17f-c73c-4edd-b92a-b28835843e66'}} -making request: {"batch": [{"id": "e3840349-5e27-4921-84fc-f11ac428b7c5", "type": "trace-create", "body": {"id": "77e94058-6f8a-43bc-97ef-1a8d4966592c", "timestamp": "2024-05-07T20:11:22.289521Z", "name": "litellm-acompletion", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "tags": ["cache_hit:False"]}, "timestamp": "2024-05-07T20:11:22.289935Z"}, {"id": "54036ec0-06ff-44d1-ac3a-f6085a3983ab", "type": "generation-create", "body": {"traceId": "77e94058-6f8a-43bc-97ef-1a8d4966592c", "name": "litellm-acompletion", "startTime": "2024-05-07T13:11:21.970003-07:00", "metadata": {"cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "level": "DEFAULT", "id": "time-13-11-21-970003_chatcmpl-30ccf23d-ac57-4183-ab2f-b93f084c4187", "endTime": "2024-05-07T13:11:22.286720-07:00", "completionStartTime": "2024-05-07T13:11:22.286720-07:00", "model": "chatgpt-v-2", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS"}}, "timestamp": "2024-05-07T20:11:22.290909Z"}, {"id": "4bf88864-4937-48a4-8e9b-b1cf6a29cc5c", "type": "trace-create", "body": {"id": "04190fd5-8a1f-41d9-b8be-878945c35293", "timestamp": "2024-05-07T20:11:22.292743Z", "name": "litellm-acompletion", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "tags": ["cache_hit:False"]}, "timestamp": "2024-05-07T20:11:22.292929Z"}, {"id": "050a1ed2-b54e-46ab-9145-04baca33524e", "type": "generation-create", "body": {"traceId": "04190fd5-8a1f-41d9-b8be-878945c35293", "name": "litellm-acompletion", "startTime": "2024-05-07T13:11:22.282826-07:00", "metadata": {"cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "level": "DEFAULT", "id": "time-13-11-22-282826_chatcmpl-684e7a99-b0ad-43e3-a0e9-acbce76e5457", "endTime": "2024-05-07T13:11:22.288054-07:00", "completionStartTime": "2024-05-07T13:11:22.288054-07:00", "model": "chatgpt-v-2", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS"}}, "timestamp": "2024-05-07T20:11:22.293730Z"}, {"id": "94b80fdf-7df9-4b69-8500-df55a4748802", "type": "trace-create", "body": {"id": "82588025-780b-4045-a131-06dcaf2c54ca", "timestamp": "2024-05-07T20:11:22.293803Z", "name": "litellm-acompletion", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "tags": ["cache_hit:False"]}, "timestamp": "2024-05-07T20:11:22.293964Z"}, {"id": "659db88e-6adc-4c52-82d8-dac517773242", "type": "generation-create", "body": {"traceId": "82588025-780b-4045-a131-06dcaf2c54ca", "name": "litellm-acompletion", "startTime": "2024-05-07T13:11:22.280988-07:00", "metadata": {"cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "level": "DEFAULT", "id": "time-13-11-22-280988_chatcmpl-4ecaabdd-be67-4122-a3bf-b95466ffee0a", "endTime": "2024-05-07T13:11:22.287168-07:00", "completionStartTime": "2024-05-07T13:11:22.287168-07:00", "model": "chatgpt-v-2", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS"}}, "timestamp": "2024-05-07T20:11:22.294336Z"}, {"id": "383b9518-93ff-4943-ae0c-b3256ee3c1a7", "type": "trace-create", "body": {"id": "fe18bb31-ded9-4ad2-8417-41e0e3de195c", "timestamp": "2024-05-07T20:11:22.295711Z", "name": "litellm-acompletion", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "tags": ["cache_hit:False"]}, "timestamp": "2024-05-07T20:11:22.295870Z"}, {"id": "127d6d13-e8b0-44f2-bba1-cc5d9710b0b4", "type": "generation-create", "body": {"traceId": "fe18bb31-ded9-4ad2-8417-41e0e3de195c", "name": "litellm-acompletion", "startTime": "2024-05-07T13:11:22.284370-07:00", "metadata": {"cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "level": "DEFAULT", "id": "time-13-11-22-284370_chatcmpl-bf93ab8e-ecf2-4f04-9506-ef51a1c4c9d0", "endTime": "2024-05-07T13:11:22.288779-07:00", "completionStartTime": "2024-05-07T13:11:22.288779-07:00", "model": "chatgpt-v-2", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS"}}, "timestamp": "2024-05-07T20:11:22.296237Z"}, {"id": "2bc81d4d-f6a5-415b-acaa-feb883c41bbb", "type": "trace-create", "body": {"id": "99b7014a-c3c0-4040-92ad-64a665ab6abe", "timestamp": "2024-05-07T20:11:22.297355Z", "name": "litellm-acompletion", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "tags": ["cache_hit:False"]}, "timestamp": "2024-05-07T20:11:22.297502Z"}, {"id": "d51705a9-088a-4f49-b494-f4fa8e6bc59e", "type": "generation-create", "body": {"traceId": "99b7014a-c3c0-4040-92ad-64a665ab6abe", "name": "litellm-acompletion", "startTime": "2024-05-07T13:11:22.285989-07:00", "metadata": {"cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "It's simple to use and easy to get started", "role": "assistant"}, "level": "DEFAULT", "id": "time-13-11-22-285989_chatcmpl-1a3c46e4-d474-4d19-92d8-8a7ee7ac0799", "endTime": "2024-05-07T13:11:22.295600-07:00", "completionStartTime": "2024-05-07T13:11:22.295600-07:00", "model": "chatgpt-v-2", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS"}}, "timestamp": "2024-05-07T20:11:22.297845Z"}, {"id": "9d380abe-bb42-480b-b48f-952ed6776e1c", "type": "trace-create", "body": {"id": "litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "timestamp": "2024-05-07T20:11:22.420643Z", "name": "litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "userId": "litellm-test-user1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "input": {"messages": [{"role": "user", "content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5"}]}, "output": {"content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "role": "assistant"}, "sessionId": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "release": "litellm-test-release", "version": "litellm-test-version", "metadata": {"trace_actual_metadata_key": "trace_actual_metadata_value", "generation_id": "litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5"}, "tags": ["litellm-test-tag1", "litellm-test-tag2", "cache_hit:False"]}, "timestamp": "2024-05-07T20:11:22.421233Z"}, {"id": "0d3ae4f8-e352-4acd-98ec-d21be7e8f5eb", "type": "generation-create", "body": {"traceId": "litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "name": "litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "startTime": "2024-05-07T13:11:22.419075-07:00", "metadata": {"gen_metadata_key": "gen_metadata_value", "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5"}]}, "output": {"content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "role": "assistant"}, "level": "DEFAULT", "version": "litellm-test-version", "id": "litellm-test-trace1-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "endTime": "2024-05-07T13:11:22.419879-07:00", "completionStartTime": "2024-05-07T13:11:22.419879-07:00", "model": "gpt-3.5-turbo", "modelParameters": {"temperature": "0.2", "max_tokens": 100, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS", "totalCost": 5.4999999999999995e-05}}, "timestamp": "2024-05-07T20:11:22.421804Z"}, {"id": "1b34abb5-4a24-4042-a8c3-9f3ea0254f2b", "type": "trace-create", "body": {"id": "litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "timestamp": "2024-05-07T20:11:22.423093Z", "name": "litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "userId": "litellm-test-user1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "input": {"messages": [{"role": "user", "content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5"}]}, "output": {"content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "role": "assistant"}, "sessionId": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "release": "litellm-test-release", "version": "litellm-test-version", "metadata": {"trace_actual_metadata_key": "trace_actual_metadata_value", "generation_id": "litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5"}, "tags": ["litellm-test-tag1", "litellm-test-tag2", "cache_hit:False"]}, "timestamp": "2024-05-07T20:11:22.423311Z"}, {"id": "050ba9cd-3eff-443b-9637-705406ceb8cb", "type": "generation-create", "body": {"traceId": "litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "name": "litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "startTime": "2024-05-07T13:11:22.421978-07:00", "metadata": {"gen_metadata_key": "gen_metadata_value", "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5"}]}, "output": {"content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "role": "assistant"}, "level": "DEFAULT", "version": "litellm-test-version", "id": "litellm-test-trace2-generation-1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "endTime": "2024-05-07T13:11:22.422551-07:00", "completionStartTime": "2024-05-07T13:11:22.422551-07:00", "model": "gpt-3.5-turbo", "modelParameters": {"temperature": "0.2", "max_tokens": 100, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS", "totalCost": 5.4999999999999995e-05}}, "timestamp": "2024-05-07T20:11:22.423829Z"}, {"id": "d5173131-5bef-46cd-aa5a-6dcd01f6c000", "type": "trace-create", "body": {"id": "litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "timestamp": "2024-05-07T20:11:22.425221Z", "output": {"content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "role": "assistant"}, "metadata": {"trace_actual_metadata_key": "trace_actual_metadata_value", "generation_id": "litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5"}}, "timestamp": "2024-05-07T20:11:22.425370Z"}], "metadata": {"batch_size": 15, "sdk_integration": "litellm", "sdk_name": "python", "sdk_version": "2.27.0", "public_key": "pk-lf-47ddd17f-c73c-4edd-b92a-b28835843e66"}} to https://cloud.langfuse.com/api/public/ingestion -received response: {"errors":[],"successes":[{"id":"e3840349-5e27-4921-84fc-f11ac428b7c5","status":201},{"id":"54036ec0-06ff-44d1-ac3a-f6085a3983ab","status":201},{"id":"4bf88864-4937-48a4-8e9b-b1cf6a29cc5c","status":201},{"id":"050a1ed2-b54e-46ab-9145-04baca33524e","status":201},{"id":"94b80fdf-7df9-4b69-8500-df55a4748802","status":201},{"id":"659db88e-6adc-4c52-82d8-dac517773242","status":201},{"id":"383b9518-93ff-4943-ae0c-b3256ee3c1a7","status":201},{"id":"127d6d13-e8b0-44f2-bba1-cc5d9710b0b4","status":201},{"id":"2bc81d4d-f6a5-415b-acaa-feb883c41bbb","status":201},{"id":"d51705a9-088a-4f49-b494-f4fa8e6bc59e","status":201},{"id":"9d380abe-bb42-480b-b48f-952ed6776e1c","status":201},{"id":"0d3ae4f8-e352-4acd-98ec-d21be7e8f5eb","status":201},{"id":"1b34abb5-4a24-4042-a8c3-9f3ea0254f2b","status":201},{"id":"050ba9cd-3eff-443b-9637-705406ceb8cb","status":201},{"id":"d5173131-5bef-46cd-aa5a-6dcd01f6c000","status":201}]} -successfully uploaded batch of 15 items -item size 1359 -Getting trace litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5 -Getting observations... None, None, None, None, litellm-test-trace1-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5, None, GENERATION -uploading batch of 1 items -uploading data: {'batch': [{'id': 'ed61fc8d-aede-4c33-9ce8-377d498169f4', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'name': 'litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 424075), 'metadata': {'gen_metadata_key': 'gen_metadata_value', 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5'}]}, 'output': {'content': 'litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'role': 'assistant'}, 'level': , 'version': 'litellm-test-version', 'id': 'litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 424526), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 22, 424526), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.2', 'max_tokens': 100, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 22, 425776, tzinfo=datetime.timezone.utc)}], 'metadata': {'batch_size': 1, 'sdk_integration': 'litellm', 'sdk_name': 'python', 'sdk_version': '2.27.0', 'public_key': 'pk-lf-47ddd17f-c73c-4edd-b92a-b28835843e66'}} -making request: {"batch": [{"id": "ed61fc8d-aede-4c33-9ce8-377d498169f4", "type": "generation-create", "body": {"traceId": "litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "name": "litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "startTime": "2024-05-07T13:11:22.424075-07:00", "metadata": {"gen_metadata_key": "gen_metadata_value", "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5"}]}, "output": {"content": "litellm-test-session-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5:litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "role": "assistant"}, "level": "DEFAULT", "version": "litellm-test-version", "id": "litellm-test-trace2-generation-2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5", "endTime": "2024-05-07T13:11:22.424526-07:00", "completionStartTime": "2024-05-07T13:11:22.424526-07:00", "model": "gpt-3.5-turbo", "modelParameters": {"temperature": "0.2", "max_tokens": 100, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS", "totalCost": 5.4999999999999995e-05}}, "timestamp": "2024-05-07T20:11:22.425776Z"}], "metadata": {"batch_size": 1, "sdk_integration": "litellm", "sdk_name": "python", "sdk_version": "2.27.0", "public_key": "pk-lf-47ddd17f-c73c-4edd-b92a-b28835843e66"}} to https://cloud.langfuse.com/api/public/ingestion -Getting trace litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5 -received response: {"errors":[],"successes":[{"id":"ed61fc8d-aede-4c33-9ce8-377d498169f4","status":201}]} -successfully uploaded batch of 1 items -Getting observations... None, None, None, None, litellm-test-trace2-c8f258e1-002a-4ab9-98e1-1bf3c84c0bd5, None, GENERATION -consumer is running... -flushing queue -successfully flushed about 0 items. -Creating trace id='litellm-test-a87c7c71-32cd-4e6c-acc0-7378d6d81bb1' timestamp=datetime.datetime(2024, 5, 7, 20, 11, 45, 796169, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id=None input={'messages': 'redacted-by-litellm'} output={'content': 'redacted-by-litellm', 'role': 'assistant'} session_id=None release=None version=None metadata=None tags=['cache_hit:False'] public=None -adding task {'id': '244ffc62-a30d-4281-8a86-bdfcb3edef05', 'type': 'trace-create', 'body': {'id': 'litellm-test-a87c7c71-32cd-4e6c-acc0-7378d6d81bb1', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 45, 796169, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'input': {'messages': 'redacted-by-litellm'}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'tags': ['cache_hit:False']}} -Creating generation trace_id='litellm-test-a87c7c71-32cd-4e6c-acc0-7378d6d81bb1' name='litellm-acompletion' start_time=datetime.datetime(2024, 5, 7, 13, 11, 45, 794599) metadata={'cache_hit': False} input={'messages': 'redacted-by-litellm'} output={'content': 'redacted-by-litellm', 'role': 'assistant'} level= status_message=None parent_observation_id=None version=None id='time-13-11-45-794599_chatcmpl-28d76a11-56a6-43d2-9bf6-a6ddf7d8895a' end_time=datetime.datetime(2024, 5, 7, 13, 11, 45, 795329) completion_start_time=datetime.datetime(2024, 5, 7, 13, 11, 45, 795329) model='gpt-3.5-turbo' model_parameters={'temperature': '0.7', 'stream': False, 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=5.4999999999999995e-05) prompt_name=None prompt_version=None... -item size 400 -adding task {'id': 'e9d12a6d-3fca-4adb-a018-bf276733ffa6', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-a87c7c71-32cd-4e6c-acc0-7378d6d81bb1', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 45, 794599), 'metadata': {'cache_hit': False}, 'input': {'messages': 'redacted-by-litellm'}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'level': , 'id': 'time-13-11-45-794599_chatcmpl-28d76a11-56a6-43d2-9bf6-a6ddf7d8895a', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 45, 795329), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 45, 795329), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.7', 'stream': False, 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}} -item size 876 +item size 956 +~0 items in the Langfuse queue uploading batch of 2 items -uploading data: {'batch': [{'id': '244ffc62-a30d-4281-8a86-bdfcb3edef05', 'type': 'trace-create', 'body': {'id': 'litellm-test-a87c7c71-32cd-4e6c-acc0-7378d6d81bb1', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 45, 796169, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'input': {'messages': 'redacted-by-litellm'}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'tags': ['cache_hit:False']}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 45, 796433, tzinfo=datetime.timezone.utc)}, {'id': 'e9d12a6d-3fca-4adb-a018-bf276733ffa6', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-a87c7c71-32cd-4e6c-acc0-7378d6d81bb1', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 45, 794599), 'metadata': {'cache_hit': False}, 'input': {'messages': 'redacted-by-litellm'}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'level': , 'id': 'time-13-11-45-794599_chatcmpl-28d76a11-56a6-43d2-9bf6-a6ddf7d8895a', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 45, 795329), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 45, 795329), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.7', 'stream': False, 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 45, 797038, tzinfo=datetime.timezone.utc)}], 'metadata': {'batch_size': 2, 'sdk_integration': 'litellm', 'sdk_name': 'python', 'sdk_version': '2.27.0', 'public_key': 'pk-lf-47ddd17f-c73c-4edd-b92a-b28835843e66'}} -making request: {"batch": [{"id": "244ffc62-a30d-4281-8a86-bdfcb3edef05", "type": "trace-create", "body": {"id": "litellm-test-a87c7c71-32cd-4e6c-acc0-7378d6d81bb1", "timestamp": "2024-05-07T20:11:45.796169Z", "name": "litellm-acompletion", "input": {"messages": "redacted-by-litellm"}, "output": {"content": "redacted-by-litellm", "role": "assistant"}, "tags": ["cache_hit:False"]}, "timestamp": "2024-05-07T20:11:45.796433Z"}, {"id": "e9d12a6d-3fca-4adb-a018-bf276733ffa6", "type": "generation-create", "body": {"traceId": "litellm-test-a87c7c71-32cd-4e6c-acc0-7378d6d81bb1", "name": "litellm-acompletion", "startTime": "2024-05-07T13:11:45.794599-07:00", "metadata": {"cache_hit": false}, "input": {"messages": "redacted-by-litellm"}, "output": {"content": "redacted-by-litellm", "role": "assistant"}, "level": "DEFAULT", "id": "time-13-11-45-794599_chatcmpl-28d76a11-56a6-43d2-9bf6-a6ddf7d8895a", "endTime": "2024-05-07T13:11:45.795329-07:00", "completionStartTime": "2024-05-07T13:11:45.795329-07:00", "model": "gpt-3.5-turbo", "modelParameters": {"temperature": "0.7", "stream": false, "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS", "totalCost": 5.4999999999999995e-05}}, "timestamp": "2024-05-07T20:11:45.797038Z"}], "metadata": {"batch_size": 2, "sdk_integration": "litellm", "sdk_name": "python", "sdk_version": "2.27.0", "public_key": "pk-lf-47ddd17f-c73c-4edd-b92a-b28835843e66"}} to https://cloud.langfuse.com/api/public/ingestion -received response: {"errors":[],"successes":[{"id":"244ffc62-a30d-4281-8a86-bdfcb3edef05","status":201},{"id":"e9d12a6d-3fca-4adb-a018-bf276733ffa6","status":201}]} +uploading data: {'batch': [{'id': '997346c5-9bb9-4789-9ba9-33893bc65ee3', 'type': 'trace-create', 'body': {'id': 'litellm-test-4d2a861a-39d1-451c-8187-c1bc8f5253bf', 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 47, 529980, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'userId': 'langfuse_latency_test_user', 'input': {'messages': [{'role': 'user', 'content': 'redacted-by-litellm'}]}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'tags': []}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 47, 530178, tzinfo=datetime.timezone.utc)}, {'id': 'c1c856eb-0aad-4da1-b68c-b68295b847e1', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-4d2a861a-39d1-451c-8187-c1bc8f5253bf', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 6, 22, 23, 26, 47, 528930), 'metadata': {'litellm_response_cost': 5.4999999999999995e-05, 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'redacted-by-litellm'}]}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'level': , 'id': 'time-23-26-47-528930_chatcmpl-811d9755-120c-4934-9efd-5ec08b8c41c6', 'endTime': datetime.datetime(2024, 6, 22, 23, 26, 47, 529521), 'completionStartTime': datetime.datetime(2024, 6, 22, 23, 26, 47, 529521), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.7', 'stream': False, 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 47, 530501, tzinfo=datetime.timezone.utc)}], 'metadata': {'batch_size': 2, 'sdk_integration': 'default', 'sdk_name': 'python', 'sdk_version': '2.32.0', 'public_key': 'pk-lf-b3db7e8e-c2f6-4fc7-825c-a541a8fbe003'}} +making request: {"batch": [{"id": "997346c5-9bb9-4789-9ba9-33893bc65ee3", "type": "trace-create", "body": {"id": "litellm-test-4d2a861a-39d1-451c-8187-c1bc8f5253bf", "timestamp": "2024-06-23T06:26:47.529980Z", "name": "litellm-acompletion", "userId": "langfuse_latency_test_user", "input": {"messages": [{"role": "user", "content": "redacted-by-litellm"}]}, "output": {"content": "redacted-by-litellm", "role": "assistant"}, "tags": []}, "timestamp": "2024-06-23T06:26:47.530178Z"}, {"id": "c1c856eb-0aad-4da1-b68c-b68295b847e1", "type": "generation-create", "body": {"traceId": "litellm-test-4d2a861a-39d1-451c-8187-c1bc8f5253bf", "name": "litellm-acompletion", "startTime": "2024-06-22T23:26:47.528930-07:00", "metadata": {"litellm_response_cost": 5.4999999999999995e-05, "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "redacted-by-litellm"}]}, "output": {"content": "redacted-by-litellm", "role": "assistant"}, "level": "DEFAULT", "id": "time-23-26-47-528930_chatcmpl-811d9755-120c-4934-9efd-5ec08b8c41c6", "endTime": "2024-06-22T23:26:47.529521-07:00", "completionStartTime": "2024-06-22T23:26:47.529521-07:00", "model": "gpt-3.5-turbo", "modelParameters": {"temperature": "0.7", "stream": false, "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS", "totalCost": 5.4999999999999995e-05}}, "timestamp": "2024-06-23T06:26:47.530501Z"}], "metadata": {"batch_size": 2, "sdk_integration": "default", "sdk_name": "python", "sdk_version": "2.32.0", "public_key": "pk-lf-b3db7e8e-c2f6-4fc7-825c-a541a8fbe003"}} to https://us.cloud.langfuse.com/api/public/ingestion +received response: {"errors":[],"successes":[{"id":"997346c5-9bb9-4789-9ba9-33893bc65ee3","status":201},{"id":"c1c856eb-0aad-4da1-b68c-b68295b847e1","status":201}]} successfully uploaded batch of 2 items -Getting observations... None, None, None, None, litellm-test-a87c7c71-32cd-4e6c-acc0-7378d6d81bb1, None, GENERATION +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +Getting observations... None, None, None, None, litellm-test-4d2a861a-39d1-451c-8187-c1bc8f5253bf, None, GENERATION +~0 items in the Langfuse queue consumer is running... +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. flushing queue successfully flushed about 0 items. -Creating trace id='litellm-test-d9136466-2e87-4afc-8367-dc51764251c7' timestamp=datetime.datetime(2024, 5, 7, 20, 11, 48, 286447, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id=None input={'messages': 'redacted-by-litellm'} output={'content': 'redacted-by-litellm', 'role': 'assistant'} session_id=None release=None version=None metadata=None tags=['cache_hit:False'] public=None -adding task {'id': 'cab47524-1e1e-4404-b8bd-5f526895ac0c', 'type': 'trace-create', 'body': {'id': 'litellm-test-d9136466-2e87-4afc-8367-dc51764251c7', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 48, 286447, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'input': {'messages': 'redacted-by-litellm'}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'tags': ['cache_hit:False']}} -Creating generation trace_id='litellm-test-d9136466-2e87-4afc-8367-dc51764251c7' name='litellm-acompletion' start_time=datetime.datetime(2024, 5, 7, 13, 11, 48, 276681) metadata={'cache_hit': False} input={'messages': 'redacted-by-litellm'} output={'content': 'redacted-by-litellm', 'role': 'assistant'} level= status_message=None parent_observation_id=None version=None id='time-13-11-48-276681_chatcmpl-ef076c31-4977-4687-bc83-07bb1f0aa1b2' end_time=datetime.datetime(2024, 5, 7, 13, 11, 48, 285026) completion_start_time=datetime.datetime(2024, 5, 7, 13, 11, 48, 278853) model='gpt-3.5-turbo' model_parameters={'temperature': '0.7', 'stream': True, 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=0, output=98, total=None, unit=, input_cost=None, output_cost=None, total_cost=0.000196) prompt_name=None prompt_version=None... -item size 400 -adding task {'id': '6bacab4d-822a-430f-85a9-4de1fa7ce259', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-d9136466-2e87-4afc-8367-dc51764251c7', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 48, 276681), 'metadata': {'cache_hit': False}, 'input': {'messages': 'redacted-by-litellm'}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'level': , 'id': 'time-13-11-48-276681_chatcmpl-ef076c31-4977-4687-bc83-07bb1f0aa1b2', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 48, 285026), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 48, 278853), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.7', 'stream': True, 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 0, 'output': 98, 'unit': , 'totalCost': 0.000196}}} +~0 items in the Langfuse queue +~0 items in the Langfuse queue +Creating trace id='litellm-test-a6ce08b7-2364-4efd-b030-7ee3a9ed6996' timestamp=datetime.datetime(2024, 6, 23, 6, 26, 50, 95341, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id='langfuse_latency_test_user' input={'messages': [{'role': 'user', 'content': 'redacted-by-litellm'}]} output={'content': 'redacted-by-litellm', 'role': 'assistant'} session_id=None release=None version=None metadata=None tags=[] public=None +Creating generation trace_id='litellm-test-a6ce08b7-2364-4efd-b030-7ee3a9ed6996' name='litellm-acompletion' start_time=datetime.datetime(2024, 6, 22, 23, 26, 49, 844949) metadata={'litellm_response_cost': 4.1e-05, 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'redacted-by-litellm'}]} output={'content': 'redacted-by-litellm', 'role': 'assistant'} level= status_message=None parent_observation_id=None version=None id='time-23-26-49-844949_chatcmpl-61f43be5-fc8e-4d92-ad89-8080b51f60de' end_time=datetime.datetime(2024, 6, 22, 23, 26, 49, 855530) completion_start_time=datetime.datetime(2024, 6, 22, 23, 26, 49, 846913) model='gpt-3.5-turbo' model_parameters={'temperature': '0.7', 'stream': True, 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=14, output=10, total=None, unit=, input_cost=None, output_cost=None, total_cost=4.1e-05) prompt_name=None prompt_version=None... +item size 454 +item size 925 +~0 items in the Langfuse queue +~0 items in the Langfuse queue +uploading batch of 2 items +uploading data: {'batch': [{'id': '9bde426a-b7e9-480f-adc2-e1530b572882', 'type': 'trace-create', 'body': {'id': 'litellm-test-a6ce08b7-2364-4efd-b030-7ee3a9ed6996', 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 50, 95341, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'userId': 'langfuse_latency_test_user', 'input': {'messages': [{'role': 'user', 'content': 'redacted-by-litellm'}]}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'tags': []}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 50, 95711, tzinfo=datetime.timezone.utc)}, {'id': '77964887-be69-42b6-b903-8b01d37643ca', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-a6ce08b7-2364-4efd-b030-7ee3a9ed6996', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 6, 22, 23, 26, 49, 844949), 'metadata': {'litellm_response_cost': 4.1e-05, 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'redacted-by-litellm'}]}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'level': , 'id': 'time-23-26-49-844949_chatcmpl-61f43be5-fc8e-4d92-ad89-8080b51f60de', 'endTime': datetime.datetime(2024, 6, 22, 23, 26, 49, 855530), 'completionStartTime': datetime.datetime(2024, 6, 22, 23, 26, 49, 846913), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.7', 'stream': True, 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 14, 'output': 10, 'unit': , 'totalCost': 4.1e-05}}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 50, 96374, tzinfo=datetime.timezone.utc)}], 'metadata': {'batch_size': 2, 'sdk_integration': 'default', 'sdk_name': 'python', 'sdk_version': '2.32.0', 'public_key': 'pk-lf-b3db7e8e-c2f6-4fc7-825c-a541a8fbe003'}} +making request: {"batch": [{"id": "9bde426a-b7e9-480f-adc2-e1530b572882", "type": "trace-create", "body": {"id": "litellm-test-a6ce08b7-2364-4efd-b030-7ee3a9ed6996", "timestamp": "2024-06-23T06:26:50.095341Z", "name": "litellm-acompletion", "userId": "langfuse_latency_test_user", "input": {"messages": [{"role": "user", "content": "redacted-by-litellm"}]}, "output": {"content": "redacted-by-litellm", "role": "assistant"}, "tags": []}, "timestamp": "2024-06-23T06:26:50.095711Z"}, {"id": "77964887-be69-42b6-b903-8b01d37643ca", "type": "generation-create", "body": {"traceId": "litellm-test-a6ce08b7-2364-4efd-b030-7ee3a9ed6996", "name": "litellm-acompletion", "startTime": "2024-06-22T23:26:49.844949-07:00", "metadata": {"litellm_response_cost": 4.1e-05, "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "redacted-by-litellm"}]}, "output": {"content": "redacted-by-litellm", "role": "assistant"}, "level": "DEFAULT", "id": "time-23-26-49-844949_chatcmpl-61f43be5-fc8e-4d92-ad89-8080b51f60de", "endTime": "2024-06-22T23:26:49.855530-07:00", "completionStartTime": "2024-06-22T23:26:49.846913-07:00", "model": "gpt-3.5-turbo", "modelParameters": {"temperature": "0.7", "stream": true, "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 14, "output": 10, "unit": "TOKENS", "totalCost": 4.1e-05}}, "timestamp": "2024-06-23T06:26:50.096374Z"}], "metadata": {"batch_size": 2, "sdk_integration": "default", "sdk_name": "python", "sdk_version": "2.32.0", "public_key": "pk-lf-b3db7e8e-c2f6-4fc7-825c-a541a8fbe003"}} to https://us.cloud.langfuse.com/api/public/ingestion +~0 items in the Langfuse queue +received response: {"errors":[],"successes":[{"id":"9bde426a-b7e9-480f-adc2-e1530b572882","status":201},{"id":"77964887-be69-42b6-b903-8b01d37643ca","status":201}]} +successfully uploaded batch of 2 items +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +Getting observations... None, None, None, None, litellm-test-a6ce08b7-2364-4efd-b030-7ee3a9ed6996, None, GENERATION +~0 items in the Langfuse queue +consumer is running... +~0 items in the Langfuse queue +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +Creating trace id='litellm-test-b3e968bf-c9cb-4f4d-a834-b0cba57e4695' timestamp=datetime.datetime(2024, 6, 23, 6, 26, 52, 198564, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id='langfuse_latency_test_user' input='redacted-by-litellm' output='redacted-by-litellm' session_id=None release=None version=None metadata=None tags=[] public=None +Creating generation trace_id='litellm-test-b3e968bf-c9cb-4f4d-a834-b0cba57e4695' name='litellm-acompletion' start_time=datetime.datetime(2024, 6, 22, 23, 26, 52, 197638) metadata={'litellm_response_cost': 5.4999999999999995e-05, 'cache_hit': False} input='redacted-by-litellm' output='redacted-by-litellm' level= status_message=None parent_observation_id=None version=None id='time-23-26-52-197638_chatcmpl-089072da-028d-4425-ae6d-76e71d21df0d' end_time=datetime.datetime(2024, 6, 22, 23, 26, 52, 198243) completion_start_time=datetime.datetime(2024, 6, 22, 23, 26, 52, 198243) model='gpt-3.5-turbo' model_parameters={'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=5.4999999999999995e-05) prompt_name=None prompt_version=None... +item size 375 item size 860 +flushing queue +successfully flushed about 0 items. +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue uploading batch of 2 items -uploading data: {'batch': [{'id': 'cab47524-1e1e-4404-b8bd-5f526895ac0c', 'type': 'trace-create', 'body': {'id': 'litellm-test-d9136466-2e87-4afc-8367-dc51764251c7', 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 48, 286447, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'input': {'messages': 'redacted-by-litellm'}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'tags': ['cache_hit:False']}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 48, 286752, tzinfo=datetime.timezone.utc)}, {'id': '6bacab4d-822a-430f-85a9-4de1fa7ce259', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-d9136466-2e87-4afc-8367-dc51764251c7', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 5, 7, 13, 11, 48, 276681), 'metadata': {'cache_hit': False}, 'input': {'messages': 'redacted-by-litellm'}, 'output': {'content': 'redacted-by-litellm', 'role': 'assistant'}, 'level': , 'id': 'time-13-11-48-276681_chatcmpl-ef076c31-4977-4687-bc83-07bb1f0aa1b2', 'endTime': datetime.datetime(2024, 5, 7, 13, 11, 48, 285026), 'completionStartTime': datetime.datetime(2024, 5, 7, 13, 11, 48, 278853), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.7', 'stream': True, 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 0, 'output': 98, 'unit': , 'totalCost': 0.000196}}, 'timestamp': datetime.datetime(2024, 5, 7, 20, 11, 48, 287077, tzinfo=datetime.timezone.utc)}], 'metadata': {'batch_size': 2, 'sdk_integration': 'litellm', 'sdk_name': 'python', 'sdk_version': '2.27.0', 'public_key': 'pk-lf-47ddd17f-c73c-4edd-b92a-b28835843e66'}} -making request: {"batch": [{"id": "cab47524-1e1e-4404-b8bd-5f526895ac0c", "type": "trace-create", "body": {"id": "litellm-test-d9136466-2e87-4afc-8367-dc51764251c7", "timestamp": "2024-05-07T20:11:48.286447Z", "name": "litellm-acompletion", "input": {"messages": "redacted-by-litellm"}, "output": {"content": "redacted-by-litellm", "role": "assistant"}, "tags": ["cache_hit:False"]}, "timestamp": "2024-05-07T20:11:48.286752Z"}, {"id": "6bacab4d-822a-430f-85a9-4de1fa7ce259", "type": "generation-create", "body": {"traceId": "litellm-test-d9136466-2e87-4afc-8367-dc51764251c7", "name": "litellm-acompletion", "startTime": "2024-05-07T13:11:48.276681-07:00", "metadata": {"cache_hit": false}, "input": {"messages": "redacted-by-litellm"}, "output": {"content": "redacted-by-litellm", "role": "assistant"}, "level": "DEFAULT", "id": "time-13-11-48-276681_chatcmpl-ef076c31-4977-4687-bc83-07bb1f0aa1b2", "endTime": "2024-05-07T13:11:48.285026-07:00", "completionStartTime": "2024-05-07T13:11:48.278853-07:00", "model": "gpt-3.5-turbo", "modelParameters": {"temperature": "0.7", "stream": true, "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 0, "output": 98, "unit": "TOKENS", "totalCost": 0.000196}}, "timestamp": "2024-05-07T20:11:48.287077Z"}], "metadata": {"batch_size": 2, "sdk_integration": "litellm", "sdk_name": "python", "sdk_version": "2.27.0", "public_key": "pk-lf-47ddd17f-c73c-4edd-b92a-b28835843e66"}} to https://cloud.langfuse.com/api/public/ingestion -received response: {"errors":[],"successes":[{"id":"cab47524-1e1e-4404-b8bd-5f526895ac0c","status":201},{"id":"6bacab4d-822a-430f-85a9-4de1fa7ce259","status":201}]} +uploading data: {'batch': [{'id': 'a44cc9e3-8b12-4a3f-b8d5-f7a3949ac5c2', 'type': 'trace-create', 'body': {'id': 'litellm-test-b3e968bf-c9cb-4f4d-a834-b0cba57e4695', 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 52, 198564, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'userId': 'langfuse_latency_test_user', 'input': 'redacted-by-litellm', 'output': 'redacted-by-litellm', 'tags': []}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 52, 198832, tzinfo=datetime.timezone.utc)}, {'id': 'fceda986-a5a6-4e87-b7e6-bf208a2f7589', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-b3e968bf-c9cb-4f4d-a834-b0cba57e4695', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 6, 22, 23, 26, 52, 197638), 'metadata': {'litellm_response_cost': 5.4999999999999995e-05, 'cache_hit': False}, 'input': 'redacted-by-litellm', 'output': 'redacted-by-litellm', 'level': , 'id': 'time-23-26-52-197638_chatcmpl-089072da-028d-4425-ae6d-76e71d21df0d', 'endTime': datetime.datetime(2024, 6, 22, 23, 26, 52, 198243), 'completionStartTime': datetime.datetime(2024, 6, 22, 23, 26, 52, 198243), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 52, 199379, tzinfo=datetime.timezone.utc)}], 'metadata': {'batch_size': 2, 'sdk_integration': 'default', 'sdk_name': 'python', 'sdk_version': '2.32.0', 'public_key': 'pk-lf-b3db7e8e-c2f6-4fc7-825c-a541a8fbe003'}} +making request: {"batch": [{"id": "a44cc9e3-8b12-4a3f-b8d5-f7a3949ac5c2", "type": "trace-create", "body": {"id": "litellm-test-b3e968bf-c9cb-4f4d-a834-b0cba57e4695", "timestamp": "2024-06-23T06:26:52.198564Z", "name": "litellm-acompletion", "userId": "langfuse_latency_test_user", "input": "redacted-by-litellm", "output": "redacted-by-litellm", "tags": []}, "timestamp": "2024-06-23T06:26:52.198832Z"}, {"id": "fceda986-a5a6-4e87-b7e6-bf208a2f7589", "type": "generation-create", "body": {"traceId": "litellm-test-b3e968bf-c9cb-4f4d-a834-b0cba57e4695", "name": "litellm-acompletion", "startTime": "2024-06-22T23:26:52.197638-07:00", "metadata": {"litellm_response_cost": 5.4999999999999995e-05, "cache_hit": false}, "input": "redacted-by-litellm", "output": "redacted-by-litellm", "level": "DEFAULT", "id": "time-23-26-52-197638_chatcmpl-089072da-028d-4425-ae6d-76e71d21df0d", "endTime": "2024-06-22T23:26:52.198243-07:00", "completionStartTime": "2024-06-22T23:26:52.198243-07:00", "model": "gpt-3.5-turbo", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS", "totalCost": 5.4999999999999995e-05}}, "timestamp": "2024-06-23T06:26:52.199379Z"}], "metadata": {"batch_size": 2, "sdk_integration": "default", "sdk_name": "python", "sdk_version": "2.32.0", "public_key": "pk-lf-b3db7e8e-c2f6-4fc7-825c-a541a8fbe003"}} to https://us.cloud.langfuse.com/api/public/ingestion +~0 items in the Langfuse queue +received response: {"errors":[],"successes":[{"id":"a44cc9e3-8b12-4a3f-b8d5-f7a3949ac5c2","status":201},{"id":"fceda986-a5a6-4e87-b7e6-bf208a2f7589","status":201}]} successfully uploaded batch of 2 items -Getting observations... None, None, None, None, litellm-test-d9136466-2e87-4afc-8367-dc51764251c7, None, GENERATION -joining 1 consumer threads -consumer thread 0 joined -joining 1 consumer threads -consumer thread 0 joined +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +Getting trace litellm-test-b3e968bf-c9cb-4f4d-a834-b0cba57e4695 +~0 items in the Langfuse queue +~0 items in the Langfuse queue +Getting observations... None, None, None, None, litellm-test-b3e968bf-c9cb-4f4d-a834-b0cba57e4695, None, GENERATION +~0 items in the Langfuse queue +`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs. +flushing queue +Creating trace id='litellm-test-2a7ed10d-b0aa-41c3-874e-adb2e128a9a6' timestamp=datetime.datetime(2024, 6, 23, 6, 26, 54, 545241, tzinfo=datetime.timezone.utc) name='litellm-acompletion' user_id='langfuse_latency_test_user' input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': 'This is a test response', 'role': 'assistant'} session_id=None release=None version=None metadata=None tags=[] public=None +successfully flushed about 0 items. +Creating generation trace_id='litellm-test-2a7ed10d-b0aa-41c3-874e-adb2e128a9a6' name='litellm-acompletion' start_time=datetime.datetime(2024, 6, 22, 23, 26, 54, 540644) metadata={'litellm_response_cost': 5.4999999999999995e-05, 'cache_hit': False} input={'messages': [{'role': 'user', 'content': 'This is a test'}]} output={'content': 'This is a test response', 'role': 'assistant'} level= status_message=None parent_observation_id=None version=None id='time-23-26-54-540644_chatcmpl-5c5777de-9eaf-4515-ad2c-b9a9cf2cfbe5' end_time=datetime.datetime(2024, 6, 22, 23, 26, 54, 543392) completion_start_time=datetime.datetime(2024, 6, 22, 23, 26, 54, 543392) model='gpt-3.5-turbo' model_parameters={'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'} usage=Usage(input=10, output=20, total=None, unit=, input_cost=None, output_cost=None, total_cost=5.4999999999999995e-05) prompt_name=None prompt_version=None... +item size 453 +item size 938 +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +uploading batch of 2 items +uploading data: {'batch': [{'id': '696d738d-b46a-418f-be31-049e9add4bd8', 'type': 'trace-create', 'body': {'id': 'litellm-test-2a7ed10d-b0aa-41c3-874e-adb2e128a9a6', 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 54, 545241, tzinfo=datetime.timezone.utc), 'name': 'litellm-acompletion', 'userId': 'langfuse_latency_test_user', 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': 'This is a test response', 'role': 'assistant'}, 'tags': []}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 54, 545804, tzinfo=datetime.timezone.utc)}, {'id': 'caf378b4-ae86-4a74-a7ac-2f9a83ed9d67', 'type': 'generation-create', 'body': {'traceId': 'litellm-test-2a7ed10d-b0aa-41c3-874e-adb2e128a9a6', 'name': 'litellm-acompletion', 'startTime': datetime.datetime(2024, 6, 22, 23, 26, 54, 540644), 'metadata': {'litellm_response_cost': 5.4999999999999995e-05, 'cache_hit': False}, 'input': {'messages': [{'role': 'user', 'content': 'This is a test'}]}, 'output': {'content': 'This is a test response', 'role': 'assistant'}, 'level': , 'id': 'time-23-26-54-540644_chatcmpl-5c5777de-9eaf-4515-ad2c-b9a9cf2cfbe5', 'endTime': datetime.datetime(2024, 6, 22, 23, 26, 54, 543392), 'completionStartTime': datetime.datetime(2024, 6, 22, 23, 26, 54, 543392), 'model': 'gpt-3.5-turbo', 'modelParameters': {'temperature': '0.7', 'max_tokens': 5, 'user': 'langfuse_latency_test_user', 'extra_body': '{}'}, 'usage': {'input': 10, 'output': 20, 'unit': , 'totalCost': 5.4999999999999995e-05}}, 'timestamp': datetime.datetime(2024, 6, 23, 6, 26, 54, 547005, tzinfo=datetime.timezone.utc)}], 'metadata': {'batch_size': 2, 'sdk_integration': 'default', 'sdk_name': 'python', 'sdk_version': '2.32.0', 'public_key': 'pk-lf-b3db7e8e-c2f6-4fc7-825c-a541a8fbe003'}} +making request: {"batch": [{"id": "696d738d-b46a-418f-be31-049e9add4bd8", "type": "trace-create", "body": {"id": "litellm-test-2a7ed10d-b0aa-41c3-874e-adb2e128a9a6", "timestamp": "2024-06-23T06:26:54.545241Z", "name": "litellm-acompletion", "userId": "langfuse_latency_test_user", "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "This is a test response", "role": "assistant"}, "tags": []}, "timestamp": "2024-06-23T06:26:54.545804Z"}, {"id": "caf378b4-ae86-4a74-a7ac-2f9a83ed9d67", "type": "generation-create", "body": {"traceId": "litellm-test-2a7ed10d-b0aa-41c3-874e-adb2e128a9a6", "name": "litellm-acompletion", "startTime": "2024-06-22T23:26:54.540644-07:00", "metadata": {"litellm_response_cost": 5.4999999999999995e-05, "cache_hit": false}, "input": {"messages": [{"role": "user", "content": "This is a test"}]}, "output": {"content": "This is a test response", "role": "assistant"}, "level": "DEFAULT", "id": "time-23-26-54-540644_chatcmpl-5c5777de-9eaf-4515-ad2c-b9a9cf2cfbe5", "endTime": "2024-06-22T23:26:54.543392-07:00", "completionStartTime": "2024-06-22T23:26:54.543392-07:00", "model": "gpt-3.5-turbo", "modelParameters": {"temperature": "0.7", "max_tokens": 5, "user": "langfuse_latency_test_user", "extra_body": "{}"}, "usage": {"input": 10, "output": 20, "unit": "TOKENS", "totalCost": 5.4999999999999995e-05}}, "timestamp": "2024-06-23T06:26:54.547005Z"}], "metadata": {"batch_size": 2, "sdk_integration": "default", "sdk_name": "python", "sdk_version": "2.32.0", "public_key": "pk-lf-b3db7e8e-c2f6-4fc7-825c-a541a8fbe003"}} to https://us.cloud.langfuse.com/api/public/ingestion +~0 items in the Langfuse queue +~0 items in the Langfuse queue +received response: {"errors":[],"successes":[{"id":"696d738d-b46a-418f-be31-049e9add4bd8","status":201},{"id":"caf378b4-ae86-4a74-a7ac-2f9a83ed9d67","status":201}]} +successfully uploaded batch of 2 items +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +Getting trace litellm-test-2a7ed10d-b0aa-41c3-874e-adb2e128a9a6 +~0 items in the Langfuse queue +Getting observations... None, None, None, None, litellm-test-2a7ed10d-b0aa-41c3-874e-adb2e128a9a6, None, GENERATION +~0 items in the Langfuse queue joining 1 consumer threads +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue +~0 items in the Langfuse queue consumer thread 0 joined joining 1 consumer threads +~0 items in the Langfuse queue +~0 items in the Langfuse queue consumer thread 0 joined joining 1 consumer threads +~0 items in the Langfuse queue consumer thread 0 joined joining 1 consumer threads +~0 items in the Langfuse queue consumer thread 0 joined From 9cff2b03f36923692be9996bacbc519a6c85c12c Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 22 Jun 2024 23:53:18 -0700 Subject: [PATCH 014/137] test(test_proxy_server_langfuse.py): cleanup tests causing OOM issues. --- litellm/tests/test_proxy_server_langfuse.py | 26 ++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/litellm/tests/test_proxy_server_langfuse.py b/litellm/tests/test_proxy_server_langfuse.py index 4f896f792..abd4d2788 100644 --- a/litellm/tests/test_proxy_server_langfuse.py +++ b/litellm/tests/test_proxy_server_langfuse.py @@ -1,19 +1,24 @@ -import sys, os +import os +import sys import traceback + from dotenv import load_dotenv load_dotenv() -import os, io +import io +import os # this file is to test litellm/proxy sys.path.insert( 0, os.path.abspath("../..") ) # Adds the parent directory to the system path -import pytest, logging +import logging + +import pytest + import litellm -from litellm import embedding, completion, completion_cost, Timeout -from litellm import RateLimitError +from litellm import RateLimitError, Timeout, completion, completion_cost, embedding # Configure logging logging.basicConfig( @@ -21,14 +26,16 @@ logging.basicConfig( format="%(asctime)s - %(levelname)s - %(message)s", ) +from fastapi import FastAPI + # test /chat/completion request to the proxy from fastapi.testclient import TestClient -from fastapi import FastAPI -from litellm.proxy.proxy_server import ( + +from litellm.proxy.proxy_server import ( # Replace with the actual module where your FastAPI router is defined router, save_worker_config, startup_event, -) # Replace with the actual module where your FastAPI router is defined +) filepath = os.path.dirname(os.path.abspath(__file__)) config_fp = f"{filepath}/test_configs/test_config.yaml" @@ -67,6 +74,9 @@ def client(): yield client +@pytest.mark.skip( + reason="Init multiple Langfuse clients causing OOM issues. Reduce init clients on ci/cd. " +) def test_chat_completion(client): try: # Your test data From 3d09a196a46e68a3e316d1932252b20dd6608ac0 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sun, 23 Jun 2024 00:06:15 -0700 Subject: [PATCH 015/137] test(test_completion.py): handle replicate api error --- litellm/tests/test_completion.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 31ac792d8..830b3acd3 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -2580,6 +2580,8 @@ async def test_completion_replicate_llama3(sync_mode): # Add any assertions here to check the response assert isinstance(response, litellm.ModelResponse) response_format_tests(response=response) + except litellm.APIError as e: + pass except Exception as e: pytest.fail(f"Error occurred: {e}") From 769c60e4f6e6e98e91e0619b5c20906d1c43cb20 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sun, 23 Jun 2024 00:30:45 -0700 Subject: [PATCH 016/137] test: skip unstable tests --- litellm/tests/test_dynamic_rate_limit_handler.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/litellm/tests/test_dynamic_rate_limit_handler.py b/litellm/tests/test_dynamic_rate_limit_handler.py index 6e1b55d18..4f49abff8 100644 --- a/litellm/tests/test_dynamic_rate_limit_handler.py +++ b/litellm/tests/test_dynamic_rate_limit_handler.py @@ -296,6 +296,9 @@ async def test_update_cache( assert active_projects == 1 +@pytest.mark.skip( + reason="Unstable on ci/cd due to curr minute changes. Refactor to handle minute changing" +) @pytest.mark.parametrize("num_projects", [2]) @pytest.mark.asyncio async def test_multiple_projects( @@ -350,8 +353,10 @@ async def test_multiple_projects( prev_availability: Optional[int] = None print("expected_runs: {}".format(expected_runs)) + for i in range(expected_runs + 1): # check availability + availability, _, _ = await dynamic_rate_limit_handler.check_available_tpm( model=model ) @@ -390,6 +395,9 @@ async def test_multiple_projects( assert availability == 0 +@pytest.mark.skip( + reason="Unstable on ci/cd due to curr minute changes. Refactor to handle minute changing" +) @pytest.mark.parametrize("num_projects", [2]) @pytest.mark.asyncio async def test_multiple_projects_e2e( From b2c1a3ad791df72c4bbf5160a5b621f219dc12e4 Mon Sep 17 00:00:00 2001 From: corrm Date: Mon, 24 Jun 2024 05:54:58 +0300 Subject: [PATCH 017/137] chore: Improved prompt generation in ollama_pt function --- litellm/llms/prompt_templates/factory.py | 25 +++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index 398e96af7..02ed93fae 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -172,14 +172,21 @@ def ollama_pt( images.append(base64_image) return {"prompt": prompt, "images": images} else: - prompt = "".join( - ( - m["content"] - if isinstance(m["content"], str) is str - else "".join(m["content"]) - ) - for m in messages - ) + prompt = "" + for message in messages: + role = message["role"] + content = message.get("content", "") + + if "tool_calls" in message: + for call in message["tool_calls"]: + function_name = call["function"]["name"] + arguments = json.loads(call["function"]["arguments"]) + prompt += f"### Tool Call ({call["id"]}):\nFunction: {function_name}\nArguments: {json.dumps(arguments)}\n\n" + elif "tool_call_id" in message: + prompt += f"### Tool Call Result ({message["tool_call_id"]}):\n{message["content"]}\n\n" + elif content: + prompt += f"### {role.capitalize()}:\n{content}\n\n" + return prompt @@ -710,7 +717,7 @@ def convert_to_anthropic_tool_result_xml(message: dict) -> str: """ Anthropic tool_results look like: - + [Successful results] From 423a60c8bcfc1e7b4fd81ccbcba7757e772b85d8 Mon Sep 17 00:00:00 2001 From: corrm Date: Mon, 24 Jun 2024 05:55:22 +0300 Subject: [PATCH 018/137] chore: Improved OllamaConfig get_required_params and ollama_acompletion and ollama_async_streaming functions --- litellm/llms/ollama.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/ollama.py b/litellm/llms/ollama.py index e7dd1d5f5..1939715b3 100644 --- a/litellm/llms/ollama.py +++ b/litellm/llms/ollama.py @@ -126,7 +126,7 @@ class OllamaConfig: ) and v is not None } - + def get_required_params(self) -> List[ProviderField]: """For a given provider, return it's required fields with a description""" return [ @@ -451,7 +451,7 @@ async def ollama_acompletion(url, data, model_response, encoding, logging_obj): { "id": f"call_{str(uuid.uuid4())}", "function": { - "name": function_call["name"], + "name": function_call.get("name", function_call.get("function", None)), "arguments": json.dumps(function_call["arguments"]), }, "type": "function", From b8a8b0847c9e68726e56674add4aaa2951482a99 Mon Sep 17 00:00:00 2001 From: corrm Date: Mon, 24 Jun 2024 05:56:56 +0300 Subject: [PATCH 019/137] Added improved function name handling in ollama_async_streaming --- litellm/llms/ollama_chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/ollama_chat.py b/litellm/llms/ollama_chat.py index a7439bbcc..af6fd5b80 100644 --- a/litellm/llms/ollama_chat.py +++ b/litellm/llms/ollama_chat.py @@ -434,7 +434,7 @@ async def ollama_async_streaming( { "id": f"call_{str(uuid.uuid4())}", "function": { - "name": function_call["name"], + "name": function_call.get("name", function_call.get("function", None)), "arguments": json.dumps(function_call["arguments"]), }, "type": "function", From e2af13550a29db7ea0757a313c3590105c925a74 Mon Sep 17 00:00:00 2001 From: Islam Nofl Date: Mon, 24 Jun 2024 08:01:15 +0300 Subject: [PATCH 020/137] Rename ollama prompt 'Function' word to 'Name' --- litellm/llms/prompt_templates/factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index 02ed93fae..109c5b8d8 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -181,7 +181,7 @@ def ollama_pt( for call in message["tool_calls"]: function_name = call["function"]["name"] arguments = json.loads(call["function"]["arguments"]) - prompt += f"### Tool Call ({call["id"]}):\nFunction: {function_name}\nArguments: {json.dumps(arguments)}\n\n" + prompt += f"### Tool Call ({call["id"]}):\nName: {function_name}\nArguments: {json.dumps(arguments)}\n\n" elif "tool_call_id" in message: prompt += f"### Tool Call Result ({message["tool_call_id"]}):\n{message["content"]}\n\n" elif content: From 515dc9acc7f8bc133952585ab4bccd912bb0bd1a Mon Sep 17 00:00:00 2001 From: 7HR4IZ3 <90985774+7HR4IZ3@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:09:40 +0100 Subject: [PATCH 021/137] fix: Lunary integration Fixes the bug of litellm not logging system messages to lunary --- litellm/integrations/lunary.py | 1 + 1 file changed, 1 insertion(+) diff --git a/litellm/integrations/lunary.py b/litellm/integrations/lunary.py index f9b2f25e7..b0cc069c4 100644 --- a/litellm/integrations/lunary.py +++ b/litellm/integrations/lunary.py @@ -108,6 +108,7 @@ class LunaryLogger: try: print_verbose(f"Lunary Logging - Logging request for model {model}") + template_id = None litellm_params = kwargs.get("litellm_params", {}) optional_params = kwargs.get("optional_params", {}) metadata = litellm_params.get("metadata", {}) or {} From acbcfef5af24663ac61b377ac860378019b00197 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 07:54:08 -0700 Subject: [PATCH 022/137] fix ui login bug --- litellm/proxy/proxy_server.py | 52 ++++++++++++++--------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 630aa3f3e..1e987778c 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -7502,12 +7502,6 @@ async def login(request: Request): litellm_dashboard_ui += "/ui/" import jwt - if litellm_master_key_hash is None: - raise HTTPException( - status_code=500, - detail={"error": "No master key set, please set LITELLM_MASTER_KEY"}, - ) - jwt_token = jwt.encode( { "user_id": user_id, @@ -7517,7 +7511,7 @@ async def login(request: Request): "login_method": "username_password", "premium_user": premium_user, }, - litellm_master_key_hash, + master_key, algorithm="HS256", ) litellm_dashboard_ui += "?userID=" + user_id @@ -7572,14 +7566,6 @@ async def login(request: Request): litellm_dashboard_ui += "/ui/" import jwt - if litellm_master_key_hash is None: - raise HTTPException( - status_code=500, - detail={ - "error": "No master key set, please set LITELLM_MASTER_KEY" - }, - ) - jwt_token = jwt.encode( { "user_id": user_id, @@ -7589,7 +7575,7 @@ async def login(request: Request): "login_method": "username_password", "premium_user": premium_user, }, - litellm_master_key_hash, + master_key, algorithm="HS256", ) litellm_dashboard_ui += "?userID=" + user_id @@ -7636,7 +7622,14 @@ async def onboarding(invite_link: str): - Get user from db - Pass in user_email if set """ - global prisma_client + global prisma_client, master_key + if master_key is None: + raise ProxyException( + message="Master Key not set for Proxy. Please set Master Key to use Admin UI. Set `LITELLM_MASTER_KEY` in .env or set general_settings:master_key in config.yaml. https://docs.litellm.ai/docs/proxy/virtual_keys. If set, use `--detailed_debug` to debug issue.", + type="auth_error", + param="master_key", + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) ### VALIDATE INVITE LINK ### if prisma_client is None: raise HTTPException( @@ -7708,12 +7701,6 @@ async def onboarding(invite_link: str): litellm_dashboard_ui += "/ui/onboarding" import jwt - if litellm_master_key_hash is None: - raise HTTPException( - status_code=500, - detail={"error": "No master key set, please set LITELLM_MASTER_KEY"}, - ) - jwt_token = jwt.encode( { "user_id": user_obj.user_id, @@ -7723,7 +7710,7 @@ async def onboarding(invite_link: str): "login_method": "username_password", "premium_user": premium_user, }, - litellm_master_key_hash, + master_key, algorithm="HS256", ) @@ -7856,11 +7843,18 @@ def get_image(): @app.get("/sso/callback", tags=["experimental"], include_in_schema=False) async def auth_callback(request: Request): """Verify login""" - global general_settings, ui_access_mode, premium_user + global general_settings, ui_access_mode, premium_user, master_key microsoft_client_id = os.getenv("MICROSOFT_CLIENT_ID", None) google_client_id = os.getenv("GOOGLE_CLIENT_ID", None) generic_client_id = os.getenv("GENERIC_CLIENT_ID", None) # get url from request + if master_key is None: + raise ProxyException( + message="Master Key not set for Proxy. Please set Master Key to use Admin UI. Set `LITELLM_MASTER_KEY` in .env or set general_settings:master_key in config.yaml. https://docs.litellm.ai/docs/proxy/virtual_keys. If set, use `--detailed_debug` to debug issue.", + type="auth_error", + param="master_key", + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) redirect_url = os.getenv("PROXY_BASE_URL", str(request.base_url)) if redirect_url.endswith("/"): redirect_url += "sso/callback" @@ -8134,12 +8128,6 @@ async def auth_callback(request: Request): import jwt - if litellm_master_key_hash is None: - raise HTTPException( - status_code=500, - detail={"error": "No master key set, please set LITELLM_MASTER_KEY"}, - ) - jwt_token = jwt.encode( { "user_id": user_id, @@ -8149,7 +8137,7 @@ async def auth_callback(request: Request): "login_method": "sso", "premium_user": premium_user, }, - litellm_master_key_hash, + master_key, algorithm="HS256", ) litellm_dashboard_ui += "?userID=" + user_id From 70a605b3cc927b55192c80e04fd4139eb28ca496 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 13:21:16 -0700 Subject: [PATCH 023/137] docs - update telemetry --- docs/my-website/docs/observability/telemetry.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/my-website/docs/observability/telemetry.md b/docs/my-website/docs/observability/telemetry.md index 78267b9c5..232295566 100644 --- a/docs/my-website/docs/observability/telemetry.md +++ b/docs/my-website/docs/observability/telemetry.md @@ -1,13 +1,8 @@ # Telemetry -LiteLLM contains a telemetry feature that tells us what models are used, and what errors are hit. +There is no Telemetry on LiteLLM - no data is stored by us ## What is logged? -Only the model name and exception raised is logged. +NOTHING - no data is sent to LiteLLM Servers -## Why? -We use this information to help us understand how LiteLLM is used, and improve stability. - -## Opting out -If you prefer to opt out of telemetry, you can do this by setting `litellm.telemetry = False`. \ No newline at end of file From 123477b55a62825b429ebe5cbaedd7b3bad4b900 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Jun 2024 16:55:19 -0700 Subject: [PATCH 024/137] fix(utils.py): fix exception_mapping check for errors If exception already mapped - don't attach traceback to it --- litellm/exceptions.py | 16 +++++----------- litellm/utils.py | 4 ++++ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/litellm/exceptions.py b/litellm/exceptions.py index 9674d48b1..98b519278 100644 --- a/litellm/exceptions.py +++ b/litellm/exceptions.py @@ -9,10 +9,11 @@ ## LiteLLM versions of the OpenAI Exception Types -import openai -import httpx from typing import Optional +import httpx +import openai + class AuthenticationError(openai.AuthenticationError): # type: ignore def __init__( @@ -658,15 +659,8 @@ class APIResponseValidationError(openai.APIResponseValidationError): # type: ig class OpenAIError(openai.OpenAIError): # type: ignore - def __init__(self, original_exception): - self.status_code = original_exception.http_status - super().__init__( - http_body=original_exception.http_body, - http_status=original_exception.http_status, - json_body=original_exception.json_body, - headers=original_exception.headers, - code=original_exception.code, - ) + def __init__(self, original_exception=None): + super().__init__() self.llm_provider = "openai" diff --git a/litellm/utils.py b/litellm/utils.py index 0849ba3a2..ce66d0fbb 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5914,6 +5914,7 @@ def exception_type( ) else: # if no status code then it is an APIConnectionError: https://github.com/openai/openai-python#handling-errors + # exception_mapping_worked = True raise APIConnectionError( message=f"APIConnectionError: {exception_provider} - {message}", llm_provider=custom_llm_provider, @@ -7460,6 +7461,9 @@ def exception_type( if exception_mapping_worked: raise e else: + for error_type in litellm.LITELLM_EXCEPTION_TYPES: + if isinstance(e, error_type): + raise e # it's already mapped raise APIConnectionError( message="{}\n{}".format(original_exception, traceback.format_exc()), llm_provider="", From f5fbdf0feebcfe252e97dcf9be10732c45500dca Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Jun 2024 17:25:26 -0700 Subject: [PATCH 025/137] fix(router.py): use user-defined model_input_tokens for pre-call filter checks --- litellm/proxy/_new_secret_config.yaml | 16 ++++++++-- litellm/router.py | 42 +++++++++++++++++++++++++-- litellm/tests/test_router.py | 5 ++++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 640a3b2cf..78d7dc70c 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -4,7 +4,17 @@ model_list: 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 -litellm_settings: - success_callback: ["langfuse"] - failure_callback: ["langfuse"] +router_settings: + enable_pre_call_checks: True diff --git a/litellm/router.py b/litellm/router.py index e9b0cc00a..6163da487 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -404,6 +404,7 @@ class Router: litellm.failure_callback = [self.deployment_callback_on_failure] print( # noqa f"Intialized router with Routing strategy: {self.routing_strategy}\n\n" + f"Routing enable_pre_call_checks: {self.enable_pre_call_checks}\n\n" f"Routing fallbacks: {self.fallbacks}\n\n" f"Routing content fallbacks: {self.content_policy_fallbacks}\n\n" f"Routing context window fallbacks: {self.context_window_fallbacks}\n\n" @@ -3915,9 +3916,38 @@ class Router: raise Exception("Model invalid format - {}".format(type(model))) return None + def get_router_model_info(self, deployment: dict) -> ModelMapInfo: + """ + For a given model id, return the model info (max tokens, input cost, output cost, etc.). + + Augment litellm info with additional params set in `model_info`. + + Returns + - ModelInfo - If found -> typed dict with max tokens, input cost, etc. + """ + ## SET MODEL NAME + 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_info = litellm.get_model_info(model=model) + + ## CHECK USER SET MODEL INFO + user_model_info = deployment.get("model_info", {}) + + model_info.update(user_model_info) + + return model_info + def get_model_info(self, id: str) -> Optional[dict]: """ For a given model id, return the model info + + Returns + - dict: the model in list with 'model_name', 'litellm_params', Optional['model_info'] + - None: could not find deployment in list """ for model in self.model_list: if "model_info" in model and "id" in model["model_info"]: @@ -4307,6 +4337,7 @@ class Router: return _returned_deployments _context_window_error = False + _potential_error_str = "" _rate_limit_error = False ## get model group RPM ## @@ -4327,7 +4358,7 @@ class Router: model = base_model or deployment.get("litellm_params", {}).get( "model", None ) - model_info = litellm.get_model_info(model=model) + model_info = self.get_router_model_info(deployment=deployment) if ( isinstance(model_info, dict) @@ -4339,6 +4370,11 @@ class Router: ): invalid_model_indices.append(idx) _context_window_error = True + _potential_error_str += ( + "Model={}, Max Input Tokens={}, Got={}".format( + model, model_info["max_input_tokens"], input_tokens + ) + ) continue except Exception as e: verbose_router_logger.debug("An error occurs - {}".format(str(e))) @@ -4440,7 +4476,9 @@ class Router: ) elif _context_window_error == True: raise litellm.ContextWindowExceededError( - message="Context Window exceeded for given call", + message="litellm._pre_call_checks: Context Window exceeded for given call. No models have context window large enough for this call.\n{}".format( + _potential_error_str + ), model=model, llm_provider="", response=httpx.Response( diff --git a/litellm/tests/test_router.py b/litellm/tests/test_router.py index 2e8814327..84ea9e1c9 100644 --- a/litellm/tests/test_router.py +++ b/litellm/tests/test_router.py @@ -755,6 +755,7 @@ def test_router_context_window_check_pre_call_check_in_group(): "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), "base_model": "azure/gpt-35-turbo", + "mock_response": "Hello world 1!", }, }, { @@ -762,6 +763,7 @@ def test_router_context_window_check_pre_call_check_in_group(): "litellm_params": { # params for litellm completion/embedding call "model": "gpt-3.5-turbo-1106", "api_key": os.getenv("OPENAI_API_KEY"), + "mock_response": "Hello world 2!", }, }, ] @@ -777,6 +779,9 @@ def test_router_context_window_check_pre_call_check_in_group(): ) print(f"response: {response}") + + assert response.choices[0].message.content == "Hello world 2!" + assert False except Exception as e: pytest.fail(f"Got unexpected exception on router! - {str(e)}") From 341c7857c10b60af87602eabdea7ea0948a80f49 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Jun 2024 17:28:12 -0700 Subject: [PATCH 026/137] test(test_router.py): add testing --- litellm/tests/test_router.py | 57 ++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/litellm/tests/test_router.py b/litellm/tests/test_router.py index 84ea9e1c9..3237c8084 100644 --- a/litellm/tests/test_router.py +++ b/litellm/tests/test_router.py @@ -732,7 +732,61 @@ def test_router_rpm_pre_call_check(): pytest.fail(f"Got unexpected exception on router! - {str(e)}") -def test_router_context_window_check_pre_call_check_in_group(): +def test_router_context_window_check_pre_call_check_in_group_custom_model_info(): + """ + - Give a gpt-3.5-turbo model group with different context windows (4k vs. 16k) + - Send a 5k prompt + - Assert it works + """ + import os + + from large_text import text + + litellm.set_verbose = False + + print(f"len(text): {len(text)}") + try: + model_list = [ + { + "model_name": "gpt-3.5-turbo", # openai model name + "litellm_params": { # params for litellm completion/embedding call + "model": "azure/chatgpt-v-2", + "api_key": os.getenv("AZURE_API_KEY"), + "api_version": os.getenv("AZURE_API_VERSION"), + "api_base": os.getenv("AZURE_API_BASE"), + "base_model": "azure/gpt-35-turbo", + "mock_response": "Hello world 1!", + }, + "model_info": {"max_input_tokens": 100}, + }, + { + "model_name": "gpt-3.5-turbo", # openai model name + "litellm_params": { # params for litellm completion/embedding call + "model": "gpt-3.5-turbo-1106", + "api_key": os.getenv("OPENAI_API_KEY"), + "mock_response": "Hello world 2!", + }, + "model_info": {"max_input_tokens": 0}, + }, + ] + + router = Router(model_list=model_list, set_verbose=True, enable_pre_call_checks=True, num_retries=0) # type: ignore + + response = router.completion( + model="gpt-3.5-turbo", + messages=[ + {"role": "user", "content": "Who was Alexander?"}, + ], + ) + + print(f"response: {response}") + + assert response.choices[0].message.content == "Hello world 1!" + except Exception as e: + pytest.fail(f"Got unexpected exception on router! - {str(e)}") + + +def test_router_context_window_check_pre_call_check(): """ - Give a gpt-3.5-turbo model group with different context windows (4k vs. 16k) - Send a 5k prompt @@ -781,7 +835,6 @@ def test_router_context_window_check_pre_call_check_in_group(): print(f"response: {response}") assert response.choices[0].message.content == "Hello world 2!" - assert False except Exception as e: pytest.fail(f"Got unexpected exception on router! - {str(e)}") From 2f90759db5ab4c3183695e11ae5a1822b5ec8206 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 17:45:13 -0700 Subject: [PATCH 027/137] feat - allow user to define public routes --- litellm/proxy/auth/user_api_key_auth.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 3d14f5300..f6e3a0dfe 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -137,7 +137,9 @@ async def user_api_key_auth( """ route: str = request.url.path - if route in LiteLLMRoutes.public_routes.value: + if route in LiteLLMRoutes.public_routes.value or route in general_settings.get( + "public_routes", [] + ): # check if public endpoint return UserAPIKeyAuth(user_role=LitellmUserRoles.INTERNAL_USER_VIEW_ONLY) From 5309be245652e2f64a7584c72a82919322ef6c5a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 17:46:38 -0700 Subject: [PATCH 028/137] example config with public routes --- litellm/proxy/proxy_config.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index d5190455f..8898dd8cb 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -21,6 +21,9 @@ model_list: general_settings: master_key: sk-1234 alerting: ["slack", "email"] + public_routes: [ + "/spend/calculate", + ] litellm_settings: success_callback: ["prometheus"] From a4bea47a2dad4b7d801e5aac8835d78f1d3794d8 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Jun 2024 17:52:01 -0700 Subject: [PATCH 029/137] fix(router.py): log rejected router requests to langfuse Fixes issue where rejected requests weren't being logged --- .gitignore | 1 + litellm/integrations/langfuse.py | 38 ++-- litellm/proxy/_new_secret_config.yaml | 4 + litellm/router.py | 262 ++++++++++++++------------ 4 files changed, 167 insertions(+), 138 deletions(-) diff --git a/.gitignore b/.gitignore index b633e1d3d..8a9095b84 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ litellm/proxy/_experimental/out/model_hub/index.html litellm/proxy/_experimental/out/onboarding/index.html litellm/tests/log.txt litellm/tests/langfuse.log +litellm/tests/langfuse.log diff --git a/litellm/integrations/langfuse.py b/litellm/integrations/langfuse.py index eae8b8e22..794524684 100644 --- a/litellm/integrations/langfuse.py +++ b/litellm/integrations/langfuse.py @@ -36,9 +36,9 @@ class LangFuseLogger: self.langfuse_debug = os.getenv("LANGFUSE_DEBUG") parameters = { - "public_key": self.public_key, - "secret_key": self.secret_key, - "host": self.langfuse_host, + "public_key": "pk-lf-a65841e9-5192-4397-a679-cfff029fd5b0", + "secret_key": "sk-lf-d58c2891-3717-4f98-89dd-df44826215fd", + "host": "https://us.cloud.langfuse.com", "release": self.langfuse_release, "debug": self.langfuse_debug, "flush_interval": flush_interval, # flush interval in seconds @@ -311,22 +311,22 @@ 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 + # 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 supports_tags = Version(langfuse.version.__version__) >= Version("2.6.3") supports_prompt = Version(langfuse.version.__version__) >= Version("2.7.3") diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 78d7dc70c..16436c0ef 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -18,3 +18,7 @@ model_list: router_settings: enable_pre_call_checks: True + + +litellm_settings: + failure_callback: ["langfuse"] \ No newline at end of file diff --git a/litellm/router.py b/litellm/router.py index 6163da487..30bdbcba2 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -4474,17 +4474,13 @@ class Router: raise ValueError( f"{RouterErrors.no_deployments_available.value}, Try again in {self.cooldown_time} seconds. Passed model={model}. Try again in {self.cooldown_time} seconds." ) - elif _context_window_error == True: + elif _context_window_error is True: raise litellm.ContextWindowExceededError( message="litellm._pre_call_checks: Context Window exceeded for given call. No models have context window large enough for this call.\n{}".format( _potential_error_str ), model=model, llm_provider="", - response=httpx.Response( - status_code=400, - request=httpx.Request("GET", "https://example.com"), - ), ) if len(invalid_model_indices) > 0: for idx in reversed(invalid_model_indices): @@ -4596,127 +4592,155 @@ class Router: specific_deployment=specific_deployment, request_kwargs=request_kwargs, ) - - model, healthy_deployments = self._common_checks_available_deployment( - model=model, - messages=messages, - input=input, - specific_deployment=specific_deployment, - ) # type: ignore - - if isinstance(healthy_deployments, dict): - return healthy_deployments - - # filter out the deployments currently cooling down - deployments_to_remove = [] - # cooldown_deployments is a list of model_id's cooling down, cooldown_deployments = ["16700539-b3cd-42f4-b426-6a12a1bb706a", "16700539-b3cd-42f4-b426-7899"] - cooldown_deployments = await self._async_get_cooldown_deployments() - verbose_router_logger.debug( - f"async cooldown deployments: {cooldown_deployments}" - ) - # Find deployments in model_list whose model_id is cooling down - for deployment in healthy_deployments: - deployment_id = deployment["model_info"]["id"] - if deployment_id in cooldown_deployments: - deployments_to_remove.append(deployment) - # remove unhealthy deployments from healthy deployments - for deployment in deployments_to_remove: - healthy_deployments.remove(deployment) - - # filter pre-call checks - _allowed_model_region = ( - request_kwargs.get("allowed_model_region") - if request_kwargs is not None - else None - ) - - if self.enable_pre_call_checks and messages is not None: - healthy_deployments = self._pre_call_checks( + try: + model, healthy_deployments = self._common_checks_available_deployment( model=model, - healthy_deployments=healthy_deployments, - messages=messages, - request_kwargs=request_kwargs, - ) - - if len(healthy_deployments) == 0: - 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}" - ) - - if ( - self.routing_strategy == "usage-based-routing-v2" - and self.lowesttpm_logger_v2 is not None - ): - deployment = await self.lowesttpm_logger_v2.async_get_available_deployments( - model_group=model, - healthy_deployments=healthy_deployments, # type: ignore messages=messages, input=input, - ) - if ( - self.routing_strategy == "cost-based-routing" - and self.lowestcost_logger is not None - ): - deployment = await self.lowestcost_logger.async_get_available_deployments( - model_group=model, - healthy_deployments=healthy_deployments, # type: ignore - messages=messages, - input=input, - ) - elif self.routing_strategy == "simple-shuffle": - # if users pass rpm or tpm, we do a random weighted pick - based on rpm/tpm - ############## Check if we can do a RPM/TPM based weighted pick ################# - rpm = healthy_deployments[0].get("litellm_params").get("rpm", None) - if rpm is not None: - # use weight-random pick if rpms provided - rpms = [m["litellm_params"].get("rpm", 0) for m in healthy_deployments] - verbose_router_logger.debug(f"\nrpms {rpms}") - total_rpm = sum(rpms) - weights = [rpm / total_rpm for rpm in rpms] - verbose_router_logger.debug(f"\n weights {weights}") - # Perform weighted random pick - selected_index = random.choices(range(len(rpms)), weights=weights)[0] - verbose_router_logger.debug(f"\n selected index, {selected_index}") - deployment = healthy_deployments[selected_index] - verbose_router_logger.info( - f"get_available_deployment for model: {model}, Selected deployment: {self.print_deployment(deployment) or deployment[0]} for model: {model}" - ) - return deployment or deployment[0] - ############## Check if we can do a RPM/TPM based weighted pick ################# - tpm = healthy_deployments[0].get("litellm_params").get("tpm", None) - if tpm is not None: - # use weight-random pick if rpms provided - tpms = [m["litellm_params"].get("tpm", 0) for m in healthy_deployments] - verbose_router_logger.debug(f"\ntpms {tpms}") - total_tpm = sum(tpms) - weights = [tpm / total_tpm for tpm in tpms] - verbose_router_logger.debug(f"\n weights {weights}") - # Perform weighted random pick - selected_index = random.choices(range(len(tpms)), weights=weights)[0] - verbose_router_logger.debug(f"\n selected index, {selected_index}") - deployment = healthy_deployments[selected_index] - verbose_router_logger.info( - f"get_available_deployment for model: {model}, Selected deployment: {self.print_deployment(deployment) or deployment[0]} for model: {model}" - ) - return deployment or deployment[0] + specific_deployment=specific_deployment, + ) # type: ignore - ############## No RPM/TPM passed, we do a random pick ################# - item = random.choice(healthy_deployments) - return item or item[0] - if deployment is None: + if isinstance(healthy_deployments, dict): + return healthy_deployments + + # filter out the deployments currently cooling down + deployments_to_remove = [] + # cooldown_deployments is a list of model_id's cooling down, cooldown_deployments = ["16700539-b3cd-42f4-b426-6a12a1bb706a", "16700539-b3cd-42f4-b426-7899"] + cooldown_deployments = await self._async_get_cooldown_deployments() + verbose_router_logger.debug( + f"async cooldown deployments: {cooldown_deployments}" + ) + # Find deployments in model_list whose model_id is cooling down + for deployment in healthy_deployments: + deployment_id = deployment["model_info"]["id"] + if deployment_id in cooldown_deployments: + deployments_to_remove.append(deployment) + # remove unhealthy deployments from healthy deployments + for deployment in deployments_to_remove: + healthy_deployments.remove(deployment) + + # filter pre-call checks + _allowed_model_region = ( + request_kwargs.get("allowed_model_region") + if request_kwargs is not None + else None + ) + + if self.enable_pre_call_checks and messages is not None: + healthy_deployments = self._pre_call_checks( + model=model, + healthy_deployments=healthy_deployments, + messages=messages, + request_kwargs=request_kwargs, + ) + + if len(healthy_deployments) == 0: + 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}" + ) + + if ( + self.routing_strategy == "usage-based-routing-v2" + and self.lowesttpm_logger_v2 is not None + ): + deployment = ( + await self.lowesttpm_logger_v2.async_get_available_deployments( + model_group=model, + healthy_deployments=healthy_deployments, # type: ignore + messages=messages, + input=input, + ) + ) + if ( + self.routing_strategy == "cost-based-routing" + and self.lowestcost_logger is not None + ): + deployment = ( + await self.lowestcost_logger.async_get_available_deployments( + model_group=model, + healthy_deployments=healthy_deployments, # type: ignore + messages=messages, + input=input, + ) + ) + elif self.routing_strategy == "simple-shuffle": + # if users pass rpm or tpm, we do a random weighted pick - based on rpm/tpm + ############## Check if we can do a RPM/TPM based weighted pick ################# + rpm = healthy_deployments[0].get("litellm_params").get("rpm", None) + if rpm is not None: + # use weight-random pick if rpms provided + rpms = [ + m["litellm_params"].get("rpm", 0) for m in healthy_deployments + ] + verbose_router_logger.debug(f"\nrpms {rpms}") + total_rpm = sum(rpms) + weights = [rpm / total_rpm for rpm in rpms] + verbose_router_logger.debug(f"\n weights {weights}") + # Perform weighted random pick + selected_index = random.choices(range(len(rpms)), weights=weights)[ + 0 + ] + verbose_router_logger.debug(f"\n selected index, {selected_index}") + deployment = healthy_deployments[selected_index] + verbose_router_logger.info( + f"get_available_deployment for model: {model}, Selected deployment: {self.print_deployment(deployment) or deployment[0]} for model: {model}" + ) + return deployment or deployment[0] + ############## Check if we can do a RPM/TPM based weighted pick ################# + tpm = healthy_deployments[0].get("litellm_params").get("tpm", None) + if tpm is not None: + # use weight-random pick if rpms provided + tpms = [ + m["litellm_params"].get("tpm", 0) for m in healthy_deployments + ] + verbose_router_logger.debug(f"\ntpms {tpms}") + total_tpm = sum(tpms) + weights = [tpm / total_tpm for tpm in tpms] + verbose_router_logger.debug(f"\n weights {weights}") + # Perform weighted random pick + selected_index = random.choices(range(len(tpms)), weights=weights)[ + 0 + ] + verbose_router_logger.debug(f"\n selected index, {selected_index}") + deployment = healthy_deployments[selected_index] + verbose_router_logger.info( + f"get_available_deployment for model: {model}, Selected deployment: {self.print_deployment(deployment) or deployment[0]} for model: {model}" + ) + return deployment or deployment[0] + + ############## No RPM/TPM passed, we do a random pick ################# + item = random.choice(healthy_deployments) + return item or item[0] + if deployment is None: + verbose_router_logger.info( + f"get_available_deployment for model: {model}, No deployment available" + ) + raise ValueError( + f"{RouterErrors.no_deployments_available.value}, Try again in {self.cooldown_time} seconds. Passed model={model}" + ) verbose_router_logger.info( - f"get_available_deployment for model: {model}, No deployment available" + f"get_available_deployment for model: {model}, Selected deployment: {self.print_deployment(deployment)} for model: {model}" ) - raise ValueError( - f"{RouterErrors.no_deployments_available.value}, Try again in {self.cooldown_time} seconds. Passed model={model}" - ) - verbose_router_logger.info( - f"get_available_deployment for model: {model}, Selected deployment: {self.print_deployment(deployment)} for model: {model}" - ) - return deployment + return deployment + except Exception as e: + traceback_exception = traceback.format_exc() + # if router rejects call -> log to langfuse/otel/etc. + if request_kwargs is not None: + logging_obj = request_kwargs.get("litellm_logging_obj", None) + if logging_obj is not None: + ## LOGGING + threading.Thread( + target=logging_obj.failure_handler, + args=(e, traceback_exception), + ).start() # log response + # Handle any exceptions that might occur during streaming + asyncio.create_task( + logging_obj.async_failure_handler(e, traceback_exception) # type: ignore + ) + raise e def get_available_deployment( self, From 02ffed25453f5dbe15f52811dcc937f531b81148 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 19:05:34 -0700 Subject: [PATCH 030/137] feat - refactor /spend/calculate --- litellm/proxy/_types.py | 6 ++ .../spend_management_endpoints.py | 75 +++++++++++++++++-- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 0883763d1..640c7695a 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1627,3 +1627,9 @@ class CommonProxyErrors(enum.Enum): no_llm_router = "No models configured on proxy" not_allowed_access = "Admin-only endpoint. Not allowed to access this." not_premium_user = "You must be a LiteLLM Enterprise user to use this feature. If you have a license please set `LITELLM_LICENSE` in your env. If you want to obtain a license meet with us here: https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat" + + +class SpendCalculateRequest(LiteLLMBase): + model: Optional[str] = None + messages: Optional[List] = None + completion_response: Optional[dict] = None diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 11edd1887..8089c7acb 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -1199,7 +1199,7 @@ async def _get_spend_report_for_time_range( } }, ) -async def calculate_spend(request: Request): +async def calculate_spend(request: SpendCalculateRequest): """ Accepts all the params of completion_cost. @@ -1248,14 +1248,75 @@ async def calculate_spend(request: Request): }' ``` """ - from litellm import completion_cost + try: + from litellm import completion_cost + from litellm.cost_calculator import CostPerToken + from litellm.proxy.proxy_server import llm_router - data = await request.json() - if "completion_response" in data: - data["completion_response"] = litellm.ModelResponse( - **data["completion_response"] + _cost = None + if request.model is not None: + if request.messages is None: + raise HTTPException( + status_code=400, + detail="Bad Request - messages must be provided if 'model' is provided", + ) + + # check if model in llm_router + _model_in_llm_router = None + cost_per_token: Optional[CostPerToken] = None + if llm_router is not None: + for model in llm_router.model_list: + if model.get("model_name") == request.model: + _model_in_llm_router = model + + """ + 3 cases for /spend/calculate + + 1. user passes model, and model is defined on litellm config.yaml or in DB. use info on config or in DB in this case + 2. user passes model, and model is not defined on litellm config.yaml or in DB. Pass model as is to litellm.completion_cost + 3. user passes completion_response + + """ + if _model_in_llm_router is not None: + _litellm_params = _model_in_llm_router.get("litellm_params") + _litellm_model_name = _litellm_params.get("model") + input_cost_per_token = _litellm_params.get("input_cost_per_token") + output_cost_per_token = _litellm_params.get("output_cost_per_token") + if ( + input_cost_per_token is not None + or output_cost_per_token is not None + ): + cost_per_token = CostPerToken( + input_cost_per_token=input_cost_per_token, + output_cost_per_token=output_cost_per_token, + ) + + _cost = completion_cost( + model=_litellm_model_name, + messages=request.messages, + custom_cost_per_token=cost_per_token, + ) + else: + _cost = completion_cost(model=request.model, messages=request.messages) + else: + _completion_response = litellm.ModelResponse(request.completion_response) + _cost = completion_cost(completion_response=_completion_response) + return {"cost": _cost} + except Exception as e: + if isinstance(e, HTTPException): + raise ProxyException( + message=getattr(e, "detail", str(e)), + type=getattr(e, "type", "None"), + param=getattr(e, "param", "None"), + code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + ) + 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), ) - return {"cost": completion_cost(**data)} @router.get( From 1ff0129a94e8fa3b422e38b49d6ec24df6745791 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Jun 2024 19:13:56 -0700 Subject: [PATCH 031/137] fix(vertex_httpx.py): cover gemini content violation (on prompt) --- litellm/llms/vertex_httpx.py | 87 +++++++++++++++++++++---- litellm/proxy/_super_secret_config.yaml | 3 + litellm/types/llms/vertex_ai.py | 6 +- 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index 63bcd9f4f..028c3f721 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -563,6 +563,43 @@ class VertexLLM(BaseLLM): ) ## CHECK IF RESPONSE FLAGGED + if "promptFeedback" in completion_response: + if "blockReason" in completion_response["promptFeedback"]: + # If set, the prompt was blocked and no candidates are returned. Rephrase your prompt + model_response.choices[0].finish_reason = "content_filter" + + chat_completion_message: ChatCompletionResponseMessage = { + "role": "assistant", + "content": None, + } + + choice = litellm.Choices( + finish_reason="content_filter", + index=0, + message=chat_completion_message, # type: ignore + logprobs=None, + enhancements=None, + ) + + model_response.choices = [choice] + + ## GET USAGE ## + usage = litellm.Usage( + prompt_tokens=completion_response["usageMetadata"][ + "promptTokenCount" + ], + completion_tokens=completion_response["usageMetadata"].get( + "candidatesTokenCount", 0 + ), + total_tokens=completion_response["usageMetadata"][ + "totalTokenCount" + ], + ) + + setattr(model_response, "usage", usage) + + return model_response + if len(completion_response["candidates"]) > 0: content_policy_violations = ( VertexGeminiConfig().get_flagged_finish_reasons() @@ -573,16 +610,40 @@ class VertexLLM(BaseLLM): in content_policy_violations.keys() ): ## CONTENT POLICY VIOLATION ERROR - raise VertexAIError( - status_code=400, - message="The response was blocked. Reason={}. Raw Response={}".format( - content_policy_violations[ - completion_response["candidates"][0]["finishReason"] - ], - completion_response, - ), + model_response.choices[0].finish_reason = "content_filter" + + chat_completion_message = { + "role": "assistant", + "content": None, + } + + choice = litellm.Choices( + finish_reason="content_filter", + index=0, + message=chat_completion_message, # type: ignore + logprobs=None, + enhancements=None, ) + model_response.choices = [choice] + + ## GET USAGE ## + usage = litellm.Usage( + prompt_tokens=completion_response["usageMetadata"][ + "promptTokenCount" + ], + completion_tokens=completion_response["usageMetadata"].get( + "candidatesTokenCount", 0 + ), + total_tokens=completion_response["usageMetadata"][ + "totalTokenCount" + ], + ) + + setattr(model_response, "usage", usage) + + return model_response + model_response.choices = [] # type: ignore ## GET MODEL ## @@ -590,9 +651,7 @@ class VertexLLM(BaseLLM): try: ## GET TEXT ## - chat_completion_message: ChatCompletionResponseMessage = { - "role": "assistant" - } + chat_completion_message = {"role": "assistant"} content_str = "" tools: List[ChatCompletionToolCallChunk] = [] for idx, candidate in enumerate(completion_response["candidates"]): @@ -632,9 +691,9 @@ class VertexLLM(BaseLLM): ## GET USAGE ## usage = litellm.Usage( prompt_tokens=completion_response["usageMetadata"]["promptTokenCount"], - completion_tokens=completion_response["usageMetadata"][ - "candidatesTokenCount" - ], + completion_tokens=completion_response["usageMetadata"].get( + "candidatesTokenCount", 0 + ), total_tokens=completion_response["usageMetadata"]["totalTokenCount"], ) diff --git a/litellm/proxy/_super_secret_config.yaml b/litellm/proxy/_super_secret_config.yaml index 04a4806c1..c5f1b4768 100644 --- a/litellm/proxy/_super_secret_config.yaml +++ b/litellm/proxy/_super_secret_config.yaml @@ -1,4 +1,7 @@ model_list: +- model_name: gemini-1.5-flash-gemini + litellm_params: + model: gemini/gemini-1.5-flash - litellm_params: api_base: http://0.0.0.0:8080 api_key: '' diff --git a/litellm/types/llms/vertex_ai.py b/litellm/types/llms/vertex_ai.py index 1612f8761..2dda57c2e 100644 --- a/litellm/types/llms/vertex_ai.py +++ b/litellm/types/llms/vertex_ai.py @@ -227,9 +227,9 @@ class PromptFeedback(TypedDict): blockReasonMessage: str -class UsageMetadata(TypedDict): - promptTokenCount: int - totalTokenCount: int +class UsageMetadata(TypedDict, total=False): + promptTokenCount: Required[int] + totalTokenCount: Required[int] candidatesTokenCount: int From 8e6e5a6d37cd30f17304ccf7bfa5b58aa76e74fc Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Jun 2024 19:22:20 -0700 Subject: [PATCH 032/137] fix(vertex_httpx.py): Return empty model response for content filter violations --- litellm/llms/vertex_httpx.py | 6 +-- .../tests/test_amazing_vertex_completion.py | 41 ++++++++++++++----- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/litellm/llms/vertex_httpx.py b/litellm/llms/vertex_httpx.py index 028c3f721..856b05f61 100644 --- a/litellm/llms/vertex_httpx.py +++ b/litellm/llms/vertex_httpx.py @@ -562,6 +562,9 @@ class VertexLLM(BaseLLM): status_code=422, ) + ## GET MODEL ## + model_response.model = model + ## CHECK IF RESPONSE FLAGGED if "promptFeedback" in completion_response: if "blockReason" in completion_response["promptFeedback"]: @@ -646,9 +649,6 @@ class VertexLLM(BaseLLM): model_response.choices = [] # type: ignore - ## GET MODEL ## - model_response.model = model - try: ## GET TEXT ## chat_completion_message = {"role": "assistant"} diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index fb2891249..c9e5501a8 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -696,6 +696,18 @@ async def test_gemini_pro_function_calling_httpx(provider, sync_mode): pytest.fail("An unexpected exception occurred - {}".format(str(e))) +def vertex_httpx_mock_reject_prompt_post(*args, **kwargs): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"Content-Type": "application/json"} + mock_response.json.return_value = { + "promptFeedback": {"blockReason": "OTHER"}, + "usageMetadata": {"promptTokenCount": 6285, "totalTokenCount": 6285}, + } + + return mock_response + + # @pytest.mark.skip(reason="exhausted vertex quota. need to refactor to mock the call") def vertex_httpx_mock_post(url, data=None, json=None, headers=None): mock_response = MagicMock() @@ -817,8 +829,11 @@ def vertex_httpx_mock_post(url, data=None, json=None, headers=None): @pytest.mark.parametrize("provider", ["vertex_ai_beta"]) # "vertex_ai", +@pytest.mark.parametrize("content_filter_type", ["prompt", "response"]) # "vertex_ai", @pytest.mark.asyncio -async def test_gemini_pro_json_schema_httpx_content_policy_error(provider): +async def test_gemini_pro_json_schema_httpx_content_policy_error( + provider, content_filter_type +): load_vertex_ai_credentials() litellm.set_verbose = True messages = [ @@ -839,16 +854,20 @@ Using this JSON schema: client = HTTPHandler() - with patch.object(client, "post", side_effect=vertex_httpx_mock_post) as mock_call: - try: - response = completion( - model="vertex_ai_beta/gemini-1.5-flash", - messages=messages, - response_format={"type": "json_object"}, - client=client, - ) - except litellm.ContentPolicyViolationError as e: - pass + if content_filter_type == "prompt": + _side_effect = vertex_httpx_mock_reject_prompt_post + else: + _side_effect = vertex_httpx_mock_post + + with patch.object(client, "post", side_effect=_side_effect) as mock_call: + response = completion( + model="vertex_ai_beta/gemini-1.5-flash", + messages=messages, + response_format={"type": "json_object"}, + client=client, + ) + + assert response.choices[0].finish_reason == "content_filter" mock_call.assert_called_once() From 11117665e1ee9e9ac9207f68dc17c63dc6d77e4e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 19:32:52 -0700 Subject: [PATCH 033/137] test - spend/calculate endpoints --- .../spend_management_endpoints.py | 9 +- .../tests/test_spend_calculate_endpoint.py | 103 ++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 litellm/tests/test_spend_calculate_endpoint.py diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 8089c7acb..abbdc3419 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -1298,9 +1298,14 @@ async def calculate_spend(request: SpendCalculateRequest): ) else: _cost = completion_cost(model=request.model, messages=request.messages) - else: - _completion_response = litellm.ModelResponse(request.completion_response) + elif request.completion_response is not None: + _completion_response = litellm.ModelResponse(**request.completion_response) _cost = completion_cost(completion_response=_completion_response) + else: + raise HTTPException( + status_code=400, + detail="Bad Request - Either 'model' or 'completion_response' must be provided", + ) return {"cost": _cost} except Exception as e: if isinstance(e, HTTPException): diff --git a/litellm/tests/test_spend_calculate_endpoint.py b/litellm/tests/test_spend_calculate_endpoint.py new file mode 100644 index 000000000..f8aff337e --- /dev/null +++ b/litellm/tests/test_spend_calculate_endpoint.py @@ -0,0 +1,103 @@ +import os +import sys + +import pytest +from dotenv import load_dotenv +from fastapi import Request +from fastapi.routing import APIRoute + +import litellm +from litellm.proxy._types import SpendCalculateRequest +from litellm.proxy.spend_tracking.spend_management_endpoints import calculate_spend +from litellm.router import Router + +# this file is to test litellm/proxy + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + + +@pytest.mark.asyncio +async def test_spend_calc_model_messages(): + cost_obj = await calculate_spend( + request=SpendCalculateRequest( + model="gpt-3.5-turbo", + messages=[ + {"role": "user", "content": "What is the capital of France?"}, + ], + ) + ) + + print("calculated cost", cost_obj) + cost = cost_obj["cost"] + assert cost > 0.0 + + +@pytest.mark.asyncio +async def test_spend_calc_model_on_router_messages(): + from litellm.proxy.proxy_server import llm_router as init_llm_router + + temp_llm_router = Router( + model_list=[ + { + "model_name": "special-llama-model", + "litellm_params": { + "model": "groq/llama3-8b-8192", + }, + } + ] + ) + + setattr(litellm.proxy.proxy_server, "llm_router", temp_llm_router) + + cost_obj = await calculate_spend( + request=SpendCalculateRequest( + model="special-llama-model", + messages=[ + {"role": "user", "content": "What is the capital of France?"}, + ], + ) + ) + + print("calculated cost", cost_obj) + _cost = cost_obj["cost"] + + assert _cost > 0.0 + + # set router to init value + setattr(litellm.proxy.proxy_server, "llm_router", init_llm_router) + + +@pytest.mark.asyncio +async def test_spend_calc_using_response(): + cost_obj = await calculate_spend( + request=SpendCalculateRequest( + completion_response={ + "id": "chatcmpl-3bc7abcd-f70b-48ab-a16c-dfba0b286c86", + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "Yooo! What's good?", + "role": "assistant", + }, + } + ], + "created": "1677652288", + "model": "groq/llama3-8b-8192", + "object": "chat.completion", + "system_fingerprint": "fp_873a560973", + "usage": { + "completion_tokens": 8, + "prompt_tokens": 12, + "total_tokens": 20, + }, + } + ) + ) + + print("calculated cost", cost_obj) + cost = cost_obj["cost"] + assert cost > 0.0 From d182ea0f77668902e4a6a8fe5bb32c914b387188 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Jun 2024 19:41:29 -0700 Subject: [PATCH 034/137] fix(utils.py): catch 422-status errors --- litellm/llms/replicate.py | 27 ++++++++++++++++++++------- litellm/utils.py | 8 ++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/litellm/llms/replicate.py b/litellm/llms/replicate.py index ce62e51e9..56549cfd4 100644 --- a/litellm/llms/replicate.py +++ b/litellm/llms/replicate.py @@ -1,13 +1,18 @@ -import os, types +import asyncio import json -import requests # type: ignore +import os import time -from typing import Callable, Optional, Union, Tuple, Any -from litellm.utils import ModelResponse, Usage, CustomStreamWrapper -import litellm, asyncio +import types +from typing import Any, Callable, Optional, Tuple, Union + import httpx # type: ignore -from .prompt_templates.factory import prompt_factory, custom_prompt +import requests # type: ignore + +import litellm from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler +from litellm.utils import CustomStreamWrapper, ModelResponse, Usage + +from .prompt_templates.factory import custom_prompt, prompt_factory class ReplicateError(Exception): @@ -329,7 +334,15 @@ async def async_handle_prediction_response_streaming( response_data = response.json() status = response_data["status"] if "output" in response_data: - output_string = "".join(response_data["output"]) + try: + output_string = "".join(response_data["output"]) + except Exception as e: + raise ReplicateError( + status_code=422, + message="Unable to parse response. Got={}".format( + response_data["output"] + ), + ) new_output = output_string[len(previous_output) :] print_verbose(f"New chunk: {new_output}") yield {"output": new_output, "status": status} diff --git a/litellm/utils.py b/litellm/utils.py index ce66d0fbb..1bc8bf771 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -6068,6 +6068,14 @@ def exception_type( model=model, llm_provider="replicate", ) + elif original_exception.status_code == 422: + exception_mapping_worked = True + raise UnprocessableEntityError( + message=f"ReplicateException - {original_exception.message}", + llm_provider="replicate", + model=model, + response=original_exception.response, + ) elif original_exception.status_code == 429: exception_mapping_worked = True raise RateLimitError( From 5e1403cd0af8af747875662ce334cbb8489af678 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 19:50:35 -0700 Subject: [PATCH 035/137] add helper to check route_in_additonal_public_routes --- litellm/proxy/auth/auth_utils.py | 42 +++++++++++++++++++++++++ litellm/proxy/auth/user_api_key_auth.py | 6 ++-- 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 litellm/proxy/auth/auth_utils.py diff --git a/litellm/proxy/auth/auth_utils.py b/litellm/proxy/auth/auth_utils.py new file mode 100644 index 000000000..60e59a584 --- /dev/null +++ b/litellm/proxy/auth/auth_utils.py @@ -0,0 +1,42 @@ +from litellm._logging import verbose_proxy_logger +from litellm.proxy._types import LiteLLMRoutes +from litellm.proxy.proxy_server import general_settings, premium_user + + +def route_in_additonal_public_routes(current_route: str): + """ + Helper to check if the user defined public_routes on config.yaml + + Parameters: + - current_route: str - the route the user is trying to call + + Returns: + - bool - True if the route is defined in public_routes + - bool - False if the route is not defined in public_routes + + + In order to use this the litellm config.yaml should have the following in general_settings: + + ```yaml + general_settings: + master_key: sk-1234 + public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate"] + ``` + """ + + # check if user is premium_user - if not do nothing + try: + if premium_user is not True: + return False + # check if this is defined on the config + if general_settings is None: + return False + + routes_defined = general_settings.get("public_routes", []) + if current_route in routes_defined: + return True + + return False + except Exception as e: + verbose_proxy_logger.error(f"route_in_additonal_public_routes: {str(e)}") + return False diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index f6e3a0dfe..d3e937734 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -56,6 +56,7 @@ from litellm.proxy.auth.auth_checks import ( get_user_object, log_to_opentelemetry, ) +from litellm.proxy.auth.auth_utils import route_in_additonal_public_routes from litellm.proxy.common_utils.http_parsing_utils import _read_request_body from litellm.proxy.utils import _to_ns @@ -137,8 +138,9 @@ async def user_api_key_auth( """ route: str = request.url.path - if route in LiteLLMRoutes.public_routes.value or route in general_settings.get( - "public_routes", [] + if ( + route in LiteLLMRoutes.public_routes.value + or route_in_additonal_public_routes(current_route=route) ): # check if public endpoint return UserAPIKeyAuth(user_role=LitellmUserRoles.INTERNAL_USER_VIEW_ONLY) From bd2298eba4598e338904f2eea1a9dd055af8a8fd Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 19:51:23 -0700 Subject: [PATCH 036/137] example cofnig with public routes --- litellm/proxy/proxy_config.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 8898dd8cb..caa6bc13b 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -21,9 +21,8 @@ model_list: general_settings: master_key: sk-1234 alerting: ["slack", "email"] - public_routes: [ - "/spend/calculate", - ] + public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate"] + litellm_settings: success_callback: ["prometheus"] From 2aa0ce50b5438b1c1f4ea0cb8ad2405bc3ef7031 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 19:58:53 -0700 Subject: [PATCH 037/137] fix importing litellm --- litellm/proxy/auth/auth_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/auth/auth_utils.py b/litellm/proxy/auth/auth_utils.py index 60e59a584..cc09a9689 100644 --- a/litellm/proxy/auth/auth_utils.py +++ b/litellm/proxy/auth/auth_utils.py @@ -1,6 +1,4 @@ from litellm._logging import verbose_proxy_logger -from litellm.proxy._types import LiteLLMRoutes -from litellm.proxy.proxy_server import general_settings, premium_user def route_in_additonal_public_routes(current_route: str): @@ -25,6 +23,9 @@ def route_in_additonal_public_routes(current_route: str): """ # check if user is premium_user - if not do nothing + from litellm.proxy._types import LiteLLMRoutes + from litellm.proxy.proxy_server import general_settings, premium_user + try: if premium_user is not True: return False From 7f8c502a44ad5fb9601c29de1fcf23689157e064 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 20:54:43 -0700 Subject: [PATCH 038/137] docs control available public routes --- docs/my-website/docs/enterprise.md | 1 + docs/my-website/docs/proxy/enterprise.md | 43 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/docs/my-website/docs/enterprise.md b/docs/my-website/docs/enterprise.md index 0edf937ed..2d45ea3ea 100644 --- a/docs/my-website/docs/enterprise.md +++ b/docs/my-website/docs/enterprise.md @@ -12,6 +12,7 @@ This covers: - ✅ [**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) - ✅ [**Prompt Injection Detection**](#prompt-injection-detection-lakeraai) - ✅ [**Invite Team Members to access `/spend` Routes**](../docs/proxy/cost_tracking#allowing-non-proxy-admins-to-access-spend-endpoints) - ✅ **Feature Prioritization** diff --git a/docs/my-website/docs/proxy/enterprise.md b/docs/my-website/docs/proxy/enterprise.md index e657d3b73..40a5261cd 100644 --- a/docs/my-website/docs/proxy/enterprise.md +++ b/docs/my-website/docs/proxy/enterprise.md @@ -14,6 +14,7 @@ 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) - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) - ✅ [Content Moderation with LLM Guard, LlamaGuard, Google Text Moderations](#content-moderation) - ✅ [Prompt Injection Detection (with LakeraAI API)](#prompt-injection-detection---lakeraai) @@ -448,6 +449,48 @@ Expected Response +## Control available public, private routes + +:::info + +❓ Use this when you want to make an existing private route -> public + +Example - Make `/spend/calculate` a publicly available route (by default `/spend/calculate` on LiteLLM Proxy requires authentication) + +::: + +#### Usage - Define public routes + +**Step 1** - set allowed public routes on config.yaml + +`LiteLLMRoutes.public_routes` is an ENUM corresponding to the default public routes on LiteLLM. [You can see this here](https://github.com/BerriAI/litellm/blob/main/litellm/proxy/_types.py) + +```yaml +general_settings: + master_key: sk-1234 + public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate"] +``` + +**Step 2** - start proxy + +```shell +litellm --config config.yaml +``` + +**Step 3** - Test it + +```shell +curl --request POST \ + --url 'http://localhost:4000/spend/calculate' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "gpt-4", + "messages": [{"role": "user", "content": "Hey, how'\''s it going?"}] + }' +``` + +🎉 Expect this endpoint to work without an `Authorization / Bearer Token` + From a64d1de80f988fda56304db2cc74abac1a82d500 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 21:13:01 -0700 Subject: [PATCH 039/137] test - aliases on /spend/calculate --- .../tests/test_spend_calculate_endpoint.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/litellm/tests/test_spend_calculate_endpoint.py b/litellm/tests/test_spend_calculate_endpoint.py index f8aff337e..8bdd4a54d 100644 --- a/litellm/tests/test_spend_calculate_endpoint.py +++ b/litellm/tests/test_spend_calculate_endpoint.py @@ -101,3 +101,41 @@ async def test_spend_calc_using_response(): print("calculated cost", cost_obj) cost = cost_obj["cost"] assert cost > 0.0 + + +@pytest.mark.asyncio +async def test_spend_calc_model_alias_on_router_messages(): + from litellm.proxy.proxy_server import llm_router as init_llm_router + + temp_llm_router = Router( + model_list=[ + { + "model_name": "gpt-4o", + "litellm_params": { + "model": "gpt-4o", + }, + } + ], + model_group_alias={ + "gpt4o": "gpt-4o", + }, + ) + + setattr(litellm.proxy.proxy_server, "llm_router", temp_llm_router) + + cost_obj = await calculate_spend( + request=SpendCalculateRequest( + model="gpt4o", + messages=[ + {"role": "user", "content": "What is the capital of France?"}, + ], + ) + ) + + print("calculated cost", cost_obj) + _cost = cost_obj["cost"] + + assert _cost > 0.0 + + # set router to init value + setattr(litellm.proxy.proxy_server, "llm_router", init_llm_router) From 9e7d8ba910cac16a61a88b109a41181ceb746746 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 21:14:00 -0700 Subject: [PATCH 040/137] /spend/calculate use model aliases on this endpoint --- .../spend_management_endpoints.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index abbdc3419..1fbd95b3c 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -1265,9 +1265,22 @@ async def calculate_spend(request: SpendCalculateRequest): _model_in_llm_router = None cost_per_token: Optional[CostPerToken] = None if llm_router is not None: - for model in llm_router.model_list: - if model.get("model_name") == request.model: - _model_in_llm_router = model + if ( + llm_router.model_group_alias is not None + and request.model in llm_router.model_group_alias + ): + # lookup alias in llm_router + _model_group_name = llm_router.model_group_alias[request.model] + for model in llm_router.model_list: + if model.get("model_name") == _model_group_name: + _model_in_llm_router = model + + else: + # no model_group aliases set -> try finding model in llm_router + # find model in llm_router + for model in llm_router.model_list: + if model.get("model_name") == request.model: + _model_in_llm_router = model """ 3 cases for /spend/calculate From 93dbdf6d928c25374df81aca8807f066dc1d0496 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 24 Jun 2024 21:15:36 -0700 Subject: [PATCH 041/137] =?UTF-8?q?bump:=20version=201.40.25=20=E2=86=92?= =?UTF-8?q?=201.40.26?= 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 fc3526dcc..6b4884b5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.40.25" +version = "1.40.26" 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.25" +version = "1.40.26" version_files = [ "pyproject.toml:^version" ] From f8b390d421d6dbc7a4bd1d812ddf855bd4244841 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Jun 2024 21:43:40 -0700 Subject: [PATCH 042/137] fix(langfuse.py): cleanup --- litellm/integrations/langfuse.py | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/litellm/integrations/langfuse.py b/litellm/integrations/langfuse.py index 794524684..eae8b8e22 100644 --- a/litellm/integrations/langfuse.py +++ b/litellm/integrations/langfuse.py @@ -36,9 +36,9 @@ class LangFuseLogger: self.langfuse_debug = os.getenv("LANGFUSE_DEBUG") parameters = { - "public_key": "pk-lf-a65841e9-5192-4397-a679-cfff029fd5b0", - "secret_key": "sk-lf-d58c2891-3717-4f98-89dd-df44826215fd", - "host": "https://us.cloud.langfuse.com", + "public_key": self.public_key, + "secret_key": self.secret_key, + "host": self.langfuse_host, "release": self.langfuse_release, "debug": self.langfuse_debug, "flush_interval": flush_interval, # flush interval in seconds @@ -311,22 +311,22 @@ 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 + 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 supports_tags = Version(langfuse.version.__version__) >= Version("2.6.3") supports_prompt = Version(langfuse.version.__version__) >= Version("2.7.3") From 86cb5aa031478e825b6b222a48d020a96de8c3e6 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 24 Jun 2024 22:25:39 -0700 Subject: [PATCH 043/137] docs(routing.md): add quickstart --- docs/my-website/docs/routing.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/my-website/docs/routing.md b/docs/my-website/docs/routing.md index fd4fb8658..de0a4a796 100644 --- a/docs/my-website/docs/routing.md +++ b/docs/my-website/docs/routing.md @@ -901,6 +901,39 @@ response = await router.acompletion( If a call fails after num_retries, fall back to another model group. +### Quick Start + +```python +from litellm import Router +router = Router( + model_list=[ + { # bad model + "model_name": "bad-model", + "litellm_params": { + "model": "openai/my-bad-model", + "api_key": "my-bad-api-key", + "mock_response": "Bad call" + }, + }, + { # good model + "model_name": "my-good-model", + "litellm_params": { + "model": "gpt-4o", + "api_key": os.getenv("OPENAI_API_KEY"), + "mock_response": "Good call" + }, + }, + ], + fallbacks=[{"bad-model": ["my-good-model"]}] # 👈 KEY CHANGE +) + +response = router.completion( + model="bad-model", + messages=[{"role": "user", "content": "Hey, how's it going?"}], + mock_testing_fallbacks=True, +) +``` + If the error is a context window exceeded error, fall back to a larger model group (if given). Fallbacks are done in-order - ["gpt-3.5-turbo, "gpt-4", "gpt-4-32k"], will do 'gpt-3.5-turbo' first, then 'gpt-4', etc. From b69a092f56c713ac9dcaf9dceb59c29faec2d315 Mon Sep 17 00:00:00 2001 From: corrm Date: Tue, 25 Jun 2024 12:40:07 +0300 Subject: [PATCH 044/137] Rename ollama prompt: - 'Function' word to 'FunctionName' - 'Tool Call' to `FunctionCall` - 'Tool Call Result' to 'FunctionCall Result' _I found that changes make some models better_ --- litellm/llms/prompt_templates/factory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index 109c5b8d8..7864d5ebc 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -181,9 +181,9 @@ def ollama_pt( for call in message["tool_calls"]: function_name = call["function"]["name"] arguments = json.loads(call["function"]["arguments"]) - prompt += f"### Tool Call ({call["id"]}):\nName: {function_name}\nArguments: {json.dumps(arguments)}\n\n" + prompt += f"### FunctionCall ({call["id"]}):\nFunctionName: {function_name}\nArguments: {json.dumps(arguments)}\n\n" elif "tool_call_id" in message: - prompt += f"### Tool Call Result ({message["tool_call_id"]}):\n{message["content"]}\n\n" + prompt += f"### FunctionCall Result ({message["tool_call_id"]}):\n{message["content"]}\n\n" elif content: prompt += f"### {role.capitalize()}:\n{content}\n\n" From 5b720137fdb2e65c9584bc1dbdb7a7e50120eb7e Mon Sep 17 00:00:00 2001 From: corrm Date: Tue, 25 Jun 2024 13:53:27 +0300 Subject: [PATCH 045/137] Improve ollama prompt: this formula give good result with AutoGen --- litellm/llms/prompt_templates/factory.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index 7864d5ebc..e359d36f4 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -135,7 +135,7 @@ def convert_to_ollama_image(openai_image_url: str): def ollama_pt( - model, messages + model, messages ): # https://github.com/ollama/ollama/blob/af4cf55884ac54b9e637cd71dadfe9b7a5685877/docs/modelfile.md#template if "instruct" in model: prompt = custom_prompt( @@ -178,12 +178,27 @@ def ollama_pt( content = message.get("content", "") if "tool_calls" in message: + tool_calls = [] + for call in message["tool_calls"]: - function_name = call["function"]["name"] + call_id: str = call["id"] + function_name: str = call["function"]["name"] arguments = json.loads(call["function"]["arguments"]) - prompt += f"### FunctionCall ({call["id"]}):\nFunctionName: {function_name}\nArguments: {json.dumps(arguments)}\n\n" + + tool_calls.append({ + "id": call_id, + "type": "function", + "function": { + "name": function_name, + "arguments": arguments + } + }) + + prompt += f"### Assistant:\nTool Calls: {json.dumps(tool_calls, indent=2)}\n\n" + elif "tool_call_id" in message: - prompt += f"### FunctionCall Result ({message["tool_call_id"]}):\n{message["content"]}\n\n" + prompt += f"### User:\n{message["content"]}\n\n" + elif content: prompt += f"### {role.capitalize()}:\n{content}\n\n" From a45e6064fa48fe6b4c574a52a851685c32e97749 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 25 Jun 2024 07:35:49 -0700 Subject: [PATCH 046/137] Added openrouter/anthropic/claude-3.5-sonnet to model json --- model_prices_and_context_window.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index ef07d87cc..45db23770 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -2073,6 +2073,18 @@ "supports_function_calling": true, "supports_vision": true }, + "openrouter/anthropic/claude-3.5-sonnet": { + "max_tokens": 4096, + "max_input_tokens": 200000, + "max_output_tokens": 4096, + "input_cost_per_token": 0.000003, + "output_cost_per_token": 0.000015, + "litellm_provider": "openrouter", + "mode": "chat", + "supports_function_calling": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, "openrouter/anthropic/claude-3-sonnet": { "max_tokens": 200000, "input_cost_per_token": 0.000003, From 34493fb22584f897469d1a87a8f1d068bcce0236 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 25 Jun 2024 07:43:58 -0700 Subject: [PATCH 047/137] Added openrouter/anthropic/claude-3-haiku-20240307 --- model_prices_and_context_window.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 45db23770..c4bccfb08 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -2073,6 +2073,18 @@ "supports_function_calling": true, "supports_vision": true }, + "openrouter/anthropic/claude-3-haiku-20240307": { + "max_tokens": 4096, + "max_input_tokens": 200000, + "max_output_tokens": 4096, + "input_cost_per_token": 0.00000025, + "output_cost_per_token": 0.00000125, + "litellm_provider": "openrouter", + "mode": "chat", + "supports_function_calling": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 264 + }, "openrouter/anthropic/claude-3.5-sonnet": { "max_tokens": 4096, "max_input_tokens": 200000, From 8765e120f07c1720e8b98dd74006951d352e673d Mon Sep 17 00:00:00 2001 From: Kyrylo Yefimenko Date: Tue, 25 Jun 2024 16:36:40 +0100 Subject: [PATCH 048/137] Fix Groq prices --- model_prices_and_context_window.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index ef07d87cc..415d220f2 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -887,7 +887,7 @@ "max_input_tokens": 8192, "max_output_tokens": 8192, "input_cost_per_token": 0.00000005, - "output_cost_per_token": 0.00000010, + "output_cost_per_token": 0.00000008, "litellm_provider": "groq", "mode": "chat", "supports_function_calling": true @@ -906,8 +906,8 @@ "max_tokens": 32768, "max_input_tokens": 32768, "max_output_tokens": 32768, - "input_cost_per_token": 0.00000027, - "output_cost_per_token": 0.00000027, + "input_cost_per_token": 0.00000024, + "output_cost_per_token": 0.00000024, "litellm_provider": "groq", "mode": "chat", "supports_function_calling": true @@ -916,8 +916,8 @@ "max_tokens": 8192, "max_input_tokens": 8192, "max_output_tokens": 8192, - "input_cost_per_token": 0.00000010, - "output_cost_per_token": 0.00000010, + "input_cost_per_token": 0.00000007, + "output_cost_per_token": 0.00000007, "litellm_provider": "groq", "mode": "chat", "supports_function_calling": true From abf919eef244118ebf5b12bd98a3b8810a9aa59b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 08:53:06 -0700 Subject: [PATCH 049/137] add nvidia nim to __init__ --- litellm/__init__.py | 3 +++ litellm/llms/prompt_templates/factory.py | 17 ++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index f07ce8809..d23247d53 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -401,6 +401,7 @@ openai_compatible_endpoints: List = [ "codestral.mistral.ai/v1/chat/completions", "codestral.mistral.ai/v1/fim/completions", "api.groq.com/openai/v1", + "https://integrate.api.nvidia.com/v1", "api.deepseek.com/v1", "api.together.xyz/v1", "inference.friendli.ai/v1", @@ -411,6 +412,7 @@ openai_compatible_providers: List = [ "anyscale", "mistral", "groq", + "nvidia_nim", "codestral", "deepseek", "deepinfra", @@ -640,6 +642,7 @@ provider_list: List = [ "anyscale", "mistral", "groq", + "nvidia_nim", "codestral", "text-completion-codestral", "deepseek", diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index e359d36f4..a97d6812c 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -135,7 +135,7 @@ def convert_to_ollama_image(openai_image_url: str): def ollama_pt( - model, messages + model, messages ): # https://github.com/ollama/ollama/blob/af4cf55884ac54b9e637cd71dadfe9b7a5685877/docs/modelfile.md#template if "instruct" in model: prompt = custom_prompt( @@ -185,19 +185,18 @@ def ollama_pt( function_name: str = call["function"]["name"] arguments = json.loads(call["function"]["arguments"]) - tool_calls.append({ - "id": call_id, - "type": "function", - "function": { - "name": function_name, - "arguments": arguments + tool_calls.append( + { + "id": call_id, + "type": "function", + "function": {"name": function_name, "arguments": arguments}, } - }) + ) prompt += f"### Assistant:\nTool Calls: {json.dumps(tool_calls, indent=2)}\n\n" elif "tool_call_id" in message: - prompt += f"### User:\n{message["content"]}\n\n" + prompt += f"### User:\n{message['content']}\n\n" elif content: prompt += f"### {role.capitalize()}:\n{content}\n\n" From d829d6393d46d17cab0ee192b7ca9b4da084184b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 08:57:11 -0700 Subject: [PATCH 050/137] feat - add nvidia nim to main.py --- litellm/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/litellm/main.py b/litellm/main.py index 307659c8a..8c531643b 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -348,6 +348,7 @@ async def acompletion( or custom_llm_provider == "deepinfra" or custom_llm_provider == "perplexity" or custom_llm_provider == "groq" + or custom_llm_provider == "nvidia_nim" or custom_llm_provider == "codestral" or custom_llm_provider == "text-completion-codestral" or custom_llm_provider == "deepseek" @@ -1171,6 +1172,7 @@ def completion( or custom_llm_provider == "deepinfra" or custom_llm_provider == "perplexity" or custom_llm_provider == "groq" + or custom_llm_provider == "nvidia_nim" or custom_llm_provider == "codestral" or custom_llm_provider == "deepseek" or custom_llm_provider == "anyscale" @@ -2932,6 +2934,7 @@ async def aembedding(*args, **kwargs) -> EmbeddingResponse: or custom_llm_provider == "deepinfra" or custom_llm_provider == "perplexity" or custom_llm_provider == "groq" + or custom_llm_provider == "nvidia_nim" or custom_llm_provider == "deepseek" or custom_llm_provider == "fireworks_ai" or custom_llm_provider == "ollama" @@ -3507,6 +3510,7 @@ async def atext_completion( or custom_llm_provider == "deepinfra" or custom_llm_provider == "perplexity" or custom_llm_provider == "groq" + or custom_llm_provider == "nvidia_nim" or custom_llm_provider == "text-completion-codestral" or custom_llm_provider == "deepseek" or custom_llm_provider == "fireworks_ai" From f3016250147304cb5a52fe1e4e402a2140d7540b Mon Sep 17 00:00:00 2001 From: Steven Osborn Date: Tue, 25 Jun 2024 09:03:05 -0700 Subject: [PATCH 051/137] create litellm user to fix issue in k8s where prisma fails due to user nobody without home directory --- Dockerfile.database | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Dockerfile.database b/Dockerfile.database index 22084bab8..1901200d5 100644 --- a/Dockerfile.database +++ b/Dockerfile.database @@ -9,6 +9,27 @@ FROM $LITELLM_BUILD_IMAGE as builder # Set the working directory to /app WORKDIR /app +ARG LITELLM_USER=litellm LITELLM_UID=1729 +ARG LITELLM_GROUP=litellm LITELLM_GID=1729 + +RUN groupadd \ + --gid ${LITELLM_GID} \ + ${LITELLM_GROUP} \ + && useradd \ + --create-home \ + --shell /bin/sh \ + --gid ${LITELLM_GID} \ + --uid ${LITELLM_UID} \ + ${LITELLM_USER} + +# Allows user to update python install. +# This is necessary for prisma. +RUN chown -R ${LITELLM_USER}:${LITELLM_GROUP} /usr/local/lib/python3.11 + +# Set the HOME var forcefully because of prisma. +ENV HOME=/home/${LITELLM_USER} +USER ${LITELLM_USER} + # Install build dependencies RUN apt-get clean && apt-get update && \ apt-get install -y gcc python3-dev && \ From 07829514d11df6543d85c38e92ba477e1691cff8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 09:13:08 -0700 Subject: [PATCH 052/137] feat - add param mapping for nvidia nim --- litellm/__init__.py | 1 + litellm/llms/nvidia_nim.py | 79 ++++++++++++++++++++++++++++++++++++++ litellm/utils.py | 23 +++++++++++ 3 files changed, 103 insertions(+) create mode 100644 litellm/llms/nvidia_nim.py diff --git a/litellm/__init__.py b/litellm/__init__.py index d23247d53..08ee84aaa 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -816,6 +816,7 @@ from .llms.openai import ( DeepInfraConfig, AzureAIStudioConfig, ) +from .llms.nvidia_nim import NvidiaNimConfig from .llms.text_completion_codestral import MistralTextCompletionConfig from .llms.azure import ( AzureOpenAIConfig, diff --git a/litellm/llms/nvidia_nim.py b/litellm/llms/nvidia_nim.py new file mode 100644 index 000000000..ebcc84c13 --- /dev/null +++ b/litellm/llms/nvidia_nim.py @@ -0,0 +1,79 @@ +""" +Nvidia NIM endpoint: https://docs.api.nvidia.com/nim/reference/databricks-dbrx-instruct-infer + +This is OpenAI compatible + +This file only contains param mapping logic + +API calling is done using the OpenAI SDK with an api_base +""" + +import types +from typing import Optional, Union + + +class NvidiaNimConfig: + """ + Reference: https://docs.api.nvidia.com/nim/reference/databricks-dbrx-instruct-infer + + The class `NvidiaNimConfig` provides configuration for the Nvidia NIM's Chat Completions API interface. Below are the parameters: + """ + + temperature: Optional[int] = None + top_p: Optional[int] = None + frequency_penalty: Optional[int] = None + presence_penalty: Optional[int] = None + max_tokens: Optional[int] = None + stop: Optional[Union[str, list]] = None + + def __init__( + self, + temperature: Optional[int] = None, + top_p: Optional[int] = None, + frequency_penalty: Optional[int] = None, + presence_penalty: Optional[int] = None, + max_tokens: Optional[int] = None, + stop: Optional[Union[str, list]] = None, + ) -> None: + locals_ = locals().copy() + for key, value in locals_.items(): + if key != "self" and value is not None: + setattr(self.__class__, key, value) + + @classmethod + def get_config(cls): + return { + k: v + for k, v in cls.__dict__.items() + if not k.startswith("__") + and not isinstance( + v, + ( + types.FunctionType, + types.BuiltinFunctionType, + classmethod, + staticmethod, + ), + ) + and v is not None + } + + def get_supported_openai_params(self): + return [ + "stream", + "temperature", + "top_p", + "frequency_penalty", + "presence_penalty", + "max_tokens", + "stop", + ] + + def map_openai_params( + self, non_default_params: dict, optional_params: dict + ) -> dict: + supported_openai_params = self.get_supported_openai_params() + for param, value in non_default_params.items(): + if param in supported_openai_params: + optional_params[param] = value + return optional_params diff --git a/litellm/utils.py b/litellm/utils.py index 1bc8bf771..7709e8821 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2410,6 +2410,7 @@ def get_optional_params( and custom_llm_provider != "anyscale" and custom_llm_provider != "together_ai" and custom_llm_provider != "groq" + and custom_llm_provider != "nvidia_nim" and custom_llm_provider != "deepseek" and custom_llm_provider != "codestral" and custom_llm_provider != "mistral" @@ -3060,6 +3061,14 @@ def get_optional_params( optional_params = litellm.DatabricksConfig().map_openai_params( non_default_params=non_default_params, optional_params=optional_params ) + elif custom_llm_provider == "nvidia_nim": + supported_params = get_supported_openai_params( + model=model, custom_llm_provider=custom_llm_provider + ) + _check_valid_arg(supported_params=supported_params) + optional_params = litellm.NvidiaNimConfig().map_openai_params( + non_default_params=non_default_params, optional_params=optional_params + ) elif custom_llm_provider == "groq": supported_params = get_supported_openai_params( model=model, custom_llm_provider=custom_llm_provider @@ -3626,6 +3635,8 @@ def get_supported_openai_params( return litellm.OllamaChatConfig().get_supported_openai_params() elif custom_llm_provider == "anthropic": return litellm.AnthropicConfig().get_supported_openai_params() + elif custom_llm_provider == "nvidia_nim": + return litellm.NvidiaNimConfig().get_supported_openai_params() elif custom_llm_provider == "groq": return [ "temperature", @@ -3986,6 +3997,10 @@ def get_llm_provider( # groq is openai compatible, we just need to set this to custom_openai and have the api_base be https://api.groq.com/openai/v1 api_base = "https://api.groq.com/openai/v1" dynamic_api_key = get_secret("GROQ_API_KEY") + elif custom_llm_provider == "nvidia_nim": + # nvidia_nim is openai compatible, we just need to set this to custom_openai and have the api_base be https://api.endpoints.anyscale.com/v1 + api_base = "https://integrate.api.nvidia.com/v1" + dynamic_api_key = get_secret("NVIDIA_NIM_API_KEY") elif custom_llm_provider == "codestral": # codestral is openai compatible, we just need to set this to custom_openai and have the api_base be https://codestral.mistral.ai/v1 api_base = "https://codestral.mistral.ai/v1" @@ -4087,6 +4102,9 @@ def get_llm_provider( elif endpoint == "api.groq.com/openai/v1": custom_llm_provider = "groq" dynamic_api_key = get_secret("GROQ_API_KEY") + elif endpoint == "https://integrate.api.nvidia.com/v1": + custom_llm_provider = "nvidia_nim" + dynamic_api_key = get_secret("NVIDIA_NIM_API_KEY") elif endpoint == "https://codestral.mistral.ai/v1": custom_llm_provider = "codestral" dynamic_api_key = get_secret("CODESTRAL_API_KEY") @@ -4900,6 +4918,11 @@ def validate_environment(model: Optional[str] = None) -> dict: keys_in_environment = True else: missing_keys.append("GROQ_API_KEY") + elif custom_llm_provider == "nvidia_nim": + if "NVIDIA_NIM_API_KEY" in os.environ: + keys_in_environment = True + else: + missing_keys.append("NVIDIA_NIM_API_KEY") elif ( custom_llm_provider == "codestral" or custom_llm_provider == "text-completion-codestral" From e5f281074f1da6f2b6e9495c4078902c64227f59 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 09:16:31 -0700 Subject: [PATCH 053/137] test - nvidia nim --- litellm/tests/test_completion.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 830b3acd3..0c6da360b 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -3470,6 +3470,28 @@ def test_completion_deep_infra_mistral(): # test_completion_deep_infra_mistral() +def test_completion_nvidia_nim(): + model_name = "nvidia_nim/databricks/dbrx-instruct" + try: + response = completion( + model=model_name, + messages=[ + { + "role": "user", + "content": "What's the weather like in Boston today in Fahrenheit?", + } + ], + ) + # Add any assertions here to check the response + print(response) + assert response.choices[0].message.content is not None + assert len(response.choices[0].message.content) > 0 + except litellm.exceptions.Timeout as e: + pass + except Exception as e: + pytest.fail(f"Error occurred: {e}") + + # Gemini tests @pytest.mark.parametrize( "model", From e354840f90d95124a2eb7fbe59097fa44139c5a3 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Tue, 25 Jun 2024 09:23:19 -0700 Subject: [PATCH 054/137] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 91b709442..ae354d1e3 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Support for more providers. Missing a provider or LLM Platform, raise a [feature > [!IMPORTANT] > LiteLLM v1.0.0 now requires `openai>=1.0.0`. Migration guide [here](https://docs.litellm.ai/docs/migration) +> LiteLLM v1.40.14+ now requires `pydantic>=2.0.0`. No changes required. Open In Colab From e2a50b66f2730db46c68ca2e9e4f8598e1effe0c Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Tue, 25 Jun 2024 09:24:00 -0700 Subject: [PATCH 055/137] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae354d1e3..6d26e92c2 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Support for more providers. Missing a provider or LLM Platform, raise a [feature # Usage ([**Docs**](https://docs.litellm.ai/docs/)) > [!IMPORTANT] -> LiteLLM v1.0.0 now requires `openai>=1.0.0`. Migration guide [here](https://docs.litellm.ai/docs/migration) +> LiteLLM v1.0.0 now requires `openai>=1.0.0`. Migration guide [here](https://docs.litellm.ai/docs/migration) > LiteLLM v1.40.14+ now requires `pydantic>=2.0.0`. No changes required. From bac5dfb30b4128b0f75d6cf823d371bddb7ef834 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 09:38:34 -0700 Subject: [PATCH 056/137] docs - add nvidia nim --- docs/my-website/docs/providers/nvidia_nim.md | 103 +++++++++++++++++++ docs/my-website/sidebars.js | 5 +- 2 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 docs/my-website/docs/providers/nvidia_nim.md diff --git a/docs/my-website/docs/providers/nvidia_nim.md b/docs/my-website/docs/providers/nvidia_nim.md new file mode 100644 index 000000000..f90450768 --- /dev/null +++ b/docs/my-website/docs/providers/nvidia_nim.md @@ -0,0 +1,103 @@ +# Nvidia NIM +https://docs.api.nvidia.com/nim/reference/ + +:::tip + +**We support ALL Nvidia NIM models, just set `model=nvidia_nim/` as a prefix when sending litellm requests** + +::: + +## API Key +```python +# env variable +os.environ['NVIDIA_NIM_API_KEY'] +``` + +## Sample Usage +```python +from litellm import completion +import os + +os.environ['NVIDIA_NIM_API_KEY'] = "" +response = completion( + model=model_name, + messages=[ + { + "role": "user", + "content": "What's the weather like in Boston today in Fahrenheit?", + } + ], + temperature=0.2, # optional + top_p=0.9, # optional + frequency_penalty=0.1, # optional + presence_penalty=0.1, # optional + max_tokens=10, # optional + stop=["\n\n"], # optional +) +print(response) +``` + +## Sample Usage - Streaming +```python +from litellm import completion +import os + +os.environ['NVIDIA_NIM_API_KEY'] = "" +response = completion( + model=model_name, + messages=[ + { + "role": "user", + "content": "What's the weather like in Boston today in Fahrenheit?", + } + ], + stream=True, + temperature=0.2, # optional + top_p=0.9, # optional + frequency_penalty=0.1, # optional + presence_penalty=0.1, # optional + max_tokens=10, # optional + stop=["\n\n"], # optional +) + +for chunk in response: + print(chunk) +``` + + +## Supported Models - 💥 ALL Nvidia NIM Models Supported! +We support ALL `nvidia_nim` models, just set `nvidia_nim/` as a prefix when sending completion requests + +| Model Name | Function Call | +|------------|---------------| +| nvidia/nemotron-4-340b-reward | `completion(model="nvidia_nim/nvidia/nemotron-4-340b-reward", messages)` | +| 01-ai/yi-large | `completion(model="nvidia_nim/01-ai/yi-large", messages)` | +| aisingapore/sea-lion-7b-instruct | `completion(model="nvidia_nim/aisingapore/sea-lion-7b-instruct", messages)` | +| databricks/dbrx-instruct | `completion(model="nvidia_nim/databricks/dbrx-instruct", messages)` | +| google/gemma-7b | `completion(model="nvidia_nim/google/gemma-7b", messages)` | +| google/gemma-2b | `completion(model="nvidia_nim/google/gemma-2b", messages)` | +| google/codegemma-1.1-7b | `completion(model="nvidia_nim/google/codegemma-1.1-7b", messages)` | +| google/codegemma-7b | `completion(model="nvidia_nim/google/codegemma-7b", messages)` | +| google/recurrentgemma-2b | `completion(model="nvidia_nim/google/recurrentgemma-2b", messages)` | +| ibm/granite-34b-code-instruct | `completion(model="nvidia_nim/ibm/granite-34b-code-instruct", messages)` | +| ibm/granite-8b-code-instruct | `completion(model="nvidia_nim/ibm/granite-8b-code-instruct", messages)` | +| mediatek/breeze-7b-instruct | `completion(model="nvidia_nim/mediatek/breeze-7b-instruct", messages)` | +| meta/codellama-70b | `completion(model="nvidia_nim/meta/codellama-70b", messages)` | +| meta/llama2-70b | `completion(model="nvidia_nim/meta/llama2-70b", messages)` | +| meta/llama3-8b | `completion(model="nvidia_nim/meta/llama3-8b", messages)` | +| meta/llama3-70b | `completion(model="nvidia_nim/meta/llama3-70b", messages)` | +| microsoft/phi-3-medium-4k-instruct | `completion(model="nvidia_nim/microsoft/phi-3-medium-4k-instruct", messages)` | +| microsoft/phi-3-mini-128k-instruct | `completion(model="nvidia_nim/microsoft/phi-3-mini-128k-instruct", messages)` | +| microsoft/phi-3-mini-4k-instruct | `completion(model="nvidia_nim/microsoft/phi-3-mini-4k-instruct", messages)` | +| microsoft/phi-3-small-128k-instruct | `completion(model="nvidia_nim/microsoft/phi-3-small-128k-instruct", messages)` | +| microsoft/phi-3-small-8k-instruct | `completion(model="nvidia_nim/microsoft/phi-3-small-8k-instruct", messages)` | +| mistralai/codestral-22b-instruct-v0.1 | `completion(model="nvidia_nim/mistralai/codestral-22b-instruct-v0.1", messages)` | +| mistralai/mistral-7b-instruct | `completion(model="nvidia_nim/mistralai/mistral-7b-instruct", messages)` | +| mistralai/mistral-7b-instruct-v0.3 | `completion(model="nvidia_nim/mistralai/mistral-7b-instruct-v0.3", messages)` | +| mistralai/mixtral-8x7b-instruct | `completion(model="nvidia_nim/mistralai/mixtral-8x7b-instruct", messages)` | +| mistralai/mixtral-8x22b-instruct | `completion(model="nvidia_nim/mistralai/mixtral-8x22b-instruct", messages)` | +| mistralai/mistral-large | `completion(model="nvidia_nim/mistralai/mistral-large", messages)` | +| nvidia/nemotron-4-340b-instruct | `completion(model="nvidia_nim/nvidia/nemotron-4-340b-instruct", messages)` | +| seallms/seallm-7b-v2.5 | `completion(model="nvidia_nim/seallms/seallm-7b-v2.5", messages)` | +| snowflake/arctic | `completion(model="nvidia_nim/snowflake/arctic", messages)` | +| upstage/solar-10.7b-instruct | `completion(model="nvidia_nim/upstage/solar-10.7b-instruct", messages)` | \ No newline at end of file diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 2673933f4..9835a260b 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -146,13 +146,14 @@ const sidebars = { "providers/databricks", "providers/watsonx", "providers/predibase", - "providers/clarifai", + "providers/nvidia_nim", "providers/triton-inference-server", "providers/ollama", "providers/perplexity", "providers/groq", "providers/deepseek", - "providers/fireworks_ai", + "providers/fireworks_ai", + "providers/clarifai", "providers/vllm", "providers/xinference", "providers/cloudflare_workers", From 100f245fefa93cc3ae950c7fec7fdd52130a5cf3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 09:46:42 -0700 Subject: [PATCH 057/137] docs nvidia_nim --- docs/my-website/docs/providers/nvidia_nim.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/my-website/docs/providers/nvidia_nim.md b/docs/my-website/docs/providers/nvidia_nim.md index f90450768..7f895aa33 100644 --- a/docs/my-website/docs/providers/nvidia_nim.md +++ b/docs/my-website/docs/providers/nvidia_nim.md @@ -20,7 +20,7 @@ import os os.environ['NVIDIA_NIM_API_KEY'] = "" response = completion( - model=model_name, + model="nvidia_nim/meta/llama3-70b-instruct", messages=[ { "role": "user", @@ -44,7 +44,7 @@ import os os.environ['NVIDIA_NIM_API_KEY'] = "" response = completion( - model=model_name, + model="nvidia_nim/meta/llama3-70b-instruct", messages=[ { "role": "user", From b4497acfc2acb5e3d869350d220bce8985490c70 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 09:48:49 -0700 Subject: [PATCH 058/137] 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 0c6da360b..30ae1d0ab 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 a230f5f6c53f0215fb66656d0b5d888bbc3f14ec Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 10:50:47 -0700 Subject: [PATCH 059/137] feat - use n in mock completion --- litellm/llms/prompt_templates/factory.py | 17 ++++++++--------- litellm/main.py | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index e359d36f4..a97d6812c 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -135,7 +135,7 @@ def convert_to_ollama_image(openai_image_url: str): def ollama_pt( - model, messages + model, messages ): # https://github.com/ollama/ollama/blob/af4cf55884ac54b9e637cd71dadfe9b7a5685877/docs/modelfile.md#template if "instruct" in model: prompt = custom_prompt( @@ -185,19 +185,18 @@ def ollama_pt( function_name: str = call["function"]["name"] arguments = json.loads(call["function"]["arguments"]) - tool_calls.append({ - "id": call_id, - "type": "function", - "function": { - "name": function_name, - "arguments": arguments + tool_calls.append( + { + "id": call_id, + "type": "function", + "function": {"name": function_name, "arguments": arguments}, } - }) + ) prompt += f"### Assistant:\nTool Calls: {json.dumps(tool_calls, indent=2)}\n\n" elif "tool_call_id" in message: - prompt += f"### User:\n{message["content"]}\n\n" + prompt += f"### User:\n{message['content']}\n\n" elif content: prompt += f"### {role.capitalize()}:\n{content}\n\n" diff --git a/litellm/main.py b/litellm/main.py index 307659c8a..07d7be2ba 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -428,6 +428,7 @@ def mock_completion( model: str, messages: List, stream: Optional[bool] = False, + n: Optional[int] = None, mock_response: Union[str, Exception, dict] = "This is a mock request", mock_tool_calls: Optional[List] = None, logging=None, @@ -496,8 +497,19 @@ def mock_completion( model_response, mock_response=mock_response, model=model ) return response - - model_response["choices"][0]["message"]["content"] = mock_response + if n is None: + model_response["choices"][0]["message"]["content"] = mock_response + else: + _all_choices = [] + for i in range(n): + _choice = litellm.utils.Choices( + index=i, + message=litellm.utils.Message( + content=mock_response, role="assistant" + ), + ) + _all_choices.append(_choice) + model_response["choices"] = _all_choices model_response["created"] = int(time.time()) model_response["model"] = model @@ -944,6 +956,7 @@ def completion( model, messages, stream=stream, + n=n, mock_response=mock_response, mock_tool_calls=mock_tool_calls, logging=logging, From 343e3f3e909c02f38533604ff998b3dbef79153e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 10:54:18 -0700 Subject: [PATCH 060/137] test - test_mock_request_n_greater_than_1 --- litellm/tests/test_mock_request.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/litellm/tests/test_mock_request.py b/litellm/tests/test_mock_request.py index 7d670feb5..6b58c94b2 100644 --- a/litellm/tests/test_mock_request.py +++ b/litellm/tests/test_mock_request.py @@ -58,3 +58,18 @@ async def test_async_mock_streaming_request(): assert ( complete_response == "LiteLLM is awesome" ), f"Unexpected response got {complete_response}" + + +def test_mock_request_n_greater_than_1(): + try: + model = "gpt-3.5-turbo" + messages = [{"role": "user", "content": "Hey, I'm a mock request"}] + response = litellm.mock_completion(model=model, messages=messages, n=5) + print("response: ", response) + + assert len(response.choices) == 5 + for choice in response.choices: + assert choice.message.content == "This is a mock request" + + except: + traceback.print_exc() From 0396d484fbce1b635519e8144352f707167205bd Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Jun 2024 10:57:32 -0700 Subject: [PATCH 061/137] feat(router.py): support mock testing content policy + context window fallbacks --- litellm/proxy/_new_secret_config.yaml | 70 +++++++++++++++++++-------- litellm/router.py | 26 ++++++++++ 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 16436c0ef..75545bb60 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -1,24 +1,54 @@ -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 +# 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 +# router_settings: +# enable_pre_call_checks: True +# litellm_settings: +# failure_callback: ["langfuse"] + +model_list: + - model_name: summarize + 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: - failure_callback: ["langfuse"] \ No newline at end of file + 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. diff --git a/litellm/router.py b/litellm/router.py index 30bdbcba2..8256a6752 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -2117,6 +2117,12 @@ class Router: If it fails after num_retries, fall back to another model group """ mock_testing_fallbacks = kwargs.pop("mock_testing_fallbacks", None) + mock_testing_context_fallbacks = kwargs.pop( + "mock_testing_context_fallbacks", None + ) + mock_testing_content_policy_fallbacks = kwargs.pop( + "mock_testing_content_policy_fallbacks", None + ) model_group = kwargs.get("model") fallbacks = kwargs.get("fallbacks", self.fallbacks) context_window_fallbacks = kwargs.get( @@ -2130,6 +2136,26 @@ class Router: raise Exception( f"This is a mock exception for model={model_group}, to trigger a fallback. Fallbacks={fallbacks}" ) + elif ( + mock_testing_context_fallbacks is not None + and mock_testing_context_fallbacks is True + ): + raise litellm.ContextWindowExceededError( + model=model_group, + llm_provider="", + message=f"This is a mock exception for model={model_group}, to trigger a fallback. \ + Context_Window_Fallbacks={context_window_fallbacks}", + ) + elif ( + mock_testing_content_policy_fallbacks is not None + and mock_testing_content_policy_fallbacks is True + ): + raise litellm.ContentPolicyViolationError( + model=model_group, + llm_provider="", + message=f"This is a mock exception for model={model_group}, to trigger a fallback. \ + Context_Policy_Fallbacks={content_policy_fallbacks}", + ) response = await self.async_function_with_retries(*args, **kwargs) verbose_router_logger.debug(f"Async Response: {response}") From 71b7c2886b1559d23ad0901c65d9adfe113956aa Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Jun 2024 11:07:07 -0700 Subject: [PATCH 062/137] docs(reliability.md): add doc on mock testing fallbacks --- docs/my-website/docs/proxy/reliability.md | 61 +++++++++++++++++++ ...odel_prices_and_context_window_backup.json | 10 +-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/docs/my-website/docs/proxy/reliability.md b/docs/my-website/docs/proxy/reliability.md index a2d24da69..c07fc3c26 100644 --- a/docs/my-website/docs/proxy/reliability.md +++ b/docs/my-website/docs/proxy/reliability.md @@ -431,6 +431,67 @@ litellm_settings: content_policy_fallbacks: [{"gpt-3.5-turbo-small": ["claude-opus"]}] ``` + + +### Test Fallbacks! + +Check if your fallbacks are working as expected. + +#### **Regular Fallbacks** +```bash +curl -X POST 'http://0.0.0.0:4000/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-D '{ + "model": "my-bad-model", + "messages": [ + { + "role": "user", + "content": "ping" + } + ], + "mock_testing_fallbacks": true # 👈 KEY CHANGE +} +' +``` + +#### **Content Policy Fallbacks** +```bash +curl -X POST 'http://0.0.0.0:4000/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-D '{ + "model": "my-bad-model", + "messages": [ + { + "role": "user", + "content": "ping" + } + ], + "mock_testing_content_policy_fallbacks": true # 👈 KEY CHANGE +} +' +``` + +#### **Context Window Fallbacks** + +```bash +curl -X POST 'http://0.0.0.0:4000/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-D '{ + "model": "my-bad-model", + "messages": [ + { + "role": "user", + "content": "ping" + } + ], + "mock_testing_context_window_fallbacks": true # 👈 KEY CHANGE +} +' +``` + ### EU-Region Filtering (Pre-Call Checks) **Before call is made** check if a call is within model context window with **`enable_pre_call_checks: true`**. diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index ef07d87cc..415d220f2 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -887,7 +887,7 @@ "max_input_tokens": 8192, "max_output_tokens": 8192, "input_cost_per_token": 0.00000005, - "output_cost_per_token": 0.00000010, + "output_cost_per_token": 0.00000008, "litellm_provider": "groq", "mode": "chat", "supports_function_calling": true @@ -906,8 +906,8 @@ "max_tokens": 32768, "max_input_tokens": 32768, "max_output_tokens": 32768, - "input_cost_per_token": 0.00000027, - "output_cost_per_token": 0.00000027, + "input_cost_per_token": 0.00000024, + "output_cost_per_token": 0.00000024, "litellm_provider": "groq", "mode": "chat", "supports_function_calling": true @@ -916,8 +916,8 @@ "max_tokens": 8192, "max_input_tokens": 8192, "max_output_tokens": 8192, - "input_cost_per_token": 0.00000010, - "output_cost_per_token": 0.00000010, + "input_cost_per_token": 0.00000007, + "output_cost_per_token": 0.00000007, "litellm_provider": "groq", "mode": "chat", "supports_function_calling": true From ccf1bbc5d75c310fc45816d4bc55a9aa5be925d1 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 11:14:40 -0700 Subject: [PATCH 063/137] fix using mock completion --- litellm/main.py | 7 ++++-- litellm/tests/test_mock_request.py | 19 +++++++++++++++ litellm/utils.py | 39 +++++++++++++++++++++++++----- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/litellm/main.py b/litellm/main.py index 07d7be2ba..ecb6edd0d 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -487,14 +487,17 @@ def mock_completion( if kwargs.get("acompletion", False) == True: return CustomStreamWrapper( completion_stream=async_mock_completion_streaming_obj( - model_response, mock_response=mock_response, model=model + model_response, mock_response=mock_response, model=model, n=n ), model=model, custom_llm_provider="openai", logging_obj=logging, ) response = mock_completion_streaming_obj( - model_response, mock_response=mock_response, model=model + model_response, + mock_response=mock_response, + model=model, + n=n, ) return response if n is None: diff --git a/litellm/tests/test_mock_request.py b/litellm/tests/test_mock_request.py index 6b58c94b2..48b054371 100644 --- a/litellm/tests/test_mock_request.py +++ b/litellm/tests/test_mock_request.py @@ -73,3 +73,22 @@ def test_mock_request_n_greater_than_1(): except: traceback.print_exc() + + +@pytest.mark.asyncio() +async def test_async_mock_streaming_request_n_greater_than_1(): + generator = await litellm.acompletion( + messages=[{"role": "user", "content": "Why is LiteLLM amazing?"}], + mock_response="LiteLLM is awesome", + stream=True, + model="gpt-3.5-turbo", + n=5, + ) + complete_response = "" + async for chunk in generator: + print(chunk) + # complete_response += chunk["choices"][0]["delta"]["content"] or "" + + # assert ( + # complete_response == "LiteLLM is awesome" + # ), f"Unexpected response got {complete_response}" diff --git a/litellm/utils.py b/litellm/utils.py index 1bc8bf771..cae86c6f8 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -9708,18 +9708,45 @@ class TextCompletionStreamWrapper: raise StopAsyncIteration -def mock_completion_streaming_obj(model_response, mock_response, model): +def mock_completion_streaming_obj( + model_response, mock_response, model, n: Optional[int] = None +): for i in range(0, len(mock_response), 3): - completion_obj = {"role": "assistant", "content": mock_response[i : i + 3]} - model_response.choices[0].delta = completion_obj + completion_obj = Delta(role="assistant", content=mock_response[i : i + 3]) + if n is None: + model_response.choices[0].delta = completion_obj + else: + _all_choices = [] + for j in range(n): + _streaming_choice = litellm.utils.StreamingChoices( + index=j, + delta=litellm.utils.Delta( + role="assistant", content=mock_response[i : i + 3] + ), + ) + _all_choices.append(_streaming_choice) + model_response.choices = _all_choices yield model_response -async def async_mock_completion_streaming_obj(model_response, mock_response, model): +async def async_mock_completion_streaming_obj( + model_response, mock_response, model, n: Optional[int] = None +): for i in range(0, len(mock_response), 3): completion_obj = Delta(role="assistant", content=mock_response[i : i + 3]) - model_response.choices[0].delta = completion_obj - model_response.choices[0].finish_reason = "stop" + if n is None: + model_response.choices[0].delta = completion_obj + else: + _all_choices = [] + for j in range(n): + _streaming_choice = litellm.utils.StreamingChoices( + index=j, + delta=litellm.utils.Delta( + role="assistant", content=mock_response[i : i + 3] + ), + ) + _all_choices.append(_streaming_choice) + model_response.choices = _all_choices yield model_response From cccc55213b1d904f4d596f87f2670c31fef55109 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Jun 2024 11:26:56 -0700 Subject: [PATCH 064/137] fix(router.py): improve error message returned for fallbacks --- litellm/proxy/_new_secret_config.yaml | 2 +- litellm/router.py | 142 ++++++++++++++----------- litellm/tests/test_router_fallbacks.py | 4 +- 3 files changed, 85 insertions(+), 63 deletions(-) diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 75545bb60..938e74b5e 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -45,7 +45,7 @@ litellm_settings: 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"]}] + # context_window_fallbacks: [{"summarize": ["summarize-l", "summarize-xl"]}, {"summarize-l": ["summarize-xl"]}] diff --git a/litellm/router.py b/litellm/router.py index 8256a6752..840df5b54 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -2175,73 +2175,93 @@ class Router: ) ): # don't retry a malformed request raise e - if ( - isinstance(e, litellm.ContextWindowExceededError) - and context_window_fallbacks is not None - ): - fallback_model_group = None - for ( - item - ) in context_window_fallbacks: # [{"gpt-3.5-turbo": ["gpt-4"]}] - if list(item.keys())[0] == model_group: - fallback_model_group = item[model_group] - break + if isinstance(e, litellm.ContextWindowExceededError): + if context_window_fallbacks is not None: + fallback_model_group = None + for ( + item + ) in context_window_fallbacks: # [{"gpt-3.5-turbo": ["gpt-4"]}] + if list(item.keys())[0] == model_group: + fallback_model_group = item[model_group] + break - if fallback_model_group is None: - raise original_exception + if fallback_model_group is None: + raise original_exception - for mg in fallback_model_group: - """ - Iterate through the model groups and try calling that deployment - """ - try: - kwargs["model"] = mg - kwargs.setdefault("metadata", {}).update( - {"model_group": mg} - ) # update model_group used, if fallbacks are done - response = await self.async_function_with_retries( - *args, **kwargs + for mg in fallback_model_group: + """ + Iterate through the model groups and try calling that deployment + """ + try: + kwargs["model"] = mg + kwargs.setdefault("metadata", {}).update( + {"model_group": mg} + ) # update model_group used, if fallbacks are done + response = await self.async_function_with_retries( + *args, **kwargs + ) + verbose_router_logger.info( + "Successful fallback b/w models." + ) + return response + except Exception as e: + pass + else: + error_message = "model={}. context_window_fallbacks={}. fallbacks={}.\n\nSet 'context_window_fallback' - https://docs.litellm.ai/docs/routing#fallbacks".format( + model_group, context_window_fallbacks, fallbacks + ) + verbose_router_logger.info( + msg="Got 'ContextWindowExceededError'. No context_window_fallback set. Defaulting \ + to fallbacks, if available.{}".format( + error_message ) - verbose_router_logger.info( - "Successful fallback b/w models." - ) - return response - except Exception as e: - pass - elif ( - isinstance(e, litellm.ContentPolicyViolationError) - and content_policy_fallbacks is not None - ): - fallback_model_group = None - for ( - item - ) in content_policy_fallbacks: # [{"gpt-3.5-turbo": ["gpt-4"]}] - if list(item.keys())[0] == model_group: - fallback_model_group = item[model_group] - break + ) - if fallback_model_group is None: - raise original_exception + e.message += "\n{}".format(error_message) + elif isinstance(e, litellm.ContentPolicyViolationError): + if content_policy_fallbacks is not None: + fallback_model_group = None + for ( + item + ) in content_policy_fallbacks: # [{"gpt-3.5-turbo": ["gpt-4"]}] + if list(item.keys())[0] == model_group: + fallback_model_group = item[model_group] + break - for mg in fallback_model_group: - """ - Iterate through the model groups and try calling that deployment - """ - try: - kwargs["model"] = mg - kwargs.setdefault("metadata", {}).update( - {"model_group": mg} - ) # update model_group used, if fallbacks are done - response = await self.async_function_with_retries( - *args, **kwargs + if fallback_model_group is None: + raise original_exception + + for mg in fallback_model_group: + """ + Iterate through the model groups and try calling that deployment + """ + try: + kwargs["model"] = mg + kwargs.setdefault("metadata", {}).update( + {"model_group": mg} + ) # update model_group used, if fallbacks are done + response = await self.async_function_with_retries( + *args, **kwargs + ) + verbose_router_logger.info( + "Successful fallback b/w models." + ) + return response + except Exception as e: + pass + else: + error_message = "model={}. content_policy_fallback={}. fallbacks={}.\n\nSet 'content_policy_fallback' - https://docs.litellm.ai/docs/routing#fallbacks".format( + model_group, content_policy_fallbacks, fallbacks + ) + verbose_router_logger.info( + msg="Got 'ContentPolicyViolationError'. No content_policy_fallback set. Defaulting \ + to fallbacks, if available.{}".format( + error_message ) - verbose_router_logger.info( - "Successful fallback b/w models." - ) - return response - except Exception as e: - pass - elif fallbacks is not None: + ) + + e.message += "\n{}".format(error_message) + if fallbacks is not None: verbose_router_logger.debug(f"inside model fallbacks: {fallbacks}") generic_fallback_idx: Optional[int] = None ## check for specific model group-specific fallbacks diff --git a/litellm/tests/test_router_fallbacks.py b/litellm/tests/test_router_fallbacks.py index 99d2a600c..2c552a64b 100644 --- a/litellm/tests/test_router_fallbacks.py +++ b/litellm/tests/test_router_fallbacks.py @@ -1129,7 +1129,9 @@ async def test_router_content_policy_fallbacks( mock_response = Exception("content filtering policy") else: mock_response = litellm.ModelResponse( - choices=[litellm.Choices(finish_reason="content_filter")] + choices=[litellm.Choices(finish_reason="content_filter")], + model="gpt-3.5-turbo", + usage=litellm.Usage(prompt_tokens=10, completion_tokens=0, total_tokens=10), ) router = Router( model_list=[ From 6e02ac00569d927648742d2b5739232e2e33dea6 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Jun 2024 11:47:39 -0700 Subject: [PATCH 065/137] fix(utils.py): add coverage for anthropic content policy error - vertex ai --- litellm/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/litellm/utils.py b/litellm/utils.py index 854998901..9f6ebaff0 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -6470,7 +6470,11 @@ def exception_type( ), litellm_debug_info=extra_information, ) - elif "The response was blocked." in error_str: + elif ( + "The response was blocked." in error_str + or "Output blocked by content filtering policy" + in error_str # anthropic on vertex ai + ): exception_mapping_worked = True raise ContentPolicyViolationError( message=f"VertexAIException ContentPolicyViolationError - {error_str}", From 6889a4c0dd2275fc112dbe0badda42bd68f1adf0 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Jun 2024 13:47:38 -0700 Subject: [PATCH 066/137] fix(utils.py): predibase exception mapping - map 424 as a badrequest error --- litellm/llms/predibase.py | 39 +++++++++++++------------ litellm/proxy/_super_secret_config.yaml | 5 +++- litellm/utils.py | 12 +++----- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/litellm/llms/predibase.py b/litellm/llms/predibase.py index 8ad294457..7a137da70 100644 --- a/litellm/llms/predibase.py +++ b/litellm/llms/predibase.py @@ -1,27 +1,26 @@ # What is this? ## Controller file for Predibase Integration - https://predibase.com/ -from functools import partial -import os, types -import traceback +import copy import json -from enum import Enum -import requests, copy # type: ignore +import os import time -from typing import Callable, Optional, List, Literal, Union -from litellm.utils import ( - ModelResponse, - Usage, - CustomStreamWrapper, - Message, - Choices, -) -from litellm.litellm_core_utils.core_helpers import map_finish_reason -import litellm -from .prompt_templates.factory import prompt_factory, custom_prompt -from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler -from .base import BaseLLM +import traceback +import types +from enum import Enum +from functools import partial +from typing import Callable, List, Literal, Optional, Union + 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 +from litellm.utils import Choices, CustomStreamWrapper, Message, ModelResponse, Usage + +from .base import BaseLLM +from .prompt_templates.factory import custom_prompt, prompt_factory class PredibaseError(Exception): @@ -496,7 +495,9 @@ class PredibaseChatCompletion(BaseLLM): except httpx.HTTPStatusError as e: raise PredibaseError( status_code=e.response.status_code, - message="HTTPStatusError - {}".format(e.response.text), + message="HTTPStatusError - received status_code={}, error_message={}".format( + e.response.status_code, e.response.text + ), ) except Exception as e: raise PredibaseError( diff --git a/litellm/proxy/_super_secret_config.yaml b/litellm/proxy/_super_secret_config.yaml index c5f1b4768..94df97c54 100644 --- a/litellm/proxy/_super_secret_config.yaml +++ b/litellm/proxy/_super_secret_config.yaml @@ -14,9 +14,10 @@ model_list: - model_name: fake-openai-endpoint litellm_params: model: predibase/llama-3-8b-instruct - api_base: "http://0.0.0.0:8000" + # api_base: "http://0.0.0.0:8081" api_key: os.environ/PREDIBASE_API_KEY tenant_id: os.environ/PREDIBASE_TENANT_ID + adapter_id: qwoiqjdoqin max_retries: 0 temperature: 0.1 max_new_tokens: 256 @@ -73,6 +74,8 @@ model_list: litellm_settings: callbacks: ["dynamic_rate_limiter"] + # success_callback: ["langfuse"] + # failure_callback: ["langfuse"] # default_team_settings: # - team_id: proj1 # success_callback: ["langfuse"] diff --git a/litellm/utils.py b/litellm/utils.py index 9f6ebaff0..00833003b 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -6157,13 +6157,6 @@ def exception_type( response=original_exception.response, litellm_debug_info=extra_information, ) - if "Request failed during generation" in error_str: - # this is an internal server error from predibase - raise litellm.InternalServerError( - message=f"PredibaseException - {error_str}", - llm_provider="predibase", - model=model, - ) elif hasattr(original_exception, "status_code"): if original_exception.status_code == 500: exception_mapping_worked = True @@ -6201,7 +6194,10 @@ def exception_type( llm_provider=custom_llm_provider, litellm_debug_info=extra_information, ) - elif original_exception.status_code == 422: + elif ( + original_exception.status_code == 422 + or original_exception.status_code == 424 + ): exception_mapping_worked = True raise BadRequestError( message=f"PredibaseException - {original_exception.message}", From 4c99010eeea1d21e1370326fd1dcd16133b4b99a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 13:55:54 -0700 Subject: [PATCH 067/137] fix - verify license without api request --- litellm/proxy/auth/litellm_license.py | 65 +++++++++++++++++++++++++++ litellm/proxy/auth/public_key.pem | 9 ++++ requirements.txt | 1 + 3 files changed, 75 insertions(+) create mode 100644 litellm/proxy/auth/public_key.pem diff --git a/litellm/proxy/auth/litellm_license.py b/litellm/proxy/auth/litellm_license.py index ffd9f5273..ec51f904c 100644 --- a/litellm/proxy/auth/litellm_license.py +++ b/litellm/proxy/auth/litellm_license.py @@ -1,6 +1,14 @@ # What is this? ## If litellm license in env, checks if it's valid +import base64 +import json import os +from datetime import datetime + +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding, rsa + +from litellm._logging import verbose_proxy_logger from litellm.llms.custom_httpx.http_handler import HTTPHandler @@ -15,6 +23,20 @@ class LicenseCheck: def __init__(self) -> None: self.license_str = os.getenv("LITELLM_LICENSE", None) self.http_handler = HTTPHandler() + self.public_key = None + self.read_public_key() + + def read_public_key(self): + # current dir + current_dir = os.path.dirname(os.path.realpath(__file__)) + + # check if public_key.pem exists + _path_to_public_key = os.path.join(current_dir, "public_key.pem") + if os.path.exists(_path_to_public_key): + with open(_path_to_public_key, "rb") as key_file: + self.public_key = serialization.load_pem_public_key(key_file.read()) + else: + self.public_key = None def _verify(self, license_str: str) -> bool: url = "{}/verify_license/{}".format(self.base_url, license_str) @@ -35,11 +57,54 @@ class LicenseCheck: return False def is_premium(self) -> bool: + """ + 1. verify_license_without_api_request: checks if license was generate using private / public key pair + 2. _verify: checks if license is valid calling litellm API. This is the old way we were generating/validating license + """ try: if self.license_str is None: return False + elif self.verify_license_without_api_request( + public_key=self.public_key, license_key=self.license_str + ): + return True elif self._verify(license_str=self.license_str): return True return False except Exception as e: return False + + def verify_license_without_api_request(self, public_key, license_key): + try: + # Decode the license key + decoded = base64.b64decode(license_key) + message, signature = decoded.split(b".", 1) + + # Verify the signature + public_key.verify( + signature, + message, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH, + ), + hashes.SHA256(), + ) + + # Decode and parse the data + license_data = json.loads(message.decode()) + + # debug information provided in license data + verbose_proxy_logger.debug("License data: %s", license_data) + + # Check expiration date + expiration_date = datetime.strptime( + license_data["expiration_date"], "%Y-%m-%d" + ) + if expiration_date < datetime.now(): + return False, "License has expired" + + return True + + except Exception as e: + return False diff --git a/litellm/proxy/auth/public_key.pem b/litellm/proxy/auth/public_key.pem new file mode 100644 index 000000000..12a69dde2 --- /dev/null +++ b/litellm/proxy/auth/public_key.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmfBuNiNzDkNWyce23koQ +w0vq3bSVHkq7fd9Sw/U1q7FwRwL221daLTyGWssd8xAoQSFXAJKoBwzJQ9wd+o44 +lfL54E3a61nfjZuF+D9ntpXZFfEAxLVtIahDeQjUz4b/EpgciWIJyUfjCJrQo6LY +eyAZPTGSO8V3zHyaU+CFywq5XCuCnfZqCZeCw051St59A2v8W32mXSCJ+A+x0hYP +yXJyRRFcefSFG5IBuRHr4Y24Vx7NUIAoco5cnxJho9g2z3J/Hb0GKW+oBNvRVumk +nuA2Ljmjh4yI0OoTIW8ZWxemvCCJHSjdfKlMyb+QI4fmeiIUZzP5Au+F561Styqq +YQIDAQAB +-----END PUBLIC KEY----- diff --git a/requirements.txt b/requirements.txt index fbf2bfc1d..8c5e4ab3b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,6 +31,7 @@ azure-identity==1.16.1 # for azure content safety opentelemetry-api==1.25.0 opentelemetry-sdk==1.25.0 opentelemetry-exporter-otlp==1.25.0 +cryptography==42.0.7 ### LITELLM PACKAGE DEPENDENCIES python-dotenv==1.0.0 # for env From e813e984f74ea09ea92646c44c5a5ab7a30bbff0 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Jun 2024 16:03:47 -0700 Subject: [PATCH 068/137] fix(predibase.py): support json schema on predibase --- litellm/llms/predibase.py | 59 ++++++++++++++++++++++--- litellm/proxy/_super_secret_config.yaml | 16 +++---- litellm/utils.py | 10 ++++- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/litellm/llms/predibase.py b/litellm/llms/predibase.py index 7a137da70..534f8e26f 100644 --- a/litellm/llms/predibase.py +++ b/litellm/llms/predibase.py @@ -15,6 +15,8 @@ import httpx # type: ignore import requests # type: ignore import litellm +import litellm.litellm_core_utils +import litellm.litellm_core_utils.litellm_logging from litellm.litellm_core_utils.core_helpers import map_finish_reason from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler from litellm.utils import Choices, CustomStreamWrapper, Message, ModelResponse, Usage @@ -145,7 +147,49 @@ class PredibaseConfig: } def get_supported_openai_params(self): - return ["stream", "temperature", "max_tokens", "top_p", "stop", "n"] + return [ + "stream", + "temperature", + "max_tokens", + "top_p", + "stop", + "n", + "response_format", + ] + + def map_openai_params(self, non_default_params: dict, optional_params: dict): + for param, value in non_default_params.items(): + # temperature, top_p, n, stream, stop, max_tokens, n, presence_penalty default to None + if param == "temperature": + if value == 0.0 or value == 0: + # hugging face exception raised when temp==0 + # Failed: Error occurred: HuggingfaceException - Input validation error: `temperature` must be strictly positive + value = 0.01 + optional_params["temperature"] = value + if param == "top_p": + optional_params["top_p"] = value + if param == "n": + optional_params["best_of"] = value + optional_params["do_sample"] = ( + True # Need to sample if you want best of for hf inference endpoints + ) + if param == "stream": + optional_params["stream"] = value + if param == "stop": + optional_params["stop"] = value + if param == "max_tokens": + # HF TGI raises the following exception when max_new_tokens==0 + # Failed: Error occurred: HuggingfaceException - Input validation error: `max_new_tokens` must be strictly positive + if value == 0: + value = 1 + optional_params["max_new_tokens"] = value + if param == "echo": + # https://huggingface.co/docs/huggingface_hub/main/en/package_reference/inference_client#huggingface_hub.InferenceClient.text_generation.decoder_input_details + # Return the decoder input token logprobs and ids. You must set details=True as well for it to be taken into account. Defaults to False + optional_params["decoder_input_details"] = True + if param == "response_format": + optional_params["response_format"] = value + return optional_params class PredibaseChatCompletion(BaseLLM): @@ -224,15 +268,16 @@ class PredibaseChatCompletion(BaseLLM): status_code=response.status_code, ) else: - if ( - not isinstance(completion_response, dict) - or "generated_text" not in completion_response - ): + if not isinstance(completion_response, dict): raise PredibaseError( status_code=422, - message=f"response is not in expected format - {completion_response}", + message=f"'completion_response' is not a dictionary - {completion_response}", + ) + elif "generated_text" not in completion_response: + raise PredibaseError( + status_code=422, + message=f"'generated_text' is not a key response dictionary - {completion_response}", ) - if len(completion_response["generated_text"]) > 0: model_response["choices"][0]["message"]["content"] = self.output_parser( completion_response["generated_text"] diff --git a/litellm/proxy/_super_secret_config.yaml b/litellm/proxy/_super_secret_config.yaml index 94df97c54..2060f61ca 100644 --- a/litellm/proxy/_super_secret_config.yaml +++ b/litellm/proxy/_super_secret_config.yaml @@ -14,14 +14,10 @@ model_list: - model_name: fake-openai-endpoint litellm_params: model: predibase/llama-3-8b-instruct - # api_base: "http://0.0.0.0:8081" + api_base: "http://0.0.0.0:8081" api_key: os.environ/PREDIBASE_API_KEY tenant_id: os.environ/PREDIBASE_TENANT_ID - adapter_id: qwoiqjdoqin - max_retries: 0 - temperature: 0.1 max_new_tokens: 256 - return_full_text: false # - litellm_params: # api_base: https://my-endpoint-europe-berri-992.openai.azure.com/ @@ -97,8 +93,8 @@ assistant_settings: router_settings: enable_pre_call_checks: true -general_settings: - alerting: ["slack"] - enable_jwt_auth: True - litellm_jwtauth: - team_id_jwt_field: "client_id" \ No newline at end of file +# general_settings: +# # alerting: ["slack"] +# enable_jwt_auth: True +# litellm_jwtauth: +# team_id_jwt_field: "client_id" \ No newline at end of file diff --git a/litellm/utils.py b/litellm/utils.py index 00833003b..4465c5b0a 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2609,7 +2609,15 @@ def get_optional_params( optional_params["top_p"] = top_p if stop is not None: optional_params["stop_sequences"] = stop - elif custom_llm_provider == "huggingface" or custom_llm_provider == "predibase": + elif custom_llm_provider == "predibase": + supported_params = get_supported_openai_params( + model=model, custom_llm_provider=custom_llm_provider + ) + _check_valid_arg(supported_params=supported_params) + optional_params = litellm.PredibaseConfig().map_openai_params( + non_default_params=non_default_params, optional_params=optional_params + ) + elif custom_llm_provider == "huggingface": ## check if unsupported param passed in supported_params = get_supported_openai_params( model=model, custom_llm_provider=custom_llm_provider From 4aa686bb0569937a1504e7952b556ee82a603ef9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 16:23:56 -0700 Subject: [PATCH 069/137] feat - add secret detection --- .../enterprise_hooks/secret_detection.py | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 enterprise/enterprise_hooks/secret_detection.py diff --git a/enterprise/enterprise_hooks/secret_detection.py b/enterprise/enterprise_hooks/secret_detection.py new file mode 100644 index 000000000..75a578b2c --- /dev/null +++ b/enterprise/enterprise_hooks/secret_detection.py @@ -0,0 +1,164 @@ +# +-------------------------------------------------------------+ +# +# Use SecretDetection /moderations for your LLM calls +# +# +-------------------------------------------------------------+ +# Thank you users! We ❤️ you! - Krrish & Ishaan + +import sys, os + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path +from typing import Optional, Literal, Union +import litellm, traceback, sys, uuid +from litellm.caching import DualCache +from litellm.proxy._types import UserAPIKeyAuth +from litellm.integrations.custom_logger import CustomLogger +from fastapi import HTTPException +from litellm._logging import verbose_proxy_logger +from litellm.utils import ( + ModelResponse, + EmbeddingResponse, + ImageResponse, + StreamingChoices, +) +from datetime import datetime +import aiohttp, asyncio +from litellm._logging import verbose_proxy_logger +import tempfile +from litellm._logging import verbose_proxy_logger + + +litellm.set_verbose = True + + +class _ENTERPRISE_SecretDetection(CustomLogger): + def __init__(self): + pass + + def scan_message_for_secrets(self, message_content: str): + from detect_secrets import SecretsCollection + from detect_secrets.settings import default_settings + + temp_file = tempfile.NamedTemporaryFile(delete=False) + temp_file.write(message_content.encode("utf-8")) + temp_file.close() + + secrets = SecretsCollection() + with default_settings(): + secrets.scan_file(temp_file.name) + + os.remove(temp_file.name) + + detected_secrets = [] + for file in secrets.files: + for found_secret in secrets[file]: + if found_secret.secret_value is None: + continue + detected_secrets.append( + {"type": found_secret.type, "value": found_secret.secret_value} + ) + + return detected_secrets + + #### CALL HOOKS - proxy only #### + def async_pre_call_hook( + self, + user_api_key_dict: UserAPIKeyAuth, + cache: DualCache, + data: dict, + call_type: str, # "completion", "embeddings", "image_generation", "moderation" + ): + from detect_secrets import SecretsCollection + from detect_secrets.settings import default_settings + + if "messages" in data and isinstance(data["messages"], list): + for message in data["messages"]: + if "content" in message and isinstance(message["content"], str): + detected_secrets = self.scan_message_for_secrets(message["content"]) + + for secret in detected_secrets: + message["content"] = message["content"].replace( + secret["value"], "[REDACTED]" + ) + + if len(detected_secrets) > 0: + secret_types = [secret["type"] for secret in detected_secrets] + verbose_proxy_logger.warning( + f"Detected and redacted secrets in message: {secret_types}" + ) + + if "prompt" in data: + if isinstance(data["prompt"], str): + detected_secrets = self.scan_message_for_secrets(data["prompt"]) + for secret in detected_secrets: + data["prompt"] = data["prompt"].replace( + secret["value"], "[REDACTED]" + ) + if len(detected_secrets) > 0: + secret_types = [secret["type"] for secret in detected_secrets] + verbose_proxy_logger.warning( + f"Detected and redacted secrets in prompt: {secret_types}" + ) + elif isinstance(data["prompt"], list): + for item in data["prompt"]: + if isinstance(item, str): + detected_secrets = self.scan_message_for_secrets(item) + for secret in detected_secrets: + item = item.replace(secret["value"], "[REDACTED]") + if len(detected_secrets) > 0: + secret_types = [ + secret["type"] for secret in detected_secrets + ] + verbose_proxy_logger.warning( + f"Detected and redacted secrets in prompt: {secret_types}" + ) + + if "input" in data: + if isinstance(data["input"], str): + detected_secrets = self.scan_message_for_secrets(data["input"]) + for secret in detected_secrets: + data["input"] = data["input"].replace(secret["value"], "[REDACTED]") + if len(detected_secrets) > 0: + secret_types = [secret["type"] for secret in detected_secrets] + verbose_proxy_logger.warning( + f"Detected and redacted secrets in input: {secret_types}" + ) + elif isinstance(data["input"], list): + for item in data["input"]: + if isinstance(item, str): + detected_secrets = self.scan_message_for_secrets(item) + for secret in detected_secrets: + item = item.replace(secret["value"], "[REDACTED]") + if len(detected_secrets) > 0: + secret_types = [ + secret["type"] for secret in detected_secrets + ] + verbose_proxy_logger.warning( + f"Detected and redacted secrets in input: {secret_types}" + ) + + +# secretDetect = _ENTERPRISE_SecretDetection() + +# from litellm.caching import DualCache +# print("running hook to detect a secret") +# test_data = { +# "messages": [ +# {"role": "user", "content": "Hey, how's it going, API_KEY = 'sk_1234567890abcdef'"}, +# {"role": "assistant", "content": "Hello! I'm doing well. How can I assist you today?"}, +# {"role": "user", "content": "this is my OPENAI_API_KEY = 'sk_1234567890abcdef'"}, +# {"role": "user", "content": "i think it is sk-1234567890abcdef"}, +# ], +# "model": "gpt-3.5-turbo", +# } +# secretDetect.async_pre_call_hook( +# data=test_data, +# user_api_key_dict=UserAPIKeyAuth(token="your_api_key"), +# cache=DualCache(), +# call_type="completion", +# ) + + +# print("finished hook to detect a secret - test data=", test_data) From e22c9ba0cc0e231b88343f28b1c782fd6515d500 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 16:25:14 -0700 Subject: [PATCH 070/137] init secret detection callback --- litellm/proxy/proxy_server.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 59ad7ba92..c3b855c5f 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -1479,6 +1479,21 @@ class ProxyConfig: llama_guard_object = _ENTERPRISE_LlamaGuard() imported_list.append(llama_guard_object) + elif ( + isinstance(callback, str) and callback == "hide_secrets" + ): + from enterprise.enterprise_hooks.secret_detection import ( + _ENTERPRISE_SecretDetection, + ) + + if premium_user != True: + raise Exception( + "Trying to use secret hiding" + + CommonProxyErrors.not_premium_user.value + ) + + _secret_detection_object = _ENTERPRISE_SecretDetection() + imported_list.append(_secret_detection_object) elif ( isinstance(callback, str) and callback == "openai_moderations" From 4abb83b12dff6b9cada26d9ce86fa29ce3860c9c Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 16:28:47 -0700 Subject: [PATCH 071/137] fix only use crypto imports when needed --- litellm/proxy/auth/litellm_license.py | 31 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/litellm/proxy/auth/litellm_license.py b/litellm/proxy/auth/litellm_license.py index ec51f904c..0310dcaf5 100644 --- a/litellm/proxy/auth/litellm_license.py +++ b/litellm/proxy/auth/litellm_license.py @@ -5,9 +5,6 @@ import json import os from datetime import datetime -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding, rsa - from litellm._logging import verbose_proxy_logger from litellm.llms.custom_httpx.http_handler import HTTPHandler @@ -27,16 +24,22 @@ class LicenseCheck: self.read_public_key() def read_public_key(self): - # current dir - current_dir = os.path.dirname(os.path.realpath(__file__)) + try: + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import padding, rsa - # check if public_key.pem exists - _path_to_public_key = os.path.join(current_dir, "public_key.pem") - if os.path.exists(_path_to_public_key): - with open(_path_to_public_key, "rb") as key_file: - self.public_key = serialization.load_pem_public_key(key_file.read()) - else: - self.public_key = None + # current dir + current_dir = os.path.dirname(os.path.realpath(__file__)) + + # check if public_key.pem exists + _path_to_public_key = os.path.join(current_dir, "public_key.pem") + if os.path.exists(_path_to_public_key): + with open(_path_to_public_key, "rb") as key_file: + self.public_key = serialization.load_pem_public_key(key_file.read()) + else: + self.public_key = None + except Exception as e: + verbose_proxy_logger.error(f"Error reading public key: {str(e)}") def _verify(self, license_str: str) -> bool: url = "{}/verify_license/{}".format(self.base_url, license_str) @@ -76,6 +79,9 @@ class LicenseCheck: def verify_license_without_api_request(self, public_key, license_key): try: + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import padding, rsa + # Decode the license key decoded = base64.b64decode(license_key) message, signature = decoded.split(b".", 1) @@ -107,4 +113,5 @@ class LicenseCheck: return True except Exception as e: + verbose_proxy_logger.error(str(e)) return False From e4653e6032319f781c479d60ebb4c5998bd7d0db Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 16:38:47 -0700 Subject: [PATCH 072/137] fix async_pre_call_hook --- enterprise/enterprise_hooks/secret_detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/enterprise_hooks/secret_detection.py b/enterprise/enterprise_hooks/secret_detection.py index 75a578b2c..ade8b7172 100644 --- a/enterprise/enterprise_hooks/secret_detection.py +++ b/enterprise/enterprise_hooks/secret_detection.py @@ -63,7 +63,7 @@ class _ENTERPRISE_SecretDetection(CustomLogger): return detected_secrets #### CALL HOOKS - proxy only #### - def async_pre_call_hook( + async def async_pre_call_hook( self, user_api_key_dict: UserAPIKeyAuth, cache: DualCache, From d98e00d1e02dbcd31023a45219487a1af0a340dd Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Jun 2024 16:51:55 -0700 Subject: [PATCH 073/137] fix(router.py): set `cooldown_time:` per model --- litellm/integrations/custom_logger.py | 12 ++-- litellm/litellm_core_utils/litellm_logging.py | 3 +- litellm/main.py | 6 ++ litellm/router.py | 4 +- litellm/tests/test_router_cooldowns.py | 56 ++++++++++++++++++- litellm/utils.py | 2 + 6 files changed, 72 insertions(+), 11 deletions(-) diff --git a/litellm/integrations/custom_logger.py b/litellm/integrations/custom_logger.py index 5a6282994..da9826b9b 100644 --- a/litellm/integrations/custom_logger.py +++ b/litellm/integrations/custom_logger.py @@ -1,11 +1,13 @@ #### What this does #### # On success, logs events to Promptlayer -import dotenv, os - -from litellm.proxy._types import UserAPIKeyAuth -from litellm.caching import DualCache -from typing import Literal, Union, Optional +import os import traceback +from typing import Literal, Optional, Union + +import dotenv + +from litellm.caching import DualCache +from litellm.proxy._types import UserAPIKeyAuth class CustomLogger: # https://docs.litellm.ai/docs/observability/custom_callback#callback-class diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index aa22b5153..add281e43 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -19,8 +19,7 @@ from litellm import ( turn_off_message_logging, verbose_logger, ) - -from litellm.caching import InMemoryCache, S3Cache, DualCache +from litellm.caching import DualCache, InMemoryCache, S3Cache from litellm.integrations.custom_logger import CustomLogger from litellm.litellm_core_utils.redact_messages import ( redact_message_input_output_from_logging, diff --git a/litellm/main.py b/litellm/main.py index 573b2c19f..b7aa47ab7 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -650,6 +650,7 @@ def completion( headers = kwargs.get("headers", None) or extra_headers num_retries = kwargs.get("num_retries", None) ## deprecated max_retries = kwargs.get("max_retries", None) + cooldown_time = kwargs.get("cooldown_time", None) context_window_fallback_dict = kwargs.get("context_window_fallback_dict", None) organization = kwargs.get("organization", None) ### CUSTOM MODEL COST ### @@ -763,6 +764,7 @@ def completion( "allowed_model_region", "model_config", "fastest_response", + "cooldown_time", ] default_params = openai_params + litellm_params @@ -947,6 +949,7 @@ def completion( input_cost_per_token=input_cost_per_token, output_cost_per_second=output_cost_per_second, output_cost_per_token=output_cost_per_token, + cooldown_time=cooldown_time, ) logging.update_environment_variables( model=model, @@ -3030,6 +3033,7 @@ def embedding( client = kwargs.pop("client", None) rpm = kwargs.pop("rpm", None) tpm = kwargs.pop("tpm", None) + cooldown_time = kwargs.get("cooldown_time", None) max_parallel_requests = kwargs.pop("max_parallel_requests", None) model_info = kwargs.get("model_info", None) metadata = kwargs.get("metadata", None) @@ -3105,6 +3109,7 @@ def embedding( "region_name", "allowed_model_region", "model_config", + "cooldown_time", ] default_params = openai_params + litellm_params non_default_params = { @@ -3165,6 +3170,7 @@ def embedding( "aembedding": aembedding, "preset_cache_key": None, "stream_response": {}, + "cooldown_time": cooldown_time, }, ) if azure == True or custom_llm_provider == "azure": diff --git a/litellm/router.py b/litellm/router.py index 840df5b54..e2f7ce8b2 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -2816,7 +2816,9 @@ class Router: exception_response = getattr(exception, "response", {}) exception_headers = getattr(exception_response, "headers", None) - _time_to_cooldown = self.cooldown_time + _time_to_cooldown = kwargs.get("litellm_params", {}).get( + "cooldown_time", self.cooldown_time + ) if exception_headers is not None: diff --git a/litellm/tests/test_router_cooldowns.py b/litellm/tests/test_router_cooldowns.py index 35095bb2c..3eef6e542 100644 --- a/litellm/tests/test_router_cooldowns.py +++ b/litellm/tests/test_router_cooldowns.py @@ -1,18 +1,26 @@ #### What this tests #### # This tests calling router with fallback models -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 +from unittest.mock import AsyncMock, MagicMock, patch + +import httpx +import openai + import litellm from litellm import Router from litellm.integrations.custom_logger import CustomLogger -import openai, httpx @pytest.mark.asyncio @@ -62,3 +70,45 @@ async def test_cooldown_badrequest_error(): assert response is not None print(response) + + +@pytest.mark.asyncio +async def test_dynamic_cooldowns(): + """ + Assert kwargs for completion/embedding have 'cooldown_time' as a litellm_param + """ + # litellm.set_verbose = True + tmp_mock = MagicMock() + + litellm.failure_callback = [tmp_mock] + + router = Router( + model_list=[ + { + "model_name": "my-fake-model", + "litellm_params": { + "model": "openai/gpt-1", + "api_key": "my-key", + "mock_response": Exception("this is an error"), + }, + } + ], + cooldown_time=60, + ) + + try: + _ = router.completion( + model="my-fake-model", + messages=[{"role": "user", "content": "Hey, how's it going?"}], + cooldown_time=0, + num_retries=0, + ) + except Exception: + pass + + tmp_mock.assert_called_once() + + print(tmp_mock.call_count) + + assert "cooldown_time" in tmp_mock.call_args[0][0]["litellm_params"] + assert tmp_mock.call_args[0][0]["litellm_params"]["cooldown_time"] == 0 diff --git a/litellm/utils.py b/litellm/utils.py index 4465c5b0a..beae7ba4a 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2017,6 +2017,7 @@ def get_litellm_params( input_cost_per_token=None, output_cost_per_token=None, output_cost_per_second=None, + cooldown_time=None, ): litellm_params = { "acompletion": acompletion, @@ -2039,6 +2040,7 @@ def get_litellm_params( "input_cost_per_second": input_cost_per_second, "output_cost_per_token": output_cost_per_token, "output_cost_per_second": output_cost_per_second, + "cooldown_time": cooldown_time, } return litellm_params From 8fbc34e7e9e1e2f8d05eafb278f39eadfd0cf866 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Jun 2024 17:01:58 -0700 Subject: [PATCH 074/137] docs(routing.md): add dynamic cooldowns to docs --- docs/my-website/docs/proxy/reliability.md | 1 + docs/my-website/docs/routing.md | 35 ++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/my-website/docs/proxy/reliability.md b/docs/my-website/docs/proxy/reliability.md index c07fc3c26..9228071b0 100644 --- a/docs/my-website/docs/proxy/reliability.md +++ b/docs/my-website/docs/proxy/reliability.md @@ -272,6 +272,7 @@ litellm_settings: fallbacks: [{"zephyr-beta": ["gpt-3.5-turbo"]}] # fallback to gpt-3.5-turbo if call fails num_retries context_window_fallbacks: [{"zephyr-beta": ["gpt-3.5-turbo-16k"]}, {"gpt-3.5-turbo": ["gpt-3.5-turbo-16k"]}] # fallback to gpt-3.5-turbo-16k if context window error allowed_fails: 3 # cooldown model if it fails > 1 call in a minute. + cooldown_time: 30 # how long to cooldown model if fails/min > allowed_fails ``` ### Context Window Fallbacks (Pre-Call Checks + Fallbacks) diff --git a/docs/my-website/docs/routing.md b/docs/my-website/docs/routing.md index de0a4a796..240e6c8e0 100644 --- a/docs/my-website/docs/routing.md +++ b/docs/my-website/docs/routing.md @@ -762,6 +762,9 @@ asyncio.run(router_acompletion()) Set the limit for how many calls a model is allowed to fail in a minute, before being cooled down for a minute. + + + ```python from litellm import Router @@ -779,9 +782,39 @@ messages = [{"content": user_message, "role": "user"}] response = router.completion(model="gpt-3.5-turbo", messages=messages) print(f"response: {response}") - ``` + + + +**Set Global Value** + +```yaml +router_settings: + allowed_fails: 3 # cooldown model if it fails > 1 call in a minute. + cooldown_time: 30 # (in seconds) how long to cooldown model if fails/min > allowed_fails +``` + +Defaults: +- allowed_fails: 0 +- cooldown_time: 60s + +**Set Per Model** + +```yaml +model_list: +- model_name: fake-openai-endpoint + litellm_params: + model: predibase/llama-3-8b-instruct + api_key: os.environ/PREDIBASE_API_KEY + tenant_id: os.environ/PREDIBASE_TENANT_ID + max_new_tokens: 256 + cooldown_time: 0 # 👈 KEY CHANGE +``` + + + + ### Retries For both async + sync functions, we support retrying failed requests. From 304e84f87c299fe77c3aff4bac089b2454ab0b2b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 17:25:53 -0700 Subject: [PATCH 075/137] clean up secret detection --- .../enterprise_hooks/secret_detection.py | 33 ++++--------------- requirements.txt | 1 + 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/enterprise/enterprise_hooks/secret_detection.py b/enterprise/enterprise_hooks/secret_detection.py index ade8b7172..ded9f27c1 100644 --- a/enterprise/enterprise_hooks/secret_detection.py +++ b/enterprise/enterprise_hooks/secret_detection.py @@ -126,11 +126,14 @@ class _ENTERPRISE_SecretDetection(CustomLogger): f"Detected and redacted secrets in input: {secret_types}" ) elif isinstance(data["input"], list): - for item in data["input"]: + _input_in_request = data["input"] + for idx, item in enumerate(_input_in_request): if isinstance(item, str): detected_secrets = self.scan_message_for_secrets(item) for secret in detected_secrets: - item = item.replace(secret["value"], "[REDACTED]") + _input_in_request[idx] = item.replace( + secret["value"], "[REDACTED]" + ) if len(detected_secrets) > 0: secret_types = [ secret["type"] for secret in detected_secrets @@ -138,27 +141,5 @@ class _ENTERPRISE_SecretDetection(CustomLogger): verbose_proxy_logger.warning( f"Detected and redacted secrets in input: {secret_types}" ) - - -# secretDetect = _ENTERPRISE_SecretDetection() - -# from litellm.caching import DualCache -# print("running hook to detect a secret") -# test_data = { -# "messages": [ -# {"role": "user", "content": "Hey, how's it going, API_KEY = 'sk_1234567890abcdef'"}, -# {"role": "assistant", "content": "Hello! I'm doing well. How can I assist you today?"}, -# {"role": "user", "content": "this is my OPENAI_API_KEY = 'sk_1234567890abcdef'"}, -# {"role": "user", "content": "i think it is sk-1234567890abcdef"}, -# ], -# "model": "gpt-3.5-turbo", -# } -# secretDetect.async_pre_call_hook( -# data=test_data, -# user_api_key_dict=UserAPIKeyAuth(token="your_api_key"), -# cache=DualCache(), -# call_type="completion", -# ) - - -# print("finished hook to detect a secret - test data=", test_data) + verbose_proxy_logger.debug("Data after redacting input %s", data) + return diff --git a/requirements.txt b/requirements.txt index fbf2bfc1d..e40c44e4d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,6 +31,7 @@ azure-identity==1.16.1 # for azure content safety opentelemetry-api==1.25.0 opentelemetry-sdk==1.25.0 opentelemetry-exporter-otlp==1.25.0 +detect-secrets==1.5.0 # Enterprise - secret detection / masking in LLM requests ### LITELLM PACKAGE DEPENDENCIES python-dotenv==1.0.0 # for env From 66f0d6ae8f611bb289508f7ba0da4fc403686f27 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 17:27:02 -0700 Subject: [PATCH 076/137] test secret detection --- litellm/proxy/proxy_config.yaml | 2 +- litellm/tests/test_secret_detect_hook.py | 216 +++++++++++++++++++++++ 2 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 litellm/tests/test_secret_detect_hook.py diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index caa6bc13b..0c0365f43 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -26,7 +26,7 @@ general_settings: litellm_settings: success_callback: ["prometheus"] - callbacks: ["otel"] + callbacks: ["otel", "hide_secrets"] failure_callback: ["prometheus"] store_audit_logs: true redact_messages_in_exceptions: True diff --git a/litellm/tests/test_secret_detect_hook.py b/litellm/tests/test_secret_detect_hook.py new file mode 100644 index 000000000..a1bf10eba --- /dev/null +++ b/litellm/tests/test_secret_detect_hook.py @@ -0,0 +1,216 @@ +# What is this? +## This tests the llm guard integration + +import asyncio +import os +import random + +# What is this? +## Unit test for presidio pii masking +import sys +import time +import traceback +from datetime import datetime + +from dotenv import load_dotenv + +load_dotenv() +import os + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path +import pytest + +import litellm +from litellm import Router, mock_completion +from litellm.caching import DualCache +from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.enterprise.enterprise_hooks.secret_detection import ( + _ENTERPRISE_SecretDetection, +) +from litellm.proxy.utils import ProxyLogging, hash_token + +### UNIT TESTS FOR OpenAI Moderation ### + + +@pytest.mark.asyncio +async def test_basic_secret_detection_chat(): + """ + Tests to see if secret detection hook will mask api keys + + + It should mask the following API_KEY = 'sk_1234567890abcdef' and OPENAI_API_KEY = 'sk_1234567890abcdef' + """ + secret_instance = _ENTERPRISE_SecretDetection() + _api_key = "sk-12345" + _api_key = hash_token("sk-12345") + user_api_key_dict = UserAPIKeyAuth(api_key=_api_key) + local_cache = DualCache() + + from litellm.proxy.proxy_server import llm_router + + test_data = { + "messages": [ + { + "role": "user", + "content": "Hey, how's it going, API_KEY = 'sk_1234567890abcdef'", + }, + { + "role": "assistant", + "content": "Hello! I'm doing well. How can I assist you today?", + }, + { + "role": "user", + "content": "this is my OPENAI_API_KEY = 'sk_1234567890abcdef'", + }, + {"role": "user", "content": "i think it is +1 412-555-5555"}, + ], + "model": "gpt-3.5-turbo", + } + + await secret_instance.async_pre_call_hook( + cache=local_cache, + data=test_data, + user_api_key_dict=user_api_key_dict, + call_type="completion", + ) + print( + "test data after running pre_call_hook: Expect all API Keys to be masked", + test_data, + ) + + assert test_data == { + "messages": [ + {"role": "user", "content": "Hey, how's it going, API_KEY = '[REDACTED]'"}, + { + "role": "assistant", + "content": "Hello! I'm doing well. How can I assist you today?", + }, + {"role": "user", "content": "this is my OPENAI_API_KEY = '[REDACTED]'"}, + {"role": "user", "content": "i think it is +1 412-555-5555"}, + ], + "model": "gpt-3.5-turbo", + }, "Expect all API Keys to be masked" + + +@pytest.mark.asyncio +async def test_basic_secret_detection_text_completion(): + """ + Tests to see if secret detection hook will mask api keys + + + It should mask the following API_KEY = 'sk_1234567890abcdef' and OPENAI_API_KEY = 'sk_1234567890abcdef' + """ + secret_instance = _ENTERPRISE_SecretDetection() + _api_key = "sk-12345" + _api_key = hash_token("sk-12345") + user_api_key_dict = UserAPIKeyAuth(api_key=_api_key) + local_cache = DualCache() + + from litellm.proxy.proxy_server import llm_router + + test_data = { + "prompt": "Hey, how's it going, API_KEY = 'sk_1234567890abcdef', my OPENAI_API_KEY = 'sk_1234567890abcdef' and i want to know what is the weather", + "model": "gpt-3.5-turbo", + } + + await secret_instance.async_pre_call_hook( + cache=local_cache, + data=test_data, + user_api_key_dict=user_api_key_dict, + call_type="completion", + ) + + test_data == { + "prompt": "Hey, how's it going, API_KEY = '[REDACTED]', my OPENAI_API_KEY = '[REDACTED]' and i want to know what is the weather", + "model": "gpt-3.5-turbo", + } + print( + "test data after running pre_call_hook: Expect all API Keys to be masked", + test_data, + ) + + +@pytest.mark.asyncio +async def test_basic_secret_detection_embeddings(): + """ + Tests to see if secret detection hook will mask api keys + + + It should mask the following API_KEY = 'sk_1234567890abcdef' and OPENAI_API_KEY = 'sk_1234567890abcdef' + """ + secret_instance = _ENTERPRISE_SecretDetection() + _api_key = "sk-12345" + _api_key = hash_token("sk-12345") + user_api_key_dict = UserAPIKeyAuth(api_key=_api_key) + local_cache = DualCache() + + from litellm.proxy.proxy_server import llm_router + + test_data = { + "input": "Hey, how's it going, API_KEY = 'sk_1234567890abcdef', my OPENAI_API_KEY = 'sk_1234567890abcdef' and i want to know what is the weather", + "model": "gpt-3.5-turbo", + } + + await secret_instance.async_pre_call_hook( + cache=local_cache, + data=test_data, + user_api_key_dict=user_api_key_dict, + call_type="embedding", + ) + + assert test_data == { + "input": "Hey, how's it going, API_KEY = '[REDACTED]', my OPENAI_API_KEY = '[REDACTED]' and i want to know what is the weather", + "model": "gpt-3.5-turbo", + } + print( + "test data after running pre_call_hook: Expect all API Keys to be masked", + test_data, + ) + + +@pytest.mark.asyncio +async def test_basic_secret_detection_embeddings_list(): + """ + Tests to see if secret detection hook will mask api keys + + + It should mask the following API_KEY = 'sk_1234567890abcdef' and OPENAI_API_KEY = 'sk_1234567890abcdef' + """ + secret_instance = _ENTERPRISE_SecretDetection() + _api_key = "sk-12345" + _api_key = hash_token("sk-12345") + user_api_key_dict = UserAPIKeyAuth(api_key=_api_key) + local_cache = DualCache() + + from litellm.proxy.proxy_server import llm_router + + test_data = { + "input": [ + "hey", + "how's it going, API_KEY = 'sk_1234567890abcdef'", + "my OPENAI_API_KEY = 'sk_1234567890abcdef' and i want to know what is the weather", + ], + "model": "gpt-3.5-turbo", + } + + await secret_instance.async_pre_call_hook( + cache=local_cache, + data=test_data, + user_api_key_dict=user_api_key_dict, + call_type="embedding", + ) + + print( + "test data after running pre_call_hook: Expect all API Keys to be masked", + test_data, + ) + assert test_data == { + "input": [ + "hey", + "how's it going, API_KEY = '[REDACTED]'", + "my OPENAI_API_KEY = '[REDACTED]' and i want to know what is the weather", + ], + "model": "gpt-3.5-turbo", + } From 734ed571e9f91d5f060116afcd5a78e35baff019 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 17:42:54 -0700 Subject: [PATCH 077/137] docs - secret detection --- docs/my-website/docs/enterprise.md | 3 +- docs/my-website/docs/proxy/enterprise.md | 101 +++++++++++++++++++++-- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/docs/my-website/docs/enterprise.md b/docs/my-website/docs/enterprise.md index 2d45ea3ea..875aec57f 100644 --- a/docs/my-website/docs/enterprise.md +++ b/docs/my-website/docs/enterprise.md @@ -13,7 +13,8 @@ This covers: - ✅ [**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) -- ✅ [**Prompt Injection Detection**](#prompt-injection-detection-lakeraai) +- ✅ [**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) - ✅ **Feature Prioritization** - ✅ **Custom Integrations** diff --git a/docs/my-website/docs/proxy/enterprise.md b/docs/my-website/docs/proxy/enterprise.md index 40a5261cd..9fff879e5 100644 --- a/docs/my-website/docs/proxy/enterprise.md +++ b/docs/my-website/docs/proxy/enterprise.md @@ -15,10 +15,10 @@ 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) -- ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) -- ✅ [Content Moderation with LLM Guard, LlamaGuard, Google Text Moderations](#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) - ✅ [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) @@ -495,7 +495,98 @@ curl --request POST \ ## Content Moderation -#### Content Moderation with LLM Guard +### Content Moderation - Secret Detection +❓ Use this to REDACT API Keys, Secrets sent in requests to an LLM. + +Example if you want to redact the value of `OPENAI_API_KEY` in the following request + +#### Incoming Request + +```json +{ + "messages": [ + { + "role": "user", + "content": "Hey, how's it going, API_KEY = 'sk_1234567890abcdef'", + } + ] +} +``` + +#### Request after Moderation + +```json +{ + "messages": [ + { + "role": "user", + "content": "Hey, how's it going, API_KEY = '[REDACTED]'", + } + ] +} +``` + +**Usage** + +**Step 1** Add this to your config.yaml + +```yaml +litellm_settings: + callbacks: ["hide_secrets"] +``` + +**Step 2** Run litellm proxy with `--detailed_debug` to see the server logs + +``` +litellm --config config.yaml --detailed_debug +``` + +**Step 3** Test it with request + +Send this request +```shell +curl --location 'http://localhost:4000/chat/completions' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "llama3", + "messages": [ + { + "role": "user", + "content": "what is the value of my open ai key? openai_api_key=sk-1234998222" + } + ] +}' +``` + + +Expect to see the following warning on your litellm server logs + +```shell +LiteLLM Proxy:WARNING: secret_detection.py:88 - Detected and redacted secrets in message: ['Secret Keyword'] +``` + + +You can also see the raw request sent from litellm to the API Provider +```json +POST Request Sent from LiteLLM: +curl -X POST \ +https://api.groq.com/openai/v1/ \ +-H 'Authorization: Bearer gsk_mySVchjY********************************************' \ +-d { + "model": "llama3-8b-8192", + "messages": [ + { + "role": "user", + "content": "what is the time today, openai_api_key=[REDACTED]" + } + ], + "stream": false, + "extra_body": {} +} +``` + +### Content Moderation with LLM Guard Set the LLM Guard API Base in your environment @@ -630,7 +721,7 @@ curl --location 'http://0.0.0.0:4000/v1/chat/completions' \ -#### Content Moderation with LlamaGuard +### Content Moderation with LlamaGuard Currently works with Sagemaker's LlamaGuard endpoint. @@ -664,7 +755,7 @@ callbacks: ["llamaguard_moderations"] -#### Content Moderation with Google Text Moderation +### Content Moderation with Google Text Moderation Requires your GOOGLE_APPLICATION_CREDENTIALS to be set in your .env (same as VertexAI). From 33c11c94f87d4deb931bb34a228885ebe8867d16 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 17:44:36 -0700 Subject: [PATCH 078/137] fix detect secrets test --- .circleci/config.yml | 3 ++- litellm/tests/test_completion.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fd1b48a9c..5dfeedcaa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -48,7 +48,8 @@ jobs: pip install opentelemetry-sdk==1.25.0 pip install opentelemetry-exporter-otlp==1.25.0 pip install openai - pip install prisma + pip install prisma + pip install "detect_secrets==1.5.0" pip install "httpx==0.24.1" pip install fastapi pip install "gunicorn==21.2.0" diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 30ae1d0ab..0c6da360b 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 45c748475bb42c3354cf01485e647510542a7952 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 18:13:31 -0700 Subject: [PATCH 079/137] =?UTF-8?q?bump:=20version=201.40.26=20=E2=86=92?= =?UTF-8?q?=201.40.27?= 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 6b4884b5b..321f44b23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.40.26" +version = "1.40.27" 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.26" +version = "1.40.27" version_files = [ "pyproject.toml:^version" ] From ac1f343487878dfb1aa02dc8b79314fb281451a9 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Tue, 25 Jun 2024 18:19:24 -0700 Subject: [PATCH 080/137] Revert "Create litellm user to fix issue with prisma in k8s " --- Dockerfile.database | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/Dockerfile.database b/Dockerfile.database index 1901200d5..22084bab8 100644 --- a/Dockerfile.database +++ b/Dockerfile.database @@ -9,27 +9,6 @@ FROM $LITELLM_BUILD_IMAGE as builder # Set the working directory to /app WORKDIR /app -ARG LITELLM_USER=litellm LITELLM_UID=1729 -ARG LITELLM_GROUP=litellm LITELLM_GID=1729 - -RUN groupadd \ - --gid ${LITELLM_GID} \ - ${LITELLM_GROUP} \ - && useradd \ - --create-home \ - --shell /bin/sh \ - --gid ${LITELLM_GID} \ - --uid ${LITELLM_UID} \ - ${LITELLM_USER} - -# Allows user to update python install. -# This is necessary for prisma. -RUN chown -R ${LITELLM_USER}:${LITELLM_GROUP} /usr/local/lib/python3.11 - -# Set the HOME var forcefully because of prisma. -ENV HOME=/home/${LITELLM_USER} -USER ${LITELLM_USER} - # Install build dependencies RUN apt-get clean && apt-get update && \ apt-get install -y gcc python3-dev && \ From eca4876bbf1cad01042c2f44aa7c018249055a73 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 25 Jun 2024 18:21:57 -0700 Subject: [PATCH 081/137] 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 0c6da360b..30ae1d0ab 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 d6ed8c10b24386f0fe642022319b684cfe64ea57 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 25 Jun 2024 18:26:16 -0700 Subject: [PATCH 082/137] docs(function_call.md): cleanup --- docs/my-website/docs/completion/function_call.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/my-website/docs/completion/function_call.md b/docs/my-website/docs/completion/function_call.md index 5daccf723..514e8cda1 100644 --- a/docs/my-website/docs/completion/function_call.md +++ b/docs/my-website/docs/completion/function_call.md @@ -502,10 +502,10 @@ response = completion(model="gpt-3.5-turbo-0613", messages=messages, functions=f print(response) ``` -## Function calling for Non-OpenAI LLMs +## Function calling for Models w/out function-calling support ### Adding Function to prompt -For Non OpenAI LLMs LiteLLM allows you to add the function to the prompt set: `litellm.add_function_to_prompt = True` +For Models/providers without function calling support, LiteLLM allows you to add the function to the prompt set: `litellm.add_function_to_prompt = True` #### Usage ```python From 493a737787120f4d2e20edfc3201b51e6fbee6a8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 06:29:21 -0700 Subject: [PATCH 083/137] feat - add fireworks ai config for param mapping --- litellm/llms/fireworks_ai.py | 107 ++++++++++++++++++ ...odel_prices_and_context_window_backup.json | 24 ++++ 2 files changed, 131 insertions(+) create mode 100644 litellm/llms/fireworks_ai.py diff --git a/litellm/llms/fireworks_ai.py b/litellm/llms/fireworks_ai.py new file mode 100644 index 000000000..18309f4c2 --- /dev/null +++ b/litellm/llms/fireworks_ai.py @@ -0,0 +1,107 @@ +import types +from typing import Literal, Optional, Union + +import litellm + + +class FireworksAIConfig: + """ + Reference: https://docs.fireworks.ai/api-reference/post-chatcompletions + + The class `FireworksAIConfig` provides configuration for the Fireworks's Chat Completions API interface. Below are the parameters: + """ + + tools: Optional[list] = None + tool_choice: Optional[Union[str, dict]] = None + max_tokens: Optional[int] = None + temperature: Optional[int] = None + top_p: Optional[int] = None + top_k: Optional[int] = None + frequency_penalty: Optional[int] = None + presence_penalty: Optional[int] = None + n: Optional[int] = None + stop: Optional[Union[str, list]] = None + response_format: Optional[dict] = None + user: Optional[str] = None + + # Non OpenAI parameters - Fireworks AI only params + prompt_truncate_length: Optional[int] = None + context_length_exceeded_behavior: Optional[Literal["error", "truncate"]] = None + + def __init__( + self, + tools: Optional[list] = None, + tool_choice: Optional[Union[str, dict]] = None, + max_tokens: Optional[int] = None, + temperature: Optional[int] = None, + top_p: Optional[int] = None, + top_k: Optional[int] = None, + frequency_penalty: Optional[int] = None, + presence_penalty: Optional[int] = None, + n: Optional[int] = None, + stop: Optional[Union[str, list]] = None, + response_format: Optional[dict] = None, + user: Optional[str] = None, + prompt_truncate_length: Optional[int] = None, + context_length_exceeded_behavior: Optional[Literal["error", "truncate"]] = None, + ) -> None: + locals_ = locals().copy() + for key, value in locals_.items(): + if key != "self" and value is not None: + setattr(self.__class__, key, value) + + @classmethod + def get_config(cls): + return { + k: v + for k, v in cls.__dict__.items() + if not k.startswith("__") + and not isinstance( + v, + ( + types.FunctionType, + types.BuiltinFunctionType, + classmethod, + staticmethod, + ), + ) + and v is not None + } + + def get_supported_openai_params(self): + return [ + "stream", + "tools", + "tool_choice", + "max_tokens", + "temperature", + "top_p", + "top_k", + "frequency_penalty", + "presence_penalty", + "n", + "stop", + "response_format", + "user", + "prompt_truncate_length", + "context_length_exceeded_behavior", + ] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + supported_openai_params = self.get_supported_openai_params() + for param, value in non_default_params.items(): + if param == "tool_choice": + if value == "required": + # relevant issue: https://github.com/BerriAI/litellm/issues/4416 + optional_params["tools"] = "any" + + if param in supported_openai_params: + if value is not None: + optional_params[param] = value + return optional_params diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 415d220f2..d7a7a7dc8 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -2073,6 +2073,30 @@ "supports_function_calling": true, "supports_vision": true }, + "openrouter/anthropic/claude-3-haiku-20240307": { + "max_tokens": 4096, + "max_input_tokens": 200000, + "max_output_tokens": 4096, + "input_cost_per_token": 0.00000025, + "output_cost_per_token": 0.00000125, + "litellm_provider": "openrouter", + "mode": "chat", + "supports_function_calling": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 264 + }, + "openrouter/anthropic/claude-3.5-sonnet": { + "max_tokens": 4096, + "max_input_tokens": 200000, + "max_output_tokens": 4096, + "input_cost_per_token": 0.000003, + "output_cost_per_token": 0.000015, + "litellm_provider": "openrouter", + "mode": "chat", + "supports_function_calling": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, "openrouter/anthropic/claude-3-sonnet": { "max_tokens": 200000, "input_cost_per_token": 0.000003, From dcdf266f366720551184c76a0f0b5469295b8dc5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 06:40:44 -0700 Subject: [PATCH 084/137] fix fireworks ai config --- litellm/llms/fireworks_ai.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/litellm/llms/fireworks_ai.py b/litellm/llms/fireworks_ai.py index 18309f4c2..7c2d3b72a 100644 --- a/litellm/llms/fireworks_ai.py +++ b/litellm/llms/fireworks_ai.py @@ -92,16 +92,15 @@ class FireworksAIConfig: non_default_params: dict, optional_params: dict, model: str, - drop_params: bool, ) -> dict: supported_openai_params = self.get_supported_openai_params() for param, value in non_default_params.items(): if param == "tool_choice": if value == "required": # relevant issue: https://github.com/BerriAI/litellm/issues/4416 - optional_params["tools"] = "any" + optional_params["tool_choice"] = "any" - if param in supported_openai_params: + elif param in supported_openai_params: if value is not None: optional_params[param] = value return optional_params From 1cfe03c8204158d281060fb38ebae6d61d2bd449 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 06:43:18 -0700 Subject: [PATCH 085/137] add fireworks ai param mapping --- litellm/__init__.py | 1 + litellm/utils.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/litellm/__init__.py b/litellm/__init__.py index 08ee84aaa..cee80a32d 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -817,6 +817,7 @@ from .llms.openai import ( AzureAIStudioConfig, ) from .llms.nvidia_nim import NvidiaNimConfig +from .llms.fireworks_ai import FireworksAIConfig from .llms.text_completion_codestral import MistralTextCompletionConfig from .llms.azure import ( AzureOpenAIConfig, diff --git a/litellm/utils.py b/litellm/utils.py index beae7ba4a..a33a160e4 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -3079,6 +3079,16 @@ def get_optional_params( optional_params = litellm.NvidiaNimConfig().map_openai_params( non_default_params=non_default_params, optional_params=optional_params ) + elif custom_llm_provider == "fireworks_ai": + supported_params = get_supported_openai_params( + model=model, custom_llm_provider=custom_llm_provider + ) + _check_valid_arg(supported_params=supported_params) + optional_params = litellm.FireworksAIConfig().map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + ) elif custom_llm_provider == "groq": supported_params = get_supported_openai_params( model=model, custom_llm_provider=custom_llm_provider @@ -3645,6 +3655,8 @@ def get_supported_openai_params( return litellm.OllamaChatConfig().get_supported_openai_params() elif custom_llm_provider == "anthropic": return litellm.AnthropicConfig().get_supported_openai_params() + elif custom_llm_provider == "fireworks_ai": + return litellm.FireworksAIConfig().get_supported_openai_params() elif custom_llm_provider == "nvidia_nim": return litellm.NvidiaNimConfig().get_supported_openai_params() elif custom_llm_provider == "groq": From 6c388dc05aa159add44fd33447ba228dfb82a457 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 06:45:29 -0700 Subject: [PATCH 086/137] test fireworks ai tool calling --- litellm/tests/test_completion.py | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index 30ae1d0ab..a3b0e6ea2 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -1222,6 +1222,44 @@ def test_completion_fireworks_ai(): pytest.fail(f"Error occurred: {e}") +def test_fireworks_ai_tool_calling(): + litellm.set_verbose = True + model_name = "fireworks_ai/accounts/fireworks/models/firefunction-v2" + tools = [ + { + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + }, + "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, + }, + "required": ["location"], + }, + }, + } + ] + messages = [ + { + "role": "user", + "content": "What's the weather like in Boston today in Fahrenheit?", + } + ] + response = completion( + model=model_name, + messages=messages, + tools=tools, + tool_choice="required", + ) + print(response) + + @pytest.mark.skip(reason="this test is flaky") def test_completion_perplexity_api(): try: From 8a7f2921f21dbb6679a20bc603b5372c10e462e7 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 06:57:04 -0700 Subject: [PATCH 087/137] fix + test fireworks ai param mapping for tools --- litellm/llms/fireworks_ai.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/litellm/llms/fireworks_ai.py b/litellm/llms/fireworks_ai.py index 7c2d3b72a..e9caf887a 100644 --- a/litellm/llms/fireworks_ai.py +++ b/litellm/llms/fireworks_ai.py @@ -99,7 +99,9 @@ class FireworksAIConfig: if value == "required": # relevant issue: https://github.com/BerriAI/litellm/issues/4416 optional_params["tool_choice"] = "any" - + else: + # pass through the value of tool choice + optional_params["tool_choice"] = value elif param in supported_openai_params: if value is not None: optional_params[param] = value From 829dece9aa9d0fd07b4d7619f30631b616759b45 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 06:58:00 -0700 Subject: [PATCH 088/137] test - fireworks ai param mapping --- litellm/tests/test_fireworks_ai.py | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 litellm/tests/test_fireworks_ai.py diff --git a/litellm/tests/test_fireworks_ai.py b/litellm/tests/test_fireworks_ai.py new file mode 100644 index 000000000..c7c1f5445 --- /dev/null +++ b/litellm/tests/test_fireworks_ai.py @@ -0,0 +1,32 @@ +import os +import sys + +import pytest + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + +from litellm.llms.fireworks_ai import FireworksAIConfig + +fireworks = FireworksAIConfig() + + +def test_map_openai_params_tool_choice(): + # Test case 1: tool_choice is "required" + result = fireworks.map_openai_params({"tool_choice": "required"}, {}, "some_model") + assert result == {"tool_choice": "any"} + + # Test case 2: tool_choice is "auto" + result = fireworks.map_openai_params({"tool_choice": "auto"}, {}, "some_model") + assert result == {"tool_choice": "auto"} + + # Test case 3: tool_choice is not present + result = fireworks.map_openai_params( + {"some_other_param": "value"}, {}, "some_model" + ) + assert result == {} + + # Test case 4: tool_choice is None + result = fireworks.map_openai_params({"tool_choice": None}, {}, "some_model") + assert result == {"tool_choice": None} From 09f4eb7617e05bd057154a2519598381b3ef265b Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Jun 2024 08:09:14 -0700 Subject: [PATCH 089/137] docs(reliable_completions.md): improve headers for easier searching --- .../docs/completion/reliable_completions.md | 14 ++++++++++---- litellm/llms/azure.py | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/my-website/docs/completion/reliable_completions.md b/docs/my-website/docs/completion/reliable_completions.md index 2656f9a4f..94102e194 100644 --- a/docs/my-website/docs/completion/reliable_completions.md +++ b/docs/my-website/docs/completion/reliable_completions.md @@ -31,9 +31,15 @@ response = completion( ) ``` -## Fallbacks +## Fallbacks (SDK) -### Context Window Fallbacks +:::info + +[See how to do on PROXY](../proxy/reliability.md) + +::: + +### Context Window Fallbacks (SDK) ```python from litellm import completion @@ -43,7 +49,7 @@ messages = [{"content": "how does a court case get to the Supreme Court?" * 500, completion(model="gpt-3.5-turbo", messages=messages, context_window_fallback_dict=ctx_window_fallback_dict) ``` -### Fallbacks - Switch Models/API Keys/API Bases +### Fallbacks - Switch Models/API Keys/API Bases (SDK) LLM APIs can be unstable, completion() with fallbacks ensures you'll always get a response from your calls @@ -69,7 +75,7 @@ response = completion(model="azure/gpt-4", messages=messages, api_key=api_key, [Check out this section for implementation details](#fallbacks-1) -## Implementation Details +## Implementation Details (SDK) ### Fallbacks #### Output from calls diff --git a/litellm/llms/azure.py b/litellm/llms/azure.py index c292c3423..b763a7c95 100644 --- a/litellm/llms/azure.py +++ b/litellm/llms/azure.py @@ -902,7 +902,7 @@ class AzureChatCompletion(BaseLLM): }, ) - if aembedding == True: + if aembedding is True: response = self.aembedding( data=data, input=input, From e78c038284178e2769d2b1a606adf694ffcf3f0a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 08:46:45 -0700 Subject: [PATCH 090/137] add gemini-1.0-ultra-001 --- ...odel_prices_and_context_window_backup.json | 54 +++++++++++++++++++ model_prices_and_context_window.json | 30 +++++++++++ 2 files changed, 84 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 415d220f2..2c72248f0 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1232,6 +1232,36 @@ "supports_function_calling": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "gemini-1.0-ultra": { + "max_tokens": 8192, + "max_input_tokens": 32760, + "max_output_tokens": 8192, + "input_cost_per_image": 0.0025, + "input_cost_per_video_per_second": 0.002, + "input_cost_per_token": 0.0000005, + "input_cost_per_character": 0.000000125, + "output_cost_per_token": 0.0000015, + "output_cost_per_character": 0.000000375, + "litellm_provider": "vertex_ai-language-models", + "mode": "chat", + "supports_function_calling": true, + "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro information here" + }, + "gemini-1.0-ultra-001": { + "max_tokens": 8192, + "max_input_tokens": 32760, + "max_output_tokens": 8192, + "input_cost_per_image": 0.0025, + "input_cost_per_video_per_second": 0.002, + "input_cost_per_token": 0.0000005, + "input_cost_per_character": 0.000000125, + "output_cost_per_token": 0.0000015, + "output_cost_per_character": 0.000000375, + "litellm_provider": "vertex_ai-language-models", + "mode": "chat", + "supports_function_calling": true, + "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro information here" + }, "gemini-1.0-pro-002": { "max_tokens": 8192, "max_input_tokens": 32760, @@ -2073,6 +2103,30 @@ "supports_function_calling": true, "supports_vision": true }, + "openrouter/anthropic/claude-3-haiku-20240307": { + "max_tokens": 4096, + "max_input_tokens": 200000, + "max_output_tokens": 4096, + "input_cost_per_token": 0.00000025, + "output_cost_per_token": 0.00000125, + "litellm_provider": "openrouter", + "mode": "chat", + "supports_function_calling": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 264 + }, + "openrouter/anthropic/claude-3.5-sonnet": { + "max_tokens": 4096, + "max_input_tokens": 200000, + "max_output_tokens": 4096, + "input_cost_per_token": 0.000003, + "output_cost_per_token": 0.000015, + "litellm_provider": "openrouter", + "mode": "chat", + "supports_function_calling": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 159 + }, "openrouter/anthropic/claude-3-sonnet": { "max_tokens": 200000, "input_cost_per_token": 0.000003, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index d7a7a7dc8..2c72248f0 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1232,6 +1232,36 @@ "supports_function_calling": true, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "gemini-1.0-ultra": { + "max_tokens": 8192, + "max_input_tokens": 32760, + "max_output_tokens": 8192, + "input_cost_per_image": 0.0025, + "input_cost_per_video_per_second": 0.002, + "input_cost_per_token": 0.0000005, + "input_cost_per_character": 0.000000125, + "output_cost_per_token": 0.0000015, + "output_cost_per_character": 0.000000375, + "litellm_provider": "vertex_ai-language-models", + "mode": "chat", + "supports_function_calling": true, + "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro information here" + }, + "gemini-1.0-ultra-001": { + "max_tokens": 8192, + "max_input_tokens": 32760, + "max_output_tokens": 8192, + "input_cost_per_image": 0.0025, + "input_cost_per_video_per_second": 0.002, + "input_cost_per_token": 0.0000005, + "input_cost_per_character": 0.000000125, + "output_cost_per_token": 0.0000015, + "output_cost_per_character": 0.000000375, + "litellm_provider": "vertex_ai-language-models", + "mode": "chat", + "supports_function_calling": true, + "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro information here" + }, "gemini-1.0-pro-002": { "max_tokens": 8192, "max_input_tokens": 32760, From 1ed5ceda8f4f1929ac977dbde1331800c8b71e29 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 08:55:04 -0700 Subject: [PATCH 091/137] fix gemini ultra info --- litellm/model_prices_and_context_window_backup.json | 12 ++++++------ model_prices_and_context_window.json | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 2c72248f0..8d9b2595f 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1234,8 +1234,8 @@ }, "gemini-1.0-ultra": { "max_tokens": 8192, - "max_input_tokens": 32760, - "max_output_tokens": 8192, + "max_input_tokens": 8192, + "max_output_tokens": 2048, "input_cost_per_image": 0.0025, "input_cost_per_video_per_second": 0.002, "input_cost_per_token": 0.0000005, @@ -1245,12 +1245,12 @@ "litellm_provider": "vertex_ai-language-models", "mode": "chat", "supports_function_calling": true, - "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro information here" + "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro pricing. Got max_tokens info here: https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.0-ultra-001": { "max_tokens": 8192, - "max_input_tokens": 32760, - "max_output_tokens": 8192, + "max_input_tokens": 8192, + "max_output_tokens": 2048, "input_cost_per_image": 0.0025, "input_cost_per_video_per_second": 0.002, "input_cost_per_token": 0.0000005, @@ -1260,7 +1260,7 @@ "litellm_provider": "vertex_ai-language-models", "mode": "chat", "supports_function_calling": true, - "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro information here" + "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro pricing. Got max_tokens info here: https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.0-pro-002": { "max_tokens": 8192, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 2c72248f0..8d9b2595f 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1234,8 +1234,8 @@ }, "gemini-1.0-ultra": { "max_tokens": 8192, - "max_input_tokens": 32760, - "max_output_tokens": 8192, + "max_input_tokens": 8192, + "max_output_tokens": 2048, "input_cost_per_image": 0.0025, "input_cost_per_video_per_second": 0.002, "input_cost_per_token": 0.0000005, @@ -1245,12 +1245,12 @@ "litellm_provider": "vertex_ai-language-models", "mode": "chat", "supports_function_calling": true, - "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro information here" + "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro pricing. Got max_tokens info here: https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.0-ultra-001": { "max_tokens": 8192, - "max_input_tokens": 32760, - "max_output_tokens": 8192, + "max_input_tokens": 8192, + "max_output_tokens": 2048, "input_cost_per_image": 0.0025, "input_cost_per_video_per_second": 0.002, "input_cost_per_token": 0.0000005, @@ -1260,7 +1260,7 @@ "litellm_provider": "vertex_ai-language-models", "mode": "chat", "supports_function_calling": true, - "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro information here" + "source": "As of Jun, 2024. There is no available doc on vertex ai pricing gemini-1.0-ultra-001. Using gemini-1.0-pro pricing. Got max_tokens info here: https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "gemini-1.0-pro-002": { "max_tokens": 8192, From faef56fe696ff3eba0fcff80c3270534b2887648 Mon Sep 17 00:00:00 2001 From: Josh Learn Date: Wed, 26 Jun 2024 12:46:59 -0400 Subject: [PATCH 092/137] Add return type annotations to util types --- litellm/types/utils.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/litellm/types/utils.py b/litellm/types/utils.py index f2b161128..378abf4b7 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -171,7 +171,7 @@ class Function(OpenAIObject): arguments: Union[Dict, str], name: Optional[str] = None, **params, - ): + ) -> None: if isinstance(arguments, Dict): arguments = json.dumps(arguments) else: @@ -242,7 +242,7 @@ class ChatCompletionMessageToolCall(OpenAIObject): id: Optional[str] = None, type: Optional[str] = None, **params, - ): + ) -> None: super(ChatCompletionMessageToolCall, self).__init__(**params) if isinstance(function, Dict): self.function = Function(**function) @@ -285,7 +285,7 @@ class Message(OpenAIObject): function_call=None, tool_calls=None, **params, - ): + ) -> None: super(Message, self).__init__(**params) self.content = content self.role = role @@ -328,7 +328,7 @@ class Delta(OpenAIObject): function_call=None, tool_calls=None, **params, - ): + ) -> None: super(Delta, self).__init__(**params) self.content = content self.role = role @@ -375,7 +375,7 @@ class Choices(OpenAIObject): logprobs=None, enhancements=None, **params, - ): + ) -> None: super(Choices, self).__init__(**params) if finish_reason is not None: self.finish_reason = map_finish_reason( @@ -416,7 +416,7 @@ class Choices(OpenAIObject): class Usage(OpenAIObject): def __init__( self, prompt_tokens=None, completion_tokens=None, total_tokens=None, **params - ): + ) -> None: super(Usage, self).__init__(**params) if prompt_tokens: self.prompt_tokens = prompt_tokens @@ -451,7 +451,7 @@ class StreamingChoices(OpenAIObject): logprobs=None, enhancements=None, **params, - ): + ) -> None: super(StreamingChoices, self).__init__(**params) if finish_reason: self.finish_reason = finish_reason @@ -657,7 +657,7 @@ class EmbeddingResponse(OpenAIObject): response_ms=None, data=None, **params, - ): + ) -> None: object = "list" if response_ms: _response_ms = response_ms @@ -708,7 +708,7 @@ class Logprobs(OpenAIObject): class TextChoices(OpenAIObject): - def __init__(self, finish_reason=None, index=0, text=None, logprobs=None, **params): + def __init__(self, finish_reason=None, index=0, text=None, logprobs=None, **params) -> None: super(TextChoices, self).__init__(**params) if finish_reason: self.finish_reason = map_finish_reason(finish_reason) @@ -790,7 +790,7 @@ class TextCompletionResponse(OpenAIObject): response_ms=None, object=None, **params, - ): + ) -> None: if stream: object = "text_completion.chunk" choices = [TextChoices()] @@ -873,7 +873,7 @@ class ImageObject(OpenAIObject): url: Optional[str] = None revised_prompt: Optional[str] = None - def __init__(self, b64_json=None, url=None, revised_prompt=None): + def __init__(self, b64_json=None, url=None, revised_prompt=None) -> None: super().__init__(b64_json=b64_json, url=url, revised_prompt=revised_prompt) def __contains__(self, key): @@ -909,7 +909,7 @@ class ImageResponse(OpenAIObject): _hidden_params: dict = {} - def __init__(self, created=None, data=None, response_ms=None): + def __init__(self, created=None, data=None, response_ms=None) -> None: if response_ms: _response_ms = response_ms else: @@ -956,7 +956,7 @@ class TranscriptionResponse(OpenAIObject): _hidden_params: dict = {} - def __init__(self, text=None): + def __init__(self, text=None) -> None: super().__init__(text=text) def __contains__(self, key): From 08412f736b3aeb1adf4c82f0001dda9590dc50f1 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 11:18:22 -0700 Subject: [PATCH 093/137] add vertex text-bison --- ...odel_prices_and_context_window_backup.json | 42 +++++++++++++++++-- model_prices_and_context_window.json | 42 +++++++++++++++++-- 2 files changed, 76 insertions(+), 8 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 8d9b2595f..b708e509b 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1028,21 +1028,55 @@ "tool_use_system_prompt_tokens": 159 }, "text-bison": { - "max_tokens": 1024, + "max_tokens": 2048, "max_input_tokens": 8192, - "max_output_tokens": 1024, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125, + "max_output_tokens": 2048, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-text-models", "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "text-bison@001": { + "max_tokens": 1024, + "max_input_tokens": 8192, + "max_output_tokens": 1024, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "text-bison@002": { + "max_tokens": 1024, + "max_input_tokens": 8192, + "max_output_tokens": 1024, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "text-bison32k": { "max_tokens": 1024, "max_input_tokens": 8192, "max_output_tokens": 1024, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "text-bison32k@002": { + "max_tokens": 1024, + "max_input_tokens": 8192, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-text-models", "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 8d9b2595f..b708e509b 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1028,21 +1028,55 @@ "tool_use_system_prompt_tokens": 159 }, "text-bison": { - "max_tokens": 1024, + "max_tokens": 2048, "max_input_tokens": 8192, - "max_output_tokens": 1024, - "input_cost_per_token": 0.000000125, - "output_cost_per_token": 0.000000125, + "max_output_tokens": 2048, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-text-models", "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, "text-bison@001": { + "max_tokens": 1024, + "max_input_tokens": 8192, + "max_output_tokens": 1024, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "text-bison@002": { + "max_tokens": 1024, + "max_input_tokens": 8192, + "max_output_tokens": 1024, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "text-bison32k": { "max_tokens": 1024, "max_input_tokens": 8192, "max_output_tokens": 1024, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "text-bison32k@002": { + "max_tokens": 1024, + "max_input_tokens": 8192, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-text-models", "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" From 553f258758b136bb0e05592cd04cc16de50d4cd6 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 11:26:14 -0700 Subject: [PATCH 094/137] add chat-bison-32k@002 --- ...odel_prices_and_context_window_backup.json | 30 +++++++++++++++++++ model_prices_and_context_window.json | 30 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index b708e509b..84c3b9de2 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1107,6 +1107,8 @@ "max_output_tokens": 4096, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1117,6 +1119,8 @@ "max_output_tokens": 4096, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1127,6 +1131,8 @@ "max_output_tokens": 4096, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1137,6 +1143,20 @@ "max_output_tokens": 8192, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-chat-models", + "mode": "chat", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "chat-bison-32k@002": { + "max_tokens": 8192, + "max_input_tokens": 32000, + "max_output_tokens": 8192, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1147,6 +1167,8 @@ "max_output_tokens": 1024, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-code-text-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1157,6 +1179,8 @@ "max_output_tokens": 1024, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-code-text-models", "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1197,6 +1221,8 @@ "max_output_tokens": 1024, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-code-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1207,6 +1233,8 @@ "max_output_tokens": 1024, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-code-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1217,6 +1245,8 @@ "max_output_tokens": 8192, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-code-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index b708e509b..84c3b9de2 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1107,6 +1107,8 @@ "max_output_tokens": 4096, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1117,6 +1119,8 @@ "max_output_tokens": 4096, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1127,6 +1131,8 @@ "max_output_tokens": 4096, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1137,6 +1143,20 @@ "max_output_tokens": 8192, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-chat-models", + "mode": "chat", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "chat-bison-32k@002": { + "max_tokens": 8192, + "max_input_tokens": 32000, + "max_output_tokens": 8192, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1147,6 +1167,8 @@ "max_output_tokens": 1024, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-code-text-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1157,6 +1179,8 @@ "max_output_tokens": 1024, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-code-text-models", "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1197,6 +1221,8 @@ "max_output_tokens": 1024, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-code-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1207,6 +1233,8 @@ "max_output_tokens": 1024, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-code-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" @@ -1217,6 +1245,8 @@ "max_output_tokens": 8192, "input_cost_per_token": 0.000000125, "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, "litellm_provider": "vertex_ai-code-chat-models", "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" From 08d3d670c1648e38ff512a16cc4936a9184abe14 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 11:28:10 -0700 Subject: [PATCH 095/137] add code-bison --- ...odel_prices_and_context_window_backup.json | 36 +++++++++++++++++++ model_prices_and_context_window.json | 36 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 84c3b9de2..f51182d8f 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1185,6 +1185,42 @@ "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "code-bison@002": { + "max_tokens": 1024, + "max_input_tokens": 6144, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "code-bison32k": { + "max_tokens": 1024, + "max_input_tokens": 6144, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "code-bison-32k@002": { + "max_tokens": 1024, + "max_input_tokens": 6144, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, "code-gecko@001": { "max_tokens": 64, "max_input_tokens": 2048, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 84c3b9de2..f51182d8f 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1185,6 +1185,42 @@ "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "code-bison@002": { + "max_tokens": 1024, + "max_input_tokens": 6144, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "code-bison32k": { + "max_tokens": 1024, + "max_input_tokens": 6144, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, + "code-bison-32k@002": { + "max_tokens": 1024, + "max_input_tokens": 6144, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, "code-gecko@001": { "max_tokens": 64, "max_input_tokens": 2048, From b13bac82049a0d53b8b5a7c24e95455341e1f4dc Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 11:34:48 -0700 Subject: [PATCH 096/137] add code-gecko-latest --- litellm/model_prices_and_context_window_backup.json | 10 ++++++++++ model_prices_and_context_window.json | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index f51182d8f..f7a23e8e1 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1251,6 +1251,16 @@ "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "code-gecko-latest": { + "max_tokens": 64, + "max_input_tokens": 2048, + "max_output_tokens": 64, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "litellm_provider": "vertex_ai-code-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, "codechat-bison": { "max_tokens": 1024, "max_input_tokens": 6144, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index f51182d8f..f7a23e8e1 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1251,6 +1251,16 @@ "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "code-gecko-latest": { + "max_tokens": 64, + "max_input_tokens": 2048, + "max_output_tokens": 64, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "litellm_provider": "vertex_ai-code-text-models", + "mode": "completion", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, "codechat-bison": { "max_tokens": 1024, "max_input_tokens": 6144, From 5dacaa2c4c644f574d4d270594756cd06eb8fad0 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 11:37:39 -0700 Subject: [PATCH 097/137] add codechat-bison@latest --- ...odel_prices_and_context_window_backup.json | 36 +++++++++++++++++++ model_prices_and_context_window.json | 36 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index f7a23e8e1..e665e79f3 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1261,6 +1261,18 @@ "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "codechat-bison@latest": { + "max_tokens": 1024, + "max_input_tokens": 6144, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-chat-models", + "mode": "chat", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, "codechat-bison": { "max_tokens": 1024, "max_input_tokens": 6144, @@ -1285,6 +1297,18 @@ "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "codechat-bison@002": { + "max_tokens": 1024, + "max_input_tokens": 6144, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-chat-models", + "mode": "chat", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, "codechat-bison-32k": { "max_tokens": 8192, "max_input_tokens": 32000, @@ -1297,6 +1321,18 @@ "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "codechat-bison-32k@002": { + "max_tokens": 8192, + "max_input_tokens": 32000, + "max_output_tokens": 8192, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-chat-models", + "mode": "chat", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, "gemini-pro": { "max_tokens": 8192, "max_input_tokens": 32760, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index f7a23e8e1..e665e79f3 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1261,6 +1261,18 @@ "mode": "completion", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "codechat-bison@latest": { + "max_tokens": 1024, + "max_input_tokens": 6144, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-chat-models", + "mode": "chat", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, "codechat-bison": { "max_tokens": 1024, "max_input_tokens": 6144, @@ -1285,6 +1297,18 @@ "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "codechat-bison@002": { + "max_tokens": 1024, + "max_input_tokens": 6144, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-chat-models", + "mode": "chat", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, "codechat-bison-32k": { "max_tokens": 8192, "max_input_tokens": 32000, @@ -1297,6 +1321,18 @@ "mode": "chat", "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "codechat-bison-32k@002": { + "max_tokens": 8192, + "max_input_tokens": 32000, + "max_output_tokens": 8192, + "input_cost_per_token": 0.000000125, + "output_cost_per_token": 0.000000125, + "input_cost_per_character": 0.00000025, + "output_cost_per_character": 0.0000005, + "litellm_provider": "vertex_ai-code-chat-models", + "mode": "chat", + "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" + }, "gemini-pro": { "max_tokens": 8192, "max_input_tokens": 32760, From b16b846711937a1145e8519693f23310cfb885ad Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 12:31:28 -0700 Subject: [PATCH 098/137] forward otel traceparent in request headers --- litellm/proxy/litellm_pre_call_utils.py | 18 ++++++++++++++++++ litellm/utils.py | 2 ++ 2 files changed, 20 insertions(+) diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 2e670de85..963cdf027 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -144,10 +144,13 @@ async def add_litellm_data_to_request( ) # do not store the original `sk-..` api key in the db data[_metadata_variable_name]["headers"] = _headers data[_metadata_variable_name]["endpoint"] = str(request.url) + + # OTEL Controls / Tracing # Add the OTEL Parent Trace before sending it LiteLLM data[_metadata_variable_name][ "litellm_parent_otel_span" ] = user_api_key_dict.parent_otel_span + _add_otel_traceparent_to_data(data, request=request) ### END-USER SPECIFIC PARAMS ### if user_api_key_dict.allowed_model_region is not None: @@ -169,3 +172,18 @@ async def add_litellm_data_to_request( } # add the team-specific configs to the completion call return data + + +def _add_otel_traceparent_to_data(data: dict, request: Request): + if data is None: + return + if request.headers: + if "traceparent" in request.headers: + # we want to forward this to the LLM Provider + # Relevant issue: https://github.com/BerriAI/litellm/issues/4419 + # pass this in extra_headers + if "extra_headers" not in data: + data["extra_headers"] = {} + _exra_headers = data["extra_headers"] + if "traceparent" not in _exra_headers: + _exra_headers["traceparent"] = request.headers["traceparent"] diff --git a/litellm/utils.py b/litellm/utils.py index a33a160e4..88b310d70 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -3670,6 +3670,8 @@ def get_supported_openai_params( "tool_choice", "response_format", "seed", + "extra_headers", + "extra_body", ] elif custom_llm_provider == "deepseek": return [ From 199bfe612fcfe1a6fae703aff4fde7ae9a9b3d24 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 12:57:09 -0700 Subject: [PATCH 099/137] fix add ollama codegemma --- litellm/model_prices_and_context_window_backup.json | 9 +++++++++ model_prices_and_context_window.json | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index d7a7a7dc8..acd03aeea 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -3369,6 +3369,15 @@ "supports_function_calling": true, "supports_parallel_function_calling": true }, + "ollama/codegemma": { + "max_tokens": 8192, + "max_input_tokens": 8192, + "max_output_tokens": 8192, + "input_cost_per_token": 0.0, + "output_cost_per_token": 0.0, + "litellm_provider": "ollama", + "mode": "completion" + }, "ollama/llama2": { "max_tokens": 4096, "max_input_tokens": 4096, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index d7a7a7dc8..acd03aeea 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -3369,6 +3369,15 @@ "supports_function_calling": true, "supports_parallel_function_calling": true }, + "ollama/codegemma": { + "max_tokens": 8192, + "max_input_tokens": 8192, + "max_output_tokens": 8192, + "input_cost_per_token": 0.0, + "output_cost_per_token": 0.0, + "litellm_provider": "ollama", + "mode": "completion" + }, "ollama/llama2": { "max_tokens": 4096, "max_input_tokens": 4096, From e0258708c724bdcd1dfd18fe343ca81f2095cde4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 14:21:57 -0700 Subject: [PATCH 100/137] fix cost tracking for whisper --- litellm/proxy/spend_tracking/spend_tracking_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index 54772ca9a..e4027b984 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -29,7 +29,7 @@ def get_logging_payload( completion_start_time = kwargs.get("completion_start_time", end_time) call_type = kwargs.get("call_type") cache_hit = kwargs.get("cache_hit", False) - usage = response_obj["usage"] + usage = response_obj.get("usage", None) or {} if type(usage) == litellm.Usage: usage = dict(usage) id = response_obj.get("id", kwargs.get("litellm_call_id")) From 5c673551a12d3282777c38e6995a5534c02c3352 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 15:21:49 -0700 Subject: [PATCH 101/137] test_spend_logs_payload_whisper --- litellm/tests/test_spend_logs.py | 87 ++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/litellm/tests/test_spend_logs.py b/litellm/tests/test_spend_logs.py index 3e8301e1e..4cd43bb04 100644 --- a/litellm/tests/test_spend_logs.py +++ b/litellm/tests/test_spend_logs.py @@ -205,3 +205,90 @@ def test_spend_logs_payload(): assert ( payload["request_tags"] == '["model-anthropic-claude-v2.1", "app-ishaan-prod"]' ) + + +def test_spend_logs_payload_whisper(): + """ + Ensure we can write /transcription request/responses to spend logs + """ + + kwargs: dict = { + "model": "whisper-1", + "messages": [{"role": "user", "content": "audio_file"}], + "optional_params": {}, + "litellm_params": { + "api_base": "", + "metadata": { + "user_api_key": "88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b", + "user_api_key_alias": None, + "user_api_end_user_max_budget": None, + "litellm_api_version": "1.40.19", + "global_max_parallel_requests": None, + "user_api_key_user_id": "default_user_id", + "user_api_key_org_id": None, + "user_api_key_team_id": None, + "user_api_key_team_alias": None, + "user_api_key_team_max_budget": None, + "user_api_key_team_spend": None, + "user_api_key_spend": 0.0, + "user_api_key_max_budget": None, + "user_api_key_metadata": {}, + "headers": { + "host": "localhost:4000", + "user-agent": "curl/7.88.1", + "accept": "*/*", + "content-length": "775501", + "content-type": "multipart/form-data; boundary=------------------------21d518e191326d20", + }, + "endpoint": "http://localhost:4000/v1/audio/transcriptions", + "litellm_parent_otel_span": None, + "model_group": "whisper-1", + "deployment": "whisper-1", + "model_info": { + "id": "d7761582311451c34d83d65bc8520ce5c1537ea9ef2bec13383cf77596d49eeb", + "db_model": False, + }, + "caching_groups": None, + }, + }, + "start_time": datetime.datetime(2024, 6, 26, 14, 20, 11, 313291), + "stream": False, + "user": "", + "call_type": "atranscription", + "litellm_call_id": "05921cf7-33f9-421c-aad9-33310c1e2702", + "completion_start_time": datetime.datetime(2024, 6, 26, 14, 20, 13, 653149), + "stream_options": None, + "input": "tmp-requestc8640aee-7d85-49c3-b3ef-bdc9255d8e37.wav", + "original_response": '{"text": "Four score and seven years ago, our fathers brought forth on this continent a new nation, conceived in liberty and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure."}', + "additional_args": { + "complete_input_dict": { + "model": "whisper-1", + "file": "<_io.BufferedReader name='tmp-requestc8640aee-7d85-49c3-b3ef-bdc9255d8e37.wav'>", + "language": None, + "prompt": None, + "response_format": None, + "temperature": None, + } + }, + "log_event_type": "post_api_call", + "end_time": datetime.datetime(2024, 6, 26, 14, 20, 13, 653149), + "cache_hit": None, + "response_cost": 0.00023398580000000003, + } + + response = litellm.utils.TranscriptionResponse( + text="Four score and seven years ago, our fathers brought forth on this continent a new nation, conceived in liberty and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure." + ) + + payload: SpendLogsPayload = get_logging_payload( + kwargs=kwargs, + response_obj=response, + start_time=datetime.datetime.now(), + end_time=datetime.datetime.now(), + end_user_id="test-user", + ) + + print("payload: ", payload) + + assert payload["call_type"] == "atranscription" + assert payload["spend"] == 0.00023398580000000003 From 90b0bd93a89c9b258dd2b979983da098e4d817c4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 15:59:38 -0700 Subject: [PATCH 102/137] Revert "Add return type annotations to util types" This reverts commit faef56fe696ff3eba0fcff80c3270534b2887648. --- litellm/types/utils.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 378abf4b7..f2b161128 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -171,7 +171,7 @@ class Function(OpenAIObject): arguments: Union[Dict, str], name: Optional[str] = None, **params, - ) -> None: + ): if isinstance(arguments, Dict): arguments = json.dumps(arguments) else: @@ -242,7 +242,7 @@ class ChatCompletionMessageToolCall(OpenAIObject): id: Optional[str] = None, type: Optional[str] = None, **params, - ) -> None: + ): super(ChatCompletionMessageToolCall, self).__init__(**params) if isinstance(function, Dict): self.function = Function(**function) @@ -285,7 +285,7 @@ class Message(OpenAIObject): function_call=None, tool_calls=None, **params, - ) -> None: + ): super(Message, self).__init__(**params) self.content = content self.role = role @@ -328,7 +328,7 @@ class Delta(OpenAIObject): function_call=None, tool_calls=None, **params, - ) -> None: + ): super(Delta, self).__init__(**params) self.content = content self.role = role @@ -375,7 +375,7 @@ class Choices(OpenAIObject): logprobs=None, enhancements=None, **params, - ) -> None: + ): super(Choices, self).__init__(**params) if finish_reason is not None: self.finish_reason = map_finish_reason( @@ -416,7 +416,7 @@ class Choices(OpenAIObject): class Usage(OpenAIObject): def __init__( self, prompt_tokens=None, completion_tokens=None, total_tokens=None, **params - ) -> None: + ): super(Usage, self).__init__(**params) if prompt_tokens: self.prompt_tokens = prompt_tokens @@ -451,7 +451,7 @@ class StreamingChoices(OpenAIObject): logprobs=None, enhancements=None, **params, - ) -> None: + ): super(StreamingChoices, self).__init__(**params) if finish_reason: self.finish_reason = finish_reason @@ -657,7 +657,7 @@ class EmbeddingResponse(OpenAIObject): response_ms=None, data=None, **params, - ) -> None: + ): object = "list" if response_ms: _response_ms = response_ms @@ -708,7 +708,7 @@ class Logprobs(OpenAIObject): class TextChoices(OpenAIObject): - def __init__(self, finish_reason=None, index=0, text=None, logprobs=None, **params) -> None: + def __init__(self, finish_reason=None, index=0, text=None, logprobs=None, **params): super(TextChoices, self).__init__(**params) if finish_reason: self.finish_reason = map_finish_reason(finish_reason) @@ -790,7 +790,7 @@ class TextCompletionResponse(OpenAIObject): response_ms=None, object=None, **params, - ) -> None: + ): if stream: object = "text_completion.chunk" choices = [TextChoices()] @@ -873,7 +873,7 @@ class ImageObject(OpenAIObject): url: Optional[str] = None revised_prompt: Optional[str] = None - def __init__(self, b64_json=None, url=None, revised_prompt=None) -> None: + def __init__(self, b64_json=None, url=None, revised_prompt=None): super().__init__(b64_json=b64_json, url=url, revised_prompt=revised_prompt) def __contains__(self, key): @@ -909,7 +909,7 @@ class ImageResponse(OpenAIObject): _hidden_params: dict = {} - def __init__(self, created=None, data=None, response_ms=None) -> None: + def __init__(self, created=None, data=None, response_ms=None): if response_ms: _response_ms = response_ms else: @@ -956,7 +956,7 @@ class TranscriptionResponse(OpenAIObject): _hidden_params: dict = {} - def __init__(self, text=None) -> None: + def __init__(self, text=None): super().__init__(text=text) def __contains__(self, key): From 57852bada9075b4d831d80776bd057fb2905cb30 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 16:01:50 -0700 Subject: [PATCH 103/137] fix handle_openai_chat_completion_chunk --- litellm/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/utils.py b/litellm/utils.py index a33a160e4..76c93d589 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -8301,7 +8301,7 @@ class CustomStreamWrapper: logprobs = None usage = None original_chunk = None # this is used for function/tool calling - if len(str_line.choices) > 0: + if str_line and str_line.choices and len(str_line.choices) > 0: if ( str_line.choices[0].delta is not None and str_line.choices[0].delta.content is not None From b7bca0af6c10ada03e768cff68ab04691e8366cd Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 16:16:58 -0700 Subject: [PATCH 104/137] fix - reuse client initialized on proxy config --- litellm/llms/azure.py | 3 ++- litellm/llms/openai.py | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/litellm/llms/azure.py b/litellm/llms/azure.py index b763a7c95..5d73b9435 100644 --- a/litellm/llms/azure.py +++ b/litellm/llms/azure.py @@ -812,7 +812,7 @@ class AzureChatCompletion(BaseLLM): azure_client_params: dict, api_key: str, input: list, - client=None, + client: Optional[AsyncAzureOpenAI] = None, logging_obj=None, timeout=None, ): @@ -911,6 +911,7 @@ class AzureChatCompletion(BaseLLM): model_response=model_response, azure_client_params=azure_client_params, timeout=timeout, + client=client, ) return response if client is None: diff --git a/litellm/llms/openai.py b/litellm/llms/openai.py index 55a0d97da..7d14fa450 100644 --- a/litellm/llms/openai.py +++ b/litellm/llms/openai.py @@ -996,11 +996,11 @@ class OpenAIChatCompletion(BaseLLM): self, input: list, data: dict, - model_response: ModelResponse, + model_response: litellm.utils.EmbeddingResponse, timeout: float, api_key: Optional[str] = None, api_base: Optional[str] = None, - client=None, + client: Optional[AsyncOpenAI] = None, max_retries=None, logging_obj=None, ): @@ -1039,9 +1039,9 @@ class OpenAIChatCompletion(BaseLLM): input: list, timeout: float, logging_obj, + model_response: litellm.utils.EmbeddingResponse, api_key: Optional[str] = None, api_base: Optional[str] = None, - model_response: Optional[litellm.utils.EmbeddingResponse] = None, optional_params=None, client=None, aembedding=None, @@ -1062,7 +1062,17 @@ class OpenAIChatCompletion(BaseLLM): ) if aembedding is True: - response = self.aembedding(data=data, input=input, logging_obj=logging_obj, model_response=model_response, api_base=api_base, api_key=api_key, timeout=timeout, client=client, max_retries=max_retries) # type: ignore + response = self.aembedding( + data=data, + input=input, + logging_obj=logging_obj, + model_response=model_response, + api_base=api_base, + api_key=api_key, + timeout=timeout, + client=client, + max_retries=max_retries, + ) return response openai_client = self._get_openai_client( From 151d19960e689588208feee240440a5c875dec46 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Jun 2024 16:19:05 -0700 Subject: [PATCH 105/137] fix(bedrock_httpx.py): Fix https://github.com/BerriAI/litellm/issues/4415 --- litellm/llms/bedrock.py | 5 ++ litellm/llms/bedrock_httpx.py | 30 +++++----- litellm/tests/test_bedrock_completion.py | 74 +++++++++++++++++++++--- 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/litellm/llms/bedrock.py b/litellm/llms/bedrock.py index d0d3bef6d..a8c47b3b9 100644 --- a/litellm/llms/bedrock.py +++ b/litellm/llms/bedrock.py @@ -1,3 +1,8 @@ +#################################### +######### DEPRECATED FILE ########## +#################################### +# logic moved to `bedrock_httpx.py` # + import copy import json import os diff --git a/litellm/llms/bedrock_httpx.py b/litellm/llms/bedrock_httpx.py index 84ab10907..14abec784 100644 --- a/litellm/llms/bedrock_httpx.py +++ b/litellm/llms/bedrock_httpx.py @@ -261,20 +261,24 @@ class BedrockLLM(BaseLLM): # handle anthropic prompts and amazon titan prompts prompt = "" chat_history: Optional[list] = None + ## CUSTOM PROMPT + if model in custom_prompt_dict: + # check if the model has a registered custom prompt + model_prompt_details = custom_prompt_dict[model] + prompt = custom_prompt( + role_dict=model_prompt_details["roles"], + initial_prompt_value=model_prompt_details.get( + "initial_prompt_value", "" + ), + final_prompt_value=model_prompt_details.get("final_prompt_value", ""), + messages=messages, + ) + return prompt, None + ## ELSE if provider == "anthropic" or provider == "amazon": - if model in custom_prompt_dict: - # check if the model has a registered custom prompt - model_prompt_details = custom_prompt_dict[model] - prompt = custom_prompt( - role_dict=model_prompt_details["roles"], - initial_prompt_value=model_prompt_details["initial_prompt_value"], - final_prompt_value=model_prompt_details["final_prompt_value"], - messages=messages, - ) - else: - prompt = prompt_factory( - model=model, messages=messages, custom_llm_provider="bedrock" - ) + prompt = prompt_factory( + model=model, messages=messages, custom_llm_provider="bedrock" + ) elif provider == "mistral": prompt = prompt_factory( model=model, messages=messages, custom_llm_provider="bedrock" diff --git a/litellm/tests/test_bedrock_completion.py b/litellm/tests/test_bedrock_completion.py index b953ca2a3..24eefceef 100644 --- a/litellm/tests/test_bedrock_completion.py +++ b/litellm/tests/test_bedrock_completion.py @@ -1,20 +1,31 @@ # @pytest.mark.skip(reason="AWS Suspended Account") -import sys, os +import os +import sys import traceback + from dotenv import load_dotenv load_dotenv() -import os, io +import io +import os sys.path.insert( 0, os.path.abspath("../..") ) # Adds the parent directory to the system path +from unittest.mock import AsyncMock, Mock, patch + import pytest + import litellm -from litellm import embedding, completion, completion_cost, Timeout, ModelResponse -from litellm import RateLimitError -from litellm.llms.custom_httpx.http_handler import HTTPHandler, AsyncHTTPHandler -from unittest.mock import patch, AsyncMock, Mock +from litellm import ( + ModelResponse, + RateLimitError, + Timeout, + completion, + completion_cost, + embedding, +) +from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler # litellm.num_retries = 3 litellm.cache = None @@ -481,7 +492,10 @@ def test_completion_claude_3_base64(): def test_provisioned_throughput(): try: litellm.set_verbose = True - import botocore, json, io + import io + import json + + import botocore import botocore.session from botocore.stub import Stubber @@ -537,7 +551,6 @@ def test_completion_bedrock_mistral_completion_auth(): # aws_access_key_id = os.environ["AWS_ACCESS_KEY_ID"] # aws_secret_access_key = os.environ["AWS_SECRET_ACCESS_KEY"] # aws_region_name = os.environ["AWS_REGION_NAME"] - # os.environ.pop("AWS_ACCESS_KEY_ID", None) # os.environ.pop("AWS_SECRET_ACCESS_KEY", None) # os.environ.pop("AWS_REGION_NAME", None) @@ -624,3 +637,48 @@ async def test_bedrock_extra_headers(): assert "test" in mock_client_post.call_args.kwargs["headers"] assert mock_client_post.call_args.kwargs["headers"]["test"] == "hello world" mock_client_post.assert_called_once() + + +@pytest.mark.asyncio +async def test_bedrock_custom_prompt_template(): + """ + Check if custom prompt template used for bedrock models + + Reference: https://github.com/BerriAI/litellm/issues/4415 + """ + client = AsyncHTTPHandler() + + with patch.object(client, "post", new=AsyncMock()) as mock_client_post: + import json + + try: + response = await litellm.acompletion( + model="bedrock/mistral.OpenOrca", + messages=[{"role": "user", "content": "What's AWS?"}], + client=client, + roles={ + "system": { + "pre_message": "<|im_start|>system\n", + "post_message": "<|im_end|>", + }, + "assistant": { + "pre_message": "<|im_start|>assistant\n", + "post_message": "<|im_end|>", + }, + "user": { + "pre_message": "<|im_start|>user\n", + "post_message": "<|im_end|>", + }, + }, + bos_token="", + eos_token="<|im_end|>", + ) + except Exception as e: + pass + + print(f"mock_client_post.call_args: {mock_client_post.call_args}") + assert "prompt" in mock_client_post.call_args.kwargs["data"] + + 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() From aa2e5d62889312c0febec264184286c2dacdcde8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 16:47:23 -0700 Subject: [PATCH 106/137] add volcengine as provider to litellm --- litellm/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/litellm/__init__.py b/litellm/__init__.py index cee80a32d..f4bc95066 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -413,6 +413,7 @@ openai_compatible_providers: List = [ "mistral", "groq", "nvidia_nim", + "volcengine", "codestral", "deepseek", "deepinfra", @@ -643,6 +644,7 @@ provider_list: List = [ "mistral", "groq", "nvidia_nim", + "volcengine", "codestral", "text-completion-codestral", "deepseek", From d213f81b4c5b7c1830127be347a4f7b320013aa3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 16:53:44 -0700 Subject: [PATCH 107/137] add initial support for volcengine --- litellm/__init__.py | 1 + litellm/llms/volcengine.py | 87 ++++++++++++++++++++++++++++++++++++++ litellm/main.py | 4 ++ litellm/utils.py | 23 ++++++++++ 4 files changed, 115 insertions(+) create mode 100644 litellm/llms/volcengine.py diff --git a/litellm/__init__.py b/litellm/__init__.py index f4bc95066..f1cc32cd1 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -820,6 +820,7 @@ from .llms.openai import ( ) from .llms.nvidia_nim import NvidiaNimConfig from .llms.fireworks_ai import FireworksAIConfig +from .llms.volcengine import VolcEngineConfig from .llms.text_completion_codestral import MistralTextCompletionConfig from .llms.azure import ( AzureOpenAIConfig, diff --git a/litellm/llms/volcengine.py b/litellm/llms/volcengine.py new file mode 100644 index 000000000..eb289d1c4 --- /dev/null +++ b/litellm/llms/volcengine.py @@ -0,0 +1,87 @@ +import types +from typing import Literal, Optional, Union + +import litellm + + +class VolcEngineConfig: + frequency_penalty: Optional[int] = None + function_call: Optional[Union[str, dict]] = None + functions: Optional[list] = None + logit_bias: Optional[dict] = None + max_tokens: Optional[int] = None + n: Optional[int] = None + presence_penalty: Optional[int] = None + stop: Optional[Union[str, list]] = None + temperature: Optional[int] = None + top_p: Optional[int] = None + response_format: Optional[dict] = None + + def __init__( + self, + frequency_penalty: Optional[int] = None, + function_call: Optional[Union[str, dict]] = None, + functions: Optional[list] = None, + logit_bias: Optional[dict] = None, + max_tokens: Optional[int] = None, + n: Optional[int] = None, + presence_penalty: Optional[int] = None, + stop: Optional[Union[str, list]] = None, + temperature: Optional[int] = None, + top_p: Optional[int] = None, + response_format: Optional[dict] = None, + ) -> None: + locals_ = locals().copy() + for key, value in locals_.items(): + if key != "self" and value is not None: + setattr(self.__class__, key, value) + + @classmethod + def get_config(cls): + return { + k: v + for k, v in cls.__dict__.items() + if not k.startswith("__") + and not isinstance( + v, + ( + types.FunctionType, + types.BuiltinFunctionType, + classmethod, + staticmethod, + ), + ) + and v is not None + } + + def get_supported_openai_params(self, model: str) -> list: + return [ + "frequency_penalty", + "logit_bias", + "logprobs", + "top_logprobs", + "max_tokens", + "n", + "presence_penalty", + "seed", + "stop", + "stream", + "stream_options", + "temperature", + "top_p", + "tools", + "tool_choice", + "function_call", + "functions", + "max_retries", + "extra_headers", + ] # works across all models + + def map_openai_params( + self, non_default_params: dict, optional_params: dict, model: str + ) -> dict: + supported_openai_params = self.get_supported_openai_params(model) + for param, value in non_default_params.items(): + if param in supported_openai_params: + optional_params[param] = value + return optional_params diff --git a/litellm/main.py b/litellm/main.py index b7aa47ab7..649581936 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -349,6 +349,7 @@ async def acompletion( or custom_llm_provider == "perplexity" or custom_llm_provider == "groq" or custom_llm_provider == "nvidia_nim" + or custom_llm_provider == "volcengine" or custom_llm_provider == "codestral" or custom_llm_provider == "text-completion-codestral" or custom_llm_provider == "deepseek" @@ -1192,6 +1193,7 @@ def completion( or custom_llm_provider == "perplexity" or custom_llm_provider == "groq" or custom_llm_provider == "nvidia_nim" + or custom_llm_provider == "volcengine" or custom_llm_provider == "codestral" or custom_llm_provider == "deepseek" or custom_llm_provider == "anyscale" @@ -2954,6 +2956,7 @@ async def aembedding(*args, **kwargs) -> EmbeddingResponse: or custom_llm_provider == "perplexity" or custom_llm_provider == "groq" or custom_llm_provider == "nvidia_nim" + or custom_llm_provider == "volcengine" or custom_llm_provider == "deepseek" or custom_llm_provider == "fireworks_ai" or custom_llm_provider == "ollama" @@ -3533,6 +3536,7 @@ async def atext_completion( or custom_llm_provider == "perplexity" or custom_llm_provider == "groq" or custom_llm_provider == "nvidia_nim" + or custom_llm_provider == "volcengine" or custom_llm_provider == "text-completion-codestral" or custom_llm_provider == "deepseek" or custom_llm_provider == "fireworks_ai" diff --git a/litellm/utils.py b/litellm/utils.py index 76c93d589..42e8cba30 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2413,6 +2413,7 @@ def get_optional_params( and custom_llm_provider != "together_ai" and custom_llm_provider != "groq" and custom_llm_provider != "nvidia_nim" + and custom_llm_provider != "volcengine" and custom_llm_provider != "deepseek" and custom_llm_provider != "codestral" and custom_llm_provider != "mistral" @@ -3089,6 +3090,17 @@ def get_optional_params( optional_params=optional_params, model=model, ) + elif custom_llm_provider == "volcengine": + supported_params = get_supported_openai_params( + model=model, custom_llm_provider=custom_llm_provider + ) + _check_valid_arg(supported_params=supported_params) + optional_params = litellm.VolcEngineConfig().map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + ) + elif custom_llm_provider == "groq": supported_params = get_supported_openai_params( model=model, custom_llm_provider=custom_llm_provider @@ -3659,6 +3671,8 @@ def get_supported_openai_params( return litellm.FireworksAIConfig().get_supported_openai_params() elif custom_llm_provider == "nvidia_nim": return litellm.NvidiaNimConfig().get_supported_openai_params() + elif custom_llm_provider == "volcengine": + return litellm.VolcEngineConfig().get_supported_openai_params(model=model) elif custom_llm_provider == "groq": return [ "temperature", @@ -4023,6 +4037,10 @@ def get_llm_provider( # nvidia_nim is openai compatible, we just need to set this to custom_openai and have the api_base be https://api.endpoints.anyscale.com/v1 api_base = "https://integrate.api.nvidia.com/v1" dynamic_api_key = get_secret("NVIDIA_NIM_API_KEY") + elif custom_llm_provider == "volcengine": + # volcengine is openai compatible, we just need to set this to custom_openai and have the api_base be https://api.endpoints.anyscale.com/v1 + api_base = "https://ark.cn-beijing.volces.com/api/v3" + dynamic_api_key = get_secret("VOLCENGINE_API_KEY") elif custom_llm_provider == "codestral": # codestral is openai compatible, we just need to set this to custom_openai and have the api_base be https://codestral.mistral.ai/v1 api_base = "https://codestral.mistral.ai/v1" @@ -4945,6 +4963,11 @@ def validate_environment(model: Optional[str] = None) -> dict: keys_in_environment = True else: missing_keys.append("NVIDIA_NIM_API_KEY") + elif custom_llm_provider == "volcengine": + if "VOLCENGINE_API_KEY" in os.environ: + keys_in_environment = True + else: + missing_keys.append("VOLCENGINE_API_KEY") elif ( custom_llm_provider == "codestral" or custom_llm_provider == "text-completion-codestral" From fcdda417bbb38ff40c2e08f5e534e2fe7eb8875e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 17:04:19 -0700 Subject: [PATCH 108/137] docs - volcengine --- docs/my-website/docs/providers/volcano.md | 98 +++++++++++++++++++++++ docs/my-website/sidebars.js | 1 + 2 files changed, 99 insertions(+) create mode 100644 docs/my-website/docs/providers/volcano.md diff --git a/docs/my-website/docs/providers/volcano.md b/docs/my-website/docs/providers/volcano.md new file mode 100644 index 000000000..1742a43d8 --- /dev/null +++ b/docs/my-website/docs/providers/volcano.md @@ -0,0 +1,98 @@ +# Volcano Engine (Volcengine) +https://www.volcengine.com/docs/82379/1263482 + +:::tip + +**We support ALL Volcengine NIM models, just set `model=volcengine/` as a prefix when sending litellm requests** + +::: + +## API Key +```python +# env variable +os.environ['VOLCENGINE_API_KEY'] +``` + +## Sample Usage +```python +from litellm import completion +import os + +os.environ['VOLCENGINE_API_KEY'] = "" +response = completion( + model="volcengine/", + messages=[ + { + "role": "user", + "content": "What's the weather like in Boston today in Fahrenheit?", + } + ], + temperature=0.2, # optional + top_p=0.9, # optional + frequency_penalty=0.1, # optional + presence_penalty=0.1, # optional + max_tokens=10, # optional + stop=["\n\n"], # optional +) +print(response) +``` + +## Sample Usage - Streaming +```python +from litellm import completion +import os + +os.environ['VOLCENGINE_API_KEY'] = "" +response = completion( + model="volcengine/", + messages=[ + { + "role": "user", + "content": "What's the weather like in Boston today in Fahrenheit?", + } + ], + stream=True, + temperature=0.2, # optional + top_p=0.9, # optional + frequency_penalty=0.1, # optional + presence_penalty=0.1, # optional + max_tokens=10, # optional + stop=["\n\n"], # optional +) + +for chunk in response: + print(chunk) +``` + + +## Supported Models - 💥 ALL Volcengine NIM Models Supported! +We support ALL `volcengine` models, just set `volcengine/` as a prefix when sending completion requests + +## Sample Usage - LiteLLM Proxy + +### Config.yaml setting + +```yaml +model_list: + - model_name: volcengine-model + litellm_params: + model: volcengine/ + api_key: os.environ/VOLCENGINE_API_KEY +``` + +### Send Request + +```shell +curl --location 'http://localhost:4000/chat/completions' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "volcengine-model", + "messages": [ + { + "role": "user", + "content": "here is my api key. openai_api_key=sk-1234" + } + ] +}' +``` \ No newline at end of file diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 9835a260b..31bc6abcb 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -147,6 +147,7 @@ const sidebars = { "providers/watsonx", "providers/predibase", "providers/nvidia_nim", + "providers/volcano", "providers/triton-inference-server", "providers/ollama", "providers/perplexity", From 0f489b68eb1da0d1db06dc25af302c1538cd3ddc Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 17:09:30 -0700 Subject: [PATCH 109/137] test volcengine --- litellm/tests/test_completion.py | 62 +++++++++++++------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py index a3b0e6ea2..2ceb11a79 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -1222,44 +1222,6 @@ def test_completion_fireworks_ai(): pytest.fail(f"Error occurred: {e}") -def test_fireworks_ai_tool_calling(): - litellm.set_verbose = True - model_name = "fireworks_ai/accounts/fireworks/models/firefunction-v2" - tools = [ - { - "type": "function", - "function": { - "name": "get_current_weather", - "description": "Get the current weather in a given location", - "parameters": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g. San Francisco, CA", - }, - "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, - }, - "required": ["location"], - }, - }, - } - ] - messages = [ - { - "role": "user", - "content": "What's the weather like in Boston today in Fahrenheit?", - } - ] - response = completion( - model=model_name, - messages=messages, - tools=tools, - tool_choice="required", - ) - print(response) - - @pytest.mark.skip(reason="this test is flaky") def test_completion_perplexity_api(): try: @@ -3508,6 +3470,30 @@ def test_completion_deep_infra_mistral(): # test_completion_deep_infra_mistral() +@pytest.mark.skip(reason="Local test - don't have a volcengine account as yet") +def test_completion_volcengine(): + litellm.set_verbose = True + model_name = "volcengine/" + try: + response = completion( + model=model_name, + messages=[ + { + "role": "user", + "content": "What's the weather like in Boston today in Fahrenheit?", + } + ], + api_key="", + ) + # Add any assertions here to check the response + print(response) + + except litellm.exceptions.Timeout as e: + pass + except Exception as e: + pytest.fail(f"Error occurred: {e}") + + def test_completion_nvidia_nim(): model_name = "nvidia_nim/databricks/dbrx-instruct" try: From d002a804b7edc21339c8ee0715eb21956affb954 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 17:28:29 -0700 Subject: [PATCH 110/137] add codestral pricing --- ...odel_prices_and_context_window_backup.json | 36 +++++++++++++++++++ model_prices_and_context_window.json | 36 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index acd03aeea..1954cb57b 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -863,6 +863,42 @@ "litellm_provider": "deepseek", "mode": "chat" }, + "codestral/codestral-latest": { + "max_tokens": 8191, + "max_input_tokens": 32000, + "max_output_tokens": 8191, + "input_cost_per_token": 0.000000, + "output_cost_per_token": 0.000000, + "litellm_provider": "codestral", + "mode": "chat" + }, + "codestral/codestral-2405": { + "max_tokens": 8191, + "max_input_tokens": 32000, + "max_output_tokens": 8191, + "input_cost_per_token": 0.000000, + "output_cost_per_token": 0.000000, + "litellm_provider": "codestral", + "mode": "chat" + }, + "text-completion-codestral/codestral-latest": { + "max_tokens": 8191, + "max_input_tokens": 32000, + "max_output_tokens": 8191, + "input_cost_per_token": 0.000000, + "output_cost_per_token": 0.000000, + "litellm_provider": "text-completion-codestral", + "mode": "completion" + }, + "text-completion-codestral/codestral-2405": { + "max_tokens": 8191, + "max_input_tokens": 32000, + "max_output_tokens": 8191, + "input_cost_per_token": 0.000000, + "output_cost_per_token": 0.000000, + "litellm_provider": "text-completion-codestral", + "mode": "completion" + }, "deepseek-coder": { "max_tokens": 4096, "max_input_tokens": 32000, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index acd03aeea..1954cb57b 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -863,6 +863,42 @@ "litellm_provider": "deepseek", "mode": "chat" }, + "codestral/codestral-latest": { + "max_tokens": 8191, + "max_input_tokens": 32000, + "max_output_tokens": 8191, + "input_cost_per_token": 0.000000, + "output_cost_per_token": 0.000000, + "litellm_provider": "codestral", + "mode": "chat" + }, + "codestral/codestral-2405": { + "max_tokens": 8191, + "max_input_tokens": 32000, + "max_output_tokens": 8191, + "input_cost_per_token": 0.000000, + "output_cost_per_token": 0.000000, + "litellm_provider": "codestral", + "mode": "chat" + }, + "text-completion-codestral/codestral-latest": { + "max_tokens": 8191, + "max_input_tokens": 32000, + "max_output_tokens": 8191, + "input_cost_per_token": 0.000000, + "output_cost_per_token": 0.000000, + "litellm_provider": "text-completion-codestral", + "mode": "completion" + }, + "text-completion-codestral/codestral-2405": { + "max_tokens": 8191, + "max_input_tokens": 32000, + "max_output_tokens": 8191, + "input_cost_per_token": 0.000000, + "output_cost_per_token": 0.000000, + "litellm_provider": "text-completion-codestral", + "mode": "completion" + }, "deepseek-coder": { "max_tokens": 4096, "max_input_tokens": 32000, From caf28c7441caee6244fa7c5d9577a6e94d053fda Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 17:31:26 -0700 Subject: [PATCH 111/137] add source for codestral pricing --- litellm/model_prices_and_context_window_backup.json | 12 ++++++++---- model_prices_and_context_window.json | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 1954cb57b..6b15084a9 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -870,7 +870,8 @@ "input_cost_per_token": 0.000000, "output_cost_per_token": 0.000000, "litellm_provider": "codestral", - "mode": "chat" + "mode": "chat", + "source": "https://docs.mistral.ai/capabilities/code_generation/" }, "codestral/codestral-2405": { "max_tokens": 8191, @@ -879,7 +880,8 @@ "input_cost_per_token": 0.000000, "output_cost_per_token": 0.000000, "litellm_provider": "codestral", - "mode": "chat" + "mode": "chat", + "source": "https://docs.mistral.ai/capabilities/code_generation/" }, "text-completion-codestral/codestral-latest": { "max_tokens": 8191, @@ -888,7 +890,8 @@ "input_cost_per_token": 0.000000, "output_cost_per_token": 0.000000, "litellm_provider": "text-completion-codestral", - "mode": "completion" + "mode": "completion", + "source": "https://docs.mistral.ai/capabilities/code_generation/" }, "text-completion-codestral/codestral-2405": { "max_tokens": 8191, @@ -897,7 +900,8 @@ "input_cost_per_token": 0.000000, "output_cost_per_token": 0.000000, "litellm_provider": "text-completion-codestral", - "mode": "completion" + "mode": "completion", + "source": "https://docs.mistral.ai/capabilities/code_generation/" }, "deepseek-coder": { "max_tokens": 4096, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 1954cb57b..6b15084a9 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -870,7 +870,8 @@ "input_cost_per_token": 0.000000, "output_cost_per_token": 0.000000, "litellm_provider": "codestral", - "mode": "chat" + "mode": "chat", + "source": "https://docs.mistral.ai/capabilities/code_generation/" }, "codestral/codestral-2405": { "max_tokens": 8191, @@ -879,7 +880,8 @@ "input_cost_per_token": 0.000000, "output_cost_per_token": 0.000000, "litellm_provider": "codestral", - "mode": "chat" + "mode": "chat", + "source": "https://docs.mistral.ai/capabilities/code_generation/" }, "text-completion-codestral/codestral-latest": { "max_tokens": 8191, @@ -888,7 +890,8 @@ "input_cost_per_token": 0.000000, "output_cost_per_token": 0.000000, "litellm_provider": "text-completion-codestral", - "mode": "completion" + "mode": "completion", + "source": "https://docs.mistral.ai/capabilities/code_generation/" }, "text-completion-codestral/codestral-2405": { "max_tokens": 8191, @@ -897,7 +900,8 @@ "input_cost_per_token": 0.000000, "output_cost_per_token": 0.000000, "litellm_provider": "text-completion-codestral", - "mode": "completion" + "mode": "completion", + "source": "https://docs.mistral.ai/capabilities/code_generation/" }, "deepseek-coder": { "max_tokens": 4096, From 7264113c2587cb0c5115f9722d9c51fd36f9654a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 18:08:54 -0700 Subject: [PATCH 112/137] vertex testing --- .../tests/test_amazing_vertex_completion.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/litellm/tests/test_amazing_vertex_completion.py b/litellm/tests/test_amazing_vertex_completion.py index c9e5501a8..901d68ef3 100644 --- a/litellm/tests/test_amazing_vertex_completion.py +++ b/litellm/tests/test_amazing_vertex_completion.py @@ -329,11 +329,14 @@ def test_vertex_ai(): "code-gecko@001", "code-gecko@002", "code-gecko@latest", + "codechat-bison@latest", "code-bison@001", "text-bison@001", "gemini-1.5-pro", "gemini-1.5-pro-preview-0215", - ]: + ] or ( + "gecko" in model or "32k" in model or "ultra" in model or "002" in model + ): # our account does not have access to this model continue print("making request", model) @@ -381,12 +384,15 @@ def test_vertex_ai_stream(): "code-gecko@001", "code-gecko@002", "code-gecko@latest", + "codechat-bison@latest", "code-bison@001", "text-bison@001", "gemini-1.5-pro", "gemini-1.5-pro-preview-0215", - ]: - # ouraccount does not have access to this model + ] or ( + "gecko" in model or "32k" in model or "ultra" in model or "002" in model + ): + # our account does not have access to this model continue print("making request", model) response = completion( @@ -433,11 +439,12 @@ async def test_async_vertexai_response(): "code-gecko@001", "code-gecko@002", "code-gecko@latest", + "codechat-bison@latest", "code-bison@001", "text-bison@001", "gemini-1.5-pro", "gemini-1.5-pro-preview-0215", - ]: + ] or ("gecko" in model or "32k" in model or "ultra" in model or "002" in model): # our account does not have access to this model continue try: @@ -479,11 +486,12 @@ async def test_async_vertexai_streaming_response(): "code-gecko@001", "code-gecko@002", "code-gecko@latest", + "codechat-bison@latest", "code-bison@001", "text-bison@001", "gemini-1.5-pro", "gemini-1.5-pro-preview-0215", - ]: + ] or ("gecko" in model or "32k" in model or "ultra" in model or "002" in model): # our account does not have access to this model continue try: From 4707c4fccd71bbbc71058f08b94f993cd3ea77e6 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 19:00:30 -0700 Subject: [PATCH 113/137] fix gemini test --- 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 856b05f61..bf650aa4a 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 e93181310efdcdb1410a5a433a12a15c2c7ca3ea Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 19:03:17 -0700 Subject: [PATCH 114/137] =?UTF-8?q?bump:=20version=201.40.27=20=E2=86=92?= =?UTF-8?q?=201.40.28?= 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 321f44b23..4c7192acf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.40.27" +version = "1.40.28" 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.27" +version = "1.40.28" version_files = [ "pyproject.toml:^version" ] From dad09fdc3d51dd2914de384a522016bd1f8cfea9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 26 Jun 2024 19:18:12 -0700 Subject: [PATCH 115/137] 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 2ceb11a79..5138e9b61 100644 --- a/litellm/tests/test_completion.py +++ b/litellm/tests/test_completion.py @@ -11,7 +11,7 @@ import os sys.path.insert( 0, os.path.abspath("../..") -) # Adds the parent directory to the system path +) # Adds-the parent directory to the system path import os from unittest.mock import MagicMock, patch From 31dc3cd84f69693a5a65bc1ae08a07fce0d3792c Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Jun 2024 22:45:29 -0700 Subject: [PATCH 116/137] docs(openai_compatible.md): doc on disabling system messages --- .../docs/providers/openai_compatible.md | 15 +++++++++++++++ docs/my-website/docs/proxy/configs.md | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/my-website/docs/providers/openai_compatible.md b/docs/my-website/docs/providers/openai_compatible.md index ff0e85709..f02149024 100644 --- a/docs/my-website/docs/providers/openai_compatible.md +++ b/docs/my-website/docs/providers/openai_compatible.md @@ -115,3 +115,18 @@ Here's how to call an OpenAI-Compatible Endpoint with the LiteLLM Proxy Server + + +### Advanced - Disable System Messages + +Some VLLM models (e.g. gemma) don't support system messages. To map those requests to 'user' messages, use the `supports_system_message` flag. + +```yaml +model_list: +- model_name: my-custom-model + litellm_params: + model: openai/google/gemma + api_base: http://my-custom-base + api_key: "" + supports_system_message: False # 👈 KEY CHANGE +``` \ No newline at end of file diff --git a/docs/my-website/docs/proxy/configs.md b/docs/my-website/docs/proxy/configs.md index 9381a14a4..80235586c 100644 --- a/docs/my-website/docs/proxy/configs.md +++ b/docs/my-website/docs/proxy/configs.md @@ -427,7 +427,7 @@ model_list: ```shell $ litellm --config /path/to/config.yaml -``` +``` ## Setting Embedding Models From 23a1f21f869c2161b6407654eafe75dc0f896f81 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 26 Jun 2024 22:52:50 -0700 Subject: [PATCH 117/137] fix(utils.py): add new special token for cleanup --- litellm/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/litellm/utils.py b/litellm/utils.py index 515918822..dbc988bb9 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -7805,6 +7805,7 @@ class CustomStreamWrapper: "", "", "<|im_end|>", + "<|im_start|>", ] self.holding_chunk = "" self.complete_response = "" From e1d844a9a0548c5a6d15aa33ecd085b593d7bcca Mon Sep 17 00:00:00 2001 From: Daniel Liden Date: Thu, 27 Jun 2024 09:11:09 -0400 Subject: [PATCH 118/137] Update databricks.md updates some references to predibase to refer to Databricks --- docs/my-website/docs/providers/databricks.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/my-website/docs/providers/databricks.md b/docs/my-website/docs/providers/databricks.md index 24c7c40cf..fcc1d4813 100644 --- a/docs/my-website/docs/providers/databricks.md +++ b/docs/my-website/docs/providers/databricks.md @@ -27,7 +27,7 @@ import os os.environ["DATABRICKS_API_KEY"] = "databricks key" os.environ["DATABRICKS_API_BASE"] = "databricks base url" # e.g.: https://adb-3064715882934586.6.azuredatabricks.net/serving-endpoints -# predibase llama-3 call +# Databricks dbrx-instruct call response = completion( model="databricks/databricks-dbrx-instruct", messages = [{ "content": "Hello, how are you?","role": "user"}] @@ -143,8 +143,8 @@ response = completion( model_list: - model_name: llama-3 litellm_params: - model: predibase/llama-3-8b-instruct - api_key: os.environ/PREDIBASE_API_KEY + model: databricks/databricks-dbrx-instruct + api_key: os.environ/DATABRICKS_API_KEY max_tokens: 20 temperature: 0.5 ``` @@ -162,7 +162,7 @@ import os os.environ["DATABRICKS_API_KEY"] = "databricks key" os.environ["DATABRICKS_API_BASE"] = "databricks url" -# predibase llama3 call +# Databricks bge-large-en call response = litellm.embedding( model="databricks/databricks-bge-large-en", input=["good morning from litellm"], From 86010bc440f4022ce8565d3c0f05284ae53ec53f Mon Sep 17 00:00:00 2001 From: Daniel Liden Date: Thu, 27 Jun 2024 09:36:45 -0400 Subject: [PATCH 119/137] Update databricks.md fixes a couple of examples to use correct endpoints/point to correct models --- docs/my-website/docs/providers/databricks.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/my-website/docs/providers/databricks.md b/docs/my-website/docs/providers/databricks.md index fcc1d4813..c81b0174a 100644 --- a/docs/my-website/docs/providers/databricks.md +++ b/docs/my-website/docs/providers/databricks.md @@ -143,13 +143,13 @@ response = completion( model_list: - model_name: llama-3 litellm_params: - model: databricks/databricks-dbrx-instruct + model: databricks/databricks-meta-llama-3-70b-instruct api_key: os.environ/DATABRICKS_API_KEY max_tokens: 20 temperature: 0.5 ``` -## Passings Database specific params - 'instruction' +## Passings Databricks specific params - 'instruction' For embedding models, databricks lets you pass in an additional param 'instruction'. [Full Spec](https://github.com/BerriAI/litellm/blob/43353c28b341df0d9992b45c6ce464222ebd7984/litellm/llms/databricks.py#L164) @@ -177,14 +177,13 @@ response = litellm.embedding( - model_name: bge-large litellm_params: model: databricks/databricks-bge-large-en - api_key: os.environ/DATABRICKS_API_KEY - api_base: os.environ/DATABRICKS_API_BASE + api_key: ${DATABRICKS_API_KEY} + api_base: ${DATABRICKS_API_BASE} instruction: "Represent this sentence for searching relevant passages:" ``` ## Supported Databricks Chat Completion Models -Here's an example of using a Databricks models with LiteLLM | Model Name | Command | |----------------------------|------------------------------------------------------------------| @@ -196,8 +195,8 @@ Here's an example of using a Databricks models with LiteLLM | databricks-mpt-7b-instruct | `completion(model='databricks/databricks-mpt-7b-instruct', messages=messages)` | ## Supported Databricks Embedding Models -Here's an example of using a databricks models with LiteLLM | Model Name | Command | |----------------------------|------------------------------------------------------------------| -| databricks-bge-large-en | `completion(model='databricks/databricks-bge-large-en', messages=messages)` | +| databricks-bge-large-en | `embedding(model='databricks/databricks-bge-large-en', messages=messages)` | +| databricks-gte-large-en | `embedding(model='databricks/databricks-gte-large-en', messages=messages)` | From 010b55e6db7edcc5cba56813eed8ff696d16505f Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Jun 2024 08:56:52 -0700 Subject: [PATCH 120/137] fix(utils.py): handle arguments being None Fixes https://github.com/BerriAI/litellm/issues/4440 --- litellm/types/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/litellm/types/utils.py b/litellm/types/utils.py index f2b161128..a63e34738 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -168,11 +168,13 @@ class Function(OpenAIObject): def __init__( self, - arguments: Union[Dict, str], + arguments: Optional[Union[Dict, str]], name: Optional[str] = None, **params, ): - if isinstance(arguments, Dict): + if arguments is None: + arguments = "" + elif isinstance(arguments, Dict): arguments = json.dumps(arguments) else: arguments = arguments From 0c5014c323c05c91ee087e2709c4fe8100f2045c Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Jun 2024 08:58:25 -0700 Subject: [PATCH 121/137] =?UTF-8?q?bump:=20version=201.40.28=20=E2=86=92?= =?UTF-8?q?=201.40.29?= 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 4c7192acf..6a620d650 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.40.28" +version = "1.40.29" 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.28" +version = "1.40.29" version_files = [ "pyproject.toml:^version" ] From 101bd38996e561820a7853b9142561d69af17a89 Mon Sep 17 00:00:00 2001 From: Daniel Liden Date: Thu, 27 Jun 2024 12:51:00 -0400 Subject: [PATCH 122/137] undoes changes to proxy yaml api key/base --- docs/my-website/docs/providers/databricks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/my-website/docs/providers/databricks.md b/docs/my-website/docs/providers/databricks.md index c81b0174a..633350d22 100644 --- a/docs/my-website/docs/providers/databricks.md +++ b/docs/my-website/docs/providers/databricks.md @@ -177,8 +177,8 @@ response = litellm.embedding( - model_name: bge-large litellm_params: model: databricks/databricks-bge-large-en - api_key: ${DATABRICKS_API_KEY} - api_base: ${DATABRICKS_API_BASE} + api_key: os.environ/DATABRICKS_API_KEY + api_base: os.environ/DATABRICKS_API_BASE instruction: "Represent this sentence for searching relevant passages:" ``` From 9d50fc1f2a34ddaadc95a8f8b52783c9a104f436 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 10:40:03 -0700 Subject: [PATCH 123/137] docs - fix model name on claude-3-5-sonnet-20240620 anthropic --- docs/my-website/docs/providers/anthropic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/my-website/docs/providers/anthropic.md b/docs/my-website/docs/providers/anthropic.md index 3b9e67969..e7d3352f9 100644 --- a/docs/my-website/docs/providers/anthropic.md +++ b/docs/my-website/docs/providers/anthropic.md @@ -172,7 +172,7 @@ print(response) |------------------|--------------------------------------------| | 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 | `completion('claude-3-5-sonnet-20240620', messages)` | `os.environ['ANTHROPIC_API_KEY']` | +| claude-3-5-sonnet-20240620 | `completion('claude-3-5-sonnet-20240620', messages)` | `os.environ['ANTHROPIC_API_KEY']` | | claude-3-sonnet | `completion('claude-3-sonnet-20240229', messages)` | `os.environ['ANTHROPIC_API_KEY']` | | claude-2.1 | `completion('claude-2.1', messages)` | `os.environ['ANTHROPIC_API_KEY']` | | claude-2 | `completion('claude-2', messages)` | `os.environ['ANTHROPIC_API_KEY']` | From 80d8bf5d8f954b357d695fe77975764ffcf735e3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 12:02:19 -0700 Subject: [PATCH 124/137] fix raise better error message on reaching failed vertex import --- litellm/llms/vertex_ai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/vertex_ai.py b/litellm/llms/vertex_ai.py index 1dbd93048..4a4abaef4 100644 --- a/litellm/llms/vertex_ai.py +++ b/litellm/llms/vertex_ai.py @@ -437,7 +437,7 @@ def completion( except: raise VertexAIError( status_code=400, - message="vertexai import failed please run `pip install google-cloud-aiplatform`", + message="vertexai import failed please run `pip install google-cloud-aiplatform`. This is required for the 'vertex_ai/' route on LiteLLM", ) if not ( From 80960facfaf4a7e9a523c75d927ff1f3f08365b9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 13:19:54 -0700 Subject: [PATCH 125/137] fix secret redaction logic --- litellm/proxy/proxy_server.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index c3b855c5f..b9972a723 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -2954,6 +2954,11 @@ async def chat_completion( if isinstance(data["model"], str) and data["model"] in litellm.model_alias_map: data["model"] = litellm.model_alias_map[data["model"]] + ### CALL HOOKS ### - modify/reject incoming data before calling the model + data = await proxy_logging_obj.pre_call_hook( # type: ignore + user_api_key_dict=user_api_key_dict, data=data, call_type="completion" + ) + ## LOGGING OBJECT ## - initialize logging object for logging success/failure events for call data["litellm_call_id"] = str(uuid.uuid4()) logging_obj, data = litellm.utils.function_setup( @@ -2965,11 +2970,6 @@ async def chat_completion( data["litellm_logging_obj"] = logging_obj - ### CALL HOOKS ### - modify/reject incoming data before calling the model - data = await proxy_logging_obj.pre_call_hook( # type: ignore - user_api_key_dict=user_api_key_dict, data=data, call_type="completion" - ) - tasks = [] tasks.append( proxy_logging_obj.during_call_hook( From c9cee3d91091e56fde371e68ca2b6ca54893b323 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 13:48:25 -0700 Subject: [PATCH 126/137] test - test_chat_completion_request_with_redaction --- litellm/tests/test_secret_detect_hook.py | 84 ++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/litellm/tests/test_secret_detect_hook.py b/litellm/tests/test_secret_detect_hook.py index a1bf10eba..cb1e01810 100644 --- a/litellm/tests/test_secret_detect_hook.py +++ b/litellm/tests/test_secret_detect_hook.py @@ -21,15 +21,20 @@ sys.path.insert( 0, os.path.abspath("../..") ) # Adds the parent directory to the system path import pytest +from fastapi import Request, Response +from starlette.datastructures import URL import litellm from litellm import Router, mock_completion from litellm.caching import DualCache +from litellm.integrations.custom_logger import CustomLogger from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.enterprise.enterprise_hooks.secret_detection import ( _ENTERPRISE_SecretDetection, ) +from litellm.proxy.proxy_server import chat_completion from litellm.proxy.utils import ProxyLogging, hash_token +from litellm.router import Router ### UNIT TESTS FOR OpenAI Moderation ### @@ -214,3 +219,82 @@ async def test_basic_secret_detection_embeddings_list(): ], "model": "gpt-3.5-turbo", } + + +class testLogger(CustomLogger): + + def __init__(self): + self.logged_message = None + + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + print(f"On Async Success") + + self.logged_message = kwargs.get("messages") + + +router = Router( + model_list=[ + { + "model_name": "fake-model", + "litellm_params": { + "model": "openai/fake", + "api_base": "https://exampleopenaiendpoint-production.up.railway.app/", + "api_key": "sk-12345", + }, + } + ] +) + + +@pytest.mark.asyncio +async def test_chat_completion_request_with_redaction(): + """ + IMPORTANT Enterprise Test - Do not delete it: + Makes a /chat/completions request on LiteLLM Proxy + + Ensures that the secret is redacted EVEN on the callback + """ + from litellm.proxy import proxy_server + + setattr(proxy_server, "llm_router", router) + _test_logger = testLogger() + litellm.callbacks = [_ENTERPRISE_SecretDetection(), _test_logger] + litellm.set_verbose = True + + # Prepare the query string + query_params = "param1=value1¶m2=value2" + + # Create the Request object with query parameters + request = Request( + scope={ + "type": "http", + "method": "POST", + "headers": [(b"content-type", b"application/json")], + "query_string": query_params.encode(), + } + ) + + request._url = URL(url="/chat/completions") + + async def return_body(): + return b'{"model": "fake-model", "messages": [{"role": "user", "content": "Hello here is my OPENAI_API_KEY = sk-12345"}]}' + + request.body = return_body + + response = await chat_completion( + request=request, + user_api_key_dict=UserAPIKeyAuth( + api_key="sk-12345", + token="hashed_sk-12345", + ), + fastapi_response=Response(), + ) + + await asyncio.sleep(3) + + print("Info in callback after running request=", _test_logger.logged_message) + + assert _test_logger.logged_message == [ + {"role": "user", "content": "Hello here is my OPENAI_API_KEY = [REDACTED]"} + ] + pass From 552bac586fc282162f6559b0223c6deed494d7a9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 15:07:38 -0700 Subject: [PATCH 127/137] feat - improve secret detection --- .../enterprise_hooks/secret_detection.py | 411 +++++++++++++++++- 1 file changed, 409 insertions(+), 2 deletions(-) diff --git a/enterprise/enterprise_hooks/secret_detection.py b/enterprise/enterprise_hooks/secret_detection.py index ded9f27c1..23dd2a7e0 100644 --- a/enterprise/enterprise_hooks/secret_detection.py +++ b/enterprise/enterprise_hooks/secret_detection.py @@ -33,27 +33,433 @@ from litellm._logging import verbose_proxy_logger litellm.set_verbose = True +_custom_plugins_path = "file://" + os.path.join( + os.path.dirname(os.path.abspath(__file__)), "secrets_plugins" +) +print("custom plugins path", _custom_plugins_path) +_default_detect_secrets_config = { + "plugins_used": [ + {"name": "SoftlayerDetector"}, + {"name": "StripeDetector"}, + {"name": "NpmDetector"}, + {"name": "IbmCosHmacDetector"}, + {"name": "DiscordBotTokenDetector"}, + {"name": "BasicAuthDetector"}, + {"name": "AzureStorageKeyDetector"}, + {"name": "ArtifactoryDetector"}, + {"name": "AWSKeyDetector"}, + {"name": "CloudantDetector"}, + {"name": "IbmCloudIamDetector"}, + {"name": "JwtTokenDetector"}, + {"name": "MailchimpDetector"}, + {"name": "SquareOAuthDetector"}, + {"name": "PrivateKeyDetector"}, + {"name": "TwilioKeyDetector"}, + { + "name": "AdafruitKeyDetector", + "path": _custom_plugins_path + "/adafruit.py", + }, + { + "name": "AdobeSecretDetector", + "path": _custom_plugins_path + "/adobe.py", + }, + { + "name": "AgeSecretKeyDetector", + "path": _custom_plugins_path + "/age_secret_key.py", + }, + { + "name": "AirtableApiKeyDetector", + "path": _custom_plugins_path + "/airtable_api_key.py", + }, + { + "name": "AlgoliaApiKeyDetector", + "path": _custom_plugins_path + "/algolia_api_key.py", + }, + { + "name": "AlibabaSecretDetector", + "path": _custom_plugins_path + "/alibaba.py", + }, + { + "name": "AsanaSecretDetector", + "path": _custom_plugins_path + "/asana.py", + }, + { + "name": "AtlassianApiTokenDetector", + "path": _custom_plugins_path + "/atlassian_api_token.py", + }, + { + "name": "AuthressAccessKeyDetector", + "path": _custom_plugins_path + "/authress_access_key.py", + }, + { + "name": "BittrexDetector", + "path": _custom_plugins_path + "/beamer_api_token.py", + }, + { + "name": "BitbucketDetector", + "path": _custom_plugins_path + "/bitbucket.py", + }, + { + "name": "BeamerApiTokenDetector", + "path": _custom_plugins_path + "/bittrex.py", + }, + { + "name": "ClojarsApiTokenDetector", + "path": _custom_plugins_path + "/clojars_api_token.py", + }, + { + "name": "CodecovAccessTokenDetector", + "path": _custom_plugins_path + "/codecov_access_token.py", + }, + { + "name": "CoinbaseAccessTokenDetector", + "path": _custom_plugins_path + "/coinbase_access_token.py", + }, + { + "name": "ConfluentDetector", + "path": _custom_plugins_path + "/confluent.py", + }, + { + "name": "ContentfulApiTokenDetector", + "path": _custom_plugins_path + "/contentful_api_token.py", + }, + { + "name": "DatabricksApiTokenDetector", + "path": _custom_plugins_path + "/databricks_api_token.py", + }, + { + "name": "DatadogAccessTokenDetector", + "path": _custom_plugins_path + "/datadog_access_token.py", + }, + { + "name": "DefinedNetworkingApiTokenDetector", + "path": _custom_plugins_path + "/defined_networking_api_token.py", + }, + { + "name": "DigitaloceanDetector", + "path": _custom_plugins_path + "/digitalocean.py", + }, + { + "name": "DopplerApiTokenDetector", + "path": _custom_plugins_path + "/doppler_api_token.py", + }, + { + "name": "DroneciAccessTokenDetector", + "path": _custom_plugins_path + "/droneci_access_token.py", + }, + { + "name": "DuffelApiTokenDetector", + "path": _custom_plugins_path + "/duffel_api_token.py", + }, + { + "name": "DynatraceApiTokenDetector", + "path": _custom_plugins_path + "/dynatrace_api_token.py", + }, + { + "name": "DiscordDetector", + "path": _custom_plugins_path + "/discord.py", + }, + { + "name": "DropboxDetector", + "path": _custom_plugins_path + "/dropbox.py", + }, + { + "name": "EasyPostDetector", + "path": _custom_plugins_path + "/easypost.py", + }, + { + "name": "EtsyAccessTokenDetector", + "path": _custom_plugins_path + "/etsy_access_token.py", + }, + { + "name": "FacebookAccessTokenDetector", + "path": _custom_plugins_path + "/facebook_access_token.py", + }, + { + "name": "FastlyApiKeyDetector", + "path": _custom_plugins_path + "/fastly_api_token.py", + }, + { + "name": "FinicityDetector", + "path": _custom_plugins_path + "/finicity.py", + }, + { + "name": "FinnhubAccessTokenDetector", + "path": _custom_plugins_path + "/finnhub_access_token.py", + }, + { + "name": "FlickrAccessTokenDetector", + "path": _custom_plugins_path + "/flickr_access_token.py", + }, + { + "name": "FlutterwaveDetector", + "path": _custom_plugins_path + "/flutterwave.py", + }, + { + "name": "FrameIoApiTokenDetector", + "path": _custom_plugins_path + "/frameio_api_token.py", + }, + { + "name": "FreshbooksAccessTokenDetector", + "path": _custom_plugins_path + "/freshbooks_access_token.py", + }, + { + "name": "GCPApiKeyDetector", + "path": _custom_plugins_path + "/gcp_api_key.py", + }, + { + "name": "GitHubTokenCustomDetector", + "path": _custom_plugins_path + "/github_token.py", + }, + { + "name": "GitLabDetector", + "path": _custom_plugins_path + "/gitlab.py", + }, + { + "name": "GitterAccessTokenDetector", + "path": _custom_plugins_path + "/gitter_access_token.py", + }, + { + "name": "GoCardlessApiTokenDetector", + "path": _custom_plugins_path + "/gocardless_api_token.py", + }, + { + "name": "GrafanaDetector", + "path": _custom_plugins_path + "/grafana.py", + }, + { + "name": "HashiCorpTFApiTokenDetector", + "path": _custom_plugins_path + "/hashicorp_tf_api_token.py", + }, + { + "name": "HerokuApiKeyDetector", + "path": _custom_plugins_path + "/heroku_api_key.py", + }, + { + "name": "HubSpotApiTokenDetector", + "path": _custom_plugins_path + "/hubspot_api_key.py", + }, + { + "name": "HuggingFaceDetector", + "path": _custom_plugins_path + "/huggingface.py", + }, + { + "name": "IntercomApiTokenDetector", + "path": _custom_plugins_path + "/intercom_api_key.py", + }, + { + "name": "JFrogDetector", + "path": _custom_plugins_path + "/jfrog.py", + }, + { + "name": "JWTBase64Detector", + "path": _custom_plugins_path + "/jwt.py", + }, + { + "name": "KrakenAccessTokenDetector", + "path": _custom_plugins_path + "/kraken_access_token.py", + }, + { + "name": "KucoinDetector", + "path": _custom_plugins_path + "/kucoin.py", + }, + { + "name": "LaunchdarklyAccessTokenDetector", + "path": _custom_plugins_path + "/launchdarkly_access_token.py", + }, + { + "name": "LinearDetector", + "path": _custom_plugins_path + "/linear.py", + }, + { + "name": "LinkedInDetector", + "path": _custom_plugins_path + "/linkedin.py", + }, + { + "name": "LobDetector", + "path": _custom_plugins_path + "/lob.py", + }, + { + "name": "MailgunDetector", + "path": _custom_plugins_path + "/mailgun.py", + }, + { + "name": "MapBoxApiTokenDetector", + "path": _custom_plugins_path + "/mapbox_api_token.py", + }, + { + "name": "MattermostAccessTokenDetector", + "path": _custom_plugins_path + "/mattermost_access_token.py", + }, + { + "name": "MessageBirdDetector", + "path": _custom_plugins_path + "/messagebird.py", + }, + { + "name": "MicrosoftTeamsWebhookDetector", + "path": _custom_plugins_path + "/microsoft_teams_webhook.py", + }, + { + "name": "NetlifyAccessTokenDetector", + "path": _custom_plugins_path + "/netlify_access_token.py", + }, + { + "name": "NewRelicDetector", + "path": _custom_plugins_path + "/new_relic.py", + }, + { + "name": "NYTimesAccessTokenDetector", + "path": _custom_plugins_path + "/nytimes_access_token.py", + }, + { + "name": "OktaAccessTokenDetector", + "path": _custom_plugins_path + "/okta_access_token.py", + }, + { + "name": "OpenAIApiKeyDetector", + "path": _custom_plugins_path + "/openai_api_key.py", + }, + { + "name": "PlanetScaleDetector", + "path": _custom_plugins_path + "/planetscale.py", + }, + { + "name": "PostmanApiTokenDetector", + "path": _custom_plugins_path + "/postman_api_token.py", + }, + { + "name": "PrefectApiTokenDetector", + "path": _custom_plugins_path + "/prefect_api_token.py", + }, + { + "name": "PulumiApiTokenDetector", + "path": _custom_plugins_path + "/pulumi_api_token.py", + }, + { + "name": "PyPiUploadTokenDetector", + "path": _custom_plugins_path + "/pypi_upload_token.py", + }, + { + "name": "RapidApiAccessTokenDetector", + "path": _custom_plugins_path + "/rapidapi_access_token.py", + }, + { + "name": "ReadmeApiTokenDetector", + "path": _custom_plugins_path + "/readme_api_token.py", + }, + { + "name": "RubygemsApiTokenDetector", + "path": _custom_plugins_path + "/rubygems_api_token.py", + }, + { + "name": "ScalingoApiTokenDetector", + "path": _custom_plugins_path + "/scalingo_api_token.py", + }, + { + "name": "SendbirdDetector", + "path": _custom_plugins_path + "/sendbird.py", + }, + { + "name": "SendGridApiTokenDetector", + "path": _custom_plugins_path + "/sendgrid_api_token.py", + }, + { + "name": "SendinBlueApiTokenDetector", + "path": _custom_plugins_path + "/sendinblue_api_token.py", + }, + { + "name": "SentryAccessTokenDetector", + "path": _custom_plugins_path + "/sentry_access_token.py", + }, + { + "name": "ShippoApiTokenDetector", + "path": _custom_plugins_path + "/shippo_api_token.py", + }, + { + "name": "ShopifyDetector", + "path": _custom_plugins_path + "/shopify.py", + }, + { + "name": "SidekiqDetector", + "path": _custom_plugins_path + "/sidekiq.py", + }, + { + "name": "SlackDetector", + "path": _custom_plugins_path + "/slack.py", + }, + { + "name": "SnykApiTokenDetector", + "path": _custom_plugins_path + "/snyk_api_token.py", + }, + { + "name": "SquarespaceAccessTokenDetector", + "path": _custom_plugins_path + "/squarespace_access_token.py", + }, + { + "name": "SumoLogicDetector", + "path": _custom_plugins_path + "/sumologic.py", + }, + { + "name": "TelegramBotApiTokenDetector", + "path": _custom_plugins_path + "/telegram_bot_api_token.py", + }, + { + "name": "TravisCiAccessTokenDetector", + "path": _custom_plugins_path + "/travisci_access_token.py", + }, + { + "name": "TwitchApiTokenDetector", + "path": _custom_plugins_path + "/twitch_api_token.py", + }, + { + "name": "TwitterDetector", + "path": _custom_plugins_path + "/twitter.py", + }, + { + "name": "TypeformApiTokenDetector", + "path": _custom_plugins_path + "/typeform_api_token.py", + }, + { + "name": "VaultDetector", + "path": _custom_plugins_path + "/vault.py", + }, + { + "name": "YandexDetector", + "path": _custom_plugins_path + "/yandex.py", + }, + { + "name": "ZendeskSecretKeyDetector", + "path": _custom_plugins_path + "/zendesk_secret_key.py", + }, + {"name": "Base64HighEntropyString", "limit": 3.0}, + {"name": "HexHighEntropyString", "limit": 3.0}, + ] +} + + class _ENTERPRISE_SecretDetection(CustomLogger): def __init__(self): pass def scan_message_for_secrets(self, message_content: str): from detect_secrets import SecretsCollection - from detect_secrets.settings import default_settings + from detect_secrets.settings import transient_settings temp_file = tempfile.NamedTemporaryFile(delete=False) temp_file.write(message_content.encode("utf-8")) temp_file.close() secrets = SecretsCollection() - with default_settings(): + with transient_settings(_default_detect_secrets_config): secrets.scan_file(temp_file.name) os.remove(temp_file.name) detected_secrets = [] for file in secrets.files: + for found_secret in secrets[file]: + if found_secret.secret_value is None: continue detected_secrets.append( @@ -76,6 +482,7 @@ class _ENTERPRISE_SecretDetection(CustomLogger): if "messages" in data and isinstance(data["messages"], list): for message in data["messages"]: if "content" in message and isinstance(message["content"], str): + detected_secrets = self.scan_message_for_secrets(message["content"]) for secret in detected_secrets: From 84ee37086ce746a9bb2c5d4818f765180de0c727 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 15:12:13 -0700 Subject: [PATCH 128/137] add stricter secret detection --- .../secrets_plugins/__init__.py | 0 .../secrets_plugins/adafruit.py | 23 +++++++++++ .../enterprise_hooks/secrets_plugins/adobe.py | 26 +++++++++++++ .../secrets_plugins/age_secret_key.py | 21 ++++++++++ .../secrets_plugins/airtable_api_key.py | 23 +++++++++++ .../secrets_plugins/algolia_api_key.py | 21 ++++++++++ .../secrets_plugins/alibaba.py | 26 +++++++++++++ .../enterprise_hooks/secrets_plugins/asana.py | 28 ++++++++++++++ .../secrets_plugins/atlassian_api_token.py | 24 ++++++++++++ .../secrets_plugins/authress_access_key.py | 24 ++++++++++++ .../secrets_plugins/beamer_api_token.py | 24 ++++++++++++ .../secrets_plugins/bitbucket.py | 28 ++++++++++++++ .../secrets_plugins/bittrex.py | 28 ++++++++++++++ .../secrets_plugins/clojars_api_token.py | 22 +++++++++++ .../secrets_plugins/codecov_access_token.py | 24 ++++++++++++ .../secrets_plugins/coinbase_access_token.py | 24 ++++++++++++ .../secrets_plugins/confluent.py | 28 ++++++++++++++ .../secrets_plugins/contentful_api_token.py | 23 +++++++++++ .../secrets_plugins/databricks_api_token.py | 21 ++++++++++ .../secrets_plugins/datadog_access_token.py | 23 +++++++++++ .../defined_networking_api_token.py | 23 +++++++++++ .../secrets_plugins/digitalocean.py | 26 +++++++++++++ .../secrets_plugins/discord.py | 32 ++++++++++++++++ .../secrets_plugins/doppler_api_token.py | 22 +++++++++++ .../secrets_plugins/droneci_access_token.py | 24 ++++++++++++ .../secrets_plugins/dropbox.py | 32 ++++++++++++++++ .../secrets_plugins/duffel_api_token.py | 22 +++++++++++ .../secrets_plugins/dynatrace_api_token.py | 22 +++++++++++ .../secrets_plugins/easypost.py | 24 ++++++++++++ .../secrets_plugins/etsy_access_token.py | 24 ++++++++++++ .../secrets_plugins/facebook_access_token.py | 24 ++++++++++++ .../secrets_plugins/fastly_api_token.py | 24 ++++++++++++ .../secrets_plugins/finicity.py | 28 ++++++++++++++ .../secrets_plugins/finnhub_access_token.py | 24 ++++++++++++ .../secrets_plugins/flickr_access_token.py | 24 ++++++++++++ .../secrets_plugins/flutterwave.py | 26 +++++++++++++ .../secrets_plugins/frameio_api_token.py | 22 +++++++++++ .../freshbooks_access_token.py | 24 ++++++++++++ .../secrets_plugins/gcp_api_key.py | 24 ++++++++++++ .../secrets_plugins/github_token.py | 26 +++++++++++++ .../secrets_plugins/gitlab.py | 26 +++++++++++++ .../secrets_plugins/gitter_access_token.py | 24 ++++++++++++ .../secrets_plugins/gocardless_api_token.py | 25 ++++++++++++ .../secrets_plugins/grafana.py | 32 ++++++++++++++++ .../secrets_plugins/hashicorp_tf_api_token.py | 22 +++++++++++ .../secrets_plugins/heroku_api_key.py | 23 +++++++++++ .../secrets_plugins/hubspot_api_key.py | 24 ++++++++++++ .../secrets_plugins/huggingface.py | 26 +++++++++++++ .../secrets_plugins/intercom_api_key.py | 23 +++++++++++ .../enterprise_hooks/secrets_plugins/jfrog.py | 28 ++++++++++++++ .../enterprise_hooks/secrets_plugins/jwt.py | 24 ++++++++++++ .../secrets_plugins/kraken_access_token.py | 24 ++++++++++++ .../secrets_plugins/kucoin.py | 28 ++++++++++++++ .../launchdarkly_access_token.py | 23 +++++++++++ .../secrets_plugins/linear.py | 26 +++++++++++++ .../secrets_plugins/linkedin.py | 28 ++++++++++++++ .../enterprise_hooks/secrets_plugins/lob.py | 28 ++++++++++++++ .../secrets_plugins/mailgun.py | 32 ++++++++++++++++ .../secrets_plugins/mapbox_api_token.py | 24 ++++++++++++ .../mattermost_access_token.py | 24 ++++++++++++ .../secrets_plugins/messagebird.py | 28 ++++++++++++++ .../microsoft_teams_webhook.py | 24 ++++++++++++ .../secrets_plugins/netlify_access_token.py | 24 ++++++++++++ .../secrets_plugins/new_relic.py | 32 ++++++++++++++++ .../secrets_plugins/nytimes_access_token.py | 23 +++++++++++ .../secrets_plugins/okta_access_token.py | 23 +++++++++++ .../secrets_plugins/openai_api_key.py | 19 ++++++++++ .../secrets_plugins/planetscale.py | 32 ++++++++++++++++ .../secrets_plugins/postman_api_token.py | 23 +++++++++++ .../secrets_plugins/prefect_api_token.py | 19 ++++++++++ .../secrets_plugins/pulumi_api_token.py | 19 ++++++++++ .../secrets_plugins/pypi_upload_token.py | 19 ++++++++++ .../secrets_plugins/rapidapi_access_token.py | 23 +++++++++++ .../secrets_plugins/readme_api_token.py | 21 ++++++++++ .../secrets_plugins/rubygems_api_token.py | 21 ++++++++++ .../secrets_plugins/scalingo_api_token.py | 19 ++++++++++ .../secrets_plugins/sendbird.py | 28 ++++++++++++++ .../secrets_plugins/sendgrid_api_token.py | 23 +++++++++++ .../secrets_plugins/sendinblue_api_token.py | 23 +++++++++++ .../secrets_plugins/sentry_access_token.py | 23 +++++++++++ .../secrets_plugins/shippo_api_token.py | 23 +++++++++++ .../secrets_plugins/shopify.py | 31 +++++++++++++++ .../secrets_plugins/sidekiq.py | 28 ++++++++++++++ .../enterprise_hooks/secrets_plugins/slack.py | 38 +++++++++++++++++++ .../secrets_plugins/snyk_api_token.py | 23 +++++++++++ .../squarespace_access_token.py | 23 +++++++++++ .../secrets_plugins/sumologic.py | 22 +++++++++++ .../secrets_plugins/telegram_bot_api_token.py | 23 +++++++++++ .../secrets_plugins/travisci_access_token.py | 23 +++++++++++ .../secrets_plugins/twitch_api_token.py | 23 +++++++++++ .../secrets_plugins/twitter.py | 36 ++++++++++++++++++ .../secrets_plugins/typeform_api_token.py | 23 +++++++++++ .../enterprise_hooks/secrets_plugins/vault.py | 24 ++++++++++++ .../secrets_plugins/yandex.py | 28 ++++++++++++++ .../secrets_plugins/zendesk_secret_key.py | 23 +++++++++++ litellm/tests/test_secret_detect_hook.py | 8 ++++ 96 files changed, 2337 insertions(+) create mode 100644 enterprise/enterprise_hooks/secrets_plugins/__init__.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/adafruit.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/adobe.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/age_secret_key.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/airtable_api_key.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/algolia_api_key.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/alibaba.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/asana.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/atlassian_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/authress_access_key.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/beamer_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/bitbucket.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/bittrex.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/clojars_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/codecov_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/coinbase_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/confluent.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/contentful_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/databricks_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/datadog_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/defined_networking_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/digitalocean.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/discord.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/doppler_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/droneci_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/dropbox.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/duffel_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/dynatrace_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/easypost.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/etsy_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/facebook_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/fastly_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/finicity.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/finnhub_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/flickr_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/flutterwave.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/frameio_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/freshbooks_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/gcp_api_key.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/github_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/gitlab.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/gitter_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/gocardless_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/grafana.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/hashicorp_tf_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/heroku_api_key.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/hubspot_api_key.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/huggingface.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/intercom_api_key.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/jfrog.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/jwt.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/kraken_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/kucoin.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/launchdarkly_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/linear.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/linkedin.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/lob.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/mailgun.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/mapbox_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/mattermost_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/messagebird.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/microsoft_teams_webhook.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/netlify_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/new_relic.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/nytimes_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/okta_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/openai_api_key.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/planetscale.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/postman_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/prefect_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/pulumi_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/pypi_upload_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/rapidapi_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/readme_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/rubygems_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/scalingo_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/sendbird.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/sendgrid_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/sendinblue_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/sentry_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/shippo_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/shopify.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/sidekiq.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/slack.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/snyk_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/squarespace_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/sumologic.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/telegram_bot_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/travisci_access_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/twitch_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/twitter.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/typeform_api_token.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/vault.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/yandex.py create mode 100644 enterprise/enterprise_hooks/secrets_plugins/zendesk_secret_key.py diff --git a/enterprise/enterprise_hooks/secrets_plugins/__init__.py b/enterprise/enterprise_hooks/secrets_plugins/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/enterprise/enterprise_hooks/secrets_plugins/adafruit.py b/enterprise/enterprise_hooks/secrets_plugins/adafruit.py new file mode 100644 index 000000000..abee3398f --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/adafruit.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Adafruit keys +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class AdafruitKeyDetector(RegexBasedDetector): + """Scans for Adafruit keys.""" + + @property + def secret_type(self) -> str: + return "Adafruit API Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:adafruit)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/adobe.py b/enterprise/enterprise_hooks/secrets_plugins/adobe.py new file mode 100644 index 000000000..7a58ccdf9 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/adobe.py @@ -0,0 +1,26 @@ +""" +This plugin searches for Adobe keys +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class AdobeSecretDetector(RegexBasedDetector): + """Scans for Adobe client keys.""" + + @property + def secret_type(self) -> str: + return "Adobe Client Keys" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Adobe Client ID (OAuth Web) + re.compile( + r"""(?i)(?:adobe)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Adobe Client Secret + re.compile(r"(?i)\b((p8e-)[a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)"), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/age_secret_key.py b/enterprise/enterprise_hooks/secrets_plugins/age_secret_key.py new file mode 100644 index 000000000..2c0c17910 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/age_secret_key.py @@ -0,0 +1,21 @@ +""" +This plugin searches for Age secret keys +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class AgeSecretKeyDetector(RegexBasedDetector): + """Scans for Age secret keys.""" + + @property + def secret_type(self) -> str: + return "Age Secret Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile(r"""AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/airtable_api_key.py b/enterprise/enterprise_hooks/secrets_plugins/airtable_api_key.py new file mode 100644 index 000000000..8abf4f6e4 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/airtable_api_key.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Airtable API keys +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class AirtableApiKeyDetector(RegexBasedDetector): + """Scans for Airtable API keys.""" + + @property + def secret_type(self) -> str: + return "Airtable API Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:airtable)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{17})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/algolia_api_key.py b/enterprise/enterprise_hooks/secrets_plugins/algolia_api_key.py new file mode 100644 index 000000000..cd6c16a8c --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/algolia_api_key.py @@ -0,0 +1,21 @@ +""" +This plugin searches for Algolia API keys +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class AlgoliaApiKeyDetector(RegexBasedDetector): + """Scans for Algolia API keys.""" + + @property + def secret_type(self) -> str: + return "Algolia API Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile(r"""(?i)\b((LTAI)[a-z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/alibaba.py b/enterprise/enterprise_hooks/secrets_plugins/alibaba.py new file mode 100644 index 000000000..5d071f1a9 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/alibaba.py @@ -0,0 +1,26 @@ +""" +This plugin searches for Alibaba secrets +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class AlibabaSecretDetector(RegexBasedDetector): + """Scans for Alibaba AccessKey IDs and Secret Keys.""" + + @property + def secret_type(self) -> str: + return "Alibaba Secrets" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Alibaba AccessKey ID + re.compile(r"""(?i)\b((LTAI)[a-z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)"""), + # For Alibaba Secret Key + re.compile( + r"""(?i)(?:alibaba)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{30})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/asana.py b/enterprise/enterprise_hooks/secrets_plugins/asana.py new file mode 100644 index 000000000..fd96872c6 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/asana.py @@ -0,0 +1,28 @@ +""" +This plugin searches for Asana secrets +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class AsanaSecretDetector(RegexBasedDetector): + """Scans for Asana Client IDs and Client Secrets.""" + + @property + def secret_type(self) -> str: + return "Asana Secrets" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Asana Client ID + re.compile( + r"""(?i)(?:asana)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # For Asana Client Secret + re.compile( + r"""(?i)(?:asana)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/atlassian_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/atlassian_api_token.py new file mode 100644 index 000000000..42fd291ff --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/atlassian_api_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Atlassian API tokens +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class AtlassianApiTokenDetector(RegexBasedDetector): + """Scans for Atlassian API tokens.""" + + @property + def secret_type(self) -> str: + return "Atlassian API token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Atlassian API token + re.compile( + r"""(?i)(?:atlassian|confluence|jira)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/authress_access_key.py b/enterprise/enterprise_hooks/secrets_plugins/authress_access_key.py new file mode 100644 index 000000000..ff7466fc4 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/authress_access_key.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Authress Service Client Access Keys +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class AuthressAccessKeyDetector(RegexBasedDetector): + """Scans for Authress Service Client Access Keys.""" + + @property + def secret_type(self) -> str: + return "Authress Service Client Access Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Authress Service Client Access Key + re.compile( + r"""(?i)\b((?:sc|ext|scauth|authress)_[a-z0-9]{5,30}\.[a-z0-9]{4,6}\.acc[_-][a-z0-9-]{10,32}\.[a-z0-9+/_=-]{30,120})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/beamer_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/beamer_api_token.py new file mode 100644 index 000000000..5303e6262 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/beamer_api_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Beamer API tokens +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class BeamerApiTokenDetector(RegexBasedDetector): + """Scans for Beamer API tokens.""" + + @property + def secret_type(self) -> str: + return "Beamer API token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Beamer API token + re.compile( + r"""(?i)(?:beamer)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(b_[a-z0-9=_\-]{44})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/bitbucket.py b/enterprise/enterprise_hooks/secrets_plugins/bitbucket.py new file mode 100644 index 000000000..aae28dcc7 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/bitbucket.py @@ -0,0 +1,28 @@ +""" +This plugin searches for Bitbucket Client ID and Client Secret +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class BitbucketDetector(RegexBasedDetector): + """Scans for Bitbucket Client ID and Client Secret.""" + + @property + def secret_type(self) -> str: + return "Bitbucket Secrets" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Bitbucket Client ID + re.compile( + r"""(?i)(?:bitbucket)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # For Bitbucket Client Secret + re.compile( + r"""(?i)(?:bitbucket)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/bittrex.py b/enterprise/enterprise_hooks/secrets_plugins/bittrex.py new file mode 100644 index 000000000..e8bd3347b --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/bittrex.py @@ -0,0 +1,28 @@ +""" +This plugin searches for Bittrex Access Key and Secret Key +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class BittrexDetector(RegexBasedDetector): + """Scans for Bittrex Access Key and Secret Key.""" + + @property + def secret_type(self) -> str: + return "Bittrex Secrets" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Bittrex Access Key + re.compile( + r"""(?i)(?:bittrex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # For Bittrex Secret Key + re.compile( + r"""(?i)(?:bittrex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/clojars_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/clojars_api_token.py new file mode 100644 index 000000000..6eb41ec4b --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/clojars_api_token.py @@ -0,0 +1,22 @@ +""" +This plugin searches for Clojars API tokens +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class ClojarsApiTokenDetector(RegexBasedDetector): + """Scans for Clojars API tokens.""" + + @property + def secret_type(self) -> str: + return "Clojars API token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Clojars API token + re.compile(r"(?i)(CLOJARS_)[a-z0-9]{60}"), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/codecov_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/codecov_access_token.py new file mode 100644 index 000000000..51001675f --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/codecov_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Codecov Access Token +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class CodecovAccessTokenDetector(RegexBasedDetector): + """Scans for Codecov Access Token.""" + + @property + def secret_type(self) -> str: + return "Codecov Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Codecov Access Token + re.compile( + r"""(?i)(?:codecov)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/coinbase_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/coinbase_access_token.py new file mode 100644 index 000000000..0af631be9 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/coinbase_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Coinbase Access Token +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class CoinbaseAccessTokenDetector(RegexBasedDetector): + """Scans for Coinbase Access Token.""" + + @property + def secret_type(self) -> str: + return "Coinbase Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Coinbase Access Token + re.compile( + r"""(?i)(?:coinbase)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/confluent.py b/enterprise/enterprise_hooks/secrets_plugins/confluent.py new file mode 100644 index 000000000..aefbd42b9 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/confluent.py @@ -0,0 +1,28 @@ +""" +This plugin searches for Confluent Access Token and Confluent Secret Key +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class ConfluentDetector(RegexBasedDetector): + """Scans for Confluent Access Token and Confluent Secret Key.""" + + @property + def secret_type(self) -> str: + return "Confluent Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # For Confluent Access Token + re.compile( + r"""(?i)(?:confluent)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # For Confluent Secret Key + re.compile( + r"""(?i)(?:confluent)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/contentful_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/contentful_api_token.py new file mode 100644 index 000000000..33817dc4d --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/contentful_api_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Contentful delivery API token. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class ContentfulApiTokenDetector(RegexBasedDetector): + """Scans for Contentful delivery API token.""" + + @property + def secret_type(self) -> str: + return "Contentful API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:contentful)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{43})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/databricks_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/databricks_api_token.py new file mode 100644 index 000000000..9e47355b1 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/databricks_api_token.py @@ -0,0 +1,21 @@ +""" +This plugin searches for Databricks API token. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class DatabricksApiTokenDetector(RegexBasedDetector): + """Scans for Databricks API token.""" + + @property + def secret_type(self) -> str: + return "Databricks API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile(r"""(?i)\b(dapi[a-h0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/datadog_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/datadog_access_token.py new file mode 100644 index 000000000..bdb430d9b --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/datadog_access_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Datadog Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class DatadogAccessTokenDetector(RegexBasedDetector): + """Scans for Datadog Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Datadog Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:datadog)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/defined_networking_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/defined_networking_api_token.py new file mode 100644 index 000000000..b23cdb454 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/defined_networking_api_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Defined Networking API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class DefinedNetworkingApiTokenDetector(RegexBasedDetector): + """Scans for Defined Networking API Tokens.""" + + @property + def secret_type(self) -> str: + return "Defined Networking API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:dnkey)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(dnkey-[a-z0-9=_\-]{26}-[a-z0-9=_\-]{52})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/digitalocean.py b/enterprise/enterprise_hooks/secrets_plugins/digitalocean.py new file mode 100644 index 000000000..5ffc4f600 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/digitalocean.py @@ -0,0 +1,26 @@ +""" +This plugin searches for DigitalOcean tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class DigitaloceanDetector(RegexBasedDetector): + """Scans for various DigitalOcean Tokens.""" + + @property + def secret_type(self) -> str: + return "DigitalOcean Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # OAuth Access Token + re.compile(r"""(?i)\b(doo_v1_[a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)"""), + # Personal Access Token + re.compile(r"""(?i)\b(dop_v1_[a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)"""), + # OAuth Refresh Token + re.compile(r"""(?i)\b(dor_v1_[a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/discord.py b/enterprise/enterprise_hooks/secrets_plugins/discord.py new file mode 100644 index 000000000..c51406b60 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/discord.py @@ -0,0 +1,32 @@ +""" +This plugin searches for Discord Client tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class DiscordDetector(RegexBasedDetector): + """Scans for various Discord Client Tokens.""" + + @property + def secret_type(self) -> str: + return "Discord Client Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Discord API key + re.compile( + r"""(?i)(?:discord)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Discord client ID + re.compile( + r"""(?i)(?:discord)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9]{18})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Discord client secret + re.compile( + r"""(?i)(?:discord)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/doppler_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/doppler_api_token.py new file mode 100644 index 000000000..56c594fc1 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/doppler_api_token.py @@ -0,0 +1,22 @@ +""" +This plugin searches for Doppler API tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class DopplerApiTokenDetector(RegexBasedDetector): + """Scans for Doppler API Tokens.""" + + @property + def secret_type(self) -> str: + return "Doppler API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Doppler API token + re.compile(r"""(?i)dp\.pt\.[a-z0-9]{43}"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/droneci_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/droneci_access_token.py new file mode 100644 index 000000000..8afffb802 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/droneci_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Droneci Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class DroneciAccessTokenDetector(RegexBasedDetector): + """Scans for Droneci Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Droneci Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Droneci Access Token + re.compile( + r"""(?i)(?:droneci)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/dropbox.py b/enterprise/enterprise_hooks/secrets_plugins/dropbox.py new file mode 100644 index 000000000..b19815b26 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/dropbox.py @@ -0,0 +1,32 @@ +""" +This plugin searches for Dropbox tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class DropboxDetector(RegexBasedDetector): + """Scans for various Dropbox Tokens.""" + + @property + def secret_type(self) -> str: + return "Dropbox Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Dropbox API secret + re.compile( + r"""(?i)(?:dropbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{15})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Dropbox long-lived API token + re.compile( + r"""(?i)(?:dropbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{11}(AAAAAAAAAA)[a-z0-9\-_=]{43})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Dropbox short-lived API token + re.compile( + r"""(?i)(?:dropbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(sl\.[a-z0-9\-=_]{135})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/duffel_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/duffel_api_token.py new file mode 100644 index 000000000..aab681598 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/duffel_api_token.py @@ -0,0 +1,22 @@ +""" +This plugin searches for Duffel API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class DuffelApiTokenDetector(RegexBasedDetector): + """Scans for Duffel API Tokens.""" + + @property + def secret_type(self) -> str: + return "Duffel API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Duffel API Token + re.compile(r"""(?i)duffel_(test|live)_[a-z0-9_\-=]{43}"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/dynatrace_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/dynatrace_api_token.py new file mode 100644 index 000000000..caf7dd719 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/dynatrace_api_token.py @@ -0,0 +1,22 @@ +""" +This plugin searches for Dynatrace API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class DynatraceApiTokenDetector(RegexBasedDetector): + """Scans for Dynatrace API Tokens.""" + + @property + def secret_type(self) -> str: + return "Dynatrace API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Dynatrace API Token + re.compile(r"""(?i)dt0c01\.[a-z0-9]{24}\.[a-z0-9]{64}"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/easypost.py b/enterprise/enterprise_hooks/secrets_plugins/easypost.py new file mode 100644 index 000000000..73d27cb49 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/easypost.py @@ -0,0 +1,24 @@ +""" +This plugin searches for EasyPost tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class EasyPostDetector(RegexBasedDetector): + """Scans for various EasyPost Tokens.""" + + @property + def secret_type(self) -> str: + return "EasyPost Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # EasyPost API token + re.compile(r"""(?i)\bEZAK[a-z0-9]{54}"""), + # EasyPost test API token + re.compile(r"""(?i)\bEZTK[a-z0-9]{54}"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/etsy_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/etsy_access_token.py new file mode 100644 index 000000000..1775a4b41 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/etsy_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Etsy Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class EtsyAccessTokenDetector(RegexBasedDetector): + """Scans for Etsy Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Etsy Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Etsy Access Token + re.compile( + r"""(?i)(?:etsy)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/facebook_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/facebook_access_token.py new file mode 100644 index 000000000..edc7d080c --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/facebook_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Facebook Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class FacebookAccessTokenDetector(RegexBasedDetector): + """Scans for Facebook Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Facebook Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Facebook Access Token + re.compile( + r"""(?i)(?:facebook)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/fastly_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/fastly_api_token.py new file mode 100644 index 000000000..4d451cb74 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/fastly_api_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Fastly API keys. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class FastlyApiKeyDetector(RegexBasedDetector): + """Scans for Fastly API keys.""" + + @property + def secret_type(self) -> str: + return "Fastly API Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Fastly API key + re.compile( + r"""(?i)(?:fastly)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/finicity.py b/enterprise/enterprise_hooks/secrets_plugins/finicity.py new file mode 100644 index 000000000..97414352f --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/finicity.py @@ -0,0 +1,28 @@ +""" +This plugin searches for Finicity API tokens and Client Secrets. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class FinicityDetector(RegexBasedDetector): + """Scans for Finicity API tokens and Client Secrets.""" + + @property + def secret_type(self) -> str: + return "Finicity Credentials" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Finicity API token + re.compile( + r"""(?i)(?:finicity)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Finicity Client Secret + re.compile( + r"""(?i)(?:finicity)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/finnhub_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/finnhub_access_token.py new file mode 100644 index 000000000..eeb09682b --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/finnhub_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Finnhub Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class FinnhubAccessTokenDetector(RegexBasedDetector): + """Scans for Finnhub Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Finnhub Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Finnhub Access Token + re.compile( + r"""(?i)(?:finnhub)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/flickr_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/flickr_access_token.py new file mode 100644 index 000000000..530628547 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/flickr_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Flickr Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class FlickrAccessTokenDetector(RegexBasedDetector): + """Scans for Flickr Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Flickr Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Flickr Access Token + re.compile( + r"""(?i)(?:flickr)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/flutterwave.py b/enterprise/enterprise_hooks/secrets_plugins/flutterwave.py new file mode 100644 index 000000000..fc46ba222 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/flutterwave.py @@ -0,0 +1,26 @@ +""" +This plugin searches for Flutterwave API keys. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class FlutterwaveDetector(RegexBasedDetector): + """Scans for Flutterwave API Keys.""" + + @property + def secret_type(self) -> str: + return "Flutterwave API Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Flutterwave Encryption Key + re.compile(r"""(?i)FLWSECK_TEST-[a-h0-9]{12}"""), + # Flutterwave Public Key + re.compile(r"""(?i)FLWPUBK_TEST-[a-h0-9]{32}-X"""), + # Flutterwave Secret Key + re.compile(r"""(?i)FLWSECK_TEST-[a-h0-9]{32}-X"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/frameio_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/frameio_api_token.py new file mode 100644 index 000000000..9524e873d --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/frameio_api_token.py @@ -0,0 +1,22 @@ +""" +This plugin searches for Frame.io API tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class FrameIoApiTokenDetector(RegexBasedDetector): + """Scans for Frame.io API Tokens.""" + + @property + def secret_type(self) -> str: + return "Frame.io API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Frame.io API token + re.compile(r"""(?i)fio-u-[a-z0-9\-_=]{64}"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/freshbooks_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/freshbooks_access_token.py new file mode 100644 index 000000000..b6b16e2b8 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/freshbooks_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Freshbooks Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class FreshbooksAccessTokenDetector(RegexBasedDetector): + """Scans for Freshbooks Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Freshbooks Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Freshbooks Access Token + re.compile( + r"""(?i)(?:freshbooks)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/gcp_api_key.py b/enterprise/enterprise_hooks/secrets_plugins/gcp_api_key.py new file mode 100644 index 000000000..6055cc262 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/gcp_api_key.py @@ -0,0 +1,24 @@ +""" +This plugin searches for GCP API keys. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class GCPApiKeyDetector(RegexBasedDetector): + """Scans for GCP API keys.""" + + @property + def secret_type(self) -> str: + return "GCP API Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # GCP API Key + re.compile( + r"""(?i)\b(AIza[0-9A-Za-z\\-_]{35})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/github_token.py b/enterprise/enterprise_hooks/secrets_plugins/github_token.py new file mode 100644 index 000000000..acb5e3fc7 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/github_token.py @@ -0,0 +1,26 @@ +""" +This plugin searches for GitHub tokens +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class GitHubTokenCustomDetector(RegexBasedDetector): + """Scans for GitHub tokens.""" + + @property + def secret_type(self) -> str: + return "GitHub Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # GitHub App/Personal Access/OAuth Access/Refresh Token + # ref. https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/ + re.compile(r"(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36}"), + # GitHub Fine-Grained Personal Access Token + re.compile(r"github_pat_[0-9a-zA-Z_]{82}"), + re.compile(r"gho_[0-9a-zA-Z]{36}"), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/gitlab.py b/enterprise/enterprise_hooks/secrets_plugins/gitlab.py new file mode 100644 index 000000000..2277d8a2d --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/gitlab.py @@ -0,0 +1,26 @@ +""" +This plugin searches for GitLab secrets. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class GitLabDetector(RegexBasedDetector): + """Scans for GitLab Secrets.""" + + @property + def secret_type(self) -> str: + return "GitLab Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # GitLab Personal Access Token + re.compile(r"""glpat-[0-9a-zA-Z\-\_]{20}"""), + # GitLab Pipeline Trigger Token + re.compile(r"""glptt-[0-9a-f]{40}"""), + # GitLab Runner Registration Token + re.compile(r"""GR1348941[0-9a-zA-Z\-\_]{20}"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/gitter_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/gitter_access_token.py new file mode 100644 index 000000000..1febe70cb --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/gitter_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Gitter Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class GitterAccessTokenDetector(RegexBasedDetector): + """Scans for Gitter Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Gitter Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Gitter Access Token + re.compile( + r"""(?i)(?:gitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/gocardless_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/gocardless_api_token.py new file mode 100644 index 000000000..240f6e4c5 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/gocardless_api_token.py @@ -0,0 +1,25 @@ +""" +This plugin searches for GoCardless API tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class GoCardlessApiTokenDetector(RegexBasedDetector): + """Scans for GoCardless API Tokens.""" + + @property + def secret_type(self) -> str: + return "GoCardless API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # GoCardless API token + re.compile( + r"""(?:gocardless)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(live_[a-z0-9\-_=]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)""", + re.IGNORECASE, + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/grafana.py b/enterprise/enterprise_hooks/secrets_plugins/grafana.py new file mode 100644 index 000000000..fd37f0f63 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/grafana.py @@ -0,0 +1,32 @@ +""" +This plugin searches for Grafana secrets. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class GrafanaDetector(RegexBasedDetector): + """Scans for Grafana Secrets.""" + + @property + def secret_type(self) -> str: + return "Grafana Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Grafana API key or Grafana Cloud API key + re.compile( + r"""(?i)\b(eyJrIjoi[A-Za-z0-9]{70,400}={0,2})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Grafana Cloud API token + re.compile( + r"""(?i)\b(glc_[A-Za-z0-9+/]{32,400}={0,2})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Grafana Service Account token + re.compile( + r"""(?i)\b(glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/hashicorp_tf_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/hashicorp_tf_api_token.py new file mode 100644 index 000000000..97013fd84 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/hashicorp_tf_api_token.py @@ -0,0 +1,22 @@ +""" +This plugin searches for HashiCorp Terraform user/org API tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class HashiCorpTFApiTokenDetector(RegexBasedDetector): + """Scans for HashiCorp Terraform User/Org API Tokens.""" + + @property + def secret_type(self) -> str: + return "HashiCorp Terraform API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # HashiCorp Terraform user/org API token + re.compile(r"""(?i)[a-z0-9]{14}\.atlasv1\.[a-z0-9\-_=]{60,70}"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/heroku_api_key.py b/enterprise/enterprise_hooks/secrets_plugins/heroku_api_key.py new file mode 100644 index 000000000..53be8aa48 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/heroku_api_key.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Heroku API Keys. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class HerokuApiKeyDetector(RegexBasedDetector): + """Scans for Heroku API Keys.""" + + @property + def secret_type(self) -> str: + return "Heroku API Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:heroku)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/hubspot_api_key.py b/enterprise/enterprise_hooks/secrets_plugins/hubspot_api_key.py new file mode 100644 index 000000000..230ef659b --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/hubspot_api_key.py @@ -0,0 +1,24 @@ +""" +This plugin searches for HubSpot API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class HubSpotApiTokenDetector(RegexBasedDetector): + """Scans for HubSpot API Tokens.""" + + @property + def secret_type(self) -> str: + return "HubSpot API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # HubSpot API Token + re.compile( + r"""(?i)(?:hubspot)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/huggingface.py b/enterprise/enterprise_hooks/secrets_plugins/huggingface.py new file mode 100644 index 000000000..be83a3a0d --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/huggingface.py @@ -0,0 +1,26 @@ +""" +This plugin searches for Hugging Face Access and Organization API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class HuggingFaceDetector(RegexBasedDetector): + """Scans for Hugging Face Tokens.""" + + @property + def secret_type(self) -> str: + return "Hugging Face Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Hugging Face Access token + re.compile(r"""(?:^|[\\'"` >=:])(hf_[a-zA-Z]{34})(?:$|[\\'"` <])"""), + # Hugging Face Organization API token + re.compile( + r"""(?:^|[\\'"` >=:\(,)])(api_org_[a-zA-Z]{34})(?:$|[\\'"` <\),])""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/intercom_api_key.py b/enterprise/enterprise_hooks/secrets_plugins/intercom_api_key.py new file mode 100644 index 000000000..24e16fc73 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/intercom_api_key.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Intercom API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class IntercomApiTokenDetector(RegexBasedDetector): + """Scans for Intercom API Tokens.""" + + @property + def secret_type(self) -> str: + return "Intercom API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:intercom)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{60})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/jfrog.py b/enterprise/enterprise_hooks/secrets_plugins/jfrog.py new file mode 100644 index 000000000..3eabbfe3a --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/jfrog.py @@ -0,0 +1,28 @@ +""" +This plugin searches for JFrog-related secrets like API Key and Identity Token. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class JFrogDetector(RegexBasedDetector): + """Scans for JFrog-related secrets.""" + + @property + def secret_type(self) -> str: + return "JFrog Secrets" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # JFrog API Key + re.compile( + r"""(?i)(?:jfrog|artifactory|bintray|xray)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{73})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # JFrog Identity Token + re.compile( + r"""(?i)(?:jfrog|artifactory|bintray|xray)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/jwt.py b/enterprise/enterprise_hooks/secrets_plugins/jwt.py new file mode 100644 index 000000000..6658a0950 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/jwt.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Base64-encoded JSON Web Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class JWTBase64Detector(RegexBasedDetector): + """Scans for Base64-encoded JSON Web Tokens.""" + + @property + def secret_type(self) -> str: + return "Base64-encoded JSON Web Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Base64-encoded JSON Web Token + re.compile( + r"""\bZXlK(?:(?PaGJHY2lPaU)|(?PaGNIVWlPaU)|(?PaGNIWWlPaU)|(?PaGRXUWlPaU)|(?PaU5qUWlP)|(?PamNtbDBJanBi)|(?PamRIa2lPaU)|(?PbGNHc2lPbn)|(?PbGJtTWlPaU)|(?PcWEzVWlPaU)|(?PcWQyc2lPb)|(?PcGMzTWlPaU)|(?PcGRpSTZJ)|(?PcmFXUWlP)|(?PclpYbGZiM0J6SWpwY)|(?PcmRIa2lPaUp)|(?PdWIyNWpaU0k2)|(?Pd01tTWlP)|(?Pd01uTWlPaU)|(?Pd2NIUWlPaU)|(?PemRXSWlPaU)|(?PemRuUWlP)|(?PMFlXY2lPaU)|(?PMGVYQWlPaUp)|(?PMWNtd2l)|(?PMWMyVWlPaUp)|(?PMlpYSWlPaU)|(?PMlpYSnphVzl1SWpv)|(?PNElqb2)|(?PNE5XTWlP)|(?PNE5YUWlPaU)|(?PNE5YUWpVekkxTmlJNkl)|(?PNE5YVWlPaU)|(?PNmFYQWlPaU))[a-zA-Z0-9\/\\_+\-\r\n]{40,}={0,2}""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/kraken_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/kraken_access_token.py new file mode 100644 index 000000000..cb7357cfd --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/kraken_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Kraken Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class KrakenAccessTokenDetector(RegexBasedDetector): + """Scans for Kraken Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Kraken Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Kraken Access Token + re.compile( + r"""(?i)(?:kraken)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9\/=_\+\-]{80,90})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/kucoin.py b/enterprise/enterprise_hooks/secrets_plugins/kucoin.py new file mode 100644 index 000000000..02e990bd8 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/kucoin.py @@ -0,0 +1,28 @@ +""" +This plugin searches for Kucoin Access Tokens and Secret Keys. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class KucoinDetector(RegexBasedDetector): + """Scans for Kucoin Access Tokens and Secret Keys.""" + + @property + def secret_type(self) -> str: + return "Kucoin Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Kucoin Access Token + re.compile( + r"""(?i)(?:kucoin)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Kucoin Secret Key + re.compile( + r"""(?i)(?:kucoin)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/launchdarkly_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/launchdarkly_access_token.py new file mode 100644 index 000000000..977990984 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/launchdarkly_access_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Launchdarkly Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class LaunchdarklyAccessTokenDetector(RegexBasedDetector): + """Scans for Launchdarkly Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Launchdarkly Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:launchdarkly)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/linear.py b/enterprise/enterprise_hooks/secrets_plugins/linear.py new file mode 100644 index 000000000..1224b5ec4 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/linear.py @@ -0,0 +1,26 @@ +""" +This plugin searches for Linear API Tokens and Linear Client Secrets. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class LinearDetector(RegexBasedDetector): + """Scans for Linear secrets.""" + + @property + def secret_type(self) -> str: + return "Linear Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Linear API Token + re.compile(r"""(?i)lin_api_[a-z0-9]{40}"""), + # Linear Client Secret + re.compile( + r"""(?i)(?:linear)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/linkedin.py b/enterprise/enterprise_hooks/secrets_plugins/linkedin.py new file mode 100644 index 000000000..53ff0c30a --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/linkedin.py @@ -0,0 +1,28 @@ +""" +This plugin searches for LinkedIn Client IDs and LinkedIn Client secrets. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class LinkedInDetector(RegexBasedDetector): + """Scans for LinkedIn secrets.""" + + @property + def secret_type(self) -> str: + return "LinkedIn Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # LinkedIn Client ID + re.compile( + r"""(?i)(?:linkedin|linked-in)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{14})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # LinkedIn Client secret + re.compile( + r"""(?i)(?:linkedin|linked-in)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/lob.py b/enterprise/enterprise_hooks/secrets_plugins/lob.py new file mode 100644 index 000000000..623ac4f1f --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/lob.py @@ -0,0 +1,28 @@ +""" +This plugin searches for Lob API secrets and Lob Publishable API keys. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class LobDetector(RegexBasedDetector): + """Scans for Lob secrets.""" + + @property + def secret_type(self) -> str: + return "Lob Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Lob API Key + re.compile( + r"""(?i)(?:lob)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}((live|test)_[a-f0-9]{35})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Lob Publishable API Key + re.compile( + r"""(?i)(?:lob)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}((test|live)_pub_[a-f0-9]{31})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/mailgun.py b/enterprise/enterprise_hooks/secrets_plugins/mailgun.py new file mode 100644 index 000000000..c403d2454 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/mailgun.py @@ -0,0 +1,32 @@ +""" +This plugin searches for Mailgun API secrets, public validation keys, and webhook signing keys. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class MailgunDetector(RegexBasedDetector): + """Scans for Mailgun secrets.""" + + @property + def secret_type(self) -> str: + return "Mailgun Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Mailgun Private API Token + re.compile( + r"""(?i)(?:mailgun)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(key-[a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Mailgun Public Validation Key + re.compile( + r"""(?i)(?:mailgun)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(pubkey-[a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Mailgun Webhook Signing Key + re.compile( + r"""(?i)(?:mailgun)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-h0-9]{32}-[a-h0-9]{8}-[a-h0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/mapbox_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/mapbox_api_token.py new file mode 100644 index 000000000..0326b7102 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/mapbox_api_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for MapBox API tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class MapBoxApiTokenDetector(RegexBasedDetector): + """Scans for MapBox API tokens.""" + + @property + def secret_type(self) -> str: + return "MapBox API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # MapBox API Token + re.compile( + r"""(?i)(?:mapbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(pk\.[a-z0-9]{60}\.[a-z0-9]{22})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/mattermost_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/mattermost_access_token.py new file mode 100644 index 000000000..d65b0e755 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/mattermost_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Mattermost Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class MattermostAccessTokenDetector(RegexBasedDetector): + """Scans for Mattermost Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Mattermost Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Mattermost Access Token + re.compile( + r"""(?i)(?:mattermost)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{26})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/messagebird.py b/enterprise/enterprise_hooks/secrets_plugins/messagebird.py new file mode 100644 index 000000000..6adc8317a --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/messagebird.py @@ -0,0 +1,28 @@ +""" +This plugin searches for MessageBird API tokens and client IDs. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class MessageBirdDetector(RegexBasedDetector): + """Scans for MessageBird secrets.""" + + @property + def secret_type(self) -> str: + return "MessageBird Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # MessageBird API Token + re.compile( + r"""(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{25})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # MessageBird Client ID + re.compile( + r"""(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/microsoft_teams_webhook.py b/enterprise/enterprise_hooks/secrets_plugins/microsoft_teams_webhook.py new file mode 100644 index 000000000..298fd81b0 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/microsoft_teams_webhook.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Microsoft Teams Webhook URLs. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class MicrosoftTeamsWebhookDetector(RegexBasedDetector): + """Scans for Microsoft Teams Webhook URLs.""" + + @property + def secret_type(self) -> str: + return "Microsoft Teams Webhook" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Microsoft Teams Webhook + re.compile( + r"""https:\/\/[a-z0-9]+\.webhook\.office\.com\/webhookb2\/[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}@[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}\/IncomingWebhook\/[a-z0-9]{32}\/[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/netlify_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/netlify_access_token.py new file mode 100644 index 000000000..cc7a575a4 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/netlify_access_token.py @@ -0,0 +1,24 @@ +""" +This plugin searches for Netlify Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class NetlifyAccessTokenDetector(RegexBasedDetector): + """Scans for Netlify Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Netlify Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Netlify Access Token + re.compile( + r"""(?i)(?:netlify)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{40,46})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/new_relic.py b/enterprise/enterprise_hooks/secrets_plugins/new_relic.py new file mode 100644 index 000000000..cef640155 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/new_relic.py @@ -0,0 +1,32 @@ +""" +This plugin searches for New Relic API tokens and keys. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class NewRelicDetector(RegexBasedDetector): + """Scans for New Relic API tokens and keys.""" + + @property + def secret_type(self) -> str: + return "New Relic API Secrets" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # New Relic ingest browser API token + re.compile( + r"""(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(NRJS-[a-f0-9]{19})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # New Relic user API ID + re.compile( + r"""(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # New Relic user API Key + re.compile( + r"""(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(NRAK-[a-z0-9]{27})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/nytimes_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/nytimes_access_token.py new file mode 100644 index 000000000..567b885e5 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/nytimes_access_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for New York Times Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class NYTimesAccessTokenDetector(RegexBasedDetector): + """Scans for New York Times Access Tokens.""" + + @property + def secret_type(self) -> str: + return "New York Times Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:nytimes|new-york-times,|newyorktimes)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/okta_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/okta_access_token.py new file mode 100644 index 000000000..97109767b --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/okta_access_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Okta Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class OktaAccessTokenDetector(RegexBasedDetector): + """Scans for Okta Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Okta Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:okta)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{42})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/openai_api_key.py b/enterprise/enterprise_hooks/secrets_plugins/openai_api_key.py new file mode 100644 index 000000000..c5d20f759 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/openai_api_key.py @@ -0,0 +1,19 @@ +""" +This plugin searches for OpenAI API Keys. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class OpenAIApiKeyDetector(RegexBasedDetector): + """Scans for OpenAI API Keys.""" + + @property + def secret_type(self) -> str: + return "Strict OpenAI API Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [re.compile(r"""(sk-[a-zA-Z0-9]{5,})""")] diff --git a/enterprise/enterprise_hooks/secrets_plugins/planetscale.py b/enterprise/enterprise_hooks/secrets_plugins/planetscale.py new file mode 100644 index 000000000..23a53667e --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/planetscale.py @@ -0,0 +1,32 @@ +""" +This plugin searches for PlanetScale API tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class PlanetScaleDetector(RegexBasedDetector): + """Scans for PlanetScale API Tokens.""" + + @property + def secret_type(self) -> str: + return "PlanetScale API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # the PlanetScale API token + re.compile( + r"""(?i)\b(pscale_tkn_[a-z0-9=\-_\.]{32,64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # the PlanetScale OAuth token + re.compile( + r"""(?i)\b(pscale_oauth_[a-z0-9=\-_\.]{32,64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # the PlanetScale password + re.compile( + r"""(?i)\b(pscale_pw_[a-z0-9=\-_\.]{32,64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/postman_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/postman_api_token.py new file mode 100644 index 000000000..9469e8191 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/postman_api_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Postman API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class PostmanApiTokenDetector(RegexBasedDetector): + """Scans for Postman API Tokens.""" + + @property + def secret_type(self) -> str: + return "Postman API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)\b(PMAK-[a-f0-9]{24}-[a-f0-9]{34})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/prefect_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/prefect_api_token.py new file mode 100644 index 000000000..35cdb71ca --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/prefect_api_token.py @@ -0,0 +1,19 @@ +""" +This plugin searches for Prefect API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class PrefectApiTokenDetector(RegexBasedDetector): + """Scans for Prefect API Tokens.""" + + @property + def secret_type(self) -> str: + return "Prefect API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [re.compile(r"""(?i)\b(pnu_[a-z0-9]{36})(?:['|\"|\n|\r|\s|\x60|;]|$)""")] diff --git a/enterprise/enterprise_hooks/secrets_plugins/pulumi_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/pulumi_api_token.py new file mode 100644 index 000000000..bae4ce211 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/pulumi_api_token.py @@ -0,0 +1,19 @@ +""" +This plugin searches for Pulumi API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class PulumiApiTokenDetector(RegexBasedDetector): + """Scans for Pulumi API Tokens.""" + + @property + def secret_type(self) -> str: + return "Pulumi API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [re.compile(r"""(?i)\b(pul-[a-f0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)""")] diff --git a/enterprise/enterprise_hooks/secrets_plugins/pypi_upload_token.py b/enterprise/enterprise_hooks/secrets_plugins/pypi_upload_token.py new file mode 100644 index 000000000..d4cc91385 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/pypi_upload_token.py @@ -0,0 +1,19 @@ +""" +This plugin searches for PyPI Upload Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class PyPiUploadTokenDetector(RegexBasedDetector): + """Scans for PyPI Upload Tokens.""" + + @property + def secret_type(self) -> str: + return "PyPI Upload Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [re.compile(r"""pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\-_]{50,1000}""")] diff --git a/enterprise/enterprise_hooks/secrets_plugins/rapidapi_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/rapidapi_access_token.py new file mode 100644 index 000000000..18b234614 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/rapidapi_access_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for RapidAPI Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class RapidApiAccessTokenDetector(RegexBasedDetector): + """Scans for RapidAPI Access Tokens.""" + + @property + def secret_type(self) -> str: + return "RapidAPI Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:rapidapi)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{50})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/readme_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/readme_api_token.py new file mode 100644 index 000000000..47bdffb12 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/readme_api_token.py @@ -0,0 +1,21 @@ +""" +This plugin searches for Readme API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class ReadmeApiTokenDetector(RegexBasedDetector): + """Scans for Readme API Tokens.""" + + @property + def secret_type(self) -> str: + return "Readme API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile(r"""(?i)\b(rdme_[a-z0-9]{70})(?:['|\"|\n|\r|\s|\x60|;]|$)""") + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/rubygems_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/rubygems_api_token.py new file mode 100644 index 000000000..d49c58e73 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/rubygems_api_token.py @@ -0,0 +1,21 @@ +""" +This plugin searches for Rubygem API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class RubygemsApiTokenDetector(RegexBasedDetector): + """Scans for Rubygem API Tokens.""" + + @property + def secret_type(self) -> str: + return "Rubygem API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile(r"""(?i)\b(rubygems_[a-f0-9]{48})(?:['|\"|\n|\r|\s|\x60|;]|$)""") + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/scalingo_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/scalingo_api_token.py new file mode 100644 index 000000000..3f8a59ee4 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/scalingo_api_token.py @@ -0,0 +1,19 @@ +""" +This plugin searches for Scalingo API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class ScalingoApiTokenDetector(RegexBasedDetector): + """Scans for Scalingo API Tokens.""" + + @property + def secret_type(self) -> str: + return "Scalingo API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [re.compile(r"""\btk-us-[a-zA-Z0-9-_]{48}\b""")] diff --git a/enterprise/enterprise_hooks/secrets_plugins/sendbird.py b/enterprise/enterprise_hooks/secrets_plugins/sendbird.py new file mode 100644 index 000000000..4b270d71e --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/sendbird.py @@ -0,0 +1,28 @@ +""" +This plugin searches for Sendbird Access IDs and Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class SendbirdDetector(RegexBasedDetector): + """Scans for Sendbird Access IDs and Tokens.""" + + @property + def secret_type(self) -> str: + return "Sendbird Credential" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Sendbird Access ID + re.compile( + r"""(?i)(?:sendbird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Sendbird Access Token + re.compile( + r"""(?i)(?:sendbird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/sendgrid_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/sendgrid_api_token.py new file mode 100644 index 000000000..bf974f4fd --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/sendgrid_api_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for SendGrid API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class SendGridApiTokenDetector(RegexBasedDetector): + """Scans for SendGrid API Tokens.""" + + @property + def secret_type(self) -> str: + return "SendGrid API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)\b(SG\.[a-z0-9=_\-\.]{66})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/sendinblue_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/sendinblue_api_token.py new file mode 100644 index 000000000..a6ed8c15e --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/sendinblue_api_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for SendinBlue API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class SendinBlueApiTokenDetector(RegexBasedDetector): + """Scans for SendinBlue API Tokens.""" + + @property + def secret_type(self) -> str: + return "SendinBlue API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)\b(xkeysib-[a-f0-9]{64}-[a-z0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/sentry_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/sentry_access_token.py new file mode 100644 index 000000000..181fad2c7 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/sentry_access_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Sentry Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class SentryAccessTokenDetector(RegexBasedDetector): + """Scans for Sentry Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Sentry Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:sentry)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/shippo_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/shippo_api_token.py new file mode 100644 index 000000000..4314c6876 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/shippo_api_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Shippo API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class ShippoApiTokenDetector(RegexBasedDetector): + """Scans for Shippo API Tokens.""" + + @property + def secret_type(self) -> str: + return "Shippo API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)\b(shippo_(live|test)_[a-f0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/shopify.py b/enterprise/enterprise_hooks/secrets_plugins/shopify.py new file mode 100644 index 000000000..f5f97c447 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/shopify.py @@ -0,0 +1,31 @@ +""" +This plugin searches for Shopify Access Tokens, Custom Access Tokens, +Private App Access Tokens, and Shared Secrets. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class ShopifyDetector(RegexBasedDetector): + """Scans for Shopify Access Tokens, Custom Access Tokens, Private App Access Tokens, + and Shared Secrets. + """ + + @property + def secret_type(self) -> str: + return "Shopify Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Shopify access token + re.compile(r"""shpat_[a-fA-F0-9]{32}"""), + # Shopify custom access token + re.compile(r"""shpca_[a-fA-F0-9]{32}"""), + # Shopify private app access token + re.compile(r"""shppa_[a-fA-F0-9]{32}"""), + # Shopify shared secret + re.compile(r"""shpss_[a-fA-F0-9]{32}"""), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/sidekiq.py b/enterprise/enterprise_hooks/secrets_plugins/sidekiq.py new file mode 100644 index 000000000..431ce7b8e --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/sidekiq.py @@ -0,0 +1,28 @@ +""" +This plugin searches for Sidekiq secrets and sensitive URLs. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class SidekiqDetector(RegexBasedDetector): + """Scans for Sidekiq secrets and sensitive URLs.""" + + @property + def secret_type(self) -> str: + return "Sidekiq Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Sidekiq Secret + re.compile( + r"""(?i)(?:BUNDLE_ENTERPRISE__CONTRIBSYS__COM|BUNDLE_GEMS__CONTRIBSYS__COM)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{8}:[a-f0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Sidekiq Sensitive URL + re.compile( + r"""(?i)\b(http(?:s??):\/\/)([a-f0-9]{8}:[a-f0-9]{8})@(?:gems.contribsys.com|enterprise.contribsys.com)(?:[\/|\#|\?|:]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/slack.py b/enterprise/enterprise_hooks/secrets_plugins/slack.py new file mode 100644 index 000000000..4896fd76b --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/slack.py @@ -0,0 +1,38 @@ +""" +This plugin searches for Slack tokens and webhooks. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class SlackDetector(RegexBasedDetector): + """Scans for Slack tokens and webhooks.""" + + @property + def secret_type(self) -> str: + return "Slack Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Slack App-level token + re.compile(r"""(?i)(xapp-\d-[A-Z0-9]+-\d+-[a-z0-9]+)"""), + # Slack Bot token + re.compile(r"""(xoxb-[0-9]{10,13}\-[0-9]{10,13}[a-zA-Z0-9-]*)"""), + # Slack Configuration access token and refresh token + re.compile(r"""(?i)(xoxe.xox[bp]-\d-[A-Z0-9]{163,166})"""), + re.compile(r"""(?i)(xoxe-\d-[A-Z0-9]{146})"""), + # Slack Legacy bot token and token + re.compile(r"""(xoxb-[0-9]{8,14}\-[a-zA-Z0-9]{18,26})"""), + re.compile(r"""(xox[os]-\d+-\d+-\d+-[a-fA-F\d]+)"""), + # Slack Legacy Workspace token + re.compile(r"""(xox[ar]-(?:\d-)?[0-9a-zA-Z]{8,48})"""), + # Slack User token and enterprise token + re.compile(r"""(xox[pe](?:-[0-9]{10,13}){3}-[a-zA-Z0-9-]{28,34})"""), + # Slack Webhook URL + re.compile( + r"""(https?:\/\/)?hooks.slack.com\/(services|workflows)\/[A-Za-z0-9+\/]{43,46}""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/snyk_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/snyk_api_token.py new file mode 100644 index 000000000..839bb5731 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/snyk_api_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Snyk API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class SnykApiTokenDetector(RegexBasedDetector): + """Scans for Snyk API Tokens.""" + + @property + def secret_type(self) -> str: + return "Snyk API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:snyk)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/squarespace_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/squarespace_access_token.py new file mode 100644 index 000000000..0dc83ad91 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/squarespace_access_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Squarespace Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class SquarespaceAccessTokenDetector(RegexBasedDetector): + """Scans for Squarespace Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Squarespace Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:squarespace)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/sumologic.py b/enterprise/enterprise_hooks/secrets_plugins/sumologic.py new file mode 100644 index 000000000..7117629ac --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/sumologic.py @@ -0,0 +1,22 @@ +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class SumoLogicDetector(RegexBasedDetector): + """Scans for SumoLogic Access ID and Access Token.""" + + @property + def secret_type(self) -> str: + return "SumoLogic" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i:(?:sumo)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3})(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(su[a-zA-Z0-9]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + re.compile( + r"""(?i)(?:sumo)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/telegram_bot_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/telegram_bot_api_token.py new file mode 100644 index 000000000..30854fda1 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/telegram_bot_api_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Telegram Bot API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class TelegramBotApiTokenDetector(RegexBasedDetector): + """Scans for Telegram Bot API Tokens.""" + + @property + def secret_type(self) -> str: + return "Telegram Bot API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:^|[^0-9])([0-9]{5,16}:A[a-zA-Z0-9_\-]{34})(?:$|[^a-zA-Z0-9_\-])""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/travisci_access_token.py b/enterprise/enterprise_hooks/secrets_plugins/travisci_access_token.py new file mode 100644 index 000000000..90f9b48f4 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/travisci_access_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Travis CI Access Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class TravisCiAccessTokenDetector(RegexBasedDetector): + """Scans for Travis CI Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Travis CI Access Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:travis)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{22})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/twitch_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/twitch_api_token.py new file mode 100644 index 000000000..1e0e3ccf8 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/twitch_api_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Twitch API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class TwitchApiTokenDetector(RegexBasedDetector): + """Scans for Twitch API Tokens.""" + + @property + def secret_type(self) -> str: + return "Twitch API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:twitch)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{30})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/twitter.py b/enterprise/enterprise_hooks/secrets_plugins/twitter.py new file mode 100644 index 000000000..99ad170d1 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/twitter.py @@ -0,0 +1,36 @@ +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class TwitterDetector(RegexBasedDetector): + """Scans for Twitter Access Secrets, Access Tokens, API Keys, API Secrets, and Bearer Tokens.""" + + @property + def secret_type(self) -> str: + return "Twitter Secret" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Twitter Access Secret + re.compile( + r"""(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{45})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Twitter Access Token + re.compile( + r"""(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9]{15,25}-[a-zA-Z0-9]{20,40})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Twitter API Key + re.compile( + r"""(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{25})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Twitter API Secret + re.compile( + r"""(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{50})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Twitter Bearer Token + re.compile( + r"""(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(A{22}[a-zA-Z0-9%]{80,100})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/typeform_api_token.py b/enterprise/enterprise_hooks/secrets_plugins/typeform_api_token.py new file mode 100644 index 000000000..8d9dc0e87 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/typeform_api_token.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Typeform API Tokens. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class TypeformApiTokenDetector(RegexBasedDetector): + """Scans for Typeform API Tokens.""" + + @property + def secret_type(self) -> str: + return "Typeform API Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:typeform)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(tfp_[a-z0-9\-_\.=]{59})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/vault.py b/enterprise/enterprise_hooks/secrets_plugins/vault.py new file mode 100644 index 000000000..5ca552cd9 --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/vault.py @@ -0,0 +1,24 @@ +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class VaultDetector(RegexBasedDetector): + """Scans for Vault Batch Tokens and Vault Service Tokens.""" + + @property + def secret_type(self) -> str: + return "Vault Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Vault Batch Token + re.compile( + r"""(?i)\b(hvb\.[a-z0-9_-]{138,212})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Vault Service Token + re.compile( + r"""(?i)\b(hvs\.[a-z0-9_-]{90,100})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/yandex.py b/enterprise/enterprise_hooks/secrets_plugins/yandex.py new file mode 100644 index 000000000..a58faec0d --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/yandex.py @@ -0,0 +1,28 @@ +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class YandexDetector(RegexBasedDetector): + """Scans for Yandex Access Tokens, API Keys, and AWS Access Tokens.""" + + @property + def secret_type(self) -> str: + return "Yandex Token" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + # Yandex Access Token + re.compile( + r"""(?i)(?:yandex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(t1\.[A-Z0-9a-z_-]+[=]{0,2}\.[A-Z0-9a-z_-]{86}[=]{0,2})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Yandex API Key + re.compile( + r"""(?i)(?:yandex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(AQVN[A-Za-z0-9_\-]{35,38})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + # Yandex AWS Access Token + re.compile( + r"""(?i)(?:yandex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(YC[a-zA-Z0-9_\-]{38})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ), + ] diff --git a/enterprise/enterprise_hooks/secrets_plugins/zendesk_secret_key.py b/enterprise/enterprise_hooks/secrets_plugins/zendesk_secret_key.py new file mode 100644 index 000000000..42c087c5b --- /dev/null +++ b/enterprise/enterprise_hooks/secrets_plugins/zendesk_secret_key.py @@ -0,0 +1,23 @@ +""" +This plugin searches for Zendesk Secret Keys. +""" + +import re + +from detect_secrets.plugins.base import RegexBasedDetector + + +class ZendeskSecretKeyDetector(RegexBasedDetector): + """Scans for Zendesk Secret Keys.""" + + @property + def secret_type(self) -> str: + return "Zendesk Secret Key" + + @property + def denylist(self) -> list[re.Pattern]: + return [ + re.compile( + r"""(?i)(?:zendesk)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)""" + ) + ] diff --git a/litellm/tests/test_secret_detect_hook.py b/litellm/tests/test_secret_detect_hook.py index cb1e01810..2c2007164 100644 --- a/litellm/tests/test_secret_detect_hook.py +++ b/litellm/tests/test_secret_detect_hook.py @@ -69,6 +69,10 @@ async def test_basic_secret_detection_chat(): "role": "user", "content": "this is my OPENAI_API_KEY = 'sk_1234567890abcdef'", }, + { + "role": "user", + "content": "My hi API Key is sk-Pc4nlxVoMz41290028TbMCxx, does it seem to be in the correct format?", + }, {"role": "user", "content": "i think it is +1 412-555-5555"}, ], "model": "gpt-3.5-turbo", @@ -93,6 +97,10 @@ async def test_basic_secret_detection_chat(): "content": "Hello! I'm doing well. How can I assist you today?", }, {"role": "user", "content": "this is my OPENAI_API_KEY = '[REDACTED]'"}, + { + "role": "user", + "content": "My hi API Key is [REDACTED], does it seem to be in the correct format?", + }, {"role": "user", "content": "i think it is +1 412-555-5555"}, ], "model": "gpt-3.5-turbo", From baf55a86c98e342f79e88d24c6a03468b390e202 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 15:20:30 -0700 Subject: [PATCH 129/137] fix secret scanner --- .../secrets_plugins/sidekiq.py | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 enterprise/enterprise_hooks/secrets_plugins/sidekiq.py diff --git a/enterprise/enterprise_hooks/secrets_plugins/sidekiq.py b/enterprise/enterprise_hooks/secrets_plugins/sidekiq.py deleted file mode 100644 index 431ce7b8e..000000000 --- a/enterprise/enterprise_hooks/secrets_plugins/sidekiq.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -This plugin searches for Sidekiq secrets and sensitive URLs. -""" - -import re - -from detect_secrets.plugins.base import RegexBasedDetector - - -class SidekiqDetector(RegexBasedDetector): - """Scans for Sidekiq secrets and sensitive URLs.""" - - @property - def secret_type(self) -> str: - return "Sidekiq Secret" - - @property - def denylist(self) -> list[re.Pattern]: - return [ - # Sidekiq Secret - re.compile( - r"""(?i)(?:BUNDLE_ENTERPRISE__CONTRIBSYS__COM|BUNDLE_GEMS__CONTRIBSYS__COM)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{8}:[a-f0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)""" - ), - # Sidekiq Sensitive URL - re.compile( - r"""(?i)\b(http(?:s??):\/\/)([a-f0-9]{8}:[a-f0-9]{8})@(?:gems.contribsys.com|enterprise.contribsys.com)(?:[\/|\#|\?|:]|$)""" - ), - ] From 0c6cd2c894ce2f1a2388af7f7a77485f8b9a7cee Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 16:29:11 -0700 Subject: [PATCH 130/137] fix error message on v2/model info --- 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 c3b855c5f..5fa5e91a3 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -6284,7 +6284,7 @@ async def model_info_v2( raise HTTPException( status_code=500, detail={ - "error": f"Invalid llm model list. llm_model_list={llm_model_list}" + "error": f"No model list passed, models={llm_model_list}. You can add a model through the config.yaml or on the LiteLLM Admin UI." }, ) From a012f231b66d66d1a7b2223826ae43b41b9a07e8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 17:37:02 -0700 Subject: [PATCH 131/137] azure - fix custom logger on post call --- litellm/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/main.py b/litellm/main.py index 649581936..318d0b7fe 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1025,7 +1025,7 @@ def completion( client=client, # pass AsyncAzureOpenAI, AzureOpenAI client ) - if optional_params.get("stream", False) or acompletion == True: + if optional_params.get("stream", False): ## LOGGING logging.post_call( input=messages, From 57ba0a46b702f33864d059c9f17522b2ec608d04 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 17:38:03 -0700 Subject: [PATCH 132/137] azure - log post api call --- litellm/llms/azure.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/litellm/llms/azure.py b/litellm/llms/azure.py index 5d73b9435..fe10cc017 100644 --- a/litellm/llms/azure.py +++ b/litellm/llms/azure.py @@ -660,8 +660,16 @@ class AzureChatCompletion(BaseLLM): response = await azure_client.chat.completions.create( **data, timeout=timeout ) + + stringified_response = response.model_dump() + logging_obj.post_call( + input=data["messages"], + api_key=api_key, + original_response=stringified_response, + additional_args={"complete_input_dict": data}, + ) return convert_to_model_response_object( - response_object=response.model_dump(), + response_object=stringified_response, model_response_object=model_response, ) except AzureOpenAIError as e: From eaa6441030ae5981631b60b8e2ec809ed2aff806 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 17:42:44 -0700 Subject: [PATCH 133/137] test fix secret detection --- enterprise/enterprise_hooks/secret_detection.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/enterprise/enterprise_hooks/secret_detection.py b/enterprise/enterprise_hooks/secret_detection.py index 23dd2a7e0..d2bd22a5d 100644 --- a/enterprise/enterprise_hooks/secret_detection.py +++ b/enterprise/enterprise_hooks/secret_detection.py @@ -379,10 +379,6 @@ _default_detect_secrets_config = { "name": "ShopifyDetector", "path": _custom_plugins_path + "/shopify.py", }, - { - "name": "SidekiqDetector", - "path": _custom_plugins_path + "/sidekiq.py", - }, { "name": "SlackDetector", "path": _custom_plugins_path + "/slack.py", From 96d3780a53a0cfe55d0b7de92f6aa35ed5eeb609 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 17:47:10 -0700 Subject: [PATCH 134/137] fix test secrets --- enterprise/enterprise_hooks/secret_detection.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/enterprise/enterprise_hooks/secret_detection.py b/enterprise/enterprise_hooks/secret_detection.py index 23dd2a7e0..d2bd22a5d 100644 --- a/enterprise/enterprise_hooks/secret_detection.py +++ b/enterprise/enterprise_hooks/secret_detection.py @@ -379,10 +379,6 @@ _default_detect_secrets_config = { "name": "ShopifyDetector", "path": _custom_plugins_path + "/shopify.py", }, - { - "name": "SidekiqDetector", - "path": _custom_plugins_path + "/sidekiq.py", - }, { "name": "SlackDetector", "path": _custom_plugins_path + "/slack.py", From 2faa6f704a5c742813ba398257472d906641c935 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Jun 2024 18:19:16 -0700 Subject: [PATCH 135/137] fix(factory.py): get image type from response headers Fixes https://github.com/BerriAI/litellm/issues/4441 --- litellm/llms/prompt_templates/factory.py | 28 ++++++++++++++---------- litellm/tests/test_prompt_factory.py | 16 +++++++++++--- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index a97d6812c..b35914584 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -663,19 +663,23 @@ def convert_url_to_base64(url): image_bytes = response.content base64_image = base64.b64encode(image_bytes).decode("utf-8") - img_type = url.split(".")[-1].lower() - if img_type == "jpg" or img_type == "jpeg": - img_type = "image/jpeg" - elif img_type == "png": - img_type = "image/png" - elif img_type == "gif": - img_type = "image/gif" - elif img_type == "webp": - img_type = "image/webp" + image_type = response.headers.get("Content-Type", None) + if image_type is not None and image_type.startswith("image/"): + img_type = image_type else: - raise Exception( - f"Error: Unsupported image format. Format={img_type}. Supported types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']" - ) + img_type = url.split(".")[-1].lower() + if img_type == "jpg" or img_type == "jpeg": + img_type = "image/jpeg" + elif img_type == "png": + img_type = "image/png" + elif img_type == "gif": + img_type = "image/gif" + elif img_type == "webp": + img_type = "image/webp" + else: + raise Exception( + f"Error: Unsupported image format. Format={img_type}. Supported types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']" + ) return f"data:{img_type};base64,{base64_image}" else: diff --git a/litellm/tests/test_prompt_factory.py b/litellm/tests/test_prompt_factory.py index b3aafab6e..5a368f92d 100644 --- a/litellm/tests/test_prompt_factory.py +++ b/litellm/tests/test_prompt_factory.py @@ -1,7 +1,8 @@ #### What this tests #### # This tests if prompts are being correctly formatted -import sys import os +import sys + import pytest sys.path.insert(0, os.path.abspath("../..")) @@ -10,12 +11,13 @@ sys.path.insert(0, os.path.abspath("../..")) import litellm from litellm import completion from litellm.llms.prompt_templates.factory import ( - anthropic_pt, + _bedrock_tools_pt, anthropic_messages_pt, + anthropic_pt, claude_2_1_pt, + convert_url_to_base64, llama_2_chat_pt, prompt_factory, - _bedrock_tools_pt, ) @@ -153,3 +155,11 @@ def test_bedrock_tool_calling_pt(): converted_tools = _bedrock_tools_pt(tools=tools) print(converted_tools) + + +def test_convert_url_to_img(): + response_url = convert_url_to_base64( + url="https://images.pexels.com/photos/1319515/pexels-photo-1319515.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" + ) + + assert "image/jpeg" in response_url From 94c069e8698a4a76f47d79726dc26519892bd129 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Thu, 27 Jun 2024 18:41:04 -0700 Subject: [PATCH 136/137] fix(cost_calculator.py): infer provider name if not given Fixes https://github.com/BerriAI/litellm/issues/4452 --- litellm/cost_calculator.py | 213 +++++++++++++++++++------- litellm/tests/test_completion_cost.py | 80 +++++++--- 2 files changed, 222 insertions(+), 71 deletions(-) diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index d61e812d0..2504a95f1 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -101,8 +101,12 @@ def cost_per_token( if custom_llm_provider is not None: model_with_provider = custom_llm_provider + "/" + model if region_name is not None: - model_with_provider_and_region = f"{custom_llm_provider}/{region_name}/{model}" - if model_with_provider_and_region in model_cost_ref: # use region based pricing, if it's available + model_with_provider_and_region = ( + f"{custom_llm_provider}/{region_name}/{model}" + ) + if ( + model_with_provider_and_region in model_cost_ref + ): # use region based pricing, if it's available model_with_provider = model_with_provider_and_region else: _, custom_llm_provider, _, _ = litellm.get_llm_provider(model=model) @@ -118,7 +122,9 @@ def cost_per_token( Option2. model = "openai/gpt-4" - model = provider/model Option3. model = "anthropic.claude-3" - model = model """ - if model_with_provider in model_cost_ref: # Option 2. use model with provider, model = "openai/gpt-4" + if ( + model_with_provider in model_cost_ref + ): # Option 2. use model with provider, model = "openai/gpt-4" model = model_with_provider elif model in model_cost_ref: # Option 1. use model passed, model="gpt-4" model = model @@ -154,29 +160,45 @@ def cost_per_token( ) elif model in model_cost_ref: print_verbose(f"Success: model={model} in model_cost_map") - print_verbose(f"prompt_tokens={prompt_tokens}; completion_tokens={completion_tokens}") + print_verbose( + f"prompt_tokens={prompt_tokens}; completion_tokens={completion_tokens}" + ) if ( model_cost_ref[model].get("input_cost_per_token", None) is not None and model_cost_ref[model].get("output_cost_per_token", None) is not None ): ## COST PER TOKEN ## - prompt_tokens_cost_usd_dollar = model_cost_ref[model]["input_cost_per_token"] * prompt_tokens - completion_tokens_cost_usd_dollar = model_cost_ref[model]["output_cost_per_token"] * completion_tokens - elif model_cost_ref[model].get("output_cost_per_second", None) is not None and response_time_ms is not None: + prompt_tokens_cost_usd_dollar = ( + model_cost_ref[model]["input_cost_per_token"] * prompt_tokens + ) + completion_tokens_cost_usd_dollar = ( + model_cost_ref[model]["output_cost_per_token"] * completion_tokens + ) + elif ( + model_cost_ref[model].get("output_cost_per_second", None) is not None + and response_time_ms is not None + ): print_verbose( f"For model={model} - output_cost_per_second: {model_cost_ref[model].get('output_cost_per_second')}; response time: {response_time_ms}" ) ## COST PER SECOND ## prompt_tokens_cost_usd_dollar = 0 completion_tokens_cost_usd_dollar = ( - model_cost_ref[model]["output_cost_per_second"] * response_time_ms / 1000 + model_cost_ref[model]["output_cost_per_second"] + * response_time_ms + / 1000 ) - elif model_cost_ref[model].get("input_cost_per_second", None) is not None and response_time_ms is not None: + elif ( + model_cost_ref[model].get("input_cost_per_second", None) is not None + and response_time_ms is not None + ): print_verbose( f"For model={model} - input_cost_per_second: {model_cost_ref[model].get('input_cost_per_second')}; response time: {response_time_ms}" ) ## COST PER SECOND ## - prompt_tokens_cost_usd_dollar = model_cost_ref[model]["input_cost_per_second"] * response_time_ms / 1000 + prompt_tokens_cost_usd_dollar = ( + model_cost_ref[model]["input_cost_per_second"] * response_time_ms / 1000 + ) completion_tokens_cost_usd_dollar = 0.0 print_verbose( f"Returned custom cost for model={model} - prompt_tokens_cost_usd_dollar: {prompt_tokens_cost_usd_dollar}, completion_tokens_cost_usd_dollar: {completion_tokens_cost_usd_dollar}" @@ -185,40 +207,57 @@ def cost_per_token( elif "ft:gpt-3.5-turbo" in model: print_verbose(f"Cost Tracking: {model} is an OpenAI FinteTuned LLM") # fuzzy match ft:gpt-3.5-turbo:abcd-id-cool-litellm - prompt_tokens_cost_usd_dollar = model_cost_ref["ft:gpt-3.5-turbo"]["input_cost_per_token"] * prompt_tokens + prompt_tokens_cost_usd_dollar = ( + model_cost_ref["ft:gpt-3.5-turbo"]["input_cost_per_token"] * prompt_tokens + ) completion_tokens_cost_usd_dollar = ( - model_cost_ref["ft:gpt-3.5-turbo"]["output_cost_per_token"] * completion_tokens + model_cost_ref["ft:gpt-3.5-turbo"]["output_cost_per_token"] + * completion_tokens ) return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar elif "ft:gpt-4-0613" in model: print_verbose(f"Cost Tracking: {model} is an OpenAI FinteTuned LLM") # fuzzy match ft:gpt-4-0613:abcd-id-cool-litellm - prompt_tokens_cost_usd_dollar = model_cost_ref["ft:gpt-4-0613"]["input_cost_per_token"] * prompt_tokens - completion_tokens_cost_usd_dollar = model_cost_ref["ft:gpt-4-0613"]["output_cost_per_token"] * completion_tokens + prompt_tokens_cost_usd_dollar = ( + model_cost_ref["ft:gpt-4-0613"]["input_cost_per_token"] * prompt_tokens + ) + completion_tokens_cost_usd_dollar = ( + model_cost_ref["ft:gpt-4-0613"]["output_cost_per_token"] * completion_tokens + ) return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar elif "ft:gpt-4o-2024-05-13" in model: print_verbose(f"Cost Tracking: {model} is an OpenAI FinteTuned LLM") # fuzzy match ft:gpt-4o-2024-05-13:abcd-id-cool-litellm - prompt_tokens_cost_usd_dollar = model_cost_ref["ft:gpt-4o-2024-05-13"]["input_cost_per_token"] * prompt_tokens + prompt_tokens_cost_usd_dollar = ( + model_cost_ref["ft:gpt-4o-2024-05-13"]["input_cost_per_token"] + * prompt_tokens + ) completion_tokens_cost_usd_dollar = ( - model_cost_ref["ft:gpt-4o-2024-05-13"]["output_cost_per_token"] * completion_tokens + model_cost_ref["ft:gpt-4o-2024-05-13"]["output_cost_per_token"] + * completion_tokens ) return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar elif "ft:davinci-002" in model: print_verbose(f"Cost Tracking: {model} is an OpenAI FinteTuned LLM") # fuzzy match ft:davinci-002:abcd-id-cool-litellm - prompt_tokens_cost_usd_dollar = model_cost_ref["ft:davinci-002"]["input_cost_per_token"] * prompt_tokens + prompt_tokens_cost_usd_dollar = ( + model_cost_ref["ft:davinci-002"]["input_cost_per_token"] * prompt_tokens + ) completion_tokens_cost_usd_dollar = ( - model_cost_ref["ft:davinci-002"]["output_cost_per_token"] * completion_tokens + model_cost_ref["ft:davinci-002"]["output_cost_per_token"] + * completion_tokens ) return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar elif "ft:babbage-002" in model: print_verbose(f"Cost Tracking: {model} is an OpenAI FinteTuned LLM") # fuzzy match ft:babbage-002:abcd-id-cool-litellm - prompt_tokens_cost_usd_dollar = model_cost_ref["ft:babbage-002"]["input_cost_per_token"] * prompt_tokens + prompt_tokens_cost_usd_dollar = ( + model_cost_ref["ft:babbage-002"]["input_cost_per_token"] * prompt_tokens + ) completion_tokens_cost_usd_dollar = ( - model_cost_ref["ft:babbage-002"]["output_cost_per_token"] * completion_tokens + model_cost_ref["ft:babbage-002"]["output_cost_per_token"] + * completion_tokens ) return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar elif model in litellm.azure_llms: @@ -227,17 +266,25 @@ def cost_per_token( verbose_logger.debug( f"applying cost={model_cost_ref[model]['input_cost_per_token']} for prompt_tokens={prompt_tokens}" ) - prompt_tokens_cost_usd_dollar = model_cost_ref[model]["input_cost_per_token"] * prompt_tokens + prompt_tokens_cost_usd_dollar = ( + model_cost_ref[model]["input_cost_per_token"] * prompt_tokens + ) verbose_logger.debug( f"applying cost={model_cost_ref[model]['output_cost_per_token']} for completion_tokens={completion_tokens}" ) - completion_tokens_cost_usd_dollar = model_cost_ref[model]["output_cost_per_token"] * completion_tokens + completion_tokens_cost_usd_dollar = ( + model_cost_ref[model]["output_cost_per_token"] * completion_tokens + ) return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar elif model in litellm.azure_embedding_models: verbose_logger.debug(f"Cost Tracking: {model} is an Azure Embedding Model") model = litellm.azure_embedding_models[model] - prompt_tokens_cost_usd_dollar = model_cost_ref[model]["input_cost_per_token"] * prompt_tokens - completion_tokens_cost_usd_dollar = model_cost_ref[model]["output_cost_per_token"] * completion_tokens + prompt_tokens_cost_usd_dollar = ( + model_cost_ref[model]["input_cost_per_token"] * prompt_tokens + ) + completion_tokens_cost_usd_dollar = ( + model_cost_ref[model]["output_cost_per_token"] * completion_tokens + ) return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar else: # if model is not in model_prices_and_context_window.json. Raise an exception-let users know @@ -261,7 +308,9 @@ def get_model_params_and_category(model_name) -> str: import re model_name = model_name.lower() - re_params_match = re.search(r"(\d+b)", model_name) # catch all decimals like 3b, 70b, etc + re_params_match = re.search( + r"(\d+b)", model_name + ) # catch all decimals like 3b, 70b, etc category = None if re_params_match is not None: params_match = str(re_params_match.group(1)) @@ -292,7 +341,9 @@ def get_model_params_and_category(model_name) -> str: def get_replicate_completion_pricing(completion_response=None, total_time=0.0): # see https://replicate.com/pricing # for all litellm currently supported LLMs, almost all requests go to a100_80gb - a100_80gb_price_per_second_public = 0.001400 # assume all calls sent to A100 80GB for now + a100_80gb_price_per_second_public = ( + 0.001400 # assume all calls sent to A100 80GB for now + ) if total_time == 0.0: # total time is in ms start_time = completion_response["created"] end_time = getattr(completion_response, "ended", time.time()) @@ -377,13 +428,16 @@ def completion_cost( prompt_characters = 0 completion_tokens = 0 completion_characters = 0 - custom_llm_provider = None if completion_response is not None: # get input/output tokens from completion_response prompt_tokens = completion_response.get("usage", {}).get("prompt_tokens", 0) - completion_tokens = completion_response.get("usage", {}).get("completion_tokens", 0) + completion_tokens = completion_response.get("usage", {}).get( + "completion_tokens", 0 + ) total_time = completion_response.get("_response_ms", 0) - verbose_logger.debug(f"completion_response response ms: {completion_response.get('_response_ms')} ") + verbose_logger.debug( + f"completion_response response ms: {completion_response.get('_response_ms')} " + ) model = model or completion_response.get( "model", None ) # check if user passed an override for model, if it's none check completion_response['model'] @@ -393,16 +447,30 @@ def completion_cost( and len(completion_response._hidden_params["model"]) > 0 ): model = completion_response._hidden_params.get("model", model) - custom_llm_provider = completion_response._hidden_params.get("custom_llm_provider", "") - region_name = completion_response._hidden_params.get("region_name", region_name) - size = completion_response._hidden_params.get("optional_params", {}).get( + custom_llm_provider = completion_response._hidden_params.get( + "custom_llm_provider", "" + ) + region_name = completion_response._hidden_params.get( + "region_name", region_name + ) + size = completion_response._hidden_params.get( + "optional_params", {} + ).get( "size", "1024-x-1024" ) # openai default - quality = completion_response._hidden_params.get("optional_params", {}).get( + quality = completion_response._hidden_params.get( + "optional_params", {} + ).get( "quality", "standard" ) # openai default - n = completion_response._hidden_params.get("optional_params", {}).get("n", 1) # openai default + n = completion_response._hidden_params.get("optional_params", {}).get( + "n", 1 + ) # openai default else: + if model is None: + raise ValueError( + f"Model is None and does not exist in passed completion_response. Passed completion_response={completion_response}, model={model}" + ) if len(messages) > 0: prompt_tokens = token_counter(model=model, messages=messages) elif len(prompt) > 0: @@ -413,7 +481,19 @@ def completion_cost( f"Model is None and does not exist in passed completion_response. Passed completion_response={completion_response}, model={model}" ) - if call_type == CallTypes.image_generation.value or call_type == CallTypes.aimage_generation.value: + if custom_llm_provider is None: + try: + _, custom_llm_provider, _, _ = litellm.get_llm_provider(model=model) + except Exception as e: + verbose_logger.error( + "litellm.cost_calculator.py::completion_cost() - Error inferring custom_llm_provider - {}".format( + str(e) + ) + ) + if ( + call_type == CallTypes.image_generation.value + or call_type == CallTypes.aimage_generation.value + ): ### IMAGE GENERATION COST CALCULATION ### if custom_llm_provider == "vertex_ai": # https://cloud.google.com/vertex-ai/generative-ai/pricing @@ -431,23 +511,43 @@ def completion_cost( height = int(size[0]) # if it's 1024-x-1024 vs. 1024x1024 width = int(size[1]) verbose_logger.debug(f"image_gen_model_name: {image_gen_model_name}") - verbose_logger.debug(f"image_gen_model_name_with_quality: {image_gen_model_name_with_quality}") + verbose_logger.debug( + f"image_gen_model_name_with_quality: {image_gen_model_name_with_quality}" + ) if image_gen_model_name in litellm.model_cost: - return litellm.model_cost[image_gen_model_name]["input_cost_per_pixel"] * height * width * n + return ( + litellm.model_cost[image_gen_model_name]["input_cost_per_pixel"] + * height + * width + * n + ) elif image_gen_model_name_with_quality in litellm.model_cost: return ( - litellm.model_cost[image_gen_model_name_with_quality]["input_cost_per_pixel"] * height * width * n + litellm.model_cost[image_gen_model_name_with_quality][ + "input_cost_per_pixel" + ] + * height + * width + * n ) else: - raise Exception(f"Model={image_gen_model_name} not found in completion cost model map") + raise Exception( + f"Model={image_gen_model_name} not found in completion cost model map" + ) # Calculate cost based on prompt_tokens, completion_tokens - if "togethercomputer" in model or "together_ai" in model or custom_llm_provider == "together_ai": + if ( + "togethercomputer" in model + or "together_ai" in model + or custom_llm_provider == "together_ai" + ): # together ai prices based on size of llm # get_model_params_and_category takes a model name and returns the category of LLM size it is in model_prices_and_context_window.json model = get_model_params_and_category(model) # replicate llms are calculate based on time for request running # see https://replicate.com/pricing - elif (model in litellm.replicate_models or "replicate" in model) and model not in litellm.model_cost: + elif ( + model in litellm.replicate_models or "replicate" in model + ) and model not in litellm.model_cost: # for unmapped replicate model, default to replicate's time tracking logic return get_replicate_completion_pricing(completion_response, total_time) @@ -456,23 +556,26 @@ def completion_cost( f"Model is None and does not exist in passed completion_response. Passed completion_response={completion_response}, model={model}" ) - if ( - custom_llm_provider is not None - and custom_llm_provider == "vertex_ai" - and completion_response is not None - and isinstance(completion_response, ModelResponse) - ): + if custom_llm_provider is not None and custom_llm_provider == "vertex_ai": # Calculate the prompt characters + response characters if len("messages") > 0: - prompt_string = litellm.utils.get_formatted_prompt(data={"messages": messages}, call_type="completion") + prompt_string = litellm.utils.get_formatted_prompt( + data={"messages": messages}, call_type="completion" + ) else: prompt_string = "" prompt_characters = litellm.utils._count_characters(text=prompt_string) + if completion_response is not None and isinstance( + completion_response, ModelResponse + ): + completion_string = litellm.utils.get_response_string( + response_obj=completion_response + ) - completion_string = litellm.utils.get_response_string(response_obj=completion_response) - - completion_characters = litellm.utils._count_characters(text=completion_string) + completion_characters = litellm.utils._count_characters( + text=completion_string + ) ( prompt_tokens_cost_usd_dollar, @@ -544,7 +647,9 @@ def response_cost_calculator( ) else: if ( - model in litellm.model_cost and custom_pricing is not None and custom_llm_provider is True + model in litellm.model_cost + and custom_pricing is not None + and custom_llm_provider is True ): # override defaults if custom pricing is set base_model = model # base_model defaults to None if not set on model_info @@ -556,5 +661,7 @@ def response_cost_calculator( ) return response_cost except litellm.NotFoundError as e: - print_verbose(f"Model={model} for LLM Provider={custom_llm_provider} not found in completion cost map.") + print_verbose( + f"Model={model} for LLM Provider={custom_llm_provider} not found in completion cost map." + ) return None diff --git a/litellm/tests/test_completion_cost.py b/litellm/tests/test_completion_cost.py index e854345b3..3a65f7294 100644 --- a/litellm/tests/test_completion_cost.py +++ b/litellm/tests/test_completion_cost.py @@ -4,7 +4,9 @@ import traceback import litellm.cost_calculator -sys.path.insert(0, os.path.abspath("../..")) # Adds the parent directory to the system path +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path import asyncio import time from typing import Optional @@ -167,11 +169,15 @@ def test_cost_ft_gpt_35(): input_cost = model_cost["ft:gpt-3.5-turbo"]["input_cost_per_token"] output_cost = model_cost["ft:gpt-3.5-turbo"]["output_cost_per_token"] print(input_cost, output_cost) - expected_cost = (input_cost * resp.usage.prompt_tokens) + (output_cost * resp.usage.completion_tokens) + expected_cost = (input_cost * resp.usage.prompt_tokens) + ( + output_cost * resp.usage.completion_tokens + ) print("\n Excpected cost", expected_cost) assert cost == expected_cost except Exception as e: - pytest.fail(f"Cost Calc failed for ft:gpt-3.5. Expected {expected_cost}, Calculated cost {cost}") + pytest.fail( + f"Cost Calc failed for ft:gpt-3.5. Expected {expected_cost}, Calculated cost {cost}" + ) # test_cost_ft_gpt_35() @@ -200,15 +206,21 @@ def test_cost_azure_gpt_35(): usage=Usage(prompt_tokens=21, completion_tokens=17, total_tokens=38), ) - cost = litellm.completion_cost(completion_response=resp, model="azure/gpt-35-turbo") + cost = litellm.completion_cost( + completion_response=resp, model="azure/gpt-35-turbo" + ) print("\n Calculated Cost for azure/gpt-3.5-turbo", cost) input_cost = model_cost["azure/gpt-35-turbo"]["input_cost_per_token"] output_cost = model_cost["azure/gpt-35-turbo"]["output_cost_per_token"] - expected_cost = (input_cost * resp.usage.prompt_tokens) + (output_cost * resp.usage.completion_tokens) + expected_cost = (input_cost * resp.usage.prompt_tokens) + ( + output_cost * resp.usage.completion_tokens + ) print("\n Excpected cost", expected_cost) assert cost == expected_cost except Exception as e: - pytest.fail(f"Cost Calc failed for azure/gpt-3.5-turbo. Expected {expected_cost}, Calculated cost {cost}") + pytest.fail( + f"Cost Calc failed for azure/gpt-3.5-turbo. Expected {expected_cost}, Calculated cost {cost}" + ) # test_cost_azure_gpt_35() @@ -239,7 +251,9 @@ def test_cost_azure_embedding(): assert cost == expected_cost except Exception as e: - pytest.fail(f"Cost Calc failed for azure/gpt-3.5-turbo. Expected {expected_cost}, Calculated cost {cost}") + pytest.fail( + f"Cost Calc failed for azure/gpt-3.5-turbo. Expected {expected_cost}, Calculated cost {cost}" + ) # test_cost_azure_embedding() @@ -315,7 +329,9 @@ def test_cost_bedrock_pricing_actual_calls(): litellm.set_verbose = True model = "anthropic.claude-instant-v1" messages = [{"role": "user", "content": "Hey, how's it going?"}] - response = litellm.completion(model=model, messages=messages, mock_response="hello cool one") + response = litellm.completion( + model=model, messages=messages, mock_response="hello cool one" + ) print("response", response) cost = litellm.completion_cost( @@ -345,7 +361,8 @@ def test_whisper_openai(): print(f"cost: {cost}") print(f"whisper dict: {litellm.model_cost['whisper-1']}") expected_cost = round( - litellm.model_cost["whisper-1"]["output_cost_per_second"] * _total_time_in_seconds, + litellm.model_cost["whisper-1"]["output_cost_per_second"] + * _total_time_in_seconds, 5, ) assert cost == expected_cost @@ -365,12 +382,15 @@ def test_whisper_azure(): _total_time_in_seconds = 3 transcription._response_ms = _total_time_in_seconds * 1000 - cost = litellm.completion_cost(model="azure/azure-whisper", completion_response=transcription) + cost = litellm.completion_cost( + model="azure/azure-whisper", completion_response=transcription + ) print(f"cost: {cost}") print(f"whisper dict: {litellm.model_cost['whisper-1']}") expected_cost = round( - litellm.model_cost["whisper-1"]["output_cost_per_second"] * _total_time_in_seconds, + litellm.model_cost["whisper-1"]["output_cost_per_second"] + * _total_time_in_seconds, 5, ) assert cost == expected_cost @@ -401,7 +421,9 @@ def test_dalle_3_azure_cost_tracking(): response.usage = {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0} response._hidden_params = {"model": "dall-e-3", "model_id": None} print(f"response hidden params: {response._hidden_params}") - cost = litellm.completion_cost(completion_response=response, call_type="image_generation") + cost = litellm.completion_cost( + completion_response=response, call_type="image_generation" + ) assert cost > 0 @@ -433,7 +455,9 @@ def test_replicate_llama3_cost_tracking(): model="replicate/meta/meta-llama-3-8b-instruct", object="chat.completion", system_fingerprint=None, - usage=litellm.utils.Usage(prompt_tokens=48, completion_tokens=31, total_tokens=79), + usage=litellm.utils.Usage( + prompt_tokens=48, completion_tokens=31, total_tokens=79 + ), ) cost = litellm.completion_cost( completion_response=response, @@ -443,8 +467,14 @@ def test_replicate_llama3_cost_tracking(): print(f"cost: {cost}") cost = round(cost, 5) expected_cost = round( - litellm.model_cost["replicate/meta/meta-llama-3-8b-instruct"]["input_cost_per_token"] * 48 - + litellm.model_cost["replicate/meta/meta-llama-3-8b-instruct"]["output_cost_per_token"] * 31, + litellm.model_cost["replicate/meta/meta-llama-3-8b-instruct"][ + "input_cost_per_token" + ] + * 48 + + litellm.model_cost["replicate/meta/meta-llama-3-8b-instruct"][ + "output_cost_per_token" + ] + * 31, 5, ) assert cost == expected_cost @@ -538,7 +568,9 @@ def test_together_ai_qwen_completion_cost(): "custom_cost_per_second": None, } - response = litellm.cost_calculator.get_model_params_and_category(model_name="qwen/Qwen2-72B-Instruct") + response = litellm.cost_calculator.get_model_params_and_category( + model_name="qwen/Qwen2-72B-Instruct" + ) assert response == "together-ai-41.1b-80b" @@ -576,8 +608,12 @@ def test_gemini_completion_cost(above_128k, provider): ), "model info for model={} does not have pricing for > 128k tokens\nmodel_info={}".format( model_name, model_info ) - input_cost = prompt_tokens * model_info["input_cost_per_token_above_128k_tokens"] - output_cost = output_tokens * model_info["output_cost_per_token_above_128k_tokens"] + input_cost = ( + prompt_tokens * model_info["input_cost_per_token_above_128k_tokens"] + ) + output_cost = ( + output_tokens * model_info["output_cost_per_token_above_128k_tokens"] + ) else: input_cost = prompt_tokens * model_info["input_cost_per_token"] output_cost = output_tokens * model_info["output_cost_per_token"] @@ -674,3 +710,11 @@ def test_vertex_ai_claude_completion_cost(): ) predicted_cost = input_tokens * 0.000003 + 0.000015 * output_tokens assert cost == predicted_cost + + +def test_vertex_ai_gemini_predict_cost(): + model = "gemini-1.5-flash" + messages = [{"role": "user", "content": "Hey, hows it going???"}] + predictive_cost = completion_cost(model=model, messages=messages) + + assert predictive_cost > 0 From 413877d1c6427a9e9c7e4badc146b978337f92d8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 27 Jun 2024 21:03:36 -0700 Subject: [PATCH 137/137] fix pre call utils adding extra headers --- litellm/proxy/litellm_pre_call_utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 963cdf027..673b027ca 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -175,8 +175,14 @@ async def add_litellm_data_to_request( def _add_otel_traceparent_to_data(data: dict, request: Request): + from litellm.proxy.proxy_server import open_telemetry_logger + if data is None: return + if open_telemetry_logger is None: + # if user is not use OTEL don't send extra_headers + # relevant issue: https://github.com/BerriAI/litellm/issues/4448 + return if request.headers: if "traceparent" in request.headers: # we want to forward this to the LLM Provider