From 2bf5c60184a41bf2e17cd3ad3f3caea4d3ab7f2a Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Mon, 17 Aug 2026 19:21:57 -0700 Subject: [PATCH] feat(ui): add Ultra Lite heuristic auto-router preset Adds a fourth bundled auto-router template that tiers with the heuristic scorer instead of an LLM classifier, so routing costs no per-request classification call or latency. Tiers run cost-ascending across providers: deepseek-v4-flash for SIMPLE, minimax-m3 for MEDIUM, deepseek-v4-pro for COMPLEX, kimi-k3 for REASONING. --- ui/litellm-dashboard/src/autorouter_presets.json | 16 ++++++++++++++++ .../add_model/add_auto_router_tab.test.tsx | 4 ++-- .../src/lib/autorouter_presets.test.ts | 14 +++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/autorouter_presets.json b/ui/litellm-dashboard/src/autorouter_presets.json index 9a38c6b1a17..a658f3ca07d 100644 --- a/ui/litellm-dashboard/src/autorouter_presets.json +++ b/ui/litellm-dashboard/src/autorouter_presets.json @@ -52,5 +52,21 @@ "session_affinity": false, "deployment_affinity": true } + }, + "ultra_lite": { + "label": "Ultra Lite", + "description": "Cost-optimized routing across providers with no classifier call: DeepSeek V4 Flash for simple queries, MiniMax M3 for medium, DeepSeek V4 Pro for complex, Kimi K3 for reasoning-heavy requests. The heuristic scorer assigns tiers locally, so routing adds no per-request classification spend or latency.", + "complexity_router_config": { + "tiers": { + "SIMPLE": ["deepseek-v4-flash"], + "MEDIUM": ["minimax-m3"], + "COMPLEX": ["deepseek-v4-pro"], + "REASONING": ["kimi-k3"] + }, + "classifier_type": "heuristic", + "escalation_keywords": ["LITELLM ESCALATE"], + "session_affinity": false, + "deployment_affinity": true + } } } diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx index c2a7db1356e..94f83524c53 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx @@ -356,7 +356,7 @@ describe("AddAutoRouterTab", () => { const labels = visibleOptions().map((option) => option.querySelector(".font-medium")?.textContent); - expect(labels).toEqual(["Anthropic Family", "Lite", "OpenAI Family", "Custom Configuration"]); + expect(labels).toEqual(["Anthropic Family", "Lite", "OpenAI Family", "Ultra Lite", "Custom Configuration"]); }); describe("routing test", () => { @@ -744,7 +744,7 @@ describe("AddAutoRouterTab", () => { expect(isOptionDisabled(optionByLabel("Anthropic Family")!)).toBe(false); }); const labels = visibleOptions().map((option) => option.querySelector(".font-medium")?.textContent); - expect(labels).toEqual(["Anthropic Family", "Lite", "OpenAI Family", "Custom Configuration"]); + expect(labels).toEqual(["Anthropic Family", "Lite", "OpenAI Family", "Ultra Lite", "Custom Configuration"]); }); it.each([ diff --git a/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts b/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts index 2ff89fffe30..debd89a672a 100644 --- a/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts +++ b/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts @@ -20,7 +20,7 @@ const groupsOnly = (models: Iterable) => buildModelAvailability(models, describe("autorouter_presets", () => { it("loads exactly the bundled presets", () => { const presets = getAllPresets(); - expect(presets.map((p) => p.label).sort()).toEqual(["Anthropic Family", "Lite", "OpenAI Family"]); + expect(presets.map((p) => p.label).sort()).toEqual(["Anthropic Family", "Lite", "OpenAI Family", "Ultra Lite"]); // Every preset carries all four fields the UI relies on; a JSON typo dropping one fails here. for (const p of presets) { expect(p).toMatchObject({ key: expect.any(String), label: expect.any(String), description: expect.any(String) }); @@ -75,6 +75,18 @@ describe("autorouter_presets", () => { ); }); + it("pins the ultra_lite preset to the heuristic scorer with no classifier call", () => { + const ultraLite = getPresetByKey("ultra_lite")!; + const config = ultraLite.complexity_router_config; + expect(config.classifier_type).toBe("heuristic"); + expect(config.classifier_llm_config).toBeUndefined(); + expect(config.classifier_context_window_size).toBeUndefined(); + expect(config.classifier_fallback).toBeUndefined(); + expect(getRequiredModelsInPreset(ultraLite)).toEqual( + new Set(["deepseek-v4-flash", "minimax-m3", "deepseek-v4-pro", "kimi-k3"]), + ); + }); + it("collects every tier model as a required model", () => { const preset = getPresetByKey("anthropic_family")!; const required = getRequiredModelsInPreset(preset);