diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.test.tsx index 4a6ff77d99b..482901dfd14 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.test.tsx @@ -8,6 +8,7 @@ import { ApiError } from "@/lib/http/client"; vi.mock("./useAutoRouterBenchmarks", () => ({ useAutoRouterBenchmarks: vi.fn() })); vi.mock("@/app/(dashboard)/hooks/models/useModels", () => ({ useAutoRouters: vi.fn() })); +vi.mock("./ShadowEvalSection", () => ({ default: () =>
})); import { useAutoRouters } from "@/app/(dashboard)/hooks/models/useModels"; @@ -274,6 +275,33 @@ describe("AutoRouterBenchmarksTab", () => { expect(screen.getByText("Last 24 hours")).toBeInTheDocument(); }); + it("shows usage by default and mounts shadow evals only when its sub-tab is selected", () => { + mockHook({ data: response([group()]) }); + renderTab(); + + expect(screen.getByRole("tab", { name: "Usage" })).toHaveAttribute("aria-selected", "true"); + expect(screen.getByText("Total estimated savings")).toBeInTheDocument(); + expect(screen.queryByTestId("shadow-eval-section")).not.toBeInTheDocument(); + + fireEvent.click(screen.getByRole("tab", { name: "Shadow Evals" })); + expect(screen.getByRole("tab", { name: "Shadow Evals" })).toHaveAttribute("aria-selected", "true"); + expect(screen.getByTestId("shadow-eval-section")).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("tab", { name: "Usage" })); + expect(screen.getByText("Total estimated savings")).toBeInTheDocument(); + expect(screen.getByTestId("shadow-eval-section")).toBeInTheDocument(); + }); + + it("keeps the shadow evals sub-tab reachable while the usage body is in its error state", () => { + mockHook({ error: new ApiError("boom", 500, {}) }); + renderTab(); + + expect(screen.getByText("Auto-router usage is unavailable right now")).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("tab", { name: "Shadow Evals" })); + expect(screen.getByTestId("shadow-eval-section")).toBeInTheDocument(); + }); + it("keeps the window picker reachable while a window has no sessions", () => { mockHook({ data: response([]) }); renderTab(); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.tsx index 5d4fda765e7..80ddef29c9d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.tsx @@ -8,7 +8,7 @@ import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { ApiError } from "@/lib/http/client"; import { formatNumberWithCommas } from "@/utils/dataUtils"; @@ -31,6 +31,7 @@ import { type BucketRow, } from "./autoRouterBenchmarks"; import { usd } from "./costOptimizationUtils"; +import ShadowEvalSection from "./ShadowEvalSection"; import TierTurnsChart from "./TierTurnsChart"; import { useAutoRouterBenchmarks } from "./useAutoRouterBenchmarks"; @@ -268,7 +269,7 @@ interface AutoRouterBenchmarksTabProps { accessToken: string | null; } -const AutoRouterBenchmarksTab: React.FC{emptyResultsText(job, resultsError)}
; + } + return ( + <> ++ Router matched or beat your current model +
++ {pct(results.overall_shadow_win_rate_pct + results.overall_tie_rate_pct)} +
+of {(job.judged_count ?? 0).toLocaleString()} judged responses
++ Shadowing {job.shadow_percentage}% via {job.router_name} +
++ {(job.judged_count ?? 0).toLocaleString()} of {job.max_turns.toLocaleString()} turns judged ·{" "} + {(job.error_count ?? 0).toLocaleString()} errored · {usd(job.judge_spend ?? 0)} judge spend + {active && remaining ? ` · ${remaining}` : ""} +
++ Last failure: {job.last_error} +
+ )} ++ Duplicates a sampled slice of the key's traffic through the auto-router and has an LLM judge compare both + answers blind. The router's answers are never served to users; judge calls bill to the shadowed key. +
+Enter a value from 0.1 to 100
+ )} +Enter a value from 1 to 2000
+ )} ++ Would the auto-router have answered as well as the models you use today? Find out on your real traffic, before + switching anything. +
+Existing evaluations could not be loaded. Refresh the page to retry.
+ )} + + {isPending && error == null &&Loading evaluations...
} + + {showcased.map((job) => ( +