From 78c9a8bc317fd78d648f1076d052e185bac0ff57 Mon Sep 17 00:00:00 2001 From: dor Date: Sun, 18 Oct 2026 14:57:39 -0400 Subject: [PATCH] feat(ui): offer Nadir as an auto router decision model Add Auto Router -> Decisions Model now lists Nadir beside Jev, Laya, Bespoke Nimble and Databricks. Picking it starts the classifier on the `bucket` label, hides the Classifier Model field (the label only names log rows) and the Classifier Instructions field, and drops any instructions carried over from another provider on save, since the gateway rejects instructions for Nadir. The editor tells the admin to set NADIR_API_KEY on the gateway, or NADIR_API_BASE for a self-hosted Nadir. schema.d.ts gains "nadir" in the opensource_classifier_config provider enum, matching the backend Literal (the one line `npm run gen:api` changes for this diff). Co-Authored-By: Claude Opus 5.5 --- .../add_model/AutoRouterClassifierTabs.tsx | 2 + .../add_model/ClassifierTypeRadios.tsx | 4 +- .../JevClassifierConfig.integration.test.tsx | 22 ++++ .../add_model/JevClassifierConfig.tsx | 110 ++++++++++-------- ...d_auto_router_routing_test_request.test.ts | 2 + .../build_complexity_router_config.test.ts | 6 +- .../add_model/jev_classifier_config.test.ts | 19 ++- .../add_model/jev_classifier_config.ts | 18 ++- ui/litellm-dashboard/src/lib/http/schema.d.ts | 2 +- 9 files changed, 126 insertions(+), 59 deletions(-) diff --git a/ui/litellm-dashboard/src/components/add_model/AutoRouterClassifierTabs.tsx b/ui/litellm-dashboard/src/components/add_model/AutoRouterClassifierTabs.tsx index 509329bb948..0b32995f707 100644 --- a/ui/litellm-dashboard/src/components/add_model/AutoRouterClassifierTabs.tsx +++ b/ui/litellm-dashboard/src/components/add_model/AutoRouterClassifierTabs.tsx @@ -233,6 +233,7 @@ const AutoRouterClassifierTabs: React.FC = ({ { value: "laya", label: "Laya" }, { value: "bespoke", label: "Bespoke Nimble" }, { value: "databricks", label: "Databricks" }, + { value: "nadir", label: "Nadir" }, ]} value={normalizeJevClassifierConfig(value.jev_classifier_config).provider} onValueChange={changeProvider} @@ -245,6 +246,7 @@ const AutoRouterClassifierTabs: React.FC = ({ Laya Bespoke Nimble Databricks + Nadir diff --git a/ui/litellm-dashboard/src/components/add_model/ClassifierTypeRadios.tsx b/ui/litellm-dashboard/src/components/add_model/ClassifierTypeRadios.tsx index 6ed46e2a27d..ce98b25f36f 100644 --- a/ui/litellm-dashboard/src/components/add_model/ClassifierTypeRadios.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ClassifierTypeRadios.tsx @@ -55,7 +55,9 @@ const ClassifierTypeRadios: React.FC = ({ value, clas OSS Classifier{" "} - uses Jev, Laya, Bespoke, or Databricks to decide the tier + + uses Jev, Laya, Bespoke, Databricks, or Nadir to decide the tier + diff --git a/ui/litellm-dashboard/src/components/add_model/JevClassifierConfig.integration.test.tsx b/ui/litellm-dashboard/src/components/add_model/JevClassifierConfig.integration.test.tsx index f829a2f2e41..32f56128987 100644 --- a/ui/litellm-dashboard/src/components/add_model/JevClassifierConfig.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/JevClassifierConfig.integration.test.tsx @@ -164,6 +164,28 @@ describe("JEV classifier editor", () => { }, ); + it("hides the model and instructions for Nadir and saves it without instructions", async () => { + renderWithProviders(
); + fireEvent.click(screen.getByRole("radio", { name: /^Decisions Model$/ })); + fireEvent.change(screen.getByLabelText("Classifier Instructions"), { target: { value: "Pick carefully" } }); + await chooseSelectOption(userEvent, screen.getByRole("combobox", { name: "Decision model" }), "Nadir"); + expect(screen.queryByLabelText("Classifier Model")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Classifier Instructions")).not.toBeInTheDocument(); + expect(screen.getByText(/Set NADIR_API_KEY on the gateway/)).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Save and reload" })); + expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveTextContent("Nadir"); + fireEvent.click(screen.getByRole("button", { name: "Probe current config" })); + expect(testAutoRouterRouting).toHaveBeenLastCalledWith( + "token", + expect.objectContaining({ + complexity_router_config: expect.objectContaining({ + classifier_type: "oss_classifier", + opensource_classifier_config: { provider: "nadir", model: "bucket", timeout_ms: 3000 }, + }), + }), + ); + }); + it("allows licensed instructions and can restore built-in instructions", () => { const authorized = useAuthorized(); vi.mocked(useAuthorized).mockReturnValue({ ...authorized, premiumUser: true }); diff --git a/ui/litellm-dashboard/src/components/add_model/JevClassifierConfig.tsx b/ui/litellm-dashboard/src/components/add_model/JevClassifierConfig.tsx index 4b3cfa21f9d..f09384ca2b8 100644 --- a/ui/litellm-dashboard/src/components/add_model/JevClassifierConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/JevClassifierConfig.tsx @@ -7,7 +7,11 @@ import { Textarea } from "@/components/ui/textarea"; import ClassifierCircuitBreakerConfig from "./ClassifierCircuitBreakerConfig"; import type { ComplexityRouterConfigValue } from "./ComplexityRouterConfig"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { defaultJevClassifierConfig, fixedClassifierModels } from "./jev_classifier_config"; +import { + acceptsClassifierInstructions, + defaultJevClassifierConfig, + fixedClassifierModels, +} from "./jev_classifier_config"; const providerDescriptions = { jev: "Uses TypeSafe System One Choice evaluation with your configured tiers", @@ -16,6 +20,8 @@ const providerDescriptions = { "Uses Bespoke Nimble with your configured tiers. Set BESPOKE_API_BASE on the gateway to connect your Nimble server.", databricks: "Uses the Databricks ai_decide AI Function, or a decision serving endpoint you name, with your configured tiers. Set DATABRICKS_API_BASE to https:///serving-endpoints and DATABRICKS_API_KEY on the gateway.", + nadir: + "Uses Nadir's tier decision with your configured tiers. Set NADIR_API_KEY on the gateway; without it, calls use Nadir's anonymous, rate-limited tier. Set NADIR_API_BASE for a self-hosted Nadir.", }; export default function JevClassifierConfig({ @@ -38,34 +44,36 @@ export default function JevClassifierConfig({ {section !== "advanced" && !config.deployment_name && ( <>

{providerDescriptions[config.provider ?? "jev"]}

-
- - {models ? ( - - ) : ( - update({ model: event.target.value })} - /> - )} -
+ {config.provider !== "nadir" && ( +
+ + {models ? ( + + ) : ( + update({ model: event.target.value })} + /> + )} +
+ )} )} {section !== "model" && ( @@ -90,27 +98,29 @@ export default function JevClassifierConfig({ }) } /> -
- - -