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..911b1df2f41 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,14 @@ describe("AutoRouterBenchmarksTab", () => { expect(screen.getByText("Last 24 hours")).toBeInTheDocument(); }); + it("keeps the shadow eval section rendered while the benchmarks body is in its error state", () => { + mockHook({ error: new ApiError("boom", 500, {}) }); + renderTab(); + + expect(screen.getByTestId("shadow-eval-section")).toBeInTheDocument(); + expect(screen.getByText("Auto-router usage is unavailable right now")).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..ed2d8160efc 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 @@ -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"; @@ -310,6 +311,8 @@ 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.completed_count.toLocaleString()} judged responses
++ Shadowing {job.shadow_percentage}% via {job.router_name} +
++ {job.completed_count.toLocaleString()} responses judged · {job.failed_count.toLocaleString()} errored ·{" "} + {usd(job.cost_actual)} 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
+ )} ++ 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) => ( +