mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-22 00:31:44 +00:00
feat(ui): note on the cache leakage card when only the top-spend keys were loaded
Some checks failed
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
ai-gateway image / ai-gateway release image (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
Some checks failed
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
ai-gateway image / ai-gateway release image (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
303a9058cc
commit
b74733d4e6
4 changed files with 51 additions and 2 deletions
|
|
@ -178,4 +178,28 @@ describe("CacheLeakageCard", () => {
|
|||
screen.queryByText("Data is still loading; rows and totals will update as the rest of the range arrives."),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("says which keys are missing from the key ranking when the proxy capped the per-key lists", () => {
|
||||
const day = dayWithKeys("2026-07-12", {
|
||||
"hash-leaky": key("leaky-key", { prompt_tokens: 10000, cache_read_input_tokens: 0 }),
|
||||
});
|
||||
renderWith([day], { apiKeyTruncation: { limit: 100, total: 3000 } });
|
||||
|
||||
expect(screen.getByRole("note")).toHaveTextContent(
|
||||
"Only the 100 highest-spend keys of 3,000 are loaded, so a lower-spend key that leaks more is not listed here.",
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: "By model" }));
|
||||
|
||||
expect(screen.queryByRole("note")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the key ranking note off when every key was loaded", () => {
|
||||
const day = dayWithKeys("2026-07-12", {
|
||||
"hash-leaky": key("leaky-key", { prompt_tokens: 10000, cache_read_input_tokens: 0 }),
|
||||
});
|
||||
renderWith([day]);
|
||||
|
||||
expect(screen.queryByRole("note")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ const SortableHead = ({
|
|||
};
|
||||
|
||||
const CacheLeakageCard: React.FC<CacheLeakageCardProps> = ({ activity }) => {
|
||||
const { dateValue, onDateChange, results, loading, isFetchingMore } = activity;
|
||||
const { dateValue, onDateChange, results, loading, isFetchingMore, apiKeyTruncation } = activity;
|
||||
const [dimension, setDimension] = useState<CacheLeakageDimension>("key");
|
||||
const [sort, setSort] = useState<SortState>({ column: "potentialSavings", dir: "desc" });
|
||||
const leakage = useMemo(() => computeCacheLeakage(results, dimension), [results, dimension]);
|
||||
|
|
@ -123,6 +123,13 @@ const CacheLeakageCard: React.FC<CacheLeakageCardProps> = ({ activity }) => {
|
|||
</Tabs>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{dimension === "key" && apiKeyTruncation !== undefined && (
|
||||
<p className="mb-2 text-sm text-muted-foreground" role="note">
|
||||
Only the {apiKeyTruncation.limit.toLocaleString()} highest-spend keys of{" "}
|
||||
{apiKeyTruncation.total.toLocaleString()} are loaded, so a lower-spend key that leaks more is not listed
|
||||
here. Raise USAGE_TOP_API_KEYS_LIMIT on the proxy to load more keys.
|
||||
</p>
|
||||
)}
|
||||
{rows.length > 0 && isFetchingMore && (
|
||||
<p className="mb-2 text-sm text-muted-foreground">
|
||||
Data is still loading; rows and totals will update as the rest of the range arrives.
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ import { describe, expect, it, vi } from "vitest";
|
|||
const mockUsePaginatedDailyActivity = vi.fn();
|
||||
|
||||
const mockCancel = vi.fn();
|
||||
let mockMetadata: Record<string, number> = {};
|
||||
|
||||
vi.mock("@/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity", () => ({
|
||||
usePaginatedDailyActivity: (args: unknown) => {
|
||||
mockUsePaginatedDailyActivity(args);
|
||||
return {
|
||||
data: { results: [] },
|
||||
data: { results: [], metadata: mockMetadata },
|
||||
loading: false,
|
||||
isFetchingMore: false,
|
||||
progress: { currentPage: 4, totalPages: 9 },
|
||||
|
|
@ -80,4 +81,18 @@ describe("useDailyActivityRange", () => {
|
|||
|
||||
expect(mockUsePaginatedDailyActivity).toHaveBeenLastCalledWith(expect.objectContaining({ enabled: false }));
|
||||
});
|
||||
|
||||
it("reports how many keys the proxy left out of the per-key lists", () => {
|
||||
mockMetadata = { api_key_limit: 100, total_api_keys: 3000 };
|
||||
const { result } = renderHook(() => useDailyActivityRange("test-token", "u1", "proxy_admin"));
|
||||
|
||||
expect(result.current.apiKeyTruncation).toEqual({ limit: 100, total: 3000 });
|
||||
});
|
||||
|
||||
it("reports no key truncation when every key fit under the proxy limit", () => {
|
||||
mockMetadata = { api_key_limit: 100, total_api_keys: 100 };
|
||||
const { result } = renderHook(() => useDailyActivityRange("test-token", "u1", "proxy_admin"));
|
||||
|
||||
expect(result.current.apiKeyTruncation).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useMemo, useState } from "react";
|
||||
|
||||
import { userDailyActivityAggregatedCall, userDailyActivityCall } from "@/components/networking";
|
||||
import { ApiKeyTruncation, getApiKeyTruncation } from "@/components/EntityUsageExport/exportBlockedReason";
|
||||
import { DailyData } from "@/components/UsagePage/types";
|
||||
import { spendScopeUserId } from "@/utils/roles";
|
||||
import { usePaginatedDailyActivity } from "@/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity";
|
||||
|
|
@ -22,6 +23,7 @@ export interface DailyActivityRange {
|
|||
cancelled: boolean;
|
||||
failed: boolean;
|
||||
cancel: () => void;
|
||||
apiKeyTruncation?: ApiKeyTruncation;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -78,6 +80,7 @@ export const useScopedDailyActivityRange = (
|
|||
cancelled,
|
||||
failed,
|
||||
cancel,
|
||||
apiKeyTruncation: getApiKeyTruncation(data.metadata?.api_key_limit, data.metadata?.total_api_keys),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue