diff --git a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx index 49bbecf46e2..7484e647b33 100644 --- a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx @@ -1,3 +1,4 @@ +import { transitionClassifierType } from "./classifier_type_transition"; import JevClassifierConfig from "./JevClassifierConfig"; import { Info } from "lucide-react"; import { SimpleTooltip } from "@/components/ui/tooltip"; @@ -33,12 +34,10 @@ import { DEFAULT_CLASSIFIER_FALLBACK, DEFAULT_CLASSIFIER_TIMEOUT_MS, DEFAULT_CLASSIFICATION_RUBRIC, - NEW_CLASSIFIER_CLASSIFICATION_RUBRIC, ClassificationRubric, effectiveTierLabel, heuristicScoringRole, usesLlmClassifier, - DEFAULT_HEURISTIC_FIRST_MAX_TIER, usesClassifierContext, DEFAULT_HYBRID_BOUNDARY_MARGIN, HEURISTIC_FIRST_MAX_TIER_KEYS, @@ -270,34 +269,8 @@ const ClassificationMethodConfig: React.FC = ({ const explicitlySupportedClassifierEfforts = effortOptionsByModel[classifierModel]; const handleClassifierTypeChange = (classifierType: ClassifierType) => { - const nextValue: ComplexityRouterConfigValue = { - ...value, - classifier_type: classifierType, - classifier_llm_config: usesLlmClassifier(classifierType) - ? value.classifier_llm_config ?? { - model: "", - timeout_ms: DEFAULT_CLASSIFIER_TIMEOUT_MS, - classification_rubric: NEW_CLASSIFIER_CLASSIFICATION_RUBRIC, - } - : undefined, - classifier_context_window_size: usesLlmClassifier(classifierType) - ? value.classifier_context_window_size ?? DEFAULT_CLASSIFIER_CONTEXT_WINDOW_SIZE - : undefined, - classifier_context_budget_chars: usesLlmClassifier(classifierType) - ? value.classifier_context_budget_chars ?? DEFAULT_CLASSIFIER_CONTEXT_BUDGET_CHARS - : undefined, - classifier_context_include_assistant_turns: usesLlmClassifier(classifierType) - ? value.classifier_context_include_assistant_turns - : undefined, - classifier_fallback: usesLlmClassifier(classifierType) ? value.classifier_fallback : undefined, - heuristic_first_max_tier: - classifierType === "heuristic_first" - ? value.heuristic_first_max_tier ?? DEFAULT_HEURISTIC_FIRST_MAX_TIER - : undefined, - hybrid_boundary_margin: - classifierType === "hybrid" ? value.hybrid_boundary_margin ?? DEFAULT_HYBRID_BOUNDARY_MARGIN : undefined, - }; - onChange(nextValue); + + onChange(transitionClassifierType(value, classifierType)); }; const handleHeuristicFirstMaxTierChange = (tier: string) => { 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 896fde3a446..b671c6f50e7 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 @@ -3,7 +3,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { fireEvent, renderWithProviders, screen } from "../../../tests/test-utils"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import ClassificationMethodConfig from "./ClassificationMethodConfig"; -import AutoRouterClassifierTabs from "./AutoRouterClassifierTabs"; import JevEditor from "./JevClassifierConfig"; import { type ComplexityRouterConfigValue } from "./ComplexityRouterConfig"; import { @@ -50,7 +49,7 @@ const initial: ComplexityRouterConfigValue = { function Form() { const [value, setValue] = useState(initial); return ( - + <> Probe current config - + ); } @@ -103,7 +102,6 @@ describe("JEV classifier editor", () => { expect(screen.getByText("Classifier Prompt")).toBeInTheDocument(); expect(screen.getByRole("switch", { name: "Use images for classification" })).toBeInTheDocument(); fireEvent.click(screen.getByRole("radio", { name: /JEV Classifier/ })); - expect(screen.getByRole("tab", { name: "Complexity" })).toHaveAttribute("aria-selected", "true"); expect(screen.getByLabelText("JEV Model")).toHaveValue("jev-latest"); expect(screen.getByLabelText("JEV Instructions")).toBeDisabled(); expect(screen.queryByLabelText("Classifier Model")).not.toBeInTheDocument(); 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 d36940f7fda..07a7dcb8774 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 @@ -531,6 +531,17 @@ const AddAutoRouterTab: React.FC = ({ setIsTestModalVisible(true); }; + const jevConnectionTestParams = + effectiveClassifierType(complexityRouterConfig) === "jev" + ? { + prompt: JEV_CONNECTION_TEST_PROMPT, + config: buildComplexityRouterConfig(complexityRouterConfigParams), + defaultModel: resolveComplexityDefaultModel(complexityRouterConfig, complexityRouterConfig.default_model), + routerName: watchedName, + teamId: requiresTeamScope ? watchedTeamId ?? undefined : undefined, + } + : undefined; + return ( @@ -801,20 +812,7 @@ const AddAutoRouterTab: React.FC = ({ testId={connectionTestId} accessToken={accessToken} targets={testTargets} - jevRequest={ - effectiveClassifierType(complexityRouterConfig) === "jev" - ? buildAutoRouterRoutingTestRequest({ - prompt: JEV_CONNECTION_TEST_PROMPT, - config: buildComplexityRouterConfig(complexityRouterConfigParams), - defaultModel: resolveComplexityDefaultModel( - complexityRouterConfig, - complexityRouterConfig.default_model, - ), - routerName: watchedName, - teamId: requiresTeamScope ? watchedTeamId ?? undefined : undefined, - }) - : undefined - } + jevRequest={jevConnectionTestParams && buildAutoRouterRoutingTestRequest(jevConnectionTestParams)} onTestComplete={() => setIsTestingConnection(false)} /> diff --git a/ui/litellm-dashboard/src/components/add_model/classifier_type_transition.test.ts b/ui/litellm-dashboard/src/components/add_model/classifier_type_transition.test.ts index a26b39c2980..832ad51d1f4 100644 --- a/ui/litellm-dashboard/src/components/add_model/classifier_type_transition.test.ts +++ b/ui/litellm-dashboard/src/components/add_model/classifier_type_transition.test.ts @@ -66,39 +66,6 @@ describe("transitionClassifierType", () => { expect(result).toMatchObject(expectedSettings); }); - it.each(["capability", "llm_v2"] as const)("requires explicit policy input for a new %s classifier", (target) => { - const result = transitionClassifierType(standard, target); - expect(result.classifier_llm_config).toEqual({ model: "judge", timeout_ms: 20000 }); - expect(result.classifier_fallback).toBeUndefined(); - if (target === "capability") { - expect(result.capability_classifier_config?.base_threshold).toBeNaN(); - } else { - expect(result.llm_v2_config).toMatchObject({ efficient_profile: "", capable_profile: "", harness: "" }); - expect(result.llm_v2_config?.max_quality_gap).toBeNaN(); - } - expect(standard.tiers.MEDIUM).toEqual(["middle"]); - expect(standard.classifier_llm_config?.classification_rubric).toBe("business"); - }); - - it.each([ - ["capability", "llm"], - ["capability", "heuristic_first"], - ["capability", "hybrid"], - ["llm_v2", "llm"], - ["llm_v2", "heuristic_first"], - ["llm_v2", "hybrid"], - ] as const)("restores the complexity rubric from %s to %s while preserving the judge", (source, target) => { - const forecast = transitionClassifierType(standard, source); - const result = transitionClassifierType(forecast, target); - expect(result.classifier_llm_config).toEqual({ - model: "judge", - timeout_ms: 20000, - classification_rubric: "agentic", - }); - expect(result.capability_classifier_config).toBeUndefined(); - expect(result.llm_v2_config).toBeUndefined(); - }); - it("clears the inactive non-reasoning pool and plan floor when switching to local classification", () => { const initial: ComplexityRouterConfigValue = { ...standard, diff --git a/ui/litellm-dashboard/src/components/add_model/classifier_type_transition.ts b/ui/litellm-dashboard/src/components/add_model/classifier_type_transition.ts index 37106ab4d66..a827519d516 100644 --- a/ui/litellm-dashboard/src/components/add_model/classifier_type_transition.ts +++ b/ui/litellm-dashboard/src/components/add_model/classifier_type_transition.ts @@ -21,6 +21,7 @@ export const transitionClassifierType = ( const judgeConfig = value.classifier_llm_config ?? { model: "", timeout_ms: DEFAULT_CLASSIFIER_TIMEOUT_MS }; const nextValue: ComplexityRouterConfigValue = { ...value, + classifier_type: classifierType, jev_classifier_config: classifierType === "jev" ? value.jev_classifier_config ?? defaultJevClassifierConfig() : undefined, classification_prompt: classifierType === "jev" ? undefined : value.classification_prompt, diff --git a/ui/litellm-dashboard/src/components/add_model/classifier_types.ts b/ui/litellm-dashboard/src/components/add_model/classifier_types.ts index ec88166ed2e..7a6756b21ce 100644 --- a/ui/litellm-dashboard/src/components/add_model/classifier_types.ts +++ b/ui/litellm-dashboard/src/components/add_model/classifier_types.ts @@ -1,15 +1,7 @@ -export type ClassifierType = - | "heuristic" - | "heuristic_v2" - | "llm" - | "jev" - | "heuristic_first" - | "hybrid" - | "capability" - | "llm_v2"; +export type ClassifierType = "heuristic" | "heuristic_v2" | "llm" | "jev" | "heuristic_first" | "hybrid"; export const usesLlmClassifier = (classifierType: ClassifierType): boolean => - (["llm", "heuristic_first", "hybrid", "capability", "llm_v2"] as const).some((type) => type === classifierType); + (["llm", "heuristic_first", "hybrid"] as const).some((type) => type === classifierType); export const usesClassifierContext = (classifierType: ClassifierType): boolean => classifierType === "jev" || usesLlmClassifier(classifierType); diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx index 5993f112c87..86556100615 100644 --- a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx +++ b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx @@ -63,7 +63,9 @@ import ComplexityRouterConfig, { ClassifierLLMConfig, ClassifierType, ComplexityRouterConfigValue, - ComplexityTiers, + + effectiveClassifierType, + heuristicScoringRole, DEFAULT_ADAPTIVE_WEIGHTS, DEFAULT_SESSION_AFFINITY, DEFAULT_DEPLOYMENT_AFFINITY, @@ -340,9 +342,8 @@ export const buildUpdatedComplexityRouterConfig = ( keywordMatching?: KeywordMatchingState, ): Record => { const isManaged = (key: string): boolean => { - if (key === "classifier_context_per_turn_chars") { + if (key === "classifier_context_per_turn_chars") return !usesClassifierContext(effectiveClassifierType(value)) || Object.prototype.hasOwnProperty.call(value, key); - } if (MANAGED_COMPLEXITY_ROUTER_KEYS.has(key)) return true; if (keywordMatching !== undefined && KEYWORD_MATCHING_KEYS.has(key)) return true; return customTechnicalKeywords !== undefined && key === "custom_technical_keywords";