mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(ui/usage): guard undefined breakdown metrics when aggregating usage
This commit is contained in:
parent
d6f498ff5c
commit
5fe78056a6
4 changed files with 130 additions and 66 deletions
|
|
@ -574,6 +574,45 @@ describe("UsagePage", () => {
|
|||
expect(successfulRequestElements.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("does not crash when a breakdown entry is missing its metrics (issue #33381)", async () => {
|
||||
const malformedSpendData = {
|
||||
results: [
|
||||
{
|
||||
date: "2025-01-01",
|
||||
metrics: {
|
||||
spend: 125.75,
|
||||
api_requests: 1500,
|
||||
successful_requests: 1450,
|
||||
failed_requests: 50,
|
||||
total_tokens: 75000,
|
||||
prompt_tokens: 45000,
|
||||
completion_tokens: 30000,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
breakdown: {
|
||||
models: { "gpt-4": { metadata: {}, api_key_breakdown: {} } },
|
||||
model_groups: { "gpt-4": { metadata: {}, api_key_breakdown: {} } },
|
||||
api_keys: { "sk-test123": { metadata: {} } },
|
||||
providers: { openai: {} },
|
||||
mcp_servers: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
metadata: { total_spend: 125.75 },
|
||||
};
|
||||
mockUserDailyActivityAggregatedCall.mockResolvedValueOnce(malformedSpendData as any);
|
||||
|
||||
renderWithProviders(<UsagePage {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(screen.getByText("Daily Spend")).toBeInTheDocument();
|
||||
expect(screen.getByText("Top Virtual Keys")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display usage metrics and charts", async () => {
|
||||
renderWithProviders(<UsagePage {...defaultProps} />);
|
||||
|
||||
|
|
|
|||
|
|
@ -269,15 +269,15 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
|||
api_key_breakdown: {},
|
||||
};
|
||||
}
|
||||
modelSpend[model].metrics.spend += metrics.metrics.spend;
|
||||
modelSpend[model].metrics.prompt_tokens += metrics.metrics.prompt_tokens;
|
||||
modelSpend[model].metrics.completion_tokens += metrics.metrics.completion_tokens;
|
||||
modelSpend[model].metrics.total_tokens += metrics.metrics.total_tokens;
|
||||
modelSpend[model].metrics.api_requests += metrics.metrics.api_requests;
|
||||
modelSpend[model].metrics.successful_requests += metrics.metrics.successful_requests || 0;
|
||||
modelSpend[model].metrics.failed_requests += metrics.metrics.failed_requests || 0;
|
||||
modelSpend[model].metrics.cache_read_input_tokens += metrics.metrics.cache_read_input_tokens || 0;
|
||||
modelSpend[model].metrics.cache_creation_input_tokens += metrics.metrics.cache_creation_input_tokens || 0;
|
||||
modelSpend[model].metrics.spend += metrics.metrics?.spend ?? 0;
|
||||
modelSpend[model].metrics.prompt_tokens += metrics.metrics?.prompt_tokens ?? 0;
|
||||
modelSpend[model].metrics.completion_tokens += metrics.metrics?.completion_tokens ?? 0;
|
||||
modelSpend[model].metrics.total_tokens += metrics.metrics?.total_tokens ?? 0;
|
||||
modelSpend[model].metrics.api_requests += metrics.metrics?.api_requests ?? 0;
|
||||
modelSpend[model].metrics.successful_requests += metrics.metrics?.successful_requests ?? 0;
|
||||
modelSpend[model].metrics.failed_requests += metrics.metrics?.failed_requests ?? 0;
|
||||
modelSpend[model].metrics.cache_read_input_tokens += metrics.metrics?.cache_read_input_tokens ?? 0;
|
||||
modelSpend[model].metrics.cache_creation_input_tokens += metrics.metrics?.cache_creation_input_tokens ?? 0;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -315,16 +315,16 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
|||
api_key_breakdown: {},
|
||||
};
|
||||
}
|
||||
modelGroupSpend[modelGroup].metrics.spend += metrics.metrics.spend;
|
||||
modelGroupSpend[modelGroup].metrics.prompt_tokens += metrics.metrics.prompt_tokens;
|
||||
modelGroupSpend[modelGroup].metrics.completion_tokens += metrics.metrics.completion_tokens;
|
||||
modelGroupSpend[modelGroup].metrics.total_tokens += metrics.metrics.total_tokens;
|
||||
modelGroupSpend[modelGroup].metrics.api_requests += metrics.metrics.api_requests;
|
||||
modelGroupSpend[modelGroup].metrics.successful_requests += metrics.metrics.successful_requests || 0;
|
||||
modelGroupSpend[modelGroup].metrics.failed_requests += metrics.metrics.failed_requests || 0;
|
||||
modelGroupSpend[modelGroup].metrics.cache_read_input_tokens += metrics.metrics.cache_read_input_tokens || 0;
|
||||
modelGroupSpend[modelGroup].metrics.spend += metrics.metrics?.spend ?? 0;
|
||||
modelGroupSpend[modelGroup].metrics.prompt_tokens += metrics.metrics?.prompt_tokens ?? 0;
|
||||
modelGroupSpend[modelGroup].metrics.completion_tokens += metrics.metrics?.completion_tokens ?? 0;
|
||||
modelGroupSpend[modelGroup].metrics.total_tokens += metrics.metrics?.total_tokens ?? 0;
|
||||
modelGroupSpend[modelGroup].metrics.api_requests += metrics.metrics?.api_requests ?? 0;
|
||||
modelGroupSpend[modelGroup].metrics.successful_requests += metrics.metrics?.successful_requests ?? 0;
|
||||
modelGroupSpend[modelGroup].metrics.failed_requests += metrics.metrics?.failed_requests ?? 0;
|
||||
modelGroupSpend[modelGroup].metrics.cache_read_input_tokens += metrics.metrics?.cache_read_input_tokens ?? 0;
|
||||
modelGroupSpend[modelGroup].metrics.cache_creation_input_tokens +=
|
||||
metrics.metrics.cache_creation_input_tokens || 0;
|
||||
metrics.metrics?.cache_creation_input_tokens ?? 0;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -363,16 +363,16 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
|||
api_key_breakdown: {},
|
||||
};
|
||||
}
|
||||
providerSpendMap[provider].metrics.spend += metrics.metrics.spend;
|
||||
providerSpendMap[provider].metrics.prompt_tokens += metrics.metrics.prompt_tokens;
|
||||
providerSpendMap[provider].metrics.completion_tokens += metrics.metrics.completion_tokens;
|
||||
providerSpendMap[provider].metrics.total_tokens += metrics.metrics.total_tokens;
|
||||
providerSpendMap[provider].metrics.api_requests += metrics.metrics.api_requests;
|
||||
providerSpendMap[provider].metrics.successful_requests += metrics.metrics.successful_requests || 0;
|
||||
providerSpendMap[provider].metrics.failed_requests += metrics.metrics.failed_requests || 0;
|
||||
providerSpendMap[provider].metrics.cache_read_input_tokens += metrics.metrics.cache_read_input_tokens || 0;
|
||||
providerSpendMap[provider].metrics.spend += metrics.metrics?.spend ?? 0;
|
||||
providerSpendMap[provider].metrics.prompt_tokens += metrics.metrics?.prompt_tokens ?? 0;
|
||||
providerSpendMap[provider].metrics.completion_tokens += metrics.metrics?.completion_tokens ?? 0;
|
||||
providerSpendMap[provider].metrics.total_tokens += metrics.metrics?.total_tokens ?? 0;
|
||||
providerSpendMap[provider].metrics.api_requests += metrics.metrics?.api_requests ?? 0;
|
||||
providerSpendMap[provider].metrics.successful_requests += metrics.metrics?.successful_requests ?? 0;
|
||||
providerSpendMap[provider].metrics.failed_requests += metrics.metrics?.failed_requests ?? 0;
|
||||
providerSpendMap[provider].metrics.cache_read_input_tokens += metrics.metrics?.cache_read_input_tokens ?? 0;
|
||||
providerSpendMap[provider].metrics.cache_creation_input_tokens +=
|
||||
metrics.metrics.cache_creation_input_tokens || 0;
|
||||
metrics.metrics?.cache_creation_input_tokens ?? 0;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -405,21 +405,21 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
|||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
metadata: {
|
||||
key_alias: metrics.metadata.key_alias,
|
||||
key_alias: metrics.metadata?.key_alias,
|
||||
team_id: null,
|
||||
tags: metrics.metadata.tags || [],
|
||||
tags: metrics.metadata?.tags || [],
|
||||
},
|
||||
};
|
||||
}
|
||||
keySpend[key].metrics.spend += metrics.metrics.spend;
|
||||
keySpend[key].metrics.prompt_tokens += metrics.metrics.prompt_tokens;
|
||||
keySpend[key].metrics.completion_tokens += metrics.metrics.completion_tokens;
|
||||
keySpend[key].metrics.total_tokens += metrics.metrics.total_tokens;
|
||||
keySpend[key].metrics.api_requests += metrics.metrics.api_requests;
|
||||
keySpend[key].metrics.successful_requests += metrics.metrics.successful_requests;
|
||||
keySpend[key].metrics.failed_requests += metrics.metrics.failed_requests;
|
||||
keySpend[key].metrics.cache_read_input_tokens += metrics.metrics.cache_read_input_tokens || 0;
|
||||
keySpend[key].metrics.cache_creation_input_tokens += metrics.metrics.cache_creation_input_tokens || 0;
|
||||
keySpend[key].metrics.spend += metrics.metrics?.spend ?? 0;
|
||||
keySpend[key].metrics.prompt_tokens += metrics.metrics?.prompt_tokens ?? 0;
|
||||
keySpend[key].metrics.completion_tokens += metrics.metrics?.completion_tokens ?? 0;
|
||||
keySpend[key].metrics.total_tokens += metrics.metrics?.total_tokens ?? 0;
|
||||
keySpend[key].metrics.api_requests += metrics.metrics?.api_requests ?? 0;
|
||||
keySpend[key].metrics.successful_requests += metrics.metrics?.successful_requests ?? 0;
|
||||
keySpend[key].metrics.failed_requests += metrics.metrics?.failed_requests ?? 0;
|
||||
keySpend[key].metrics.cache_read_input_tokens += metrics.metrics?.cache_read_input_tokens ?? 0;
|
||||
keySpend[key].metrics.cache_creation_input_tokens += metrics.metrics?.cache_creation_input_tokens ?? 0;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -716,6 +716,31 @@ describe("processActivityData", () => {
|
|||
expect(result["gpt-4"].total_spend).toBe(100.5);
|
||||
});
|
||||
|
||||
it("does not crash when a breakdown entry is missing its metrics (issue #33381)", () => {
|
||||
const dailyActivityWithMissingMetrics = {
|
||||
results: [
|
||||
{
|
||||
date: "2025-01-01",
|
||||
metrics: { ...EMPTY_SPEND_METRICS, spend: 100.5, api_requests: 100 },
|
||||
breakdown: {
|
||||
...EMPTY_BREAKDOWN,
|
||||
models: {
|
||||
"gpt-4": { metadata: {}, api_key_breakdown: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as { results: DailyData[] };
|
||||
|
||||
const result = processActivityData(dailyActivityWithMissingMetrics, "models");
|
||||
|
||||
expect(result).toHaveProperty("gpt-4");
|
||||
expect(result["gpt-4"].total_spend).toBe(0);
|
||||
expect(result["gpt-4"].total_requests).toBe(0);
|
||||
expect(result["gpt-4"].daily_data[0].metrics.spend).toBe(0);
|
||||
expect(result["gpt-4"].daily_data[0].metrics.api_requests).toBe(0);
|
||||
});
|
||||
|
||||
it("should process data for mcp_servers key", () => {
|
||||
const dailyActivityWithMCP: { results: DailyData[] } = {
|
||||
results: [
|
||||
|
|
|
|||
|
|
@ -392,29 +392,29 @@ export const processActivityData = (
|
|||
};
|
||||
}
|
||||
// Update totals
|
||||
modelMetrics[model].total_requests += modelData.metrics.api_requests;
|
||||
modelMetrics[model].prompt_tokens += modelData.metrics.prompt_tokens;
|
||||
modelMetrics[model].completion_tokens += modelData.metrics.completion_tokens;
|
||||
modelMetrics[model].total_tokens += modelData.metrics.total_tokens;
|
||||
modelMetrics[model].total_spend += modelData.metrics.spend;
|
||||
modelMetrics[model].total_successful_requests += modelData.metrics.successful_requests;
|
||||
modelMetrics[model].total_failed_requests += modelData.metrics.failed_requests;
|
||||
modelMetrics[model].total_cache_read_input_tokens += modelData.metrics.cache_read_input_tokens || 0;
|
||||
modelMetrics[model].total_cache_creation_input_tokens += modelData.metrics.cache_creation_input_tokens || 0;
|
||||
modelMetrics[model].total_requests += modelData.metrics?.api_requests ?? 0;
|
||||
modelMetrics[model].prompt_tokens += modelData.metrics?.prompt_tokens ?? 0;
|
||||
modelMetrics[model].completion_tokens += modelData.metrics?.completion_tokens ?? 0;
|
||||
modelMetrics[model].total_tokens += modelData.metrics?.total_tokens ?? 0;
|
||||
modelMetrics[model].total_spend += modelData.metrics?.spend ?? 0;
|
||||
modelMetrics[model].total_successful_requests += modelData.metrics?.successful_requests ?? 0;
|
||||
modelMetrics[model].total_failed_requests += modelData.metrics?.failed_requests ?? 0;
|
||||
modelMetrics[model].total_cache_read_input_tokens += modelData.metrics?.cache_read_input_tokens ?? 0;
|
||||
modelMetrics[model].total_cache_creation_input_tokens += modelData.metrics?.cache_creation_input_tokens ?? 0;
|
||||
|
||||
// Add daily data
|
||||
modelMetrics[model].daily_data.push({
|
||||
date: day.date,
|
||||
metrics: {
|
||||
prompt_tokens: modelData.metrics.prompt_tokens,
|
||||
completion_tokens: modelData.metrics.completion_tokens,
|
||||
total_tokens: modelData.metrics.total_tokens,
|
||||
api_requests: modelData.metrics.api_requests,
|
||||
spend: modelData.metrics.spend,
|
||||
successful_requests: modelData.metrics.successful_requests,
|
||||
failed_requests: modelData.metrics.failed_requests,
|
||||
cache_read_input_tokens: modelData.metrics.cache_read_input_tokens || 0,
|
||||
cache_creation_input_tokens: modelData.metrics.cache_creation_input_tokens || 0,
|
||||
prompt_tokens: modelData.metrics?.prompt_tokens ?? 0,
|
||||
completion_tokens: modelData.metrics?.completion_tokens ?? 0,
|
||||
total_tokens: modelData.metrics?.total_tokens ?? 0,
|
||||
api_requests: modelData.metrics?.api_requests ?? 0,
|
||||
spend: modelData.metrics?.spend ?? 0,
|
||||
successful_requests: modelData.metrics?.successful_requests ?? 0,
|
||||
failed_requests: modelData.metrics?.failed_requests ?? 0,
|
||||
cache_read_input_tokens: modelData.metrics?.cache_read_input_tokens ?? 0,
|
||||
cache_creation_input_tokens: modelData.metrics?.cache_creation_input_tokens ?? 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
@ -433,17 +433,17 @@ export const processActivityData = (
|
|||
if (!apiKeyBreakdown[apiKey]) {
|
||||
apiKeyBreakdown[apiKey] = {
|
||||
api_key: apiKey,
|
||||
key_alias: keyData.metadata.key_alias,
|
||||
team_id: keyData.metadata.team_id,
|
||||
key_alias: keyData.metadata?.key_alias,
|
||||
team_id: keyData.metadata?.team_id,
|
||||
spend: 0,
|
||||
requests: 0,
|
||||
tokens: 0,
|
||||
};
|
||||
}
|
||||
|
||||
apiKeyBreakdown[apiKey].spend += keyData.metrics.spend;
|
||||
apiKeyBreakdown[apiKey].requests += keyData.metrics.api_requests;
|
||||
apiKeyBreakdown[apiKey].tokens += keyData.metrics.total_tokens;
|
||||
apiKeyBreakdown[apiKey].spend += keyData.metrics?.spend ?? 0;
|
||||
apiKeyBreakdown[apiKey].requests += keyData.metrics?.api_requests ?? 0;
|
||||
apiKeyBreakdown[apiKey].tokens += keyData.metrics?.total_tokens ?? 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -478,11 +478,11 @@ export const processActivityData = (
|
|||
};
|
||||
}
|
||||
|
||||
modelBreakdown[modelName].spend += keyDataForModel.metrics.spend;
|
||||
modelBreakdown[modelName].requests += keyDataForModel.metrics.api_requests;
|
||||
modelBreakdown[modelName].successful_requests += keyDataForModel.metrics.successful_requests || 0;
|
||||
modelBreakdown[modelName].failed_requests += keyDataForModel.metrics.failed_requests || 0;
|
||||
modelBreakdown[modelName].tokens += keyDataForModel.metrics.total_tokens;
|
||||
modelBreakdown[modelName].spend += keyDataForModel.metrics?.spend ?? 0;
|
||||
modelBreakdown[modelName].requests += keyDataForModel.metrics?.api_requests ?? 0;
|
||||
modelBreakdown[modelName].successful_requests += keyDataForModel.metrics?.successful_requests ?? 0;
|
||||
modelBreakdown[modelName].failed_requests += keyDataForModel.metrics?.failed_requests ?? 0;
|
||||
modelBreakdown[modelName].tokens += keyDataForModel.metrics?.total_tokens ?? 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue