From 3d98a462ae3b416deff406d47c192b794f38ecd1 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 3 Jun 2024 14:02:03 -0700 Subject: [PATCH 1/7] feat - return all tag names --- litellm/proxy/proxy_server.py | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 9ba05e5ff5..a222f7d27c 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -8229,6 +8229,57 @@ async def get_global_spend_report( ) +@router.get( + "/global/spend/all_tag_names", + tags=["Budget & Spend Tracking"], + dependencies=[Depends(user_api_key_auth)], + include_in_schema=False, + responses={ + 200: {"model": List[LiteLLM_SpendLogs]}, + }, +) +async def global_get_all_tag_names(): + 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" + ) + + sql_query = """ + SELECT + jsonb_array_elements_text(request_tags) AS individual_request_tag + FROM "LiteLLM_SpendLogs" + GROUP BY individual_request_tag + """ + + db_response = await prisma_client.db.query_raw(sql_query) + if db_response is None: + return [] + + _tag_names = [] + for row in db_response: + _tag_names.append(row.get("individual_request_tag")) + + return {"tag_names": _tag_names} + + except Exception as e: + if isinstance(e, HTTPException): + raise ProxyException( + message=getattr(e, "detail", f"/spend/all_tag_names 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/all_tag_names Error" + str(e), + type="internal_error", + param=getattr(e, "param", "None"), + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + @router.get( "/global/spend/tags", tags=["Budget & Spend Tracking"], From 991d418984f7e8c2e57ad3fbe5a0157ce18799a6 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 3 Jun 2024 14:15:51 -0700 Subject: [PATCH 2/7] ui - working filter by tag --- .../src/components/networking.tsx | 32 +++++++++++++ ui/litellm-dashboard/src/components/usage.tsx | 47 +++++++++++++++++-- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 7a57a0a960..9740c83e68 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -1012,6 +1012,38 @@ export const tagsSpendLogsCall = async ( } }; +export const allTagNamesCall = async ( + accessToken: String, +) => { + try { + let url = proxyBaseUrl + ? `${proxyBaseUrl}/global/spend/all_tag_names` + : `/global/spend/all_tag_names`; + + + console.log("in global/spend/all_tag_names call", url); + const response = await fetch(`${url}`, { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + if (!response.ok) { + const errorData = await response.text(); + 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 ea50422c22..90405f08d0 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -12,6 +12,8 @@ import { AreaChart, Callout, Button, + MultiSelect, + MultiSelectItem } from "@tremor/react"; import { userSpendLogsCall, @@ -22,6 +24,7 @@ import { adminTopEndUsersCall, teamSpendLogsCall, tagsSpendLogsCall, + allTagNamesCall, modelMetricsCall, modelAvailableCall, modelInfoCall, @@ -134,6 +137,7 @@ const UsagePage: React.FC = ({ const [topUsers, setTopUsers] = useState([]); const [teamSpendData, setTeamSpendData] = useState([]); const [topTagsData, setTopTagsData] = useState([]); + const [allTagNames, setAllTagNames] = useState([]); const [uniqueTeamIds, setUniqueTeamIds] = useState([]); const [totalSpendPerTeam, setTotalSpendPerTeam] = useState([]); const [spendByProvider, setSpendByProvider] = useState([]); @@ -293,6 +297,10 @@ const UsagePage: React.FC = ({ const top_tags = await tagsSpendLogsCall(accessToken, dateValue.from?.toISOString(), dateValue.to?.toISOString()); setTopTagsData(top_tags.spend_per_tag); + // all_tag_names + const all_tag_names = await allTagNamesCall(accessToken); + setAllTagNames(all_tag_names); + // get spend per end-user let spend_user_call = await adminTopEndUsersCall(accessToken, null, undefined, undefined); setTopUsers(spend_user_call); @@ -365,7 +373,7 @@ const UsagePage: React.FC = ({ All Up Team Based Usage - End User Usage + Customer Usage Tag Based Usage @@ -656,7 +664,7 @@ const UsagePage: React.FC = ({ -

End-Users of your LLM API calls. Tracked when a `user` param is passed in your LLM calls docs here

+

Customers of your LLM API calls. Tracked when a `user` param is passed in your LLM calls docs here

Select Time Range @@ -717,7 +725,7 @@ const UsagePage: React.FC = ({ - End User + Customer Spend Total Events @@ -738,8 +746,8 @@ const UsagePage: React.FC = ({ - - + + = ({ }} /> + + + + + + { + allTagNames?.map((tag: any, index: number) => { + return ( + { + updateTagSpendData(dateValue.from, dateValue.to); + }} + > + {tag} + + ); + }) + } + + + + + + + + + Spend Per Tag Get Started Tracking cost per tag here From 2ac8f1c6ec73e9f6d7cec6750a0c7489122b3ba4 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 3 Jun 2024 14:21:35 -0700 Subject: [PATCH 3/7] get all tags on ui --- litellm/proxy/proxy_server.py | 7 +++---- ui/litellm-dashboard/src/components/usage.tsx | 16 ++++++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index a222f7d27c..82f665a822 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -8246,10 +8246,9 @@ async def global_get_all_tag_names(): ) sql_query = """ - SELECT - jsonb_array_elements_text(request_tags) AS individual_request_tag - FROM "LiteLLM_SpendLogs" - GROUP BY individual_request_tag + SELECT DISTINCT + jsonb_array_elements_text(request_tags) AS individual_request_tag + FROM "LiteLLM_SpendLogs"; """ db_response = await prisma_client.db.query_raw(sql_query) diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index 90405f08d0..e3425aa8de 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -299,7 +299,7 @@ const UsagePage: React.FC = ({ // all_tag_names const all_tag_names = await allTagNamesCall(accessToken); - setAllTagNames(all_tag_names); + setAllTagNames(all_tag_names.tag_names); // get spend per end-user let spend_user_call = await adminTopEndUsersCall(accessToken, null, undefined, undefined); @@ -762,16 +762,20 @@ const UsagePage: React.FC = ({ - + + + All Tags + { - allTagNames?.map((tag: any, index: number) => { + allTagNames && allTagNames?.map((tag: any, index: number) => { return ( { - updateTagSpendData(dateValue.from, dateValue.to); - }} > {tag} From 45cb89968788c10cb098aa5eec713dc10488d0ea Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 3 Jun 2024 15:12:09 -0700 Subject: [PATCH 4/7] fix - working filter by tag query --- enterprise/utils.py | 91 +++++++++++++++++++++++++++-------- litellm/proxy/proxy_server.py | 13 +++-- 2 files changed, 81 insertions(+), 23 deletions(-) diff --git a/enterprise/utils.py b/enterprise/utils.py index 90b14314c2..b8f660927b 100644 --- a/enterprise/utils.py +++ b/enterprise/utils.py @@ -1,5 +1,7 @@ # Enterprise Proxy Util Endpoints +from typing import Optional, List from litellm._logging import verbose_logger +from litellm.proxy.proxy_server import PrismaClient, HTTPException import collections from datetime import datetime @@ -19,27 +21,76 @@ 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: str, end_date: str, prisma_client): - - sql_query = """ - 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 - DATE(s."startTime") >= $1::date - AND DATE(s."startTime") <= $2::date - GROUP BY individual_request_tag, spend_date - ORDER BY spend_date - LIMIT 100; +async def ui_get_spend_by_tags( + start_date: str, + end_date: str, + prisma_client: Optional[PrismaClient] = None, + tags_str: Optional[str] = None, +): """ - response = await prisma_client.db.query_raw( - sql_query, - start_date, - end_date, - ) + Should cover 2 cases: + 1. When user is getting spend for all_tags. "all_tags" in tags_list + 2. When user is getting spend for specific tags. + """ + + # tags_str is a list of strings csv of tags + # tags_str = tag1,tag2,tag3 + # convert to list if it's not None + tags_list: Optional[List[str]] = None + if tags_str is not None and len(tags_str) > 0: + tags_list = tags_str.split(",") + + if prisma_client is None: + raise HTTPException(status_code=500, detail={"error": "No db connected"}) + + response = None + if tags_list is None or (isinstance(tags_list, list) and "all-tags" in tags_list): + # Get spend for all tags + sql_query = """ + 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 + DATE(s."startTime") >= $1::date + AND DATE(s."startTime") <= $2::date + GROUP BY individual_request_tag, spend_date + ORDER BY total_spend DESC; + """ + response = await prisma_client.db.query_raw( + sql_query, + start_date, + end_date, + ) + else: + # filter by tags list + sql_query = """ + SELECT + individual_request_tag, + COUNT(*) AS log_count, + SUM(spend) AS total_spend + FROM ( + SELECT + jsonb_array_elements_text(request_tags) AS individual_request_tag, + DATE(s."startTime") AS spend_date, + spend + FROM "LiteLLM_SpendLogs" s + WHERE + DATE(s."startTime") >= $1::date + AND DATE(s."startTime") <= $2::date + ) AS subquery + WHERE individual_request_tag = ANY($3::text[]) + GROUP BY individual_request_tag + ORDER BY total_spend DESC; + """ + response = await prisma_client.db.query_raw( + sql_query, + start_date, + end_date, + tags_list, + ) # print("tags - spend") # print(response) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 82f665a822..1edc85ef3d 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -8297,19 +8297,23 @@ async def global_view_spend_tags( default=None, description="Time till which to view key spend", ), + tags: Optional[str] = fastapi.Query( + default=None, + description="comman separated tags to filter on", + ), ): """ LiteLLM Enterprise - View Spend Per Request Tag. Used by LiteLLM UI Example Request: ``` - curl -X GET "http://0.0.0.0:8000/spend/tags" \ + curl -X GET "http://0.0.0.0:4000/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" \ + curl -X GET "http://0.0.0.0:4000/spend/tags?start_date=2022-01-01&end_date=2022-02-01" \ -H "Authorization: Bearer sk-1234" ``` """ @@ -8331,7 +8335,10 @@ async def global_view_spend_tags( code=status.HTTP_400_BAD_REQUEST, ) response = await ui_get_spend_by_tags( - start_date=start_date, end_date=end_date, prisma_client=prisma_client + start_date=start_date, + end_date=end_date, + tags_str=tags, + prisma_client=prisma_client, ) return response From 67312204d7ccebe9a2908d992c40142d6b0ecaa6 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 3 Jun 2024 15:42:37 -0700 Subject: [PATCH 5/7] feat - clea up usage tab --- .../src/components/networking.tsx | 8 +- ui/litellm-dashboard/src/components/usage.tsx | 82 +++++++++++-------- .../src/components/view_user_spend.tsx | 4 +- 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 9740c83e68..b7660ee0e9 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -979,7 +979,8 @@ export const teamSpendLogsCall = async (accessToken: String) => { export const tagsSpendLogsCall = async ( accessToken: String, startTime: String | undefined, - endTime: String | undefined + endTime: String | undefined, + tags: String[] | undefined ) => { try { let url = proxyBaseUrl @@ -990,6 +991,11 @@ export const tagsSpendLogsCall = async ( url = `${url}?start_date=${startTime}&end_date=${endTime}`; } + // if tags, convert the list to a comma separated string + if (tags) { + url += `${url}&tags=${tags.join(",")}`; + } + console.log("in tagsSpendLogsCall:", url); const response = await fetch(`${url}`, { method: "GET", diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index e3425aa8de..780b9c0ea3 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -144,6 +144,7 @@ const UsagePage: React.FC = ({ const [globalActivity, setGlobalActivity] = useState({} as GlobalActivityData); const [globalActivityPerModel, setGlobalActivityPerModel] = useState([]); const [selectedKeyID, setSelectedKeyID] = useState(""); + const [selectedTags, setSelectedTags] = useState(["all-tags"]); const [dateValue, setDateValue] = useState({ from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), to: new Date(), @@ -175,6 +176,10 @@ const UsagePage: React.FC = ({ return formatter.format(number); } + + useEffect(() => { + updateTagSpendData(dateValue.from, dateValue.to); + }, [dateValue, selectedTags]); const updateEndUserData = async (startTime: Date | undefined, endTime: Date | undefined, uiSelectedKey: string | null) => { @@ -212,12 +217,15 @@ const UsagePage: React.FC = ({ // startTime put it to the first hour of the selected date startTime.setHours(0, 0, 0, 0); - let top_tags = await tagsSpendLogsCall(accessToken, startTime.toISOString(), endTime.toISOString()); + let top_tags = await tagsSpendLogsCall( + accessToken, + startTime.toISOString(), + endTime.toISOString(), + selectedTags.length === 0 ? undefined : selectedTags + ); setTopTagsData(top_tags.spend_per_tag); console.log("Tag spend data updated successfully"); - - } function formatDate(date: Date) { @@ -293,14 +301,15 @@ const UsagePage: React.FC = ({ setTotalSpendPerTeam(total_spend_per_team); - //get top tags - const top_tags = await tagsSpendLogsCall(accessToken, dateValue.from?.toISOString(), dateValue.to?.toISOString()); - setTopTagsData(top_tags.spend_per_tag); - - // all_tag_names + // all_tag_names -> used for dropdown const all_tag_names = await allTagNamesCall(accessToken); setAllTagNames(all_tag_names.tag_names); + //get top tags + const top_tags = await tagsSpendLogsCall(accessToken, dateValue.from?.toISOString(), dateValue.to?.toISOString(), undefined); + setTopTagsData(top_tags.spend_per_tag); + + // get spend per end-user let spend_user_call = await adminTopEndUsersCall(accessToken, null, undefined, undefined); setTopUsers(spend_user_call); @@ -362,13 +371,7 @@ const UsagePage: React.FC = ({ return (
- + All Up @@ -387,6 +390,13 @@ const UsagePage: React.FC = ({ +
Monthly Spend @@ -763,26 +773,30 @@ const UsagePage: React.FC = ({ - setSelectedTags(value as string[])} > - All Tags - - { - allTagNames && allTagNames?.map((tag: any, index: number) => { - return ( - - {tag} - - ); - }) - } - + setSelectedTags(["all-tags"])} + > + All Tags + + {allTagNames && + allTagNames + .filter((tag) => tag !== "all-tags") + .map((tag: any, index: number) => { + return ( + + {tag} + + ); + })} + diff --git a/ui/litellm-dashboard/src/components/view_user_spend.tsx b/ui/litellm-dashboard/src/components/view_user_spend.tsx index d74dcaecf2..326dedee95 100644 --- a/ui/litellm-dashboard/src/components/view_user_spend.tsx +++ b/ui/litellm-dashboard/src/components/view_user_spend.tsx @@ -131,7 +131,7 @@ const ViewUserSpend: React.FC = ({ userID, userRole, accessT ${roundedSpend}

-
+ {/*
Team Models @@ -144,7 +144,7 @@ const ViewUserSpend: React.FC = ({ userID, userRole, accessT -
+
*/} ); } From 134c7ad9fe580bd087ccec31f760c4ae8251d11b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 3 Jun 2024 16:38:44 -0700 Subject: [PATCH 6/7] feat - put filtering by tags as a enterprise only feature --- ui/litellm-dashboard/src/components/usage.tsx | 102 +++++++++++++----- 1 file changed, 75 insertions(+), 27 deletions(-) diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index 780b9c0ea3..59e56a490f 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -13,8 +13,13 @@ import { Callout, Button, MultiSelect, - MultiSelectItem + MultiSelectItem, } from "@tremor/react"; + +import { + Select as Select2 +} from "antd"; + import { userSpendLogsCall, keyInfoCall, @@ -771,32 +776,75 @@ const UsagePage: React.FC = ({
+ { + premiumUser ? ( +
+ setSelectedTags(value as string[])} + > + setSelectedTags(["all-tags"])} + > + All Tags + + {allTagNames && + allTagNames + .filter((tag) => tag !== "all-tags") + .map((tag: any, index: number) => { + return ( + + {tag} + + ); + })} + - setSelectedTags(value as string[])} - > - setSelectedTags(["all-tags"])} - > - All Tags - - {allTagNames && - allTagNames - .filter((tag) => tag !== "all-tags") - .map((tag: any, index: number) => { - return ( - - {tag} - - ); - })} - +
+ + ) : ( +
+ + setSelectedTags(value as string[])} + > + setSelectedTags(["all-tags"])} + > + All Tags + + {allTagNames && + allTagNames + .filter((tag) => tag !== "all-tags") + .map((tag: any, index: number) => { + // @ts-ignore + return ( + + ✨ {tag} (Enterpise only Feature) + + ); + })} + + + + + +
+ ) + } + @@ -807,7 +855,7 @@ const UsagePage: React.FC = ({ Spend Per Tag - Get Started Tracking cost per tag here + Get Started Tracking cost per tag here Date: Mon, 3 Jun 2024 16:43:38 -0700 Subject: [PATCH 7/7] fix ts errors --- ui/litellm-dashboard/src/components/usage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index 59e56a490f..732df45245 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -825,12 +825,12 @@ const UsagePage: React.FC = ({ allTagNames .filter((tag) => tag !== "all-tags") .map((tag: any, index: number) => { - // @ts-ignore return ( ✨ {tag} (Enterpise only Feature)