mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
refactor(dashboard): simplify tier config serialization
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
5f12fdfca2
commit
003148ceef
7 changed files with 88 additions and 15 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { fireEvent, renderWithProviders, screen, within } from "../../../tests/test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { useState } from "react";
|
||||
import { vi } from "vitest";
|
||||
import ComplexityRouterConfig, { ComplexityRouterConfigValue } from "./ComplexityRouterConfig";
|
||||
vi.mock(
|
||||
|
|
@ -102,6 +103,42 @@ describe("ComplexityRouterConfig", () => {
|
|||
expect(screen.queryByRole("option", { name: "xhigh" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("prunes tier params when a model is removed", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
const Wrapper = () => {
|
||||
const [value, setValue] = useState<ComplexityRouterConfigValue>({
|
||||
...defaultValue,
|
||||
tiers: { ...defaultValue.tiers, REASONING: ["reasoning-model"] },
|
||||
tier_model_params: {},
|
||||
});
|
||||
return (
|
||||
<ComplexityRouterConfig
|
||||
{...baseProps}
|
||||
modelInfo={[{ model_group: "reasoning-model", mode: "chat", supports_reasoning: true }]}
|
||||
value={value}
|
||||
onChange={(nextValue) => {
|
||||
setValue(nextValue);
|
||||
onChange(nextValue);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderWithProviders(<Wrapper />);
|
||||
await user.click(screen.getByRole("combobox", { name: "Reasoning effort for reasoning-model" }));
|
||||
await user.click(screen.getByText("high"));
|
||||
await user.click(screen.getByRole("combobox", { name: "Select model(s) for reasoning queries" }));
|
||||
await user.keyboard("{Backspace}");
|
||||
|
||||
expect(onChange).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
tiers: expect.objectContaining({ REASONING: [] }),
|
||||
tier_model_params: {},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should display the how classification works section", () => {
|
||||
renderWithProviders(<ComplexityRouterConfig {...baseProps} />);
|
||||
fireEvent.click(screen.getByText("Advanced: Classification Method"));
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ const ComplexityRouterConfig: React.FC<ComplexityRouterConfigProps> = ({
|
|||
? { ...existingParams, reasoning_effort: reasoningEffort }
|
||||
: Object.fromEntries(Object.entries(existingParams).filter(([key]) => key !== "reasoning_effort"));
|
||||
const updatedTierModelParams =
|
||||
updatedParams && Object.keys(updatedParams).length > 0
|
||||
Object.keys(updatedParams).length > 0
|
||||
? { ...tierModelParams, [model]: updatedParams }
|
||||
: Object.fromEntries(Object.entries(tierModelParams).filter(([key]) => key !== model));
|
||||
onChange({
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
effectiveTierLabel,
|
||||
heuristicScoringRoleFor,
|
||||
} from "./ComplexityRouterConfig";
|
||||
import { serializeTierConfig, type TierModelParamsByTier } from "./complexity_router_tiers";
|
||||
import { serializeTierConfig, type SerializedTierConfig, type TierModelParamsByTier } from "./complexity_router_tiers";
|
||||
|
||||
/**
|
||||
* Drop an empty system_prompt so the payload carries an override only when there is one. The
|
||||
|
|
@ -104,7 +104,7 @@ export interface BuildComplexityRouterConfigParams {
|
|||
}
|
||||
|
||||
export interface ComplexityRouterConfigPayload {
|
||||
tiers: Record<string, unknown>;
|
||||
tiers: SerializedTierConfig;
|
||||
default_model?: string;
|
||||
plan_mode_min_tier?: string;
|
||||
tier_labels?: ComplexityTierLabels;
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ describe("normalizeTierModels", () => {
|
|||
const tiers = { REASONING: normalizeTierModels(stored) };
|
||||
const params = { REASONING: extractTierModelParams(stored) };
|
||||
|
||||
expect(serializeTierConfig(tiers, params)).toEqual({ REASONING: stored });
|
||||
expect(serializeTierConfig(tiers, params)).toEqual({ REASONING: [stored] });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export const tierOptions = (
|
|||
export type TierModelParams = Record<string, unknown>;
|
||||
export type SerializedTierModel = string | { model_name: string; litellm_params: TierModelParams };
|
||||
|
||||
export type TierModelParamsByTier = Partial<Record<string, Record<string, TierModelParams>>>;
|
||||
export type TierModelParamsByTier = Partial<Record<keyof ComplexityTiers, Record<string, TierModelParams>>>;
|
||||
|
||||
export const extractTierModelParams = (value: unknown): Record<string, TierModelParams> => {
|
||||
const entries = Array.isArray(value) ? value : [value];
|
||||
|
|
@ -75,19 +75,23 @@ export const extractTierModelParams = (value: unknown): Record<string, TierModel
|
|||
export const serializeTierModels = (
|
||||
models: string[],
|
||||
paramsByModel: Record<string, TierModelParams> | undefined,
|
||||
): SerializedTierModel | SerializedTierModel[] => {
|
||||
): SerializedTierModel[] => {
|
||||
const entries = models.map((model) => {
|
||||
const params = paramsByModel?.[model];
|
||||
return params && Object.keys(params).length > 0 ? { model_name: model, litellm_params: params } : model;
|
||||
});
|
||||
if (entries.length === 1 && typeof entries[0] !== "string") return entries[0];
|
||||
return entries;
|
||||
};
|
||||
|
||||
export type SerializedTierConfig = Partial<Record<keyof ComplexityTiers, SerializedTierModel[]>>;
|
||||
|
||||
export const serializeTierConfig = (
|
||||
tiers: Partial<ComplexityTiers>,
|
||||
paramsByTier: TierModelParamsByTier | undefined,
|
||||
): Record<string, SerializedTierModel | SerializedTierModel[]> =>
|
||||
): SerializedTierConfig =>
|
||||
Object.fromEntries(
|
||||
Object.entries(tiers).map(([tier, models]) => [tier, serializeTierModels(models ?? [], paramsByTier?.[tier])]),
|
||||
);
|
||||
Object.entries(tiers).map(([tier, models]) => [
|
||||
tier,
|
||||
serializeTierModels(models ?? [], paramsByTier?.[tier as keyof ComplexityTiers]),
|
||||
]),
|
||||
) as SerializedTierConfig;
|
||||
|
|
|
|||
|
|
@ -68,7 +68,39 @@ describe("buildUpdatedComplexityRouterConfig keyword matching", () => {
|
|||
},
|
||||
};
|
||||
|
||||
expect(buildUpdatedComplexityRouterConfig(stored, value, undefined, hydratedState).tiers).toEqual(stored.tiers);
|
||||
expect(buildUpdatedComplexityRouterConfig(stored, value, undefined, hydratedState).tiers).toEqual({
|
||||
...stored.tiers,
|
||||
SIMPLE: [stored.tiers.SIMPLE],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps plain tier lists unchanged and widens a stored bare string tier", () => {
|
||||
const stored = {
|
||||
...STORED,
|
||||
tiers: {
|
||||
SIMPLE: "gpt-4o-mini",
|
||||
MEDIUM: ["gpt-4o"],
|
||||
COMPLEX: ["claude-sonnet-4"],
|
||||
REASONING: ["o1-preview"],
|
||||
},
|
||||
};
|
||||
|
||||
const formValue = {
|
||||
...FORM_VALUE,
|
||||
tiers: {
|
||||
SIMPLE: ["gpt-4o-mini"],
|
||||
MEDIUM: ["gpt-4o"],
|
||||
COMPLEX: ["claude-sonnet-4"],
|
||||
REASONING: ["o1-preview"],
|
||||
},
|
||||
};
|
||||
|
||||
expect(buildUpdatedComplexityRouterConfig(stored, formValue, undefined, hydratedState).tiers).toEqual({
|
||||
SIMPLE: ["gpt-4o-mini"],
|
||||
MEDIUM: ["gpt-4o"],
|
||||
COMPLEX: ["claude-sonnet-4"],
|
||||
REASONING: ["o1-preview"],
|
||||
});
|
||||
});
|
||||
|
||||
it("persists an edited keyword rule", () => {
|
||||
|
|
|
|||
|
|
@ -348,10 +348,10 @@ const EditAutoRouterModal: React.FC<EditAutoRouterModalProps> = ({
|
|||
const hydratedComplexityRouterConfig: ComplexityRouterConfigValue = {
|
||||
tiers: hydratedTiers,
|
||||
tier_model_params: Object.fromEntries(
|
||||
["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"].map((tier) => [
|
||||
tier,
|
||||
extractTierModelParams(parsedConfig.tiers?.[tier]),
|
||||
]),
|
||||
["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"].flatMap((tier) => {
|
||||
const params = extractTierModelParams(parsedConfig.tiers?.[tier]);
|
||||
return Object.keys(params).length > 0 ? [[tier, params]] : [];
|
||||
}),
|
||||
),
|
||||
default_model: hydratePinnedDefaultModel(
|
||||
parsedConfig.default_model,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue