mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
fix(ui): adapt the jev dashboard pieces to stable/1.101.x
This commit is contained in:
parent
347ec67498
commit
662a89290f
6 changed files with 86 additions and 101 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import type { JevClassifierConfig } from "./jev_classifier_config";
|
||||
import { type ClassifierType } from "./classifier_types";
|
||||
import { type ClassifierType, usesLlmClassifier } from "./classifier_types";
|
||||
import { TIER_DESCRIPTIONS, TIER_KEYS } from "./tier_descriptions";
|
||||
export { type ClassifierType, usesLlmClassifier, usesClassifierContext } from "./classifier_types";
|
||||
import { SimpleTooltip } from "@/components/ui/tooltip";
|
||||
import { MultiSelect } from "@/components/shared/MultiSelect";
|
||||
|
|
@ -142,16 +143,6 @@ export interface ClassifierLLMConfig {
|
|||
system_prompt?: string;
|
||||
}
|
||||
|
||||
export type ClassifierType = "heuristic" | "heuristic_v2" | "llm" | "heuristic_first" | "hybrid";
|
||||
|
||||
/**
|
||||
* Whether this router can call classifier_llm_config.model. Mirrors the backend's
|
||||
* ComplexityRouterConfig.uses_llm_classifier, and is the single gate for every classifier-only
|
||||
* 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";
|
||||
|
||||
export type ClassifierFallback = "heuristic" | "default_model";
|
||||
|
||||
export const DEFAULT_CLASSIFIER_FALLBACK: ClassifierFallback = "heuristic";
|
||||
|
|
@ -498,36 +489,7 @@ interface ComplexityRouterConfigProps {
|
|||
showValidationErrors?: boolean;
|
||||
}
|
||||
|
||||
export const TIER_DESCRIPTIONS: Record<
|
||||
keyof ComplexityTiers,
|
||||
{ label: string; description: string; examples: string }
|
||||
> = {
|
||||
SIMPLE: {
|
||||
label: "Simple",
|
||||
description: "Basic questions, greetings, simple factual queries",
|
||||
examples: '"Hello!", "What is Python?", "Thanks!"',
|
||||
},
|
||||
MEDIUM: {
|
||||
label: "Medium",
|
||||
description: "Standard queries requiring some reasoning or explanation",
|
||||
examples: '"Explain how REST APIs work", "Debug this error"',
|
||||
},
|
||||
COMPLEX: {
|
||||
label: "Complex",
|
||||
description: "Technical, multi-part requests requiring deep knowledge",
|
||||
examples: '"Design a microservices architecture", "Implement a rate limiter"',
|
||||
},
|
||||
REASONING: {
|
||||
label: "Reasoning",
|
||||
description: "Chain-of-thought, analysis, explicit reasoning requests",
|
||||
examples: '"Think step by step...", "Analyze the pros and cons..."',
|
||||
},
|
||||
};
|
||||
|
||||
export const TIER_KEYS = Object.keys(TIER_DESCRIPTIONS) as Array<keyof ComplexityTiers>;
|
||||
|
||||
export const effectiveTierLabel = (tier: keyof ComplexityTiers, tierLabels: ComplexityTierLabels | undefined): string =>
|
||||
tierLabels?.[tier]?.trim() || TIER_DESCRIPTIONS[tier].label;
|
||||
export { TIER_DESCRIPTIONS, TIER_KEYS, effectiveTierLabel } from "./tier_descriptions";
|
||||
|
||||
export const DEFAULT_HEURISTIC_FIRST_MAX_TIER = "SIMPLE";
|
||||
|
||||
|
|
|
|||
|
|
@ -803,8 +803,6 @@ describe("buildComplexityRouterConfig scorer knobs", () => {
|
|||
expect(buildComplexityRouterConfig(tuned).tier_boundaries).toEqual(BOUNDARIES);
|
||||
});
|
||||
|
||||
const uncheckedParams: unknown = {
|
||||
const payload = buildComplexityRouterConfig(uncheckedParams as BuildComplexityRouterConfigParams);
|
||||
it("drops them when the classifier falls back to the default model and nothing is scored", () => {
|
||||
expect(buildComplexityRouterConfig(llmWithDefaultFallback)).not.toHaveProperty("tier_boundaries");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ const scorerKnobPayload = ({
|
|||
};
|
||||
export interface BuildComplexityRouterConfigParams {
|
||||
tiers: ComplexityTiers;
|
||||
enableNonReasoningTier?: boolean;
|
||||
customTierSet?: CustomTierSet;
|
||||
defaultModel: string | undefined;
|
||||
planModeMinTier: string | undefined;
|
||||
|
|
@ -187,6 +188,7 @@ export interface TierDefinitionPayload {
|
|||
|
||||
export interface ComplexityRouterConfigPayload {
|
||||
tiers: ComplexityTiers | Record<string, string[]>;
|
||||
enable_non_reasoning_tier?: boolean;
|
||||
tier_definitions?: TierDefinitionPayload[];
|
||||
fallback_tier?: string;
|
||||
default_model?: string;
|
||||
|
|
@ -511,6 +513,7 @@ export const hydrateBuiltInTiers = (
|
|||
|
||||
export const buildComplexityRouterConfig = ({
|
||||
tiers,
|
||||
enableNonReasoningTier,
|
||||
customTierSet,
|
||||
defaultModel,
|
||||
planModeMinTier,
|
||||
|
|
@ -585,11 +588,12 @@ export const buildComplexityRouterConfig = ({
|
|||
};
|
||||
// An edited tier set forces the LLM classifier, so llm-only inputs must survive a classifier_type
|
||||
// the form never rewrote. The UI gates the same controls on this, not on the raw value.
|
||||
const effectiveType: ClassifierType = customTierSet ? "llm" : classifierType;
|
||||
const effectiveType = effectiveClassifierType({ custom_tier_set: customTierSet, classifier_type: classifierType });
|
||||
|
||||
const payload: ComplexityRouterConfigPayload = {
|
||||
tiers,
|
||||
// The backend rejects the flag beside a custom tier set.
|
||||
...(!customTierSet && enableNonReasoningTier && { enable_non_reasoning_tier: true }),
|
||||
...(serializedTierModelConfigs && { tier_model_configs: serializedTierModelConfigs }),
|
||||
...(defaultModel?.trim() && { default_model: defaultModel }),
|
||||
...(planModeMinTier?.trim() && { plan_mode_min_tier: planModeMinTier }),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import type { ComplexityTierLabels, ComplexityTiers } from "./ComplexityRouterConfig";
|
||||
|
||||
export const TIER_DESCRIPTIONS: Record<
|
||||
keyof ComplexityTiers,
|
||||
{ label: string; description: string; examples: string }
|
||||
> = {
|
||||
NON_REASONING: {
|
||||
label: "Non-reasoning",
|
||||
description: "Operational relay work: passing information along with no judgment about it",
|
||||
examples: '"Reformat this tool output", "Acknowledge the write succeeded"',
|
||||
},
|
||||
SIMPLE: {
|
||||
label: "Simple",
|
||||
description: "Basic questions, greetings, simple factual queries",
|
||||
examples: '"Hello!", "What is Python?", "Thanks!"',
|
||||
},
|
||||
MEDIUM: {
|
||||
label: "Medium",
|
||||
description: "Standard queries requiring some reasoning or explanation",
|
||||
examples: '"Explain how REST APIs work", "Debug this error"',
|
||||
},
|
||||
COMPLEX: {
|
||||
label: "Complex",
|
||||
description: "Technical, multi-part requests requiring deep knowledge",
|
||||
examples: '"Design a microservices architecture", "Implement a rate limiter"',
|
||||
},
|
||||
REASONING: {
|
||||
label: "Reasoning",
|
||||
description: "Chain-of-thought, analysis, explicit reasoning requests",
|
||||
examples: '"Think step by step...", "Analyze the pros and cons..."',
|
||||
},
|
||||
};
|
||||
|
||||
export const TIER_KEYS = Object.keys(TIER_DESCRIPTIONS) as Array<keyof ComplexityTiers>;
|
||||
|
||||
export const effectiveTierLabel = (tier: keyof ComplexityTiers, tierLabels: ComplexityTierLabels | undefined): string =>
|
||||
tierLabels?.[tier]?.trim() || TIER_DESCRIPTIONS[tier].label;
|
||||
|
|
@ -16,7 +16,7 @@ import ModelChoiceCombobox, { type ModelChoice } from "../add_model/ModelChoiceC
|
|||
import { modelAvailableCall, modelPatchUpdateCall, validateAutoRouterConfig } from "../networking";
|
||||
import { fetchAvailableModels, ModelGroup } from "@/components/llm_calls/fetch_models";
|
||||
import RouterConfigBuilder from "../add_model/RouterConfigBuilder";
|
||||
import { hydrateTierModelParams, normalizeTierModels } from "../add_model/complexity_router_tiers";
|
||||
import { hydrateTierModelParams } from "../add_model/complexity_router_tiers";
|
||||
import {
|
||||
type ActiveTierSet,
|
||||
CUSTOM_TIER_OMITTED_KEYS,
|
||||
|
|
@ -63,9 +63,8 @@ import ComplexityRouterConfig, {
|
|||
ClassifierLLMConfig,
|
||||
ClassifierType,
|
||||
ComplexityRouterConfigValue,
|
||||
|
||||
ComplexityTiers,
|
||||
effectiveClassifierType,
|
||||
heuristicScoringRole,
|
||||
DEFAULT_ADAPTIVE_WEIGHTS,
|
||||
DEFAULT_SESSION_AFFINITY,
|
||||
DEFAULT_DEPLOYMENT_AFFINITY,
|
||||
|
|
@ -139,6 +138,11 @@ export interface StoredComplexityRouterConfig {
|
|||
* The stored complexity_router_config as form state. Every key in MANAGED_COMPLEXITY_ROUTER_KEYS is
|
||||
* rewritten from this state on save, so a key missing here is silently dropped from the saved config.
|
||||
*/
|
||||
const asNumber = (v: unknown): number | undefined => (typeof v === "number" ? v : undefined);
|
||||
const asBoolean = (v: unknown): boolean | undefined => (typeof v === "boolean" ? v : undefined);
|
||||
const asNonEmptyString = (v: unknown): string | undefined =>
|
||||
typeof v === "string" && v.trim() !== "" ? v : undefined;
|
||||
|
||||
export const hydrateComplexityRouterConfig = (
|
||||
parsedConfig: StoredComplexityRouterConfig,
|
||||
complexityRouterDefaultModel: string | null | undefined,
|
||||
|
|
@ -166,38 +170,19 @@ export const hydrateComplexityRouterConfig = (
|
|||
? jevClassifierConfigSchema.safeParse(parsedConfig.jev_classifier_config ?? {}).data ??
|
||||
defaultJevClassifierConfig()
|
||||
: undefined,
|
||||
classifier_context_window_size:
|
||||
typeof parsedConfig.classifier_context_window_size === "number"
|
||||
? parsedConfig.classifier_context_window_size
|
||||
: undefined,
|
||||
classifier_context_budget_chars:
|
||||
typeof parsedConfig.classifier_context_budget_chars === "number"
|
||||
? parsedConfig.classifier_context_budget_chars
|
||||
: undefined,
|
||||
classifier_context_per_turn_chars:
|
||||
typeof parsedConfig.classifier_context_per_turn_chars === "number"
|
||||
? parsedConfig.classifier_context_per_turn_chars
|
||||
: undefined,
|
||||
classifier_context_include_assistant_turns:
|
||||
typeof parsedConfig.classifier_context_include_assistant_turns === "boolean"
|
||||
? parsedConfig.classifier_context_include_assistant_turns
|
||||
: undefined,
|
||||
classifier_context_window_size: asNumber(parsedConfig.classifier_context_window_size),
|
||||
classifier_context_budget_chars: asNumber(parsedConfig.classifier_context_budget_chars),
|
||||
classifier_context_per_turn_chars: asNumber(parsedConfig.classifier_context_per_turn_chars),
|
||||
classifier_context_include_assistant_turns: asBoolean(
|
||||
parsedConfig.classifier_context_include_assistant_turns,
|
||||
),
|
||||
classifier_fallback:
|
||||
parsedConfig.classifier_fallback === "default_model" || parsedConfig.classifier_fallback === "heuristic"
|
||||
? parsedConfig.classifier_fallback
|
||||
: undefined,
|
||||
classification_prompt:
|
||||
typeof parsedConfig.classification_prompt === "string" && parsedConfig.classification_prompt.trim() !== ""
|
||||
? parsedConfig.classification_prompt
|
||||
: undefined,
|
||||
classification_examples:
|
||||
typeof parsedConfig.classification_examples === "string" && parsedConfig.classification_examples.trim() !== ""
|
||||
? parsedConfig.classification_examples
|
||||
: undefined,
|
||||
heuristic_first_max_tier:
|
||||
typeof parsedConfig.heuristic_first_max_tier === "string" && parsedConfig.heuristic_first_max_tier.trim() !== ""
|
||||
? parsedConfig.heuristic_first_max_tier
|
||||
: undefined,
|
||||
classification_prompt: asNonEmptyString(parsedConfig.classification_prompt),
|
||||
classification_examples: asNonEmptyString(parsedConfig.classification_examples),
|
||||
heuristic_first_max_tier: asNonEmptyString(parsedConfig.heuristic_first_max_tier),
|
||||
hybrid_boundary_margin:
|
||||
typeof parsedConfig.hybrid_boundary_margin === "number" ? parsedConfig.hybrid_boundary_margin : undefined,
|
||||
classification_mode:
|
||||
|
|
@ -218,23 +203,14 @@ export const hydrateComplexityRouterConfig = (
|
|||
modality_routing: typeof parsedConfig.modality_routing === "boolean" ? parsedConfig.modality_routing : false,
|
||||
modality_pin_override:
|
||||
typeof parsedConfig.modality_pin_override === "boolean" ? parsedConfig.modality_pin_override : false,
|
||||
deployment_affinity:
|
||||
typeof parsedConfig.deployment_affinity === "boolean"
|
||||
? parsedConfig.deployment_affinity
|
||||
: DEFAULT_DEPLOYMENT_AFFINITY,
|
||||
deployment_affinity: asBoolean(parsedConfig.deployment_affinity) ?? DEFAULT_DEPLOYMENT_AFFINITY,
|
||||
adaptive: parsedConfig.adaptive || false,
|
||||
adaptive_weights: parsedConfig.adaptive_weights,
|
||||
tier_distance_penalty: parsedConfig.tier_distance_penalty,
|
||||
adaptive_eligible: parsedConfig.adaptive_eligible || "all",
|
||||
return_raw_model_name: parsedConfig.return_raw_model_name || false,
|
||||
enable_context_window_escalation:
|
||||
typeof parsedConfig.enable_context_window_escalation === "boolean"
|
||||
? parsedConfig.enable_context_window_escalation
|
||||
: undefined,
|
||||
context_window_escalation_buffer:
|
||||
typeof parsedConfig.context_window_escalation_buffer === "number"
|
||||
? parsedConfig.context_window_escalation_buffer
|
||||
: undefined,
|
||||
enable_context_window_escalation: asBoolean(parsedConfig.enable_context_window_escalation),
|
||||
context_window_escalation_buffer: asNumber(parsedConfig.context_window_escalation_buffer),
|
||||
stall_escalation_enabled: parsedConfig.stall_escalation_enabled === true || undefined,
|
||||
stall_escalation_window:
|
||||
typeof parsedConfig.stall_escalation_window === "number" ? parsedConfig.stall_escalation_window : undefined,
|
||||
|
|
|
|||
|
|
@ -155,6 +155,27 @@ function Row({ label, children }: { label: string; children: React.ReactNode })
|
|||
);
|
||||
}
|
||||
|
||||
function ClassifierRows({ decision }: { decision: RoutingDecision }) {
|
||||
return (
|
||||
<>
|
||||
{decision.classifier_model && <Row label="Classifier model">{decision.classifier_model}</Row>}
|
||||
{decision.classifier_confidence != null && (
|
||||
<Row label="Confidence">{(decision.classifier_confidence * 100).toFixed(1)}%</Row>
|
||||
)}
|
||||
{decision.classifier_probabilities && (
|
||||
<Row label="Probabilities">
|
||||
{Object.entries(decision.classifier_probabilities).map(([name, probability]) => (
|
||||
<div key={name}>
|
||||
{name}: {(probability * 100).toFixed(1)}%
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
{decision.classifier_cost != null && <Row label="Classifier cost">${decision.classifier_cost.toFixed(8)}</Row>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function RoutingDecisionCard({
|
||||
decision,
|
||||
className,
|
||||
|
|
@ -213,20 +234,7 @@ export function RoutingDecisionCard({
|
|||
{requestType && <Row label="Request type">{requestType}</Row>}
|
||||
|
||||
<Row label="Decided by">{describeCause(decision)}</Row>
|
||||
{decision.classifier_model && <Row label="Classifier model">{decision.classifier_model}</Row>}
|
||||
{decision.classifier_confidence != null && (
|
||||
<Row label="Confidence">{(decision.classifier_confidence * 100).toFixed(1)}%</Row>
|
||||
)}
|
||||
{decision.classifier_probabilities && (
|
||||
<Row label="Probabilities">
|
||||
{Object.entries(decision.classifier_probabilities).map(([name, probability]) => (
|
||||
<div key={name}>
|
||||
{name}: {(probability * 100).toFixed(1)}%
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
{decision.classifier_cost != null && <Row label="Classifier cost">${decision.classifier_cost.toFixed(8)}</Row>}
|
||||
<ClassifierRows decision={decision} />
|
||||
|
||||
{score !== undefined && (
|
||||
<Row label="Score">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue