mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-08 00:21:58 +00:00
Merge pull request #3997 from BerriAI/litellm_filter_latency_by_customer
[Admin UI] Filter Model Latency by Customer, API Key
This commit is contained in:
@@ -8917,6 +8917,38 @@ async def global_spend_per_team():
|
||||
}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/global/all_end_users",
|
||||
tags=["Budget & Spend Tracking"],
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
include_in_schema=False,
|
||||
)
|
||||
async def global_view_all_end_users():
|
||||
"""
|
||||
[BETA] This is a beta endpoint. It will change.
|
||||
|
||||
Use this to just get all the unique `end_users`
|
||||
"""
|
||||
global prisma_client
|
||||
|
||||
if prisma_client is None:
|
||||
raise HTTPException(status_code=500, detail={"error": "No db connected"})
|
||||
|
||||
sql_query = """
|
||||
SELECT DISTINCT end_user FROM "LiteLLM_SpendLogs"
|
||||
"""
|
||||
|
||||
db_response = await prisma_client.db.query_raw(query=sql_query)
|
||||
if db_response is None:
|
||||
return []
|
||||
|
||||
_end_users = []
|
||||
for row in db_response:
|
||||
_end_users.append(row["end_user"])
|
||||
|
||||
return {"end_users": _end_users}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/global/spend/end_users",
|
||||
tags=["Budget & Spend Tracking"],
|
||||
@@ -11616,6 +11648,7 @@ async def model_metrics(
|
||||
startTime: Optional[datetime] = None,
|
||||
endTime: Optional[datetime] = None,
|
||||
api_key: Optional[str] = None,
|
||||
customer: Optional[str] = None,
|
||||
):
|
||||
global prisma_client, llm_router
|
||||
if prisma_client is None:
|
||||
@@ -11631,6 +11664,9 @@ async def model_metrics(
|
||||
if api_key is None or api_key == "undefined":
|
||||
api_key = "null"
|
||||
|
||||
if customer is None or customer == "undefined":
|
||||
customer = "null"
|
||||
|
||||
sql_query = """
|
||||
SELECT
|
||||
api_base,
|
||||
@@ -11641,7 +11677,7 @@ async def model_metrics(
|
||||
FROM
|
||||
"LiteLLM_SpendLogs"
|
||||
WHERE
|
||||
"startTime" BETWEEN $2::timestamp AND $3::timestamp
|
||||
"startTime" >= $2::timestamp AND "startTime" <= $3::timestamp
|
||||
AND "model_group" = $1 AND "cache_hit" != 'True'
|
||||
AND (
|
||||
CASE
|
||||
@@ -11649,6 +11685,12 @@ async def model_metrics(
|
||||
ELSE TRUE
|
||||
END
|
||||
)
|
||||
AND (
|
||||
CASE
|
||||
WHEN $5 != 'null' THEN "end_user" = $5
|
||||
ELSE TRUE
|
||||
END
|
||||
)
|
||||
GROUP BY
|
||||
api_base,
|
||||
model_group,
|
||||
@@ -11661,7 +11703,7 @@ async def model_metrics(
|
||||
"""
|
||||
_all_api_bases = set()
|
||||
db_response = await prisma_client.db.query_raw(
|
||||
sql_query, _selected_model_group, startTime, endTime, api_key
|
||||
sql_query, _selected_model_group, startTime, endTime, api_key, customer
|
||||
)
|
||||
_daily_entries: dict = {} # {"Jun 23": {"model1": 0.002, "model2": 0.003}}
|
||||
|
||||
@@ -11721,6 +11763,7 @@ async def model_metrics_slow_responses(
|
||||
startTime: Optional[datetime] = None,
|
||||
endTime: Optional[datetime] = None,
|
||||
api_key: Optional[str] = None,
|
||||
customer: Optional[str] = None,
|
||||
):
|
||||
global prisma_client, llm_router, proxy_logging_obj
|
||||
if prisma_client is None:
|
||||
@@ -11733,6 +11776,9 @@ async def model_metrics_slow_responses(
|
||||
if api_key is None or api_key == "undefined":
|
||||
api_key = "null"
|
||||
|
||||
if customer is None or customer == "undefined":
|
||||
customer = "null"
|
||||
|
||||
startTime = startTime or datetime.now() - timedelta(days=30)
|
||||
endTime = endTime or datetime.now()
|
||||
|
||||
@@ -11762,6 +11808,12 @@ WHERE
|
||||
ELSE TRUE
|
||||
END
|
||||
)
|
||||
AND (
|
||||
CASE
|
||||
WHEN $6 != 'null' THEN "end_user" = $6
|
||||
ELSE TRUE
|
||||
END
|
||||
)
|
||||
GROUP BY
|
||||
api_base
|
||||
ORDER BY
|
||||
@@ -11775,6 +11827,7 @@ ORDER BY
|
||||
startTime,
|
||||
endTime,
|
||||
api_key,
|
||||
customer,
|
||||
)
|
||||
|
||||
if db_response is not None:
|
||||
@@ -11799,6 +11852,7 @@ async def model_metrics_exceptions(
|
||||
startTime: Optional[datetime] = None,
|
||||
endTime: Optional[datetime] = None,
|
||||
api_key: Optional[str] = None,
|
||||
customer: Optional[str] = None,
|
||||
):
|
||||
global prisma_client, llm_router
|
||||
if prisma_client is None:
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
modelSettingsCall,
|
||||
adminGlobalActivityExceptions,
|
||||
adminGlobalActivityExceptionsPerDeployment,
|
||||
allEndUsersCall,
|
||||
} from "./networking";
|
||||
import { BarChart, AreaChart } from "@tremor/react";
|
||||
import {
|
||||
@@ -319,6 +320,9 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
|
||||
const [showAdvancedFilters, setShowAdvancedFilters] = useState<boolean>(false);
|
||||
const [selectedAPIKey, setSelectedAPIKey] = useState<any | null>(null);
|
||||
const [selectedCustomer, setSelectedCustomer] = useState<any | null>(null);
|
||||
|
||||
const [allEndUsers, setAllEndUsers] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
updateModelMetrics(
|
||||
@@ -326,7 +330,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
dateValue.from,
|
||||
dateValue.to
|
||||
);
|
||||
}, [selectedAPIKey]);
|
||||
}, [selectedAPIKey, selectedCustomer]);
|
||||
|
||||
function formatCreatedAt(createdAt: string | null) {
|
||||
if (createdAt) {
|
||||
@@ -628,7 +632,8 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
_initial_model_group,
|
||||
dateValue.from?.toISOString(),
|
||||
dateValue.to?.toISOString(),
|
||||
selectedAPIKey?.token
|
||||
selectedAPIKey?.token,
|
||||
selectedCustomer
|
||||
);
|
||||
|
||||
console.log("Model metrics response:", modelMetricsResponse);
|
||||
@@ -657,7 +662,8 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
_initial_model_group,
|
||||
dateValue.from?.toISOString(),
|
||||
dateValue.to?.toISOString(),
|
||||
selectedAPIKey?.token
|
||||
selectedAPIKey?.token,
|
||||
selectedCustomer
|
||||
);
|
||||
console.log("Model exceptions response:", modelExceptionsResponse);
|
||||
setModelExceptions(modelExceptionsResponse.data);
|
||||
@@ -670,7 +676,8 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
_initial_model_group,
|
||||
dateValue.from?.toISOString(),
|
||||
dateValue.to?.toISOString(),
|
||||
selectedAPIKey?.token
|
||||
selectedAPIKey?.token,
|
||||
selectedCustomer
|
||||
);
|
||||
|
||||
const dailyExceptions = await adminGlobalActivityExceptions(
|
||||
@@ -700,6 +707,10 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
|
||||
setSlowResponsesData(slowResponses);
|
||||
|
||||
let all_end_users_data = await allEndUsersCall(accessToken);
|
||||
|
||||
setAllEndUsers(all_end_users_data?.end_users);
|
||||
|
||||
const routerSettingsInfo = await getCallbacksCall(
|
||||
accessToken,
|
||||
userID,
|
||||
@@ -911,6 +922,21 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
selected_token = null;
|
||||
}
|
||||
|
||||
let selected_customer = selectedCustomer;
|
||||
if (selected_customer === undefined) {
|
||||
selected_customer = null;
|
||||
}
|
||||
|
||||
// make startTime and endTime to last hour of the day
|
||||
startTime.setHours(0);
|
||||
startTime.setMinutes(0);
|
||||
startTime.setSeconds(0);
|
||||
|
||||
endTime.setHours(23);
|
||||
endTime.setMinutes(59);
|
||||
endTime.setSeconds(59);
|
||||
|
||||
|
||||
try {
|
||||
const modelMetricsResponse = await modelMetricsCall(
|
||||
accessToken,
|
||||
@@ -919,7 +945,8 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
modelGroup,
|
||||
startTime.toISOString(),
|
||||
endTime.toISOString(),
|
||||
selected_token
|
||||
selected_token,
|
||||
selected_customer
|
||||
);
|
||||
console.log("Model metrics response:", modelMetricsResponse);
|
||||
|
||||
@@ -947,7 +974,8 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
modelGroup,
|
||||
startTime.toISOString(),
|
||||
endTime.toISOString(),
|
||||
selected_token
|
||||
selected_token,
|
||||
selected_customer
|
||||
);
|
||||
console.log("Model exceptions response:", modelExceptionsResponse);
|
||||
setModelExceptions(modelExceptionsResponse.data);
|
||||
@@ -960,7 +988,8 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
modelGroup,
|
||||
startTime.toISOString(),
|
||||
endTime.toISOString(),
|
||||
selected_token
|
||||
selected_token,
|
||||
selected_customer
|
||||
);
|
||||
|
||||
console.log("slowResponses:", slowResponses);
|
||||
@@ -999,9 +1028,11 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
const FilterByContent = (
|
||||
<div >
|
||||
<Text className="mb-1">Select API Key Name</Text>
|
||||
|
||||
|
||||
<Select defaultValue="all-keys">
|
||||
{
|
||||
premiumUser ? (
|
||||
<div>
|
||||
<Select defaultValue="all-keys">
|
||||
<SelectItem
|
||||
key="all-keys"
|
||||
value="all-keys"
|
||||
@@ -1033,7 +1064,118 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
return null; // Add this line to handle the case when the condition is not met
|
||||
})}
|
||||
</Select>
|
||||
|
||||
|
||||
<Text className="mt-1">
|
||||
Select Customer Name
|
||||
</Text>
|
||||
|
||||
<Select defaultValue="all-customers">
|
||||
<SelectItem
|
||||
key="all-customers"
|
||||
value="all-customers"
|
||||
onClick={() => {
|
||||
setSelectedCustomer(null);
|
||||
}}
|
||||
>
|
||||
All Customers
|
||||
</SelectItem>
|
||||
{
|
||||
allEndUsers?.map((user: any, index: number) => {
|
||||
return (
|
||||
<SelectItem
|
||||
key={index}
|
||||
value={user}
|
||||
onClick={() => {
|
||||
setSelectedCustomer(user);
|
||||
}}
|
||||
>
|
||||
{user}
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
</div>
|
||||
): (
|
||||
<div>
|
||||
|
||||
<Select defaultValue="all-keys">
|
||||
<SelectItem
|
||||
key="all-keys"
|
||||
value="all-keys"
|
||||
onClick={() => {
|
||||
setSelectedAPIKey(null);
|
||||
}}
|
||||
>
|
||||
All Keys
|
||||
</SelectItem>
|
||||
{keys?.map((key: any, index: number) => {
|
||||
if (
|
||||
key &&
|
||||
key["key_alias"] !== null &&
|
||||
key["key_alias"].length > 0
|
||||
) {
|
||||
return (
|
||||
|
||||
<SelectItem
|
||||
key={index}
|
||||
value={String(index)}
|
||||
// @ts-ignore
|
||||
disabled={true}
|
||||
onClick={() => {
|
||||
setSelectedAPIKey(key);
|
||||
}}
|
||||
>
|
||||
✨ {key["key_alias"]} (Enterpise only Feature)
|
||||
</SelectItem>
|
||||
);
|
||||
}
|
||||
return null; // Add this line to handle the case when the condition is not met
|
||||
})}
|
||||
</Select>
|
||||
|
||||
|
||||
<Text className="mt-1">
|
||||
Select Customer Name
|
||||
</Text>
|
||||
|
||||
<Select defaultValue="all-customers">
|
||||
<SelectItem
|
||||
key="all-customers"
|
||||
value="all-customers"
|
||||
onClick={() => {
|
||||
setSelectedCustomer(null);
|
||||
}}
|
||||
>
|
||||
All Customers
|
||||
</SelectItem>
|
||||
{
|
||||
allEndUsers?.map((user: any, index: number) => {
|
||||
return (
|
||||
<SelectItem
|
||||
key={index}
|
||||
value={user}
|
||||
// @ts-ignore
|
||||
disabled={true}
|
||||
onClick={() => {
|
||||
setSelectedCustomer(user);
|
||||
}}
|
||||
>
|
||||
✨ {user} (Enterpise only Feature)
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
@@ -1793,12 +1935,13 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
</Card>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<Grid numItems={3} className="mt-2 mb-2">
|
||||
<Grid numItems={4} className="mt-2 mb-2">
|
||||
<Col>
|
||||
<Text>Select Time Range</Text>
|
||||
<DateRangePicker
|
||||
enableSelect={true}
|
||||
value={dateValue}
|
||||
className="mr-2"
|
||||
onValueChange={(value) => {
|
||||
setDateValue(value);
|
||||
updateModelMetrics(
|
||||
@@ -1809,7 +1952,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Col className="ml-2">
|
||||
<Text>Select Model Group</Text>
|
||||
<Select
|
||||
defaultValue={
|
||||
@@ -1838,8 +1981,10 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
||||
</Col>
|
||||
<Col>
|
||||
<Popover
|
||||
|
||||
trigger="click" content={FilterByContent}
|
||||
overlayStyle={{
|
||||
width: "20vw"
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon={FilterIcon}
|
||||
|
||||
@@ -728,6 +728,7 @@ export const modelMetricsCall = async (
|
||||
startTime: String | undefined,
|
||||
endTime: String | undefined,
|
||||
apiKey: String | null,
|
||||
customer: String | null
|
||||
) => {
|
||||
/**
|
||||
* Get all models on proxy
|
||||
@@ -735,7 +736,7 @@ export const modelMetricsCall = async (
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/model/metrics` : `/model/metrics`;
|
||||
if (modelGroup) {
|
||||
url = `${url}?_selected_model_group=${modelGroup}&startTime=${startTime}&endTime=${endTime}&api_key=${apiKey}`;
|
||||
url = `${url}?_selected_model_group=${modelGroup}&startTime=${startTime}&endTime=${endTime}&api_key=${apiKey}&customer=${customer}`;
|
||||
}
|
||||
// message.info("Requesting model data");
|
||||
const response = await fetch(url, {
|
||||
@@ -807,7 +808,8 @@ export const modelMetricsSlowResponsesCall = async (
|
||||
modelGroup: String | null,
|
||||
startTime: String | undefined,
|
||||
endTime: String | undefined,
|
||||
apiKey: String | null
|
||||
apiKey: String | null,
|
||||
customer: String | null
|
||||
) => {
|
||||
/**
|
||||
* Get all models on proxy
|
||||
@@ -817,7 +819,7 @@ export const modelMetricsSlowResponsesCall = async (
|
||||
? `${proxyBaseUrl}/model/metrics/slow_responses`
|
||||
: `/model/metrics/slow_responses`;
|
||||
if (modelGroup) {
|
||||
url = `${url}?_selected_model_group=${modelGroup}&startTime=${startTime}&endTime=${endTime}&api_key=${apiKey}`;
|
||||
url = `${url}?_selected_model_group=${modelGroup}&startTime=${startTime}&endTime=${endTime}&api_key=${apiKey}&customer=${customer}`;
|
||||
}
|
||||
|
||||
// message.info("Requesting model data");
|
||||
@@ -851,7 +853,8 @@ export const modelExceptionsCall = async (
|
||||
modelGroup: String | null,
|
||||
startTime: String | undefined,
|
||||
endTime: String | undefined,
|
||||
apiKey: String | null
|
||||
apiKey: String | null,
|
||||
customer: String | null
|
||||
) => {
|
||||
/**
|
||||
* Get all models on proxy
|
||||
@@ -862,7 +865,7 @@ export const modelExceptionsCall = async (
|
||||
: `/model/metrics/exceptions`;
|
||||
|
||||
if (modelGroup) {
|
||||
url = `${url}?_selected_model_group=${modelGroup}&startTime=${startTime}&endTime=${endTime}&api_key=${apiKey}`;
|
||||
url = `${url}?_selected_model_group=${modelGroup}&startTime=${startTime}&endTime=${endTime}&api_key=${apiKey}&customer=${customer}`;
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
@@ -1053,6 +1056,38 @@ export const allTagNamesCall = async (
|
||||
};
|
||||
|
||||
|
||||
export const allEndUsersCall = async (
|
||||
accessToken: String,
|
||||
) => {
|
||||
try {
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/global/all_end_users`
|
||||
: `/global/all_end_users`;
|
||||
|
||||
|
||||
console.log("in global/all_end_users 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,
|
||||
|
||||
Reference in New Issue
Block a user