diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts
index e18406a9e60..a9f7c54698a 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts
+++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts
@@ -90,9 +90,6 @@ export interface AutoRouterCandidateDeployment {
export interface AutoRouterDeployment extends AutoRouterCandidateDeployment {
litellm_params?: {
model?: string | null;
- base_model?: string | null;
- input_cost_per_token?: number | null;
- output_cost_per_token?: number | null;
complexity_router_config?: unknown;
complexity_router_default_model?: string | null;
auto_router_config?: unknown;
@@ -108,8 +105,6 @@ export interface AutoRouterDeployment extends AutoRouterCandidateDeployment {
/** False for config.yaml-defined deployments, which the update and delete routes refuse. */
db_model?: boolean | null;
base_model?: string | null;
- input_cost_per_token?: number | null;
- output_cost_per_token?: number | null;
created_at?: string | null;
updated_at?: string | null;
team_id?: string | null;
diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx
index 8c6dae07ddc..d2f6b10c3a6 100644
--- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx
+++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx
@@ -19,10 +19,6 @@ vi.mock(
"@/app/(dashboard)/hooks/autoRouter/useAutoRouterPresets",
async () => await import("../../../tests/mocks/autoRouterPresets"),
);
-vi.mock("@/app/(dashboard)/hooks/models/useModelCostMap", () => ({
- useModelCostMap: () => ({ data: {}, isLoading: false }),
-}));
-
const getAllPresets = (): AutoRouterPreset[] => BUNDLED_PRESETS;
const getPresetByKey = (key: string): AutoRouterPreset | undefined => BUNDLED_PRESETS.find((p) => p.key === key);
@@ -158,56 +154,38 @@ describe("AddAutoRouterTab", () => {
expect(screen.getByText("Complexity Tier Configuration")).toBeInTheDocument();
});
- it("configures four unique tiers from the available models with one click", async () => {
+ it("hides automatic setup when no available model is recommended", async () => {
mockFetchAvailableModels.mockResolvedValue([
- { model_group: "premium", mode: "chat" },
- { model_group: "cheap", mode: "chat" },
- { model_group: "best", mode: "chat" },
- { model_group: "middle", mode: "chat" },
- ]);
- mockFetchAllModelDeployments.mockResolvedValue([
- { model_name: "cheap", litellm_params: { model: "cheap", input_cost_per_token: 1 } },
- { model_name: "middle", litellm_params: { model: "middle", input_cost_per_token: 2 } },
- { model_name: "premium", litellm_params: { model: "premium", input_cost_per_token: 3 } },
- { model_name: "best", litellm_params: { model: "best", input_cost_per_token: 4 } },
+ { model_group: "unknown-model-a", mode: "chat" },
+ { model_group: "unknown-model-b", mode: "chat" },
]);
renderWithProviders();
- const button = screen.getByTestId("configure-automatically-button");
- await waitFor(() => expect(button).toBeEnabled());
- await userEvent.click(button);
-
- expect(screen.getByText(/Simple: cheap.*Medium: middle.*Complex: premium.*Reasoning: best/)).toBeInTheDocument();
+ openTemplateDropdown();
+ await waitFor(() => expect(optionByLabel("Anthropic Family")).toHaveTextContent("Missing:"));
+ expect(screen.queryByTestId("configure-automatically-button")).not.toBeInTheDocument();
});
- it("prefers the first compatible bundled template over the price fallback", async () => {
- const firstPreset = getAllPresets()[0];
+ it("mixes preferred tier models even when one complete preset is available", async () => {
+ const anthropicPreset = getPresetByKey("anthropic_family")!;
mockFetchAvailableModels.mockResolvedValue(
- [...getRequiredModelsInPreset(firstPreset)].map((model_group) => ({ model_group, mode: "chat" })),
+ [...getRequiredModelsInPreset(anthropicPreset), "gpt-5.6-luna"].map((model_group) => ({
+ model_group,
+ mode: "chat",
+ })),
);
mockFetchAllModelDeployments.mockResolvedValue([]);
renderWithProviders();
- const button = screen.getByTestId("configure-automatically-button");
- await waitFor(() => expect(button).toBeEnabled());
+ const button = await screen.findByTestId("configure-automatically-button");
await userEvent.click(button);
- expect(toast.success).toHaveBeenCalledWith(`Configured with ${firstPreset.label}`);
- });
-
- it("prefers the OpenAI template over Gemini", async () => {
- const openAiPreset = getPresetByKey("openai_family")!;
- const geminiPreset = getPresetByKey("gemini_family")!;
- const available = new Set([...getRequiredModelsInPreset(openAiPreset), ...getRequiredModelsInPreset(geminiPreset)]);
- mockFetchAvailableModels.mockResolvedValue([...available].map((model_group) => ({ model_group, mode: "chat" })));
- mockFetchAllModelDeployments.mockResolvedValue([]);
- renderWithProviders();
-
- const button = screen.getByTestId("configure-automatically-button");
- await waitFor(() => expect(button).toBeEnabled());
- await userEvent.click(button);
-
- expect(toast.success).toHaveBeenCalledWith(`Configured with ${openAiPreset.label}`);
+ expect(
+ screen.getByText(
+ /Simple: gpt-5.6-luna.*Medium: claude-sonnet-5.*Complex: claude-opus-5.*Reasoning: claude-opus-5/,
+ ),
+ ).toBeInTheDocument();
+ expect(toast.success).not.toHaveBeenCalledWith(expect.stringContaining("Configured with"));
});
it("mixes available models from the preferred tier catalog when no complete template fits", async () => {
@@ -220,8 +198,7 @@ describe("AddAutoRouterTab", () => {
mockFetchAllModelDeployments.mockResolvedValue([]);
renderWithProviders();
- const button = screen.getByTestId("configure-automatically-button");
- await waitFor(() => expect(button).toBeEnabled());
+ const button = await screen.findByTestId("configure-automatically-button");
await userEvent.click(button);
expect(
diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx
index 9f3f74685d1..2dfb8864faa 100644
--- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx
+++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx
@@ -21,7 +21,6 @@ import TeamDropdown from "../common_components/team_dropdown";
import { type AddAutoRouterValues, handleAddAutoRouterSubmit } from "./handle_add_auto_router_submit";
import { fetchAvailableModels, type ModelGroup } from "@/components/llm_calls/fetch_models";
import { autoRouterListKey, fetchAllModelDeployments } from "@/app/(dashboard)/hooks/models/useModels";
-import { useModelCostMap } from "@/app/(dashboard)/hooks/models/useModelCostMap";
import ComplexityRouterConfig, {
ComplexityRouterConfigValue,
effectiveClassifierType,
@@ -106,7 +105,6 @@ const presetDisabledHint = (availability: PresetAvailability): string | null =>
const isPresetHintAlarming = (availability: PresetAvailability): boolean => availability.kind === "missing_models";
const NO_PRESETS: AutoRouterPreset[] = [];
-const AUTO_SETUP_PRESET_PRIORITY = ["1m_context", "anthropic_family", "openai_family", "gemini_family", "lite"];
// A one-line summary of what's configured, shown when the detailed section is collapsed so a
// caller can see the shape of the config without opening it.
@@ -236,7 +234,6 @@ const AddAutoRouterTab: React.FC = ({
queryFn: () => fetchAllModelDeployments(accessToken, userId ?? "", userRole),
enabled: Boolean(accessToken),
});
- const { data: modelCostMap = {}, isLoading: costsLoading } = useModelCostMap();
const modelsLoading = groupsLoading || deploymentsLoading;
const modelInfo = React.useMemo(() => data ?? [], [data]);
const {
@@ -246,7 +243,7 @@ const AddAutoRouterTab: React.FC = ({
refetch: refetchPresets,
} = useAutoRouterPresets();
const presets = presetsData ?? NO_PRESETS;
- const automaticSetupLoading = modelsLoading || costsLoading || presetsPending;
+ const automaticSetupLoading = modelsLoading || presetsPending;
const presetsUnavailable = presetsError && presetsData === undefined;
// react-query keeps the last successful list around when a later refetch fails, so isError alone
// can't tell "never loaded" apart from "loaded, then a background refetch errored" - only the
@@ -271,6 +268,14 @@ const AddAutoRouterTab: React.FC = ({
),
[modelInfo],
);
+ const preferredTierModels = React.useMemo(
+ () => buildPreferredTierModels(presets, availability),
+ [presets, availability],
+ );
+ const automaticRouterConfig = React.useMemo(
+ () => buildAutomaticRouterConfig(modelInfo, deployments ?? [], preferredTierModels),
+ [modelInfo, deployments, preferredTierModels],
+ );
// A preset's models can only be trusted against a successfully loaded list. Selection and the
// greyed-out state derive from this one function, so a preset that cannot be selected can never
@@ -319,30 +324,11 @@ const AddAutoRouterTab: React.FC = ({
};
const handleAutomaticSetup = () => {
- const prioritizedPresets = AUTO_SETUP_PRESET_PRIORITY.flatMap((key) => {
- const preset = presets.find((candidate) => candidate.key === key);
- return preset ? [preset] : [];
- });
- const matchingPreset = prioritizedPresets.find((preset) => presetAvailability(preset).kind === "available");
- if (matchingPreset) {
- const presetState = presetAvailability(matchingPreset);
- setSelectedPreset(matchingPreset.key);
- applyPrefill(buildPresetPrefill(matchingPreset.complexity_router_config, availability));
- setDetailsExpanded(presetState.kind === "available" && presetState.viaDeployments);
- toast.success(`Configured with ${matchingPreset.label}`);
- return;
- }
-
- const preferredTierModels = buildPreferredTierModels(prioritizedPresets, availability);
- const generatedConfig = buildAutomaticRouterConfig(modelInfo, deployments ?? [], modelCostMap, preferredTierModels);
- if (generatedConfig === null) {
- toast.fromError("Add at least one chat model before configuring an Auto Router");
- return;
- }
+ if (automaticRouterConfig === null) return;
setSelectedPreset(undefined);
- applyPrefill({ ...buildEmptyPrefill(), complexityRouterConfig: generatedConfig });
+ applyPrefill({ ...buildEmptyPrefill(), complexityRouterConfig: automaticRouterConfig });
setDetailsExpanded(false);
- toast.success("Automatic setup created", { description: tierConfigSummary(generatedConfig) });
+ toast.success("Automatic setup created", { description: tierConfigSummary(automaticRouterConfig) });
};
const handlePresetChange = (presetKey: string | undefined) => {
@@ -540,24 +526,20 @@ const AddAutoRouterTab: React.FC = ({
{({ ref, ...field }) => }
-
-
-
Start with a recommended setup
-
- Uses your available models and our recommended setups. You can review and edit everything before
- saving.
+ {!automaticSetupLoading && automaticRouterConfig && (
+
+
+
Start with a recommended setup
+
+ Uses your available models and our recommended setups. You can review and edit everything before
+ saving.
+