mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
fix(ui): keep untouched stored auto-router booleans and reminder marker casing on save (#42703)
Co-authored-by: yuneng <yuneng@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
e26a6450c8
commit
04b1b077de
2 changed files with 119 additions and 1 deletions
|
|
@ -1115,3 +1115,105 @@ describe("LLM V2 configuration preservation", () => {
|
|||
expect(saved).not.toHaveProperty("classifier_llm_config");
|
||||
});
|
||||
});
|
||||
|
||||
describe("untouched save round trip", () => {
|
||||
const STORED_PRE_MANAGED_BOOLEANS: Record<string, unknown> = {
|
||||
tiers: { SIMPLE: ["gpt-4o-mini"], MEDIUM: ["gpt-4o"], COMPLEX: ["opus"], REASONING: ["o1"] },
|
||||
tier_model_configs: { REASONING: [{ model_name: "o1", litellm_params: { reasoning_effort: "high" } }] },
|
||||
default_model: "gpt-4o",
|
||||
plan_mode_min_tier: "COMPLEX",
|
||||
tier_labels: { SIMPLE: "Cheap" },
|
||||
classifier_type: "heuristic_first",
|
||||
heuristic_v2_success_threshold: 0.89,
|
||||
heuristic_first_max_tier: "SIMPLE",
|
||||
classifier_llm_config: { model: "gpt-4o-mini", timeout_ms: 3000, reasoning_effort: "low" },
|
||||
classifier_context_window_size: 5,
|
||||
classifier_context_budget_chars: 4000,
|
||||
classifier_context_include_assistant_turns: true,
|
||||
classifier_fallback: "default_model",
|
||||
classification_prompt: "Route for a payments team.",
|
||||
classification_examples: "- refund status -> SIMPLE",
|
||||
classification_mode: "user_turn",
|
||||
session_affinity: true,
|
||||
session_affinity_ttl_seconds: 300,
|
||||
modality_routing: true,
|
||||
modality_pin_override: true,
|
||||
deployment_affinity: false,
|
||||
adaptive: true,
|
||||
adaptive_weights: { quality: 0.4, cost: 0.6 },
|
||||
tier_distance_penalty: 0.25,
|
||||
adaptive_eligible: "all",
|
||||
return_raw_model_name: true,
|
||||
tier_boundaries: { simple_medium: 0.2, medium_complex: 0.4, complex_reasoning: 0.7 },
|
||||
token_thresholds: { simple: 20, complex: 500 },
|
||||
dimension_weights: { tokenCount: 0.1 },
|
||||
custom_dimensions: [{ name: "domain", weight: 0.9, keywords: ["orbitmesh"] }],
|
||||
reasoning_override_min_score: 0.3,
|
||||
enable_context_window_escalation: false,
|
||||
context_window_escalation_buffer: 0.9,
|
||||
code_keywords: ["async", "await"],
|
||||
reasoning_keywords: ["prove"],
|
||||
technical_keywords: ["api"],
|
||||
simple_keywords: ["hello"],
|
||||
plan_mode_patterns: ["plan now"],
|
||||
route_housekeeping_to_cheapest_tier: true,
|
||||
housekeeping_patterns: ["conversation title"],
|
||||
reminder_markers: [{ open: "<System-Reminder>", close: "</System-Reminder>" }],
|
||||
max_tokens_from_tier_model: true,
|
||||
};
|
||||
|
||||
it("returns the stored config unchanged when nothing was edited", () => {
|
||||
const hydrated = hydrateComplexityRouterConfig(STORED_PRE_MANAGED_BOOLEANS, undefined);
|
||||
expect(buildUpdatedComplexityRouterConfig(STORED_PRE_MANAGED_BOOLEANS, hydrated)).toEqual(
|
||||
STORED_PRE_MANAGED_BOOLEANS,
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps both housekeeping and max-token booleans stored as true", () => {
|
||||
const hydrated = hydrateComplexityRouterConfig(STORED_PRE_MANAGED_BOOLEANS, undefined);
|
||||
const saved = buildUpdatedComplexityRouterConfig(STORED_PRE_MANAGED_BOOLEANS, hydrated);
|
||||
expect(saved.route_housekeeping_to_cheapest_tier).toBe(true);
|
||||
expect(saved.max_tokens_from_tier_model).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps the stored reminder marker casing", () => {
|
||||
const hydrated = hydrateComplexityRouterConfig(STORED_PRE_MANAGED_BOOLEANS, undefined);
|
||||
const saved = buildUpdatedComplexityRouterConfig(STORED_PRE_MANAGED_BOOLEANS, hydrated);
|
||||
expect(saved.reminder_markers).toEqual(STORED_PRE_MANAGED_BOOLEANS.reminder_markers);
|
||||
});
|
||||
|
||||
it("lets an edited toggle win over the stored value", () => {
|
||||
const hydrated = hydrateComplexityRouterConfig(STORED_PRE_MANAGED_BOOLEANS, undefined);
|
||||
const edited = {
|
||||
...hydrated,
|
||||
route_housekeeping_to_cheapest_tier: false,
|
||||
max_tokens_from_tier_model: false,
|
||||
};
|
||||
const saved = buildUpdatedComplexityRouterConfig(STORED_PRE_MANAGED_BOOLEANS, edited);
|
||||
expect(saved.route_housekeeping_to_cheapest_tier).toBe(false);
|
||||
expect(saved.max_tokens_from_tier_model).toBe(false);
|
||||
|
||||
const storedDisabled: Record<string, unknown> = {
|
||||
...STORED_PRE_MANAGED_BOOLEANS,
|
||||
route_housekeeping_to_cheapest_tier: false,
|
||||
max_tokens_from_tier_model: false,
|
||||
};
|
||||
const enabled = {
|
||||
...hydrateComplexityRouterConfig(storedDisabled, undefined),
|
||||
route_housekeeping_to_cheapest_tier: true,
|
||||
max_tokens_from_tier_model: true,
|
||||
};
|
||||
const resaved = buildUpdatedComplexityRouterConfig(storedDisabled, enabled);
|
||||
expect(resaved).not.toHaveProperty("route_housekeeping_to_cheapest_tier");
|
||||
expect(resaved).not.toHaveProperty("max_tokens_from_tier_model");
|
||||
});
|
||||
|
||||
it("lowercases reminder markers the user edited", () => {
|
||||
const hydrated = hydrateComplexityRouterConfig(STORED_PRE_MANAGED_BOOLEANS, undefined);
|
||||
const saved = buildUpdatedComplexityRouterConfig(STORED_PRE_MANAGED_BOOLEANS, {
|
||||
...hydrated,
|
||||
reminder_markers: [{ open: "<Other>", close: "</Other>" }],
|
||||
});
|
||||
expect(saved.reminder_markers).toEqual([{ open: "<other>", close: "</other>" }]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {
|
|||
import { isComplexityRouter } from "../add_model/auto_router_strategies";
|
||||
import {
|
||||
type BuildComplexityRouterConfigParams,
|
||||
type StoredComplexityRouterConfig,
|
||||
buildComplexityRouterConfig,
|
||||
getClassifierModelError,
|
||||
getHeuristicV2SuccessThresholdError,
|
||||
|
|
@ -152,6 +153,12 @@ const KEYWORD_MATCHING_KEYS = new Set([
|
|||
"match_threshold",
|
||||
]);
|
||||
|
||||
const UNEDITED_STORED_VALUE_KEYS: readonly (keyof ComplexityRouterConfigValue)[] = [
|
||||
"route_housekeeping_to_cheapest_tier",
|
||||
"reminder_markers",
|
||||
"max_tokens_from_tier_model",
|
||||
];
|
||||
|
||||
const toRecord = (value: unknown): Record<string, unknown> => {
|
||||
const parsed: unknown = typeof value === "string" ? JSON.parse(value) : value;
|
||||
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
||||
|
|
@ -179,7 +186,15 @@ export const buildUpdatedComplexityRouterConfig = (
|
|||
customTechnicalKeywords?: string[],
|
||||
keywordMatching?: KeywordMatchingState,
|
||||
): Record<string, unknown> => {
|
||||
const stored = toRecord(storedConfig);
|
||||
const hydratedFromStored = hydrateComplexityRouterConfig(stored as StoredComplexityRouterConfig, undefined);
|
||||
const unedited = new Set<string>(
|
||||
UNEDITED_STORED_VALUE_KEYS.filter(
|
||||
(key) => key in stored && JSON.stringify(value[key]) === JSON.stringify(hydratedFromStored[key]),
|
||||
),
|
||||
);
|
||||
const isManaged = (key: string): boolean => {
|
||||
if (unedited.has(key)) return false;
|
||||
if (key === "classifier_context_per_turn_chars") {
|
||||
return !usesClassifierContext(effectiveClassifierType(value)) || Object.prototype.hasOwnProperty.call(value, key);
|
||||
}
|
||||
|
|
@ -190,7 +205,7 @@ export const buildUpdatedComplexityRouterConfig = (
|
|||
};
|
||||
const dropped = customTierDroppedKeys(value);
|
||||
const preservedConfig = Object.fromEntries(
|
||||
Object.entries(toRecord(storedConfig)).filter(([key]) => !isManaged(key) && !dropped.includes(key)),
|
||||
Object.entries(stored).filter(([key]) => !isManaged(key) && !dropped.includes(key)),
|
||||
);
|
||||
|
||||
const builderParams: BuildComplexityRouterConfigParams = {
|
||||
|
|
@ -208,6 +223,7 @@ export const buildUpdatedComplexityRouterConfig = (
|
|||
const unowned: readonly string[] = [
|
||||
...(keywordMatching === undefined ? [...KEYWORD_MATCHING_KEYS].filter((key) => !isManaged(key)) : []),
|
||||
...(customTechnicalKeywords === undefined ? ["custom_technical_keywords"] : []),
|
||||
...unedited,
|
||||
];
|
||||
return {
|
||||
...preservedConfig,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue