From 4bfac88281ae38c74dd34f7edbcceef469756a64 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 22 Sep 2026 17:40:03 -0700 Subject: [PATCH] fix(ui): adapt the jev dashboard pieces to stable/1.102.x --- .../add_model/ClassificationMethodConfig.tsx | 34 ++----------------- .../JevClassifierConfig.integration.test.tsx | 6 ++-- .../add_model/add_auto_router_tab.tsx | 26 +++++++------- .../build_complexity_router_config.ts | 2 +- .../classifier_type_transition.test.ts | 33 ------------------ .../add_model/classifier_type_transition.ts | 8 ++--- .../components/add_model/classifier_types.ts | 12 ++----- .../edit_auto_router_modal.tsx | 6 ++-- .../src/lib/autorouter_presets.ts | 1 - 9 files changed, 24 insertions(+), 104 deletions(-) diff --git a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx index c15c3e7d862..3b3343154a3 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"; @@ -18,7 +19,6 @@ import ClassifierReasoningEffortSelect from "./ClassifierReasoningEffortSelect"; import ClassifierCircuitBreakerConfig from "./ClassifierCircuitBreakerConfig"; import ClassifierVisionConfig from "./ClassifierVisionConfig"; import type { ReasoningEffort } from "./complexity_router_tiers"; -import { nonReasoningTierFields } from "./nonReasoningTierFields"; import { useComplexityScorerDefaults } from "@/app/(dashboard)/hooks/autoRouter/useComplexityScorerDefaults"; import { ClassificationFrequency, @@ -34,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, @@ -272,35 +270,7 @@ 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, - ...nonReasoningTierFields(classifierType, value), - }; - 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 d1ce1626c6e..1e9c5899ab3 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 @@ -535,6 +535,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 ( @@ -804,20 +815,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/build_complexity_router_config.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts index ea78bea9784..6d1b4069ef6 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 @@ -495,7 +495,7 @@ const classifierWireFields = ( | "classifierContextIncludeAssistantTurns" >, ): Partial => ({ - ...(usesClassifierContext(effectiveType) && + ...(usesLlmClassifier(effectiveType) && classifierLlmConfig && { classifier_llm_config: normalizeClassifierLlmConfig(classifierLlmConfig) }), ...(usesClassifierContext(effectiveType) && classifierFallback !== undefined && { classifier_fallback: classifierFallback }), 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 ba758eac471..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 @@ -11,19 +11,17 @@ import { usesClassifierContext, } from "./ComplexityRouterConfig"; import { defaultJevClassifierConfig } from "./jev_classifier_config"; -import { isForecastClassifier, prepareForecastClassifier } from "./forecast_classifier_config"; import { nonReasoningTierFields } from "./nonReasoningTierFields"; export const transitionClassifierType = ( value: ComplexityRouterConfigValue, classifierType: ClassifierType, ): ComplexityRouterConfigValue => { - const startsLlmRubric = - !value.classifier_llm_config || - (isForecastClassifier(value.classifier_type) && !isForecastClassifier(classifierType)); + const startsLlmRubric = !value.classifier_llm_config; 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, @@ -55,5 +53,5 @@ export const transitionClassifierType = ( classifierType === "hybrid" ? value.hybrid_boundary_margin ?? DEFAULT_HYBRID_BOUNDARY_MARGIN : undefined, ...nonReasoningTierFields(classifierType, value), }; - return prepareForecastClassifier(nextValue, classifierType); + return nextValue; }; 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 8045c05dd70..3ebe5aad7e4 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 @@ -69,7 +69,7 @@ import ComplexityRouterConfig, { ClassifierLLMConfig, ClassifierType, ComplexityRouterConfigValue, - ComplexityTiers, + effectiveClassifierType, heuristicScoringRole, DEFAULT_ADAPTIVE_WEIGHTS, DEFAULT_SESSION_AFFINITY, @@ -166,7 +166,6 @@ export const hydrateComplexityRouterConfig = ( plan_mode_min_tier: hydratePlanModeMinTier(parsedConfig.plan_mode_min_tier, custom_tier_set), tier_labels: hydrateTierLabels(parsedConfig.tier_labels), classifier_type: parsedConfig.classifier_type || "heuristic", - classifier_llm_config: parsedConfig.classifier_llm_config, classifier_llm_config: parsedConfig.classifier_type === "jev" ? undefined : parsedConfig.classifier_llm_config, jev_classifier_config: parsedConfig.classifier_type === "jev" @@ -352,9 +351,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"; diff --git a/ui/litellm-dashboard/src/lib/autorouter_presets.ts b/ui/litellm-dashboard/src/lib/autorouter_presets.ts index 8cf461d77b9..728c1e53574 100644 --- a/ui/litellm-dashboard/src/lib/autorouter_presets.ts +++ b/ui/litellm-dashboard/src/lib/autorouter_presets.ts @@ -284,7 +284,6 @@ export const buildPresetPrefill = ( tier_model_params: resolveParamKeys(hydrateTierModelParams(config.tiers, config.tier_model_configs)), tier_labels: hydrateTierLabels(config.tier_labels), classifier_type: config.classifier_type, - heuristic_v2_success_threshold: config.heuristic_v2_success_threshold, jev_classifier_config: config.classifier_type === "jev" ? config.jev_classifier_config : undefined, classifier_llm_config: config.classifier_type !== "jev" && config.classifier_llm_config