= ({
});
};
+ if (classifierType === "capability") {
+ return (
+
+ This router uses capability forecasting. Configure its classifier, threshold, and calibration through YAML or
+ the API. Saving preserves those settings
+
+ );
+ }
+
return (
<>
diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx
index febcde269f7..6da1133c57b 100644
--- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx
+++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx
@@ -143,7 +143,7 @@ export interface ClassifierLLMConfig {
system_prompt?: string;
}
-export type ClassifierType = "heuristic" | "heuristic_v2" | "llm" | "heuristic_first" | "hybrid";
+export type ClassifierType = "heuristic" | "heuristic_v2" | "llm" | "heuristic_first" | "hybrid" | "capability";
/**
* Whether this router can call classifier_llm_config.model. Mirrors the backend's
@@ -151,7 +151,7 @@ export type ClassifierType = "heuristic" | "heuristic_v2" | "llm" | "heuristic_f
* control and payload key, so a new chaining type cannot strip knobs the operator set.
*/
export const usesLlmClassifier = (classifierType: ClassifierType): boolean =>
- classifierType === "llm" || classifierType === "heuristic_first" || classifierType === "hybrid";
+ (["llm", "heuristic_first", "hybrid", "capability"] as const).some((type) => type === classifierType);
export type ClassifierFallback = "heuristic" | "default_model";
@@ -176,7 +176,7 @@ export const heuristicScoringRoleFor = (
classifierType: ClassifierType,
classifierFallback: ClassifierFallback | undefined,
): HeuristicScoringRole => {
- if (classifierType === "heuristic_v2") return "never";
+ if (classifierType === "heuristic_v2" || classifierType === "capability") return "never";
if (classifierType === "heuristic" || classifierType === "heuristic_first" || classifierType === "hybrid")
return "decides";
return (classifierFallback ?? DEFAULT_CLASSIFIER_FALLBACK) === "heuristic" ? "fallback_only" : "never";
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 5b53941bc10..822af77e0db 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
@@ -470,7 +470,10 @@ const classifierWireFields = (
>,
): Partial => ({
...(usesLlmClassifier(effectiveType) &&
- classifierLlmConfig && { classifier_llm_config: normalizeClassifierLlmConfig(classifierLlmConfig) }),
+ classifierLlmConfig && {
+ classifier_llm_config:
+ effectiveType === "capability" ? classifierLlmConfig : normalizeClassifierLlmConfig(classifierLlmConfig),
+ }),
...(usesLlmClassifier(effectiveType) &&
classifierFallback !== undefined && { classifier_fallback: classifierFallback }),
...(effectiveType === "heuristic_first" &&
diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts b/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts
index 3899361cd17..82785610646 100644
--- a/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts
+++ b/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts
@@ -164,6 +164,32 @@ const STORED_LLM = {
classifier_context_per_turn_chars: 300,
};
+describe("capability classifier configuration", () => {
+ it("preserves the judge and calibrated policy through an untouched dashboard edit", () => {
+ const stored = {
+ tiers: { SIMPLE: ["efficient-model"], REASONING: ["capable-model"] },
+ classifier_type: "capability" as const,
+ classifier_llm_config: { model: "judge", timeout_ms: 30000, temperature: 0 },
+ capability_classifier_config: {
+ efficient_tier: "SIMPLE",
+ capable_tier: "REASONING",
+ base_threshold: 0.66,
+ max_output_tokens: 512,
+ response_format: "json_object",
+ calibration: { version: "fitted-v1", slope: 0.15, intercept: 0.19 },
+ },
+ };
+ const hydrated = hydrateComplexityRouterConfig(stored, null);
+ const saved = buildUpdatedComplexityRouterConfig(stored, hydrated);
+
+ expect(saved.classifier_type).toBe("capability");
+ expect(saved.classifier_llm_config).toEqual(stored.classifier_llm_config);
+ expect(saved.capability_classifier_config).toEqual(stored.capability_classifier_config);
+ expect(saved).not.toHaveProperty("classification_prompt");
+ expect(saved).not.toHaveProperty("custom_dimensions");
+ });
+});
+
describe("buildUpdatedComplexityRouterConfig classifier context window", () => {
it("round-trips an untouched edit without changing the classifier context values", () => {
const formValue = {
diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.integration.test.tsx b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.integration.test.tsx
index 646e83a773b..0bb3340ac09 100644
--- a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.integration.test.tsx
+++ b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.integration.test.tsx
@@ -105,7 +105,7 @@ describe("EditAutoRouterModal keyword matching", () => {
expect(screen.queryByText("Advanced: Compression")).not.toBeInTheDocument();
expect(screen.queryByText("Model Access Groups")).not.toBeInTheDocument();
await user.click(screen.getByText("Advanced: Affinity"));
- await user.click(await screen.findByRole("switch", { name: "Pin a session to one deployment per model group" }));
+ await user.click(await screen.findByRole("switch", { name: "Pin one model deployment per tier" }));
await user.click(screen.getByRole("button", { name: /save changes/i }));
await waitFor(() => expect(modelPatchUpdateCall).toHaveBeenCalled());
expect(modelPatchUpdateCall).toHaveBeenLastCalledWith(
diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts
index 371fbbe67bd..ba7909b3ab1 100644
--- a/ui/litellm-dashboard/src/lib/http/schema.d.ts
+++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts
@@ -25328,6 +25328,57 @@ export interface components {
*/
status: "cancelled";
};
+ /** CapabilityCalibrationConfig */
+ CapabilityCalibrationConfig: {
+ /** Intercept */
+ intercept: number;
+ /** Slope */
+ slope: number;
+ /** Version */
+ version: string;
+ };
+ /**
+ * CapabilityClassifierConfig
+ * @description Switchyard-compatible probability threshold policy for two model tiers.
+ */
+ CapabilityClassifierConfig: {
+ /**
+ * Base Threshold
+ * @description Lowest p_solve that routes a supported task to efficient_tier
+ */
+ base_threshold: number;
+ /** @description Optional versioned sigmoid calibration fitted for this judge, capability card, efficient model, and execution setup. Applies sigmoid(slope * logit(clip(p_solve, 1e-6, 1-1e-6)) + intercept) before the threshold policy. Omit to route on the raw forecast. */
+ calibration?: components["schemas"]["CapabilityCalibrationConfig"] | null;
+ /**
+ * Capable Tier
+ * @description Higher, fail-closed tier used below the adjusted threshold or when the classifier verdict is unavailable
+ */
+ capable_tier: string;
+ /**
+ * Efficient Tier
+ * @description Tier used when the efficient model's forecasted solve probability meets the adjusted threshold
+ */
+ efficient_tier: string;
+ /**
+ * Max Output Tokens
+ * @description Maximum completion tokens available to the capability classifier verdict
+ * @default 4096
+ */
+ max_output_tokens: number;
+ /**
+ * Response Format
+ * @description Use json_object for judges without strict JSON Schema support. This appends the verdict schema to the packaged system prompt; both modes validate the returned verdict identically.
+ * @default json_schema
+ * @enum {string}
+ */
+ response_format: "json_schema" | "json_object";
+ /**
+ * Threshold Step
+ * @description Amount added once for uncertain or unmatched verdicts and twice for unsupported verdicts
+ * @default 0
+ */
+ threshold_step: number;
+ };
/** ChatCompletionAnnotation */
ChatCompletionAnnotation: {
/**
@@ -35542,6 +35593,8 @@ export interface components {
adaptive_eligible: "all" | "classified_tier";
/** @description Quality vs cost weights for adaptive selection (used when adaptive=True) */
adaptive_weights?: components["schemas"]["AdaptiveRouterWeights"];
+ /** @description Probability threshold policy required when classifier_type is 'capability'. The classifier forecasts p_solve for efficient_tier, adjusts base_threshold using the capability-card boundary, and otherwise routes to capable_tier */
+ capability_classifier_config?: components["schemas"]["CapabilityClassifierConfig"] | null;
/**
* Classification Examples
* @description Replaces the calibration examples of the LLM classifier rubric, and nothing else. Written as example lines only: the router renders the 'Calibration examples:' heading above them, after the per-tier bullets. Requires an LLM classifier and cannot be combined with classifier_llm_config.system_prompt. With built-in tiers the rubric preset still supplies the tier criteria and, unless classification_prompt replaces them, the classification instructions; a custom tier set ships no examples of its own, so the section renders only when this is set.
@@ -35589,7 +35642,7 @@ export interface components {
* @enum {string}
*/
classifier_fallback: "heuristic" | "default_model";
- /** @description Configuration for the LLM classifier; required when classifier_type is 'llm', 'heuristic_first' or 'hybrid' */
+ /** @description Configuration for the LLM classifier; required when classifier_type is 'llm', 'capability', 'heuristic_first' or 'hybrid' */
classifier_llm_config?: components["schemas"]["ClassifierLLMConfig"] | null;
/**
* Classifier Plugin
@@ -35604,11 +35657,11 @@ export interface components {
classifier_plugin_timeout_ms: number;
/**
* Classifier Type
- * @description Classification strategy: local regex/keyword scoring, the bundled trained four-tier heuristic, an LLM call, a custom classifier plugin, 'heuristic_first', which scores locally and only pays for the LLM classifier when the local scorer does not confidently land a cheap tier, or 'hybrid', which trusts the local scorer everywhere except when its score lands near a tier boundary
+ * @description Classification strategy: local regex/keyword scoring, the bundled trained four-tier heuristic, an LLM tier-selection call, a Switchyard-compatible capability forecast, a custom classifier plugin, 'heuristic_first', which scores locally and only pays for the LLM classifier when the local scorer does not confidently land a cheap tier, or 'hybrid', which trusts the local scorer everywhere except when its score lands near a tier boundary
* @default heuristic
* @enum {string}
*/
- classifier_type: "heuristic" | "heuristic_v2" | "llm" | "custom" | "heuristic_first" | "hybrid";
+ classifier_type: "heuristic" | "heuristic_v2" | "llm" | "capability" | "custom" | "heuristic_first" | "hybrid";
/**
* Code Keywords
* @description Keywords indicating code-related content
@@ -36957,11 +37010,25 @@ export interface components {
* Cause
* @enum {string}
*/
- cause?: "heuristic_scorer" | "heuristic_v2" | "reasoning_override" | "llm_classifier" | "heuristic_first_short_circuit" | "hybrid_short_circuit" | "classifier_plugin" | "classifier_fallback" | "default_model_fallback" | "literal_keyword_match" | "semantic_keyword_match" | "plan_mode" | "housekeeping" | "modality_escalation" | "modality_pin_override" | "health_failover" | "health_default_fallback" | "session_affinity_pin" | "session_affinity_escalation" | "user_turn_continuation" | "default_fallback" | "keyword" | "quality_tier" | "bandit";
+ cause?: "heuristic_scorer" | "heuristic_v2" | "reasoning_override" | "llm_classifier" | "capability_classifier" | "heuristic_first_short_circuit" | "hybrid_short_circuit" | "classifier_plugin" | "classifier_fallback" | "capability_classifier_fallback" | "default_model_fallback" | "literal_keyword_match" | "semantic_keyword_match" | "plan_mode" | "housekeeping" | "modality_escalation" | "modality_pin_override" | "health_failover" | "health_default_fallback" | "session_affinity_pin" | "session_affinity_escalation" | "user_turn_continuation" | "default_fallback" | "keyword" | "quality_tier" | "bandit";
+ /** Classifier Calibrated P Solve */
+ classifier_calibrated_p_solve?: number;
+ /** Classifier Calibration Version */
+ classifier_calibration_version?: string;
+ /** Classifier Capability Boundary */
+ classifier_capability_boundary?: string;
/** Classifier Cost */
classifier_cost?: number;
+ /** Classifier Crux */
+ classifier_crux?: string;
/** Classifier Model */
classifier_model?: string;
+ /** Classifier P Solve */
+ classifier_p_solve?: number;
+ /** Classifier Primary Rule */
+ classifier_primary_rule?: string;
+ /** Classifier Threshold */
+ classifier_threshold?: number;
/** Context Escalated */
context_escalated?: boolean;
/** Context Escalation Original Tier */