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; } } });