mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix(ui): include cache token columns in usage export (#32015)
This commit is contained in:
parent
cf6fdac304
commit
2633e8f8a8
2 changed files with 210 additions and 0 deletions
|
|
@ -358,6 +358,23 @@ describe("EntityUsageExport utils", () => {
|
|||
expect(result[0]).toHaveProperty("Total Tokens");
|
||||
expect(result[0]).toHaveProperty("Prompt Tokens");
|
||||
expect(result[0]).toHaveProperty("Completion Tokens");
|
||||
expect(result[0]).toHaveProperty("Cache Read Input Tokens");
|
||||
expect(result[0]).toHaveProperty("Cache Creation Input Tokens");
|
||||
});
|
||||
|
||||
it("should export exact cache token values per entity per day", () => {
|
||||
const result = generateDailyData(mockSpendData, "Team", mockTeamAliasMap);
|
||||
|
||||
const day1Team1 = result.find((r) => r.Date === "2025-01-01" && r["Team ID"] === "team-1");
|
||||
const day1Team2 = result.find((r) => r.Date === "2025-01-01" && r["Team ID"] === "team-2");
|
||||
const day2Team1 = result.find((r) => r.Date === "2025-01-02" && r["Team ID"] === "team-1");
|
||||
|
||||
expect(day1Team1?.["Cache Read Input Tokens"]).toBe(50);
|
||||
expect(day1Team1?.["Cache Creation Input Tokens"]).toBe(30);
|
||||
expect(day1Team2?.["Cache Read Input Tokens"]).toBe(100);
|
||||
expect(day1Team2?.["Cache Creation Input Tokens"]).toBe(60);
|
||||
expect(day2Team1?.["Cache Read Input Tokens"]).toBe(75);
|
||||
expect(day2Team1?.["Cache Creation Input Tokens"]).toBe(45);
|
||||
});
|
||||
|
||||
it("should sort data by date ascending", () => {
|
||||
|
|
@ -469,6 +486,8 @@ describe("EntityUsageExport utils", () => {
|
|||
|
||||
expect(result[0]["Prompt Tokens"]).toBe(0);
|
||||
expect(result[0]["Completion Tokens"]).toBe(0);
|
||||
expect(result[0]["Cache Read Input Tokens"]).toBe(0);
|
||||
expect(result[0]["Cache Creation Input Tokens"]).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -614,6 +633,61 @@ describe("EntityUsageExport utils", () => {
|
|||
expect(result[0]).toHaveProperty("Total Tokens");
|
||||
expect(result[0]).toHaveProperty("Prompt Tokens");
|
||||
expect(result[0]).toHaveProperty("Completion Tokens");
|
||||
expect(result[0]).toHaveProperty("Cache Read Input Tokens");
|
||||
expect(result[0]).toHaveProperty("Cache Creation Input Tokens");
|
||||
});
|
||||
|
||||
it("should export and aggregate cache token values per key", () => {
|
||||
const makeDay = (cacheRead: number, cacheCreation: number) => ({
|
||||
date: "2025-01-01",
|
||||
breakdown: {
|
||||
entities: {
|
||||
"team-1": {
|
||||
metrics: {
|
||||
spend: 5.0,
|
||||
api_requests: 50,
|
||||
successful_requests: 50,
|
||||
failed_requests: 0,
|
||||
total_tokens: 500,
|
||||
prompt_tokens: 300,
|
||||
completion_tokens: 200,
|
||||
cache_read_input_tokens: cacheRead,
|
||||
cache_creation_input_tokens: cacheCreation,
|
||||
},
|
||||
api_key_breakdown: {
|
||||
key1: {
|
||||
metrics: {
|
||||
spend: 5.0,
|
||||
api_requests: 50,
|
||||
successful_requests: 50,
|
||||
failed_requests: 0,
|
||||
total_tokens: 500,
|
||||
prompt_tokens: 300,
|
||||
completion_tokens: 200,
|
||||
cache_read_input_tokens: cacheRead,
|
||||
cache_creation_input_tokens: cacheCreation,
|
||||
},
|
||||
metadata: {
|
||||
team_id: "team-1",
|
||||
key_alias: "alias-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const spendDataWithCache: EntitySpendData = {
|
||||
results: [makeDay(40, 25), makeDay(10, 5)],
|
||||
metadata: mockSpendDataWithKeys.metadata,
|
||||
};
|
||||
|
||||
const result = generateDailyWithKeysData(spendDataWithCache, "Team");
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]["Cache Read Input Tokens"]).toBe(50);
|
||||
expect(result[0]["Cache Creation Input Tokens"]).toBe(30);
|
||||
});
|
||||
|
||||
it("should sort data by date ascending", () => {
|
||||
|
|
@ -935,6 +1009,8 @@ describe("EntityUsageExport utils", () => {
|
|||
|
||||
expect(key1Entry?.["Prompt Tokens"]).toBe(0);
|
||||
expect(key1Entry?.["Completion Tokens"]).toBe(0);
|
||||
expect(key1Entry?.["Cache Read Input Tokens"]).toBe(0);
|
||||
expect(key1Entry?.["Cache Creation Input Tokens"]).toBe(0);
|
||||
});
|
||||
|
||||
it("should handle empty api_key_breakdown", () => {
|
||||
|
|
@ -1096,6 +1172,117 @@ describe("EntityUsageExport utils", () => {
|
|||
expect(result[0]).toHaveProperty("Successful");
|
||||
expect(result[0]).toHaveProperty("Failed");
|
||||
expect(result[0]).toHaveProperty("Total Tokens");
|
||||
expect(result[0]).toHaveProperty("Prompt Tokens");
|
||||
expect(result[0]).toHaveProperty("Completion Tokens");
|
||||
expect(result[0]).toHaveProperty("Cache Read Input Tokens");
|
||||
expect(result[0]).toHaveProperty("Cache Creation Input Tokens");
|
||||
});
|
||||
|
||||
it("should export prompt, completion, and cache token values summed across keys for the same model", () => {
|
||||
const data: EntitySpendData = {
|
||||
results: [
|
||||
{
|
||||
date: "2025-03-01",
|
||||
breakdown: {
|
||||
entities: {
|
||||
"team-1": {
|
||||
metrics: {
|
||||
spend: 5.0,
|
||||
api_requests: 25,
|
||||
successful_requests: 25,
|
||||
failed_requests: 0,
|
||||
total_tokens: 1050,
|
||||
prompt_tokens: 750,
|
||||
completion_tokens: 300,
|
||||
cache_read_input_tokens: 450,
|
||||
cache_creation_input_tokens: 200,
|
||||
},
|
||||
api_key_breakdown: {
|
||||
key1: {
|
||||
metrics: {
|
||||
spend: 2.0,
|
||||
api_requests: 10,
|
||||
successful_requests: 10,
|
||||
failed_requests: 0,
|
||||
total_tokens: 700,
|
||||
},
|
||||
metadata: { team_id: "team-1" },
|
||||
},
|
||||
key2: {
|
||||
metrics: {
|
||||
spend: 3.0,
|
||||
api_requests: 15,
|
||||
successful_requests: 15,
|
||||
failed_requests: 0,
|
||||
total_tokens: 350,
|
||||
},
|
||||
metadata: { team_id: "team-1" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
"claude-sonnet-4-5": {
|
||||
metrics: {
|
||||
spend: 5.0,
|
||||
api_requests: 25,
|
||||
successful_requests: 25,
|
||||
failed_requests: 0,
|
||||
total_tokens: 1050,
|
||||
},
|
||||
api_key_breakdown: {
|
||||
key1: {
|
||||
metrics: {
|
||||
spend: 2.0,
|
||||
api_requests: 10,
|
||||
successful_requests: 10,
|
||||
failed_requests: 0,
|
||||
total_tokens: 700,
|
||||
prompt_tokens: 500,
|
||||
completion_tokens: 200,
|
||||
cache_read_input_tokens: 300,
|
||||
cache_creation_input_tokens: 120,
|
||||
},
|
||||
metadata: { team_id: "team-1" },
|
||||
},
|
||||
key2: {
|
||||
metrics: {
|
||||
spend: 3.0,
|
||||
api_requests: 15,
|
||||
successful_requests: 15,
|
||||
failed_requests: 0,
|
||||
total_tokens: 350,
|
||||
prompt_tokens: 250,
|
||||
completion_tokens: 100,
|
||||
cache_read_input_tokens: 150,
|
||||
cache_creation_input_tokens: 80,
|
||||
},
|
||||
metadata: { team_id: "team-1" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
total_spend: 5.0,
|
||||
total_api_requests: 25,
|
||||
total_successful_requests: 25,
|
||||
total_failed_requests: 0,
|
||||
total_tokens: 1050,
|
||||
},
|
||||
};
|
||||
|
||||
const result = generateDailyWithModelsData(data, "Team");
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].Model).toBe("claude-sonnet-4-5");
|
||||
expect(result[0]["Total Tokens"]).toBe(1050);
|
||||
expect(result[0]["Prompt Tokens"]).toBe(750);
|
||||
expect(result[0]["Completion Tokens"]).toBe(300);
|
||||
expect(result[0]["Cache Read Input Tokens"]).toBe(450);
|
||||
expect(result[0]["Cache Creation Input Tokens"]).toBe(200);
|
||||
});
|
||||
|
||||
it("should sort data by date ascending", () => {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@ export const generateDailyData = (
|
|||
"Total Tokens": data.metrics.total_tokens,
|
||||
"Prompt Tokens": data.metrics.prompt_tokens || 0,
|
||||
"Completion Tokens": data.metrics.completion_tokens || 0,
|
||||
"Cache Read Input Tokens": data.metrics.cache_read_input_tokens || 0,
|
||||
"Cache Creation Input Tokens": data.metrics.cache_creation_input_tokens || 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -154,6 +156,8 @@ export const generateDailyWithKeysData = (
|
|||
total_tokens: number;
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
cache_read_input_tokens: number;
|
||||
cache_creation_input_tokens: number;
|
||||
};
|
||||
};
|
||||
} = {};
|
||||
|
|
@ -186,6 +190,8 @@ export const generateDailyWithKeysData = (
|
|||
total_tokens: keyData.metrics?.total_tokens || 0,
|
||||
prompt_tokens: keyData.metrics?.prompt_tokens || 0,
|
||||
completion_tokens: keyData.metrics?.completion_tokens || 0,
|
||||
cache_read_input_tokens: keyData.metrics?.cache_read_input_tokens || 0,
|
||||
cache_creation_input_tokens: keyData.metrics?.cache_creation_input_tokens || 0,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
|
|
@ -197,6 +203,9 @@ export const generateDailyWithKeysData = (
|
|||
aggregatedData[uniqueKey].metrics.total_tokens += keyData.metrics?.total_tokens || 0;
|
||||
aggregatedData[uniqueKey].metrics.prompt_tokens += keyData.metrics?.prompt_tokens || 0;
|
||||
aggregatedData[uniqueKey].metrics.completion_tokens += keyData.metrics?.completion_tokens || 0;
|
||||
aggregatedData[uniqueKey].metrics.cache_read_input_tokens += keyData.metrics?.cache_read_input_tokens || 0;
|
||||
aggregatedData[uniqueKey].metrics.cache_creation_input_tokens +=
|
||||
keyData.metrics?.cache_creation_input_tokens || 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -216,6 +225,8 @@ export const generateDailyWithKeysData = (
|
|||
"Total Tokens": item.metrics.total_tokens,
|
||||
"Prompt Tokens": item.metrics.prompt_tokens,
|
||||
"Completion Tokens": item.metrics.completion_tokens,
|
||||
"Cache Read Input Tokens": item.metrics.cache_read_input_tokens,
|
||||
"Cache Creation Input Tokens": item.metrics.cache_creation_input_tokens,
|
||||
}));
|
||||
|
||||
return dailyKeyBreakdown.sort((a, b) => new Date(a.Date).getTime() - new Date(b.Date).getTime());
|
||||
|
|
@ -251,6 +262,10 @@ export const generateDailyWithModelsData = (
|
|||
successful: 0,
|
||||
failed: 0,
|
||||
tokens: 0,
|
||||
promptTokens: 0,
|
||||
completionTokens: 0,
|
||||
cacheReadInputTokens: 0,
|
||||
cacheCreationInputTokens: 0,
|
||||
};
|
||||
}
|
||||
dailyEntityModels[entity][model].spend += keyMetrics.spend || 0;
|
||||
|
|
@ -258,6 +273,10 @@ export const generateDailyWithModelsData = (
|
|||
dailyEntityModels[entity][model].successful += keyMetrics.successful_requests || 0;
|
||||
dailyEntityModels[entity][model].failed += keyMetrics.failed_requests || 0;
|
||||
dailyEntityModels[entity][model].tokens += keyMetrics.total_tokens || 0;
|
||||
dailyEntityModels[entity][model].promptTokens += keyMetrics.prompt_tokens || 0;
|
||||
dailyEntityModels[entity][model].completionTokens += keyMetrics.completion_tokens || 0;
|
||||
dailyEntityModels[entity][model].cacheReadInputTokens += keyMetrics.cache_read_input_tokens || 0;
|
||||
dailyEntityModels[entity][model].cacheCreationInputTokens += keyMetrics.cache_creation_input_tokens || 0;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -276,6 +295,10 @@ export const generateDailyWithModelsData = (
|
|||
Successful: metrics.successful,
|
||||
Failed: metrics.failed,
|
||||
"Total Tokens": metrics.tokens,
|
||||
"Prompt Tokens": metrics.promptTokens,
|
||||
"Completion Tokens": metrics.completionTokens,
|
||||
"Cache Read Input Tokens": metrics.cacheReadInputTokens,
|
||||
"Cache Creation Input Tokens": metrics.cacheCreationInputTokens,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue