diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx
index cf122137f91..38c17d15c0c 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx
@@ -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();
+
+ 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();
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx
index d2f75609d18..cf6d161714c 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx
@@ -269,15 +269,15 @@ const UsagePage: React.FC = ({ 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 = ({ 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 = ({ 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 = ({ 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;
});
});
diff --git a/ui/litellm-dashboard/src/components/activity_metrics.test.tsx b/ui/litellm-dashboard/src/components/activity_metrics.test.tsx
index f37643f2366..e653e553f6b 100644
--- a/ui/litellm-dashboard/src/components/activity_metrics.test.tsx
+++ b/ui/litellm-dashboard/src/components/activity_metrics.test.tsx
@@ -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: [
diff --git a/ui/litellm-dashboard/src/components/activity_metrics.tsx b/ui/litellm-dashboard/src/components/activity_metrics.tsx
index 54ac5ae0ee6..8578eb2287c 100644
--- a/ui/litellm-dashboard/src/components/activity_metrics.tsx
+++ b/ui/litellm-dashboard/src/components/activity_metrics.tsx
@@ -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;
}
}
});