Minimum match score
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 55c62224a48..ce69f8f7ae3 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
@@ -11,7 +11,11 @@ import RouterConfigBuilder from "./RouterConfigBuilder";
import ComplexityRouterConfig, { ComplexityRouterConfigValue } from "./ComplexityRouterConfig";
import { KeywordTierRule } from "./KeywordTierRules";
import { DEFAULT_MATCH_THRESHOLD } from "./SemanticKeywordMatching";
-import { buildComplexityRouterConfig, getSemanticConfigError } from "./build_complexity_router_config";
+import {
+ buildComplexityRouterConfig,
+ getMissingTiersError,
+ getSemanticConfigError,
+} from "./build_complexity_router_config";
import { buildAutoRouterTestTargets, AutoRouterTestTarget } from "./build_auto_router_test_targets";
import AutoRouterConnectionTest from "./auto_router_connection_test";
import NotificationManager from "../molecules/notifications_manager";
@@ -43,6 +47,7 @@ const AddAutoRouterTab: React.FC
= ({ form, handleOk, acc
const [semanticMatchingEnabled, setSemanticMatchingEnabled] = useState(false);
const [embeddingModel, setEmbeddingModel] = useState(undefined);
const [matchThreshold, setMatchThreshold] = useState(DEFAULT_MATCH_THRESHOLD);
+ const [showValidationErrors, setShowValidationErrors] = useState(false);
// Semantic router config (existing)
const [routerConfig, setRouterConfig] = useState(null);
@@ -86,19 +91,22 @@ const AddAutoRouterTab: React.FC = ({ form, handleOk, acc
classifier_llm_config: classifierLlmConfig,
} = complexityRouterConfig;
- const filledTiers = Object.values(tiers).filter(Boolean);
- if (filledTiers.length === 0) {
- NotificationManager.fromBackend("Please select at least one model for a complexity tier");
+ const missingTiersError = getMissingTiersError(tiers);
+ if (missingTiersError) {
+ setShowValidationErrors(true);
+ NotificationManager.fromBackend(missingTiersError);
return;
}
if (classifierType === "llm" && !classifierLlmConfig?.model) {
+ setShowValidationErrors(true);
NotificationManager.fromBackend("Please select a classifier model, or switch back to Heuristic");
return;
}
const semanticError = getSemanticConfigError({ semanticMatchingEnabled, embeddingModel, keywordTierRules });
if (semanticError) {
+ setShowValidationErrors(true);
NotificationManager.fromBackend(semanticError);
return;
}
@@ -296,6 +304,7 @@ const AddAutoRouterTab: React.FC = ({ form, handleOk, acc
onEmbeddingModelChange={setEmbeddingModel}
matchThreshold={matchThreshold}
onMatchThresholdChange={setMatchThreshold}
+ showValidationErrors={showValidationErrors}
/>
) : (
diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts
index 3c252646b57..e5b547d8240 100644
--- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts
+++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts
@@ -1,5 +1,6 @@
import {
buildComplexityRouterConfig,
+ getMissingTiersError,
getSemanticConfigError,
BuildComplexityRouterConfigParams,
} from "./build_complexity_router_config";
@@ -124,6 +125,31 @@ describe("buildComplexityRouterConfig", () => {
});
});
+describe("getMissingTiersError", () => {
+ it("returns null when all four tiers have a model", () => {
+ expect(getMissingTiersError(tiers)).toBeNull();
+ });
+
+ it("names the specific missing tier when only one is blank", () => {
+ expect(getMissingTiersError({ ...tiers, REASONING: "" })).toBe(
+ "Select a model for the following tier(s): REASONING",
+ );
+ });
+
+ it("names multiple missing tiers in SIMPLE/MEDIUM/COMPLEX/REASONING order", () => {
+ expect(getMissingTiersError({ ...tiers, SIMPLE: "", REASONING: "" })).toBe(
+ "Select a model for the following tier(s): SIMPLE, REASONING",
+ );
+ });
+
+ it("names all four tiers when none are filled", () => {
+ const noTiers = { SIMPLE: "", MEDIUM: "", COMPLEX: "", REASONING: "" };
+ expect(getMissingTiersError(noTiers)).toBe(
+ "Select a model for the following tier(s): SIMPLE, MEDIUM, COMPLEX, REASONING",
+ );
+ });
+});
+
describe("getSemanticConfigError", () => {
const rule = { id: "r1", keywords: ["k8s"], tier: "REASONING" as const };
diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts
index 82ea4f8c12f..3eddca8c35b 100644
--- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts
+++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts
@@ -30,6 +30,14 @@ export interface ComplexityRouterConfigPayload {
match_threshold?: number;
}
+const TIER_KEYS: Array