mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-28 01:32:17 +00:00
fix(ui): list every provider in the cache leakage by-model table
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
0c98afa780
commit
0a4719697e
3 changed files with 14 additions and 31 deletions
|
|
@ -122,11 +122,11 @@ describe("CacheLeakageCard", () => {
|
|||
expect(firstDataRow()).toHaveTextContent("alpha");
|
||||
});
|
||||
|
||||
it("switches to the model view and lists only Anthropic models", () => {
|
||||
it("switches to the model view and lists models from every provider", () => {
|
||||
renderWith([
|
||||
dayWithModels("2026-07-12", {
|
||||
"claude-sonnet-5": { prompt_tokens: 5000, cache_read_input_tokens: 0 },
|
||||
"gpt-4o": { prompt_tokens: 8000, cache_read_input_tokens: 0 },
|
||||
"vertex_ai/gemini-2.5-pro": { prompt_tokens: 8000, cache_read_input_tokens: 2000 },
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ describe("CacheLeakageCard", () => {
|
|||
|
||||
expect(screen.getByText("Cache leakage by model")).toBeInTheDocument();
|
||||
expect(screen.getByText("claude-sonnet-5")).toBeInTheDocument();
|
||||
expect(screen.queryByText("gpt-4o")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("vertex_ai/gemini-2.5-pro")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows an empty state when no key used tokens in the range", () => {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
classificationRatePer1kTurns,
|
||||
computeCacheLeakage,
|
||||
formatRangeLabel,
|
||||
isAnthropicModel,
|
||||
localIsoDay,
|
||||
savingsSeriesOf,
|
||||
toCumulative,
|
||||
|
|
@ -209,20 +208,21 @@ describe("computeCacheLeakage", () => {
|
|||
});
|
||||
|
||||
describe("computeCacheLeakage by model", () => {
|
||||
it("aggregates only Anthropic models and ignores other providers", () => {
|
||||
it("lists every provider's models, not only Anthropic", () => {
|
||||
const models: Record<string, Partial<SpendMetrics>> = {
|
||||
"claude-sonnet-5": { prompt_tokens: 10000, cache_read_input_tokens: 0 },
|
||||
"anthropic/claude-haiku-4-5": { prompt_tokens: 4000, cache_read_input_tokens: 0 },
|
||||
"bedrock/anthropic.claude-3-5-sonnet": { prompt_tokens: 2000, cache_read_input_tokens: 0 },
|
||||
"gpt-4o": { prompt_tokens: 9000, cache_read_input_tokens: 0 },
|
||||
"deepseek-chat": { prompt_tokens: 8000, cache_read_input_tokens: 0 },
|
||||
"vertex_ai/gemini-2.5-pro": { prompt_tokens: 9000, cache_read_input_tokens: 3000 },
|
||||
"bedrock/openai.gpt-5.6-luna": { prompt_tokens: 8000, cache_read_input_tokens: 0 },
|
||||
"deepseek-chat": { prompt_tokens: 4000, cache_read_input_tokens: 0 },
|
||||
};
|
||||
const { rows } = computeCacheLeakage([modelDay("2026-07-01", models)], "model");
|
||||
expect(rows.map((r) => r.id)).toEqual([
|
||||
"claude-sonnet-5",
|
||||
"anthropic/claude-haiku-4-5",
|
||||
"bedrock/anthropic.claude-3-5-sonnet",
|
||||
"bedrock/openai.gpt-5.6-luna",
|
||||
"vertex_ai/gemini-2.5-pro",
|
||||
"deepseek-chat",
|
||||
]);
|
||||
expect(rows.find((r) => r.id === "vertex_ai/gemini-2.5-pro")?.cacheHitRatio).toBeCloseTo(1 / 3, 6);
|
||||
});
|
||||
|
||||
it("labels model rows by model name with no sublabel", () => {
|
||||
|
|
@ -232,34 +232,20 @@ describe("computeCacheLeakage by model", () => {
|
|||
expect(rows[0].sublabel).toBeNull();
|
||||
});
|
||||
|
||||
it("prices model leakage at the Anthropic realized cache-read discount", () => {
|
||||
it("prices model leakage at the realized cache-read discount across providers", () => {
|
||||
const results = [
|
||||
modelDay("2026-07-01", {
|
||||
"claude-sonnet-5": { prompt_tokens: 1000, cache_read_input_tokens: 1000, prompt_caching_savings_spend: 2.0 },
|
||||
"claude-haiku-4-5": { prompt_tokens: 500 },
|
||||
"gemini-2.5-flash": { prompt_tokens: 500 },
|
||||
}),
|
||||
];
|
||||
const { rows, netSavingsPerCachedToken } = computeCacheLeakage(results, "model");
|
||||
expect(netSavingsPerCachedToken).toBeCloseTo(0.002, 6);
|
||||
expect(rows.map((r) => r.id)).toEqual(["claude-haiku-4-5"]);
|
||||
expect(rows.map((r) => r.id)).toEqual(["gemini-2.5-flash"]);
|
||||
expect(rows[0].potentialSavings).toBeCloseTo(1.0, 6);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isAnthropicModel", () => {
|
||||
it("matches Claude-family models across providers and rejects others", () => {
|
||||
const anthropic = [
|
||||
"claude-sonnet-5",
|
||||
"anthropic/claude-haiku-4-5",
|
||||
"bedrock/anthropic.claude-3-5-sonnet",
|
||||
"vertex_ai/claude-opus-4-8",
|
||||
];
|
||||
const others = ["gpt-4o", "deepseek-chat", "gemini-2.5-pro", "mistral-large"];
|
||||
expect(anthropic.every(isAnthropicModel)).toBe(true);
|
||||
expect(others.some(isAnthropicModel)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildDailyToolSeries", () => {
|
||||
const daily: ToolSpendDailyEntry[] = [
|
||||
{ date: "2026-07-01", tool_name: "search", spend: 1.0, call_count: 1 },
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ export interface CacheLeakageResult {
|
|||
netSavingsPerCachedToken: number | null;
|
||||
}
|
||||
|
||||
export const isAnthropicModel = (model: string): boolean => /claude|anthropic/i.test(model);
|
||||
|
||||
interface LeakageAccumulator {
|
||||
alias: string | null;
|
||||
teamId: string | null;
|
||||
|
|
@ -96,7 +94,6 @@ const aggregateByModel = (results: readonly DailyData[]): Map<string, LeakageAcc
|
|||
const byModel = new Map<string, LeakageAccumulator>();
|
||||
for (const day of results) {
|
||||
for (const [model, entry] of Object.entries(day.breakdown?.models ?? {})) {
|
||||
if (!isAnthropicModel(model)) continue;
|
||||
const acc = byModel.get(model) ?? emptyAccumulator();
|
||||
byModel.set(model, addMetrics(acc, entry.metrics, null, null));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue