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.
This commit is contained in:
Tin Chi Lo 2026-08-17 19:21:57 -07:00
parent 4d57bf0bdd
commit 2bf5c60184
3 changed files with 31 additions and 3 deletions

View file

@ -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
}
}
}

View file

@ -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([

View file

@ -20,7 +20,7 @@ const groupsOnly = (models: Iterable<string>) => 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);