diff --git a/enterprise/utils.py b/enterprise/utils.py index c575a74f6..d5f0aef85 100644 --- a/enterprise/utils.py +++ b/enterprise/utils.py @@ -1,5 +1,6 @@ # Enterprise Proxy Util Endpoints from litellm._logging import verbose_logger +import collections async def get_spend_by_tags(start_date=None, end_date=None, prisma_client=None): @@ -17,6 +18,43 @@ async def get_spend_by_tags(start_date=None, end_date=None, prisma_client=None): return response +async def ui_get_spend_by_tags(start_date=None, end_date=None, prisma_client=None): + response = await prisma_client.db.query_raw( + """ + SELECT + jsonb_array_elements_text(request_tags) AS individual_request_tag, + DATE(s."startTime") AS spend_date, + COUNT(*) AS log_count, + SUM(spend) AS total_spend + FROM "LiteLLM_SpendLogs" s + WHERE s."startTime" >= current_date - interval '30 days' + GROUP BY individual_request_tag, spend_date + ORDER BY spend_date; + """ + ) + + # print("tags - spend") + # print(response) + # Bar Chart 1 - Spend per tag - Top 10 tags by spend + total_spend_per_tag = collections.defaultdict(float) + for row in response: + tag_name = row["individual_request_tag"] + tag_spend = row["total_spend"] + + total_spend_per_tag[tag_name] += tag_spend + + # get top 10 tags + top_10_tags = sorted(total_spend_per_tag.items(), key=lambda x: x[1], reverse=True)[ + :10 + ] + # convert to ui format + ui_top_10_tags = [{"name": tag[0], "value": tag[1]} for tag in top_10_tags] + + # Bar Chart 2 - Daily Spend per tag + + return {"top_10_tags": ui_top_10_tags, "daily_spend_per_tag": total_spend_per_tag} + + async def view_spend_logs_from_clickhouse( api_key=None, user_id=None, request_id=None, start_date=None, end_date=None ): diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 8a694c39a..e68f05bc0 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -4753,6 +4753,73 @@ async def view_spend_tags( ) +@router.get( + "/global/spend/tags", + tags=["Budget & Spend Tracking"], + dependencies=[Depends(user_api_key_auth)], + include_in_schema=False, + responses={ + 200: {"model": List[LiteLLM_SpendLogs]}, + }, +) +async def global_view_spend_tags( + start_date: Optional[str] = fastapi.Query( + default=None, + description="Time from which to start viewing key spend", + ), + end_date: Optional[str] = fastapi.Query( + default=None, + description="Time till which to view key spend", + ), +): + """ + LiteLLM Enterprise - View Spend Per Request Tag. Used by LiteLLM UI + + Example Request: + ``` + curl -X GET "http://0.0.0.0:8000/spend/tags" \ +-H "Authorization: Bearer sk-1234" + ``` + + Spend with Start Date and End Date + ``` + curl -X GET "http://0.0.0.0:8000/spend/tags?start_date=2022-01-01&end_date=2022-02-01" \ +-H "Authorization: Bearer sk-1234" + ``` + """ + + from enterprise.utils import ui_get_spend_by_tags + + global prisma_client + try: + if prisma_client is None: + raise Exception( + f"Database not connected. Connect a database to your proxy - https://docs.litellm.ai/docs/simple_proxy#managing-auth---virtual-keys" + ) + + response = await ui_get_spend_by_tags( + start_date=start_date, end_date=end_date, prisma_client=prisma_client + ) + + return response + except Exception as e: + if isinstance(e, HTTPException): + raise ProxyException( + message=getattr(e, "detail", f"/spend/tags Error({str(e)})"), + type="internal_error", + param=getattr(e, "param", "None"), + code=getattr(e, "status_code", status.HTTP_500_INTERNAL_SERVER_ERROR), + ) + elif isinstance(e, ProxyException): + raise e + raise ProxyException( + message="/spend/tags Error" + str(e), + type="internal_error", + param=getattr(e, "param", "None"), + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + @router.post( "/spend/calculate", tags=["Budget & Spend Tracking"], diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index ec0945e0b..3138ffbff 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -434,6 +434,36 @@ export const teamSpendLogsCall = async (accessToken: String) => { } }; + +export const tagsSpendLogsCall = async (accessToken: String) => { + try { + const url = proxyBaseUrl + ? `${proxyBaseUrl}/global/spend/tags` + : `/global/spend/tags`; + console.log("in tagsSpendLogsCall:", url); + const response = await fetch(`${url}`, { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + if (!response.ok) { + const errorData = await response.text(); + message.error(errorData); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.log(data); + return data; + } catch (error) { + console.error("Failed to create key:", error); + throw error; + } +}; + + export const userSpendLogsCall = async ( accessToken: String, token: String, diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index 0f540f73e..670e194bc 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -11,6 +11,7 @@ import { adminTopKeysCall, adminTopModelsCall, teamSpendLogsCall, + tagsSpendLogsCall } from "./networking"; import { start } from "repl"; @@ -139,6 +140,7 @@ const UsagePage: React.FC = ({ const [topModels, setTopModels] = useState([]); const [topUsers, setTopUsers] = useState([]); const [teamSpendData, setTeamSpendData] = useState([]); + const [topTagsData, setTopTagsData] = useState([]); const [uniqueTeamIds, setUniqueTeamIds] = useState([]); const [totalSpendPerTeam, setTotalSpendPerTeam] = useState([]); @@ -217,6 +219,10 @@ const UsagePage: React.FC = ({ }) setTotalSpendPerTeam(total_spend_per_team); + + //get top tags + const top_tags = await tagsSpendLogsCall(accessToken); + setTopTagsData(top_tags.top_10_tags); } else if (userRole == "App Owner") { await userSpendLogsCall( accessToken, @@ -273,6 +279,7 @@ const UsagePage: React.FC = ({ All Up Team Based Usage + Tag Based Usage @@ -371,6 +378,34 @@ const UsagePage: React.FC = ({ + + + + + Total Spend Per Tag + + + + + Daily Spend Per Tag + + + + + + +