mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
feat(ui): explain input/output/cache token accounting on usage Cost tab
Customers were confused about what input vs output tokens mean and how cache tokens relate to the total. Adds a reusable TokenMetricLabel with info tooltips on the Cost tab's Total/Input/Output/Cache Read/Cache Write cards, documenting that input already includes cache tokens (so the breakdown sums to total) and that output includes reasoning tokens.
This commit is contained in:
parent
cff3e0b75e
commit
6b1ad046c8
2 changed files with 100 additions and 13 deletions
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* Labeled token metric heading with an info tooltip.
|
||||
*
|
||||
* Centralizes the wording for what "input", "output", "cache read", and
|
||||
* "cache write" tokens actually mean so the Cost tab (and, later, the other
|
||||
* usage views) communicate token accounting consistently.
|
||||
*
|
||||
* Backend semantics these definitions reflect:
|
||||
* - `prompt_tokens` is the *total* input, already including cache read +
|
||||
* cache creation tokens (LiteLLM normalizes Anthropic's split usage to
|
||||
* match OpenAI's convention — see anthropic/chat/transformation.py).
|
||||
* - `completion_tokens` is all output, including reasoning/thinking tokens.
|
||||
* - `total_tokens` = prompt_tokens + completion_tokens, so cache tokens are a
|
||||
* subset of input, never added on top.
|
||||
* - The "Input Tokens" card shows the non-cached portion
|
||||
* (prompt_tokens − cache_read − cache_write), so Input + Output + Cache
|
||||
* Read + Cache Write sum exactly to Total Tokens.
|
||||
*/
|
||||
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Title } from "@tremor/react";
|
||||
import { Tooltip } from "antd";
|
||||
import React from "react";
|
||||
|
||||
export type TokenMetricKind = "total" | "input" | "output" | "cache_read" | "cache_write";
|
||||
|
||||
const TOOLTIP_COPY: Record<TokenMetricKind, React.ReactNode> = {
|
||||
total: (
|
||||
<>
|
||||
Total = Input + Output. Cached tokens are counted as part of Input (not added on top), so the Input, Output, Cache
|
||||
Read, and Cache Write cards sum exactly to this number. Reflects successful requests only.
|
||||
</>
|
||||
),
|
||||
input: (
|
||||
<>
|
||||
Non-cached prompt tokens sent to the model — the prompt minus any tokens served from or written to the prompt
|
||||
cache. Billed at the model's standard input rate. Cached portions are shown separately as Cache Read / Cache
|
||||
Write.
|
||||
</>
|
||||
),
|
||||
output: (
|
||||
<>
|
||||
Tokens the model generated in its response, including any reasoning / “thinking” tokens. Billed at the
|
||||
model's output rate.
|
||||
</>
|
||||
),
|
||||
cache_read: (
|
||||
<>
|
||||
Input tokens served from the prompt cache instead of being reprocessed. Billed at a steep discount versus standard
|
||||
input (e.g. ~0.1× on Anthropic / OpenAI). Already included in total input.
|
||||
</>
|
||||
),
|
||||
cache_write: (
|
||||
<>
|
||||
Input tokens written into the prompt cache so later requests can reuse them. Billed at a premium over standard
|
||||
input on some providers (e.g. ~1.25× on Anthropic). Already included in total input.
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
interface TokenMetricLabelProps {
|
||||
kind: TokenMetricKind;
|
||||
label: string;
|
||||
/** Optional trailing node (e.g. an expand/collapse chevron) rendered after the icon. */
|
||||
trailing?: React.ReactNode;
|
||||
}
|
||||
|
||||
const TokenMetricLabel: React.FC<TokenMetricLabelProps> = ({ kind, label, trailing }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<Title>{label}</Title>
|
||||
<Tooltip title={TOOLTIP_COPY[kind]}>
|
||||
<InfoCircleOutlined className="text-gray-400 hover:text-gray-600" />
|
||||
</Tooltip>
|
||||
{trailing}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default TokenMetricLabel;
|
||||
|
|
@ -49,6 +49,7 @@ import EndpointUsage from "./EndpointUsage/EndpointUsage";
|
|||
import EntityUsage, { EntityList } from "./EntityUsage/EntityUsage";
|
||||
import SpendByProvider from "./EntityUsage/SpendByProvider";
|
||||
import TopKeyView from "./EntityUsage/TopKeyView";
|
||||
import TokenMetricLabel from "./TokenMetricLabel";
|
||||
import UsageAIChatPanel from "./UsageAIChatPanel";
|
||||
import { UsageOption, UsageViewSelect } from "./UsageViewSelect/UsageViewSelect";
|
||||
|
||||
|
|
@ -636,14 +637,17 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
|||
className="cursor-pointer hover:bg-gray-50 transition-colors"
|
||||
onClick={() => setShowTokenBreakdown(!showTokenBreakdown)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Title>Total Tokens</Title>
|
||||
{showTokenBreakdown ? (
|
||||
<DownOutlined className="text-gray-400 text-xs" />
|
||||
) : (
|
||||
<RightOutlined className="text-gray-400 text-xs" />
|
||||
)}
|
||||
</div>
|
||||
<TokenMetricLabel
|
||||
kind="total"
|
||||
label="Total Tokens"
|
||||
trailing={
|
||||
showTokenBreakdown ? (
|
||||
<DownOutlined className="text-gray-400 text-xs" />
|
||||
) : (
|
||||
<RightOutlined className="text-gray-400 text-xs" />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Text className="text-2xl font-bold mt-2">
|
||||
{userSpendData.metadata?.total_tokens?.toLocaleString() || 0}
|
||||
</Text>
|
||||
|
|
@ -652,25 +656,30 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
|||
{showTokenBreakdown && (
|
||||
<Grid numItems={4} className="gap-4 mt-4">
|
||||
<Card>
|
||||
<Title>Input Tokens</Title>
|
||||
<TokenMetricLabel kind="input" label="Input Tokens" />
|
||||
<Text className="text-2xl font-bold mt-2 text-blue-600">
|
||||
{(userSpendData.metadata?.total_prompt_tokens || 0).toLocaleString()}
|
||||
{Math.max(
|
||||
0,
|
||||
(userSpendData.metadata?.total_prompt_tokens || 0) -
|
||||
(userSpendData.metadata?.total_cache_read_input_tokens || 0) -
|
||||
(userSpendData.metadata?.total_cache_creation_input_tokens || 0)
|
||||
).toLocaleString()}
|
||||
</Text>
|
||||
</Card>
|
||||
<Card>
|
||||
<Title>Output Tokens</Title>
|
||||
<TokenMetricLabel kind="output" label="Output Tokens" />
|
||||
<Text className="text-2xl font-bold mt-2 text-cyan-600">
|
||||
{userSpendData.metadata?.total_completion_tokens?.toLocaleString() || 0}
|
||||
</Text>
|
||||
</Card>
|
||||
<Card>
|
||||
<Title>Cache Read Tokens</Title>
|
||||
<TokenMetricLabel kind="cache_read" label="Cache Read Tokens" />
|
||||
<Text className="text-2xl font-bold mt-2 text-green-600">
|
||||
{userSpendData.metadata?.total_cache_read_input_tokens?.toLocaleString() || 0}
|
||||
</Text>
|
||||
</Card>
|
||||
<Card>
|
||||
<Title>Cache Write Tokens</Title>
|
||||
<TokenMetricLabel kind="cache_write" label="Cache Write Tokens" />
|
||||
<Text className="text-2xl font-bold mt-2 text-purple-600">
|
||||
{userSpendData.metadata?.total_cache_creation_input_tokens?.toLocaleString() || 0}
|
||||
</Text>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue