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 34e52c79739..0a53a9c3dec 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 @@ -201,12 +201,16 @@ const AddAutoRouterTab: React.FC = ({ // Checks the config actually being built, not which preset (if any) it came from: a model that // was available when it entered a tier, whether via a preset or picked by hand, can have gone - // missing since (the caller's access narrowed, or a background refetch never caught it). + // missing since (the caller's access narrowed, or a background refetch never caught it). Only + // includes classifier_llm_config/embedding_model when buildComplexityRouterConfig would actually + // emit them (classifierType === "llm", semanticMatchingEnabled) - otherwise a dormant selection + // left over from a toggle no longer in effect would block submit for a model that never ships. const missingReferencedModels = getMissingModels( { tiers: complexityRouterConfig.tiers, - classifier_llm_config: complexityRouterConfig.classifier_llm_config, - embedding_model: embeddingModel, + classifier_llm_config: + complexityRouterConfig.classifier_type === "llm" ? complexityRouterConfig.classifier_llm_config : undefined, + embedding_model: semanticMatchingEnabled ? embeddingModel : undefined, }, availableModelSet, ); diff --git a/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts b/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts index a2d921292f8..f1036b4c395 100644 --- a/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts +++ b/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts @@ -4,6 +4,7 @@ import { getPresetByKey, getRequiredModelsInPreset, getMissingModelsInPreset, + getRequiredModels, } from "./autorouter_presets"; describe("autorouter_presets", () => { @@ -49,4 +50,15 @@ describe("autorouter_presets", () => { ); expect(getMissingModelsInPreset(preset, new Set(required))).toEqual([]); }); + + // A classifier_llm_config placeholder is seeded with model: "" before a caller picks one; an + // empty string is not a real model reference and must not be reported as an unavailable model. + it("does not treat an empty-string classifier or embedding model as a required model", () => { + const required = getRequiredModels({ + tiers: { SIMPLE: ["gpt-5-nano"], MEDIUM: [], COMPLEX: [], REASONING: [] }, + classifier_llm_config: { model: "" }, + embedding_model: "", + }); + expect(required).toEqual(new Set(["gpt-5-nano"])); + }); }); diff --git a/ui/litellm-dashboard/src/lib/autorouter_presets.ts b/ui/litellm-dashboard/src/lib/autorouter_presets.ts index e95c431dbe6..ad1454fb913 100644 --- a/ui/litellm-dashboard/src/lib/autorouter_presets.ts +++ b/ui/litellm-dashboard/src/lib/autorouter_presets.ts @@ -30,7 +30,9 @@ export const getRequiredModels = ( ): Set => { const { tiers, classifier_llm_config: classifier, embedding_model: embedding } = config; const models = [...tiers.SIMPLE, ...tiers.MEDIUM, ...tiers.COMPLEX, ...tiers.REASONING, classifier?.model, embedding]; - return new Set(models.filter((model): model is string => model != null)); + // Boolean(), not != null: an empty-string placeholder (e.g. classifier_llm_config seeded before a + // model is chosen) is never a real model reference either. + return new Set(models.filter((model): model is string => Boolean(model))); }; export const getMissingModels = (