From d9862520bc7f01ffd13e8de8b61bd088e35d6794 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 28 Feb 2024 15:37:48 -0800 Subject: [PATCH] fix(usage.tsx): make separate call for top api keys --- litellm/proxy/proxy_server.py | 63 ++++++++++++- .../src/components/networking.tsx | 64 ++++++++++++- ui/litellm-dashboard/src/components/usage.tsx | 91 ++++++++++++------- 3 files changed, 178 insertions(+), 40 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 5e03b5f70..069d40487 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -4054,7 +4054,7 @@ async def view_spend_logs( ) async def global_spend_logs(): """ - [BETA] This is a beta endpoint. + [BETA] This is a beta endpoint. It will change. Use this to get global spend (spend per day for last 30d). Admin-only endpoint @@ -4069,6 +4069,61 @@ async def global_spend_logs(): return response +@router.get( + "/global/spend/keys", + tags=["Budget & Spend Tracking"], + dependencies=[Depends(user_api_key_auth)], +) +async def global_spend_keys( + limit: int = fastapi.Query( + default=None, + description="Number of keys to get. Will return Top 'n' keys.", + ) +): + """ + [BETA] This is a beta endpoint. It will change. + + Use this to get the top 'n' keys with the highest spend, ordered by spend. + """ + global prisma_client + + if prisma_client is None: + raise HTTPException(status_code=500, detail={"error": "No db connected"}) + sql_query = f"""SELECT * FROM "Last30dKeysBySpend" LIMIT {limit};""" + + response = await prisma_client.db.query_raw(query=sql_query) + + return response + + +@router.get( + "/global/spend/models", + tags=["Budget & Spend Tracking"], + dependencies=[Depends(user_api_key_auth)], +) +async def global_spend_models( + limit: int = fastapi.Query( + default=None, + description="Number of models to get. Will return Top 'n' models.", + ) +): + """ + [BETA] This is a beta endpoint. It will change. + + Use this to get the top 'n' keys with the highest spend, ordered by spend. + """ + global prisma_client + + if prisma_client is None: + raise HTTPException(status_code=500, detail={"error": "No db connected"}) + + sql_query = f"""SELECT * FROM "Last30dModelsBySpend" LIMIT {limit};""" + + response = await prisma_client.db.query_raw(query=sql_query) + + return response + + @router.get( "/daily_metrics", summary="Get daily spend metrics", @@ -4085,7 +4140,11 @@ async def view_daily_metrics( description="Time till which to view key spend", ), ): - """ """ + """ + [BETA] This is a beta endpoint. It might change without notice. + + Please give feedback - https://github.com/BerriAI/litellm/issues + """ try: if os.getenv("CLICKHOUSE_HOST") is not None: # gettting spend logs from clickhouse diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 1a6b47985..749fbb35e 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -314,10 +314,6 @@ export const userSpendLogsCall = async ( ) => { try { console.log(`user role in spend logs call: ${userRole}`); - if (userRole == "Admin") { - return await adminSpendLogsCall(accessToken); - } - let url = proxyBaseUrl ? `${proxyBaseUrl}/spend/logs` : `/spend/logs`; if (userRole == "App Owner") { url = `${url}/?user_id=${userID}&start_date=${startTime}&end_date=${endTime}`; @@ -378,6 +374,66 @@ export const adminSpendLogsCall = async (accessToken: String) => { } }; +export const adminTopKeysCall = async (accessToken: String) => { + try { + let url = proxyBaseUrl + ? `${proxyBaseUrl}/global/spend/keys?limit=5` + : `/global/spend/keys?limit=5`; + + message.info("Making spend keys request"); + 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); + message.success("Spend Logs received"); + return data; + } catch (error) { + console.error("Failed to create key:", error); + throw error; + } +}; + +export const adminTopModelsCall = async (accessToken: String) => { + try { + let url = proxyBaseUrl + ? `${proxyBaseUrl}/global/spend/models?limit=5` + : `/global/spend/models?limit=5`; + + message.info("Making spend models request"); + 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); + message.success("Spend Logs received"); + return data; + } catch (error) { + console.error("Failed to create key:", error); + throw error; + } +}; + export const keyInfoCall = async (accessToken: String, keys: String[]) => { try { let url = proxyBaseUrl ? `${proxyBaseUrl}/v2/key/info` : `/v2/key/info`; diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index 1b60df3d4..afc002fd5 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -6,6 +6,8 @@ import { userSpendLogsCall, keyInfoCall, adminSpendLogsCall, + adminTopKeysCall, + adminTopModelsCall, } from "./networking"; import { start } from "repl"; @@ -168,40 +170,61 @@ const UsagePage: React.FC = ({ if (accessToken && token && userRole && userID) { const fetchData = async () => { try { - await userSpendLogsCall( - accessToken, - token, - userRole, - userID, - startTime, - endTime - ).then(async (response) => { - console.log("result from spend logs call", response); - if ("daily_spend" in response) { - // this is from clickhouse analytics - // - let daily_spend = response["daily_spend"]; - console.log("daily spend", daily_spend); - setKeySpendData(daily_spend); - let topApiKeys = response.top_api_keys; - setTopKeys(topApiKeys); - } else { - // const topKeysResponse = await keyInfoCall( - // accessToken, - // getTopKeys(response) - // ); - // const filtered_keys = topKeysResponse["info"].map((k: any) => ({ - // key: (k["key_name"] || k["key_alias"] || k["token"]).substring( - // 0, - // 7 - // ), - // spend: k["spend"], - // })); - // setTopKeys(filtered_keys); - // setTopUsers(getTopUsers(response)); - setKeySpendData(response); - } - }); + /** + * If user is Admin - query the global views endpoints + * If user is App Owner - use the normal spend logs call + */ + console.log(`user role: ${userRole}`); + if (userRole == "Admin") { + const overall_spend = await adminSpendLogsCall(accessToken); + setKeySpendData(overall_spend); + const top_keys = await adminTopKeysCall(accessToken); + const filtered_keys = top_keys.map((k: any) => ({ + key: (k["key_name"] || k["key_alias"] || k["api_key"]).substring( + 0, + 7 + ), + spend: k["total_spend"], + })); + setTopKeys(filtered_keys); + const top_models = await adminTopModelsCall(accessToken); + } else if (userRole == "App Owner") { + await userSpendLogsCall( + accessToken, + token, + userRole, + userID, + startTime, + endTime + ).then(async (response) => { + console.log("result from spend logs call", response); + if ("daily_spend" in response) { + // this is from clickhouse analytics + // + let daily_spend = response["daily_spend"]; + console.log("daily spend", daily_spend); + setKeySpendData(daily_spend); + let topApiKeys = response.top_api_keys; + setTopKeys(topApiKeys); + } else { + const topKeysResponse = await keyInfoCall( + accessToken, + getTopKeys(response) + ); + const filtered_keys = topKeysResponse["info"].map((k: any) => ({ + key: ( + k["key_name"] || + k["key_alias"] || + k["token"] + ).substring(0, 7), + spend: k["spend"], + })); + setTopKeys(filtered_keys); + setTopUsers(getTopUsers(response)); + setKeySpendData(response); + } + }); + } } catch (error) { console.error("There was an error fetching the data", error); // Optionally, update your UI to reflect the error state here as well