diff --git a/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts b/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts index 01c61ba6130..38280a0cdbd 100644 --- a/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts +++ b/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts @@ -544,5 +544,51 @@ describe("autorouter_presets", () => { const prefill = buildPresetPrefill(config, groupsOnly(["claude-sonnet-4.5"])); expect(prefill.complexityRouterConfig.tiers.SIMPLE).toEqual(["claude-sonnet-4.5"]); }); + + it("prefills the per-model litellm_params a preset carries in tier_model_configs", () => { + const config = { + tiers: { SIMPLE: ["gpt-5-nano"], MEDIUM: [], COMPLEX: [], REASONING: ["o3"] }, + tier_model_configs: { + REASONING: [{ model_name: "o3", litellm_params: { reasoning_effort: "high" } }], + }, + classifier_type: "heuristic" as const, + session_affinity: false, + deployment_affinity: true, + }; + const prefill = buildPresetPrefill(config, groupsOnly(["gpt-5-nano", "o3"])); + expect(prefill.complexityRouterConfig.tier_model_params).toEqual({ + REASONING: { o3: { reasoning_effort: "high" } }, + }); + }); + + // The params key on the preset's own spelling while the tier entry gets rewritten to the + // caller's. Leaving the key alone names a model the tier no longer holds, and + // serializeTierModelConfigs then drops the params on submit without saying so. + it("rewrites a param key to the same registered spelling its tier entry was rewritten to", () => { + const config = { + tiers: { SIMPLE: [], MEDIUM: [], COMPLEX: [], REASONING: ["claude-sonnet-4-5"] }, + tier_model_configs: { + REASONING: [{ model_name: "claude-sonnet-4-5", litellm_params: { reasoning_effort: "high" } }], + }, + classifier_type: "heuristic" as const, + session_affinity: false, + deployment_affinity: true, + }; + const prefill = buildPresetPrefill(config, groupsOnly(["claude-sonnet-4.5"])); + expect(prefill.complexityRouterConfig.tier_model_params).toEqual({ + REASONING: { "claude-sonnet-4.5": { reasoning_effort: "high" } }, + }); + }); + + it("leaves tier_model_params undefined for a preset that carries no per-model params", () => { + const config = { + tiers: { SIMPLE: ["gpt-5-nano"], MEDIUM: [], COMPLEX: [], REASONING: [] }, + classifier_type: "heuristic" as const, + session_affinity: false, + deployment_affinity: true, + }; + const prefill = buildPresetPrefill(config, groupsOnly(["gpt-5-nano"])); + expect(prefill.complexityRouterConfig.tier_model_params).toBeUndefined(); + }); }); }); diff --git a/ui/litellm-dashboard/src/lib/autorouter_presets.ts b/ui/litellm-dashboard/src/lib/autorouter_presets.ts index fd1ca7a13c2..68c4e8df79a 100644 --- a/ui/litellm-dashboard/src/lib/autorouter_presets.ts +++ b/ui/litellm-dashboard/src/lib/autorouter_presets.ts @@ -12,6 +12,7 @@ import { } from "@/components/add_model/ComplexityRouterConfig"; import { KeywordTierRule } from "@/components/add_model/KeywordTierRules"; import { hydrateKeywordTierRules } from "@/components/add_model/complexity_router_keywords"; +import { TierModelParamsByTier, hydrateTierModelParams } from "@/components/add_model/complexity_router_tiers"; import { DEFAULT_ESCALATION_KEYWORDS } from "@/components/add_model/EscalationKeywords"; import { DEFAULT_MATCH_THRESHOLD } from "@/components/add_model/SemanticKeywordMatching"; import presetsRaw from "@/autorouter_presets.json"; @@ -244,6 +245,17 @@ export const buildPresetPrefill = ( ): PresetPrefill => { const resolve = (model: string): string => resolveAvailableModel(model, availability) ?? model; const resolveTier = (models: string[]): string[] => models.map(resolve); + // Params key on the model name the preset spells while every tier entry is rewritten to the + // caller's registered spelling, so the keys have to be rewritten the same way. Otherwise + // serializeTierModelConfigs drops them for naming a model the tier no longer holds. + const resolveParamKeys = (params: TierModelParamsByTier | undefined): TierModelParamsByTier | undefined => + params && + Object.fromEntries( + Object.entries(params).map(([tier, byModel]) => [ + tier, + Object.fromEntries(Object.entries(byModel).map(([model, litellmParams]) => [resolve(model), litellmParams])), + ]), + ); return { complexityRouterConfig: { @@ -253,6 +265,7 @@ export const buildPresetPrefill = ( COMPLEX: resolveTier(config.tiers.COMPLEX), REASONING: resolveTier(config.tiers.REASONING), }, + tier_model_params: resolveParamKeys(hydrateTierModelParams(config.tiers, config.tier_model_configs)), tier_labels: hydrateTierLabels(config.tier_labels), classifier_type: config.classifier_type, classifier_llm_config: config.classifier_llm_config && {