mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-05 07:37:50 +00:00
Merge aa8cc11379 into 9b1d69e32c
This commit is contained in:
commit
bdb41290ab
5 changed files with 137 additions and 3 deletions
|
|
@ -10,6 +10,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
|
||||
import { LogEntry } from "../types";
|
||||
import { getResponseCacheStatus } from "../responseCache";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import { PROMPT_CACHE_CREATION_TOOLTIP, PROMPT_CACHE_READ_TOOLTIP } from "@/utils/promptCacheUsage";
|
||||
import GuardrailViewer from "./sections/GuardrailViewer/GuardrailViewer";
|
||||
|
|
@ -481,10 +482,10 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata:
|
|||
? new Date(completionStartTime).getTime() - new Date(logEntry.startTime).getTime()
|
||||
: null;
|
||||
|
||||
const responseCacheValue = String(logEntry.cache_hit ?? "").toLowerCase();
|
||||
const responseCacheStatus = getResponseCacheStatus(logEntry);
|
||||
const responseCacheKey = logEntry.cache_key && logEntry.cache_key !== "Cache OFF" ? logEntry.cache_key : undefined;
|
||||
const isResponseCacheHit = responseCacheValue === "true";
|
||||
const showResponseCache = isResponseCacheHit || responseCacheValue === "false" || responseCacheKey != null;
|
||||
const isResponseCacheHit = responseCacheStatus === "hit";
|
||||
const showResponseCache = responseCacheStatus !== undefined;
|
||||
const promptCacheReadTokens = Number(metadata?.additional_usage_values?.cache_read_input_tokens) || 0;
|
||||
const promptCacheCreationTokens = Number(metadata?.additional_usage_values?.cache_creation_input_tokens) || 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
import { render, screen, within } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { DataTable } from "@/components/shared/DataTable";
|
||||
|
||||
import type { LogEntry } from "../types";
|
||||
import { getRequestLogsTableColumns } from "./RequestLogsTableColumns";
|
||||
|
||||
const logEntry = (overrides: Partial<LogEntry>): LogEntry => ({
|
||||
request_id: "request",
|
||||
api_key: "key",
|
||||
team_id: "team",
|
||||
model: "test-model",
|
||||
model_id: "model",
|
||||
call_type: "acompletion",
|
||||
spend: 0,
|
||||
total_tokens: 10,
|
||||
prompt_tokens: 5,
|
||||
completion_tokens: 5,
|
||||
startTime: "2026-01-01T00:00:00Z",
|
||||
endTime: "2026-01-01T00:00:01Z",
|
||||
cache_hit: "None",
|
||||
messages: [],
|
||||
response: {},
|
||||
...overrides,
|
||||
});
|
||||
|
||||
function renderCacheCells(logs: LogEntry[]) {
|
||||
render(
|
||||
<DataTable
|
||||
data={logs}
|
||||
columns={getRequestLogsTableColumns({ onKeyHashClick: vi.fn(), onSessionClick: vi.fn() })}
|
||||
getRowId={(row) => row.request_id}
|
||||
/>,
|
||||
);
|
||||
const header = screen.getByRole("columnheader", { name: "Cache Hit" });
|
||||
const columnIndex = screen.getAllByRole("columnheader").indexOf(header);
|
||||
return screen
|
||||
.getAllByRole("row")
|
||||
.slice(1)
|
||||
.map((row) => within(row).getAllByRole("cell")[columnIndex]);
|
||||
}
|
||||
|
||||
describe("Request log cache hits", () => {
|
||||
it("distinguishes a response cache hit, an enabled cache miss and caching disabled", () => {
|
||||
const cells = renderCacheCells([
|
||||
logEntry({ request_id: "hit", cache_hit: "True" }),
|
||||
logEntry({ request_id: "miss", cache_key: "response-cache-key" }),
|
||||
logEntry({ request_id: "off", cache_key: "Cache OFF" }),
|
||||
]);
|
||||
|
||||
expect(cells[0]).toHaveTextContent(/^Yes$/);
|
||||
expect(cells[1]).toHaveTextContent(/^No$/);
|
||||
expect(cells[2]).toHaveTextContent(/^-$/);
|
||||
});
|
||||
|
||||
it("shows session hit counts instead of the representative request's cache status", () => {
|
||||
const sessions: Partial<LogEntry>[] = [
|
||||
{ request_id: "mixed", cache_hit: "False", session_total_count: 3, session_cache_hit_count: 2 },
|
||||
{ request_id: "one-hit", cache_hit: "False", session_total_count: 2, session_cache_hit_count: 1 },
|
||||
{ request_id: "no-hits", session_total_count: 2, session_cache_hit_count: 0 },
|
||||
{ request_id: "unknown", cache_hit: "True", session_total_count: 2 },
|
||||
];
|
||||
const cells = renderCacheCells(sessions.map(logEntry));
|
||||
|
||||
expect(cells[0]).toHaveTextContent(/^2 hits$/);
|
||||
expect(cells[1]).toHaveTextContent(/^1 hit$/);
|
||||
expect(cells[2]).toHaveTextContent(/^0 hits$/);
|
||||
expect(cells[3]).toHaveTextContent(/^-$/);
|
||||
});
|
||||
|
||||
it("does not report provider prompt caching as a response cache hit", () => {
|
||||
const promptCached: Partial<LogEntry> = {
|
||||
cache_key: "Cache OFF",
|
||||
session_total_count: 1,
|
||||
session_cache_hit_count: 0,
|
||||
metadata: { additional_usage_values: { cache_read_input_tokens: 100 } },
|
||||
};
|
||||
const [cell] = renderCacheCells([logEntry(promptCached)]);
|
||||
|
||||
expect(cell).toHaveTextContent(/^-$/);
|
||||
});
|
||||
});
|
||||
|
|
@ -10,6 +10,7 @@ import { copyToClipboard, getSpendString } from "@/utils/dataUtils";
|
|||
import { getProviderLogoAndName } from "../../provider_info_helpers";
|
||||
import { getBatchIdFromRequestId, getBatchRequestCounts, isBatchCallType } from "../batchLogUtils";
|
||||
import type { LogEntry } from "../types";
|
||||
import { getResponseCacheStatus } from "../responseCache";
|
||||
import { AGENT_CALL_TYPES, MCP_CALL_TYPES } from "../constants";
|
||||
import { AgentBadge, AgentIcon, BatchBadge, LlmBadge, McpBadge, SparkleIcon, WrenchIcon } from "./TypeBadges";
|
||||
|
||||
|
|
@ -159,6 +160,25 @@ export const getRequestLogsTableColumns = ({
|
|||
return <StatusBadge tone={isSuccess ? "success" : "error"} label={isSuccess ? "Success" : "Failure"} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "cache_hit",
|
||||
accessorKey: "cache_hit",
|
||||
header: "Cache Hit",
|
||||
size: 100,
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => {
|
||||
const log = row.original;
|
||||
if ((log.session_total_count ?? 1) > 1) {
|
||||
const hits = log.session_cache_hit_count;
|
||||
return (
|
||||
<span className="whitespace-nowrap">{hits == null ? "-" : `${hits} ${hits === 1 ? "hit" : "hits"}`}</span>
|
||||
);
|
||||
}
|
||||
const status = getResponseCacheStatus(log);
|
||||
if (status === "hit") return <StatusBadge tone="success" label="Yes" />;
|
||||
return <span>{status === "miss" ? "No" : "-"}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "session_id",
|
||||
accessorKey: "session_id",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getResponseCacheStatus } from "./responseCache";
|
||||
|
||||
describe("getResponseCacheStatus", () => {
|
||||
it.each([
|
||||
{ cache_hit: "True", cache_key: undefined, expected: "hit" },
|
||||
{ cache_hit: "true", cache_key: "response-key", expected: "hit" },
|
||||
{ cache_hit: "TRUE", cache_key: "Cache OFF", expected: "hit" },
|
||||
{ cache_hit: "False", cache_key: undefined, expected: "miss" },
|
||||
{ cache_hit: "false", cache_key: "response-key", expected: "miss" },
|
||||
{ cache_hit: "FALSE", cache_key: "Cache OFF", expected: "miss" },
|
||||
{ cache_hit: "None", cache_key: "response-key", expected: "miss" },
|
||||
{ cache_hit: "", cache_key: "response-key", expected: "miss" },
|
||||
{ cache_hit: "None", cache_key: "Cache OFF", expected: undefined },
|
||||
{ cache_hit: "None", cache_key: undefined, expected: undefined },
|
||||
{ cache_hit: "None", cache_key: "", expected: undefined },
|
||||
{ cache_hit: "", cache_key: undefined, expected: undefined },
|
||||
])("returns $expected for cache_hit=$cache_hit and cache_key=$cache_key", ({ cache_hit, cache_key, expected }) => {
|
||||
expect(getResponseCacheStatus({ cache_hit, cache_key })).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import type { LogEntry } from "./types";
|
||||
|
||||
export function getResponseCacheStatus(log: Pick<LogEntry, "cache_hit" | "cache_key">): "hit" | "miss" | undefined {
|
||||
const value = String(log.cache_hit ?? "").toLowerCase();
|
||||
if (value === "true") return "hit";
|
||||
if (value === "false" || (log.cache_key && log.cache_key !== "Cache OFF")) return "miss";
|
||||
return undefined;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue