mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
refactor(ui): read throttling off enforcement instead of a caveat code
`enforcement` now carries "throttled" as its own mode, so the table reads the field rather than inferring it from a note that only explains the mechanism. A row is throttling even when the note is absent, and a note without the mode no longer makes the table claim one. The enforcement badge is a lookup exhaustive over the union, so a fourth mode fails this build instead of silently defaulting to "Blocks requests", which is the claim that was wrong in the first place.
This commit is contained in:
parent
db6eec50ff
commit
6f390fc12e
5 changed files with 45 additions and 30 deletions
|
|
@ -6,6 +6,7 @@ export type KeyBudgetsResponse = components["schemas"]["KeyBudgetsResponse"];
|
|||
export type KeyBudgetEntry = KeyBudgetsResponse["budgets"][number];
|
||||
export type KeyBudgetNote = KeyBudgetEntry["notes"][number];
|
||||
export type KeyBudgetNoteCode = KeyBudgetNote["code"];
|
||||
export type KeyBudgetEnforcement = KeyBudgetEntry["enforcement"];
|
||||
|
||||
export const useKeyBudgets = (keyId: string | undefined) => {
|
||||
const { accessToken } = useAuthorized();
|
||||
|
|
|
|||
|
|
@ -43,9 +43,10 @@ const ROLLING_NOTE = {
|
|||
text: noteText("rolling_window"),
|
||||
} as const;
|
||||
|
||||
// `enforcement` carries the fact now, so the note only explains the mechanism and drops to info.
|
||||
const THROTTLE_NOTE = {
|
||||
code: "throttled_instead_of_blocked",
|
||||
severity: "warning",
|
||||
severity: "info",
|
||||
text: noteText("throttled_instead_of_blocked"),
|
||||
} as const;
|
||||
|
||||
|
|
@ -220,7 +221,7 @@ describe("isThrottled", () => {
|
|||
it("never blames a throttled budget for a denial, because going over slows rather than rejects", () => {
|
||||
const throttledOver: KeyBudgetEntry = {
|
||||
...UNCONFIGURED_BUDGET,
|
||||
enforcement: "hard",
|
||||
enforcement: "throttled",
|
||||
max_budget: 100,
|
||||
spend: 140,
|
||||
remaining: -40,
|
||||
|
|
@ -232,6 +233,13 @@ describe("isThrottled", () => {
|
|||
expect(budgetThresholdRule(throttledOver)).toBe("Throttles at ≥ $100.00");
|
||||
});
|
||||
|
||||
it("reads the mode off enforcement rather than the note, which only explains the mechanism", () => {
|
||||
const noNote: KeyBudgetEntry = { ...UNCONFIGURED_BUDGET, enforcement: "throttled", max_budget: 100, notes: [] };
|
||||
expect(isThrottled(noNote)).toBe(true);
|
||||
const noteOnly: KeyBudgetEntry = { ...UNCONFIGURED_BUDGET, enforcement: "hard", notes: [THROTTLE_NOTE] };
|
||||
expect(isThrottled(noteOnly)).toBe(false);
|
||||
});
|
||||
|
||||
it("still says a plain hard budget blocks", () => {
|
||||
expect(isThrottled(TEAM_MEMBER_AT_LIMIT)).toBe(false);
|
||||
expect(budgetThresholdRule(TEAM_MEMBER_AT_LIMIT)).toBe("Blocks at ≥ $50.00");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,12 @@
|
|||
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
import type { KeyBudgetEntry, KeyBudgetNote, KeyBudgetNoteCode } from "@/app/(dashboard)/hooks/keys/useKeyBudgets";
|
||||
import type {
|
||||
KeyBudgetEnforcement,
|
||||
KeyBudgetEntry,
|
||||
KeyBudgetNote,
|
||||
KeyBudgetNoteCode,
|
||||
} from "@/app/(dashboard)/hooks/keys/useKeyBudgets";
|
||||
import {
|
||||
CellTooltip,
|
||||
DateCell,
|
||||
|
|
@ -65,8 +70,7 @@ const noteKillsRow = (note: KeyBudgetNote): boolean => {
|
|||
export const cannotTrip = (entry: KeyBudgetEntry): boolean => entry.notes.some(noteKillsRow);
|
||||
|
||||
/** Exceeding a throttled budget slows requests rather than rejecting them, so it never denies one. */
|
||||
export const isThrottled = (entry: KeyBudgetEntry): boolean =>
|
||||
entry.notes.some((note) => note.code === "throttled_instead_of_blocked");
|
||||
export const isThrottled = (entry: KeyBudgetEntry): boolean => entry.enforcement === "throttled";
|
||||
|
||||
/** Whether going over this budget rejects a request, as opposed to alerting, throttling or nothing. */
|
||||
const canDeny = (entry: KeyBudgetEntry): boolean => !isAlertOnly(entry) && !cannotTrip(entry) && !isThrottled(entry);
|
||||
|
|
@ -159,28 +163,25 @@ function ScopeCell({ entry }: { entry: KeyBudgetEntry }) {
|
|||
);
|
||||
}
|
||||
|
||||
/** Exhaustive, so a fourth enforcement mode fails this build rather than defaulting to a claim. */
|
||||
const ENFORCEMENT_BADGE: Readonly<Record<KeyBudgetEnforcement, { tone: StatusTone; label: string; tooltip: string }>> =
|
||||
{
|
||||
soft: {
|
||||
tone: "neutral",
|
||||
label: "Alert only",
|
||||
tooltip: "Soft budget. Going over raises an alert and never rejects a request.",
|
||||
},
|
||||
throttled: {
|
||||
tone: "warning",
|
||||
label: "Throttles requests",
|
||||
tooltip: "This key opted into throttle_on_budget_exceeded, so going over reduces its rate limits.",
|
||||
},
|
||||
hard: { tone: "info", label: "Blocks requests", tooltip: "Going over this budget rejects requests on this key." },
|
||||
};
|
||||
|
||||
function EnforcementCell({ entry }: { entry: KeyBudgetEntry }) {
|
||||
if (isAlertOnly(entry)) {
|
||||
return (
|
||||
<StatusBadge
|
||||
tone="neutral"
|
||||
label="Alert only"
|
||||
tooltip="Soft budget. Going over raises an alert and never rejects a request."
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (isThrottled(entry)) {
|
||||
return (
|
||||
<StatusBadge
|
||||
tone="warning"
|
||||
label="Throttles requests"
|
||||
tooltip="This key opted into throttle_on_budget_exceeded, so going over slows requests instead of rejecting them."
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<StatusBadge tone="info" label="Blocks requests" tooltip="Going over this budget rejects requests on this key." />
|
||||
);
|
||||
const { tone, label, tooltip } = ENFORCEMENT_BADGE[entry.enforcement];
|
||||
return <StatusBadge tone={tone} label={label} tooltip={tooltip} />;
|
||||
}
|
||||
|
||||
function SpendCell({ entry }: { entry: KeyBudgetEntry }) {
|
||||
|
|
|
|||
|
|
@ -574,10 +574,11 @@ describe("KeyInfoView Budgets tab", () => {
|
|||
remaining: -40,
|
||||
source: "key.max_budget",
|
||||
status: "exceeded",
|
||||
enforcement: "throttled",
|
||||
notes: [
|
||||
{
|
||||
code: "throttled_instead_of_blocked",
|
||||
severity: "warning",
|
||||
severity: "info",
|
||||
text: noteText("throttled_instead_of_blocked"),
|
||||
},
|
||||
],
|
||||
|
|
|
|||
10
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
10
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -6834,7 +6834,9 @@ export interface paths {
|
|||
* - entity_type: Litellm_EntityType - The entity a `BudgetExceededError` from this scope
|
||||
* names, so a denial message maps back to a row here
|
||||
* - entity_id / entity_label: str | None - Which entity is limited, and its human-facing alias
|
||||
* - enforcement: str - `hard` blocks the request, `soft` only raises an alert
|
||||
* - enforcement: str - `hard` blocks the request, `soft` only raises an alert, `throttled`
|
||||
* scales the key's rate limits down instead of denying anything. Only the key's own
|
||||
* `max_budget` can be `throttled`; every other scope on the same key still blocks
|
||||
* - max_budget: float | None - The limit in effect. `null` means this scope applies to the key
|
||||
* but places no limit on it
|
||||
* - spend: float | None - Spend as the enforcing check reads it, from the same cross-pod
|
||||
|
|
@ -7532,7 +7534,9 @@ export interface paths {
|
|||
* - entity_type: Litellm_EntityType - The entity a `BudgetExceededError` from this scope
|
||||
* names, so a denial message maps back to a row here
|
||||
* - entity_id / entity_label: str | None - Which entity is limited, and its human-facing alias
|
||||
* - enforcement: str - `hard` blocks the request, `soft` only raises an alert
|
||||
* - enforcement: str - `hard` blocks the request, `soft` only raises an alert, `throttled`
|
||||
* scales the key's rate limits down instead of denying anything. Only the key's own
|
||||
* `max_budget` can be `throttled`; every other scope on the same key still blocks
|
||||
* - max_budget: float | None - The limit in effect. `null` means this scope applies to the key
|
||||
* but places no limit on it
|
||||
* - spend: float | None - Spend as the enforcing check reads it, from the same cross-pod
|
||||
|
|
@ -26361,7 +26365,7 @@ export interface components {
|
|||
* Enforcement
|
||||
* @enum {string}
|
||||
*/
|
||||
enforcement: "hard" | "soft";
|
||||
enforcement: "hard" | "soft" | "throttled";
|
||||
/** Entity Id */
|
||||
entity_id?: string | null;
|
||||
/** Entity Label */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue