diff --git a/docs/my-website/docs/enterprise.md b/docs/my-website/docs/enterprise.md index cfab07c22a..e3758266a1 100644 --- a/docs/my-website/docs/enterprise.md +++ b/docs/my-website/docs/enterprise.md @@ -10,22 +10,23 @@ Interested in Enterprise? Schedule a meeting with us here 👉 This covers: - **Enterprise Features** - **Security** - - ✅ [SSO for Admin UI](./ui.md#✨-enterprise-features) - - ✅ [Audit Logs with retention policy](#audit-logs) + - ✅ [SSO for Admin UI](./proxy/ui#✨-enterprise-features) + - ✅ [Audit Logs with retention policy](./proxy/enterprise#audit-logs) - ✅ [JWT-Auth](../docs/proxy/token_auth.md) - - ✅ [Control available public, private routes](#control-available-public-private-routes) - - ✅ [[BETA] AWS Key Manager v2 - Key Decryption](#beta-aws-key-manager---key-decryption) - - ✅ [Use LiteLLM keys/authentication on Pass Through Endpoints](pass_through#✨-enterprise---use-litellm-keysauthentication-on-pass-through-endpoints) - - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) + - ✅ [Control available public, private routes](./proxy/enterprise#control-available-public-private-routes) + - ✅ [[BETA] AWS Key Manager v2 - Key Decryption](./proxy/enterprise#beta-aws-key-manager---key-decryption) + - ✅ [Use LiteLLM keys/authentication on Pass Through Endpoints](./proxy/pass_through#✨-enterprise---use-litellm-keysauthentication-on-pass-through-endpoints) + - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](./proxy/enterprise#enforce-required-params-for-llm-requests) - **Spend Tracking** - - ✅ [Tracking Spend for Custom Tags](#tracking-spend-for-custom-tags) + - ✅ [Tracking Spend for Custom Tags](./proxy/enterprise#tracking-spend-for-custom-tags) + - ✅ [API Endpoints to get Spend Reports per Team, API Key, Customer](./proxy/cost_tracking.md#✨-enterprise-api-endpoints-to-get-spend) - **Guardrails, PII Masking, Content Moderation** - - ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](#content-moderation) - - ✅ [Prompt Injection Detection (with LakeraAI API)](#prompt-injection-detection---lakeraai) + - ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](./proxy/enterprise#content-moderation) + - ✅ [Prompt Injection Detection (with LakeraAI API)](./proxy/enterprise#prompt-injection-detection---lakeraai) - ✅ Reject calls from Blocked User list - ✅ Reject calls (incoming / outgoing) with Banned Keywords (e.g. competitors) - **Custom Branding** - - ✅ [Custom Branding + Routes on Swagger Docs](#swagger-docs---custom-routes--branding) + - ✅ [Custom Branding + Routes on Swagger Docs](./proxy/enterprise#swagger-docs---custom-routes--branding) - ✅ [Public Model Hub](../docs/proxy/enterprise.md#public-model-hub) - ✅ [Custom Email Branding](../docs/proxy/email.md#customizing-email-branding) - ✅ **Feature Prioritization** diff --git a/docs/my-website/docs/proxy/cost_tracking.md b/docs/my-website/docs/proxy/cost_tracking.md index 3ccf8f383a..fe3a462508 100644 --- a/docs/my-website/docs/proxy/cost_tracking.md +++ b/docs/my-website/docs/proxy/cost_tracking.md @@ -117,6 +117,8 @@ That's IT. Now Verify your spend was tracked +Expect to see `x-litellm-response-cost` in the response headers with calculated cost + @@ -145,16 +147,16 @@ Navigate to the Usage Tab on the LiteLLM UI (found on https://your-proxy-endpoin -## API Endpoints to get Spend -#### Getting Spend Reports - To Charge Other Teams, Customers - -Use the `/global/spend/report` endpoint to get daily spend report per -- team -- customer [this is `user` passed to `/chat/completions` request](#how-to-track-spend-with-litellm) - +## ✨ (Enterprise) API Endpoints to get Spend +#### Getting Spend Reports - To Charge Other Teams, Customers + +Use the `/global/spend/report` endpoint to get daily spend report per +- Team +- Customer [this is `user` passed to `/chat/completions` request](#how-to-track-spend-with-litellm) +- [LiteLLM API key](virtual_keys.md) @@ -337,6 +339,61 @@ curl -X GET 'http://localhost:4000/global/spend/report?start_date=2024-04-01&end ``` + + + + + +👉 Key Change: Specify `group_by=api_key` + + +```shell +curl -X GET 'http://localhost:4000/global/spend/report?start_date=2024-04-01&end_date=2024-06-30&group_by=api_key' \ + -H 'Authorization: Bearer sk-1234' +``` + +##### Example Response + + +```shell +[ + { + "api_key": "ad64768847d05d978d62f623d872bff0f9616cc14b9c1e651c84d14fe3b9f539", + "total_cost": 0.0002157, + "total_input_tokens": 45.0, + "total_output_tokens": 1375.0, + "model_details": [ + { + "model": "gpt-3.5-turbo", + "total_cost": 0.0001095, + "total_input_tokens": 9, + "total_output_tokens": 70 + }, + { + "model": "llama3-8b-8192", + "total_cost": 0.0001062, + "total_input_tokens": 36, + "total_output_tokens": 1305 + } + ] + }, + { + "api_key": "88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b", + "total_cost": 0.00012924, + "total_input_tokens": 36.0, + "total_output_tokens": 1593.0, + "model_details": [ + { + "model": "llama3-8b-8192", + "total_cost": 0.00012924, + "total_input_tokens": 36, + "total_output_tokens": 1593 + } + ] + } +] +``` + diff --git a/docs/my-website/docs/proxy/enterprise.md b/docs/my-website/docs/proxy/enterprise.md index d580f58b6b..e061a917e2 100644 --- a/docs/my-website/docs/proxy/enterprise.md +++ b/docs/my-website/docs/proxy/enterprise.md @@ -22,6 +22,7 @@ Features: - ✅ [Enforce Required Params for LLM Requests (ex. Reject requests missing ["metadata"]["generation_name"])](#enforce-required-params-for-llm-requests) - **Spend Tracking** - ✅ [Tracking Spend for Custom Tags](#tracking-spend-for-custom-tags) + - ✅ [API Endpoints to get Spend Reports per Team, API Key, Customer](cost_tracking.md#✨-enterprise-api-endpoints-to-get-spend) - **Guardrails, PII Masking, Content Moderation** - ✅ [Content Moderation with LLM Guard, LlamaGuard, Secret Detection, Google Text Moderations](#content-moderation) - ✅ [Prompt Injection Detection (with LakeraAI API)](#prompt-injection-detection---lakeraai) diff --git a/docs/my-website/img/response_cost_img.png b/docs/my-website/img/response_cost_img.png index 9f466b3fbb..2fa9c20095 100644 Binary files a/docs/my-website/img/response_cost_img.png and b/docs/my-website/img/response_cost_img.png differ diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 640c7695a0..1f1aaf0eea 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1622,7 +1622,7 @@ class ProxyException(Exception): } -class CommonProxyErrors(enum.Enum): +class CommonProxyErrors(str, enum.Enum): db_not_connected_error = "DB not connected" no_llm_router = "No models configured on proxy" not_allowed_access = "Admin-only endpoint. Not allowed to access this." diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 1fbd95b3cf..11406b162f 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -817,9 +817,9 @@ async def get_global_spend_report( default=None, description="Time till which to view spend", ), - group_by: Optional[Literal["team", "customer"]] = fastapi.Query( + group_by: Optional[Literal["team", "customer", "api_key"]] = fastapi.Query( default="team", - description="Group spend by internal team or customer", + description="Group spend by internal team or customer or api_key", ), ): """ @@ -860,7 +860,7 @@ async def get_global_spend_report( start_date_obj = datetime.strptime(start_date, "%Y-%m-%d") end_date_obj = datetime.strptime(end_date, "%Y-%m-%d") - from litellm.proxy.proxy_server import prisma_client + from litellm.proxy.proxy_server import premium_user, prisma_client try: if prisma_client is None: @@ -868,6 +868,11 @@ async def get_global_spend_report( f"Database not connected. Connect a database to your proxy - https://docs.litellm.ai/docs/simple_proxy#managing-auth---virtual-keys" ) + if premium_user is not True: + raise ValueError( + "/spend/report endpoint" + CommonProxyErrors.not_premium_user.value + ) + if group_by == "team": # first get data from spend logs -> SpendByModelApiKey # then read data from "SpendByModelApiKey" to format the response obj @@ -992,6 +997,48 @@ async def get_global_spend_report( return [] return db_response + elif group_by == "api_key": + sql_query = """ + WITH SpendByModelApiKey AS ( + SELECT + sl.api_key, + sl.model, + SUM(sl.spend) AS model_cost, + SUM(sl.prompt_tokens) AS model_input_tokens, + SUM(sl.completion_tokens) AS model_output_tokens + FROM + "LiteLLM_SpendLogs" sl + WHERE + sl."startTime" BETWEEN $1::date AND $2::date + GROUP BY + sl.api_key, + sl.model + ) + SELECT + api_key, + SUM(model_cost) AS total_cost, + SUM(model_input_tokens) AS total_input_tokens, + SUM(model_output_tokens) AS total_output_tokens, + jsonb_agg(jsonb_build_object( + 'model', model, + 'total_cost', model_cost, + 'total_input_tokens', model_input_tokens, + 'total_output_tokens', model_output_tokens + )) AS model_details + FROM + SpendByModelApiKey + GROUP BY + api_key + ORDER BY + total_cost DESC; + """ + db_response = await prisma_client.db.query_raw( + sql_query, start_date_obj, end_date_obj + ) + if db_response is None: + return [] + + return db_response except Exception as e: raise HTTPException(