fix(ui): match simple auto router models to advanced setup (#48851)

This commit is contained in:
moe-berri 2026-10-28 10:16:37 -07:00 • committed by GitHub
parent 471918c30b
commit fd9a37d233
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 77 additions and 31 deletions

View file

@ -37,6 +37,7 @@ import {
} from "./tier_rows";
import React from "react";
import { ModelGroup } from "@/components/llm_calls/fetch_models";
import { routingModelGroups } from "./auto_setup";
import ClassificationMethodConfig, { InactiveHeuristicV2Threshold } from "./ClassificationMethodConfig";
import ComplexityRouterAdvancedSections from "./ComplexityRouterAdvancedSections";
import { type TierSetAction, applyTierSetAction, setFallbackTier } from "./tier_set_actions";
@ -657,13 +658,10 @@ const ComplexityRouterConfig: React.FC<ComplexityRouterConfigProps> = ({
);
const classifierEffortOptionsByModel = classifierEffortOptionsForModels(modelInfo);
// Embedding models can't serve a chat-completion role, so they're excluded here.
const modelOptions = modelInfo
.filter((model) => model.mode !== "embedding")
.map((model) => ({
value: model.model_group,
label: model.model_group,
}));
const modelOptions = routingModelGroups(modelInfo).map((model) => ({
value: model.model_group,
label: model.model_group,
}));
const handleTierModelParamChange = (tier: string, model: string, change: TierModelParamChange) =>
onChange({

View file

@ -319,6 +319,27 @@ describe("AddAutoRouterTab", () => {
},
);
it("carries Responses model choices from simple setup into Advanced tiers", async () => {
const models = ["gpt-6-luna", "gpt-6-astra"];
mockFetchAvailableModels.mockResolvedValue(models.map((model_group) => ({ model_group, mode: "responses" })));
mockFetchAllModelDeployments.mockResolvedValue(
models.map((model_name) => ({
model_name,
litellm_params: { model: `chatgpt/${model_name}` },
model_info: { mode: "responses" },
})),
);
renderWithProviders(<Harness />);
await waitFor(() =>
expect(screen.getByRole("combobox", { name: "Everyday model" })).toHaveTextContent("gpt-6-luna"),
);
expect(screen.getByRole("combobox", { name: "Hard tasks model" })).toHaveTextContent("gpt-6-astra");
expect(screen.queryByText(/Two deployed models are needed/)).not.toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Advanced configuration", exact: true }));
expectTierModel("Simple", "gpt-6-luna");
expectTierModel("Reasoning", "gpt-6-astra");
});
it("keeps manual solver choices on goal changes and carries them into Advanced", async () => {
mockFetchAvailableModels.mockResolvedValue(
["claude-haiku-5-5", "custom-fast", "claude-opus-5-5"].map((model_group) => ({

View file

@ -7,6 +7,7 @@ import {
solverPairConfig,
nextRouterName,
simpleSetupError,
routingModelGroups,
} from "./auto_setup";
const deployment = (name: string, model: string, extra: Partial<AutoRouterDeployment> = {}): AutoRouterDeployment => ({
@ -76,7 +77,7 @@ describe("deployedRouterModels", () => {
]);
});
it("uses only caller-visible chat models and excludes blocked and recursive targets", () => {
it("matches Advanced choices while keeping blocked and recursive targets out of suggestions", () => {
const rows = [
deployment("hidden", "anthropic/claude-opus-6"),
deployment("blocked", "anthropic/claude-opus-5", { model_info: { blocked: true } }),
@ -94,9 +95,40 @@ describe("deployedRouterModels", () => {
],
rows,
).map((model) => model.name),
).toEqual(["ready"]);
).toEqual(["blocked", "ready", "recursive"]);
expect(
recommendSolverPair(
deployedRouterModels(
rows
.filter((row) => row.model_name !== "hidden" && row.model_name !== "embedding")
.map((row) => ({ model_group: row.model_name! })),
rows,
),
"balanced",
"auto",
),
).toBeNull();
});
it.each(["chat", "responses", "completion", "unknown", undefined])(
"offers the same routing choices as Advanced for mode %s",
(mode) => {
const groups = [
{ model_group: "everyday", mode },
{ model_group: "hard", mode },
{ model_group: "embedding", mode: "embedding" },
];
const rows = [
deployment("everyday", "chatgpt/gpt-6-luna", { model_info: { mode } }),
deployment("hard", "chatgpt/gpt-6-astra", { model_info: { mode } }),
];
const models = deployedRouterModels(groups, rows);
expect(models.map((model) => model.name)).toEqual(routingModelGroups(groups).map((group) => group.model_group));
expect(models.map((model) => model.name)).toEqual(["everyday", "hard"]);
expect(names(recommendSolverPair(models, "balanced", "openai"))).toEqual(["everyday", "hard"]);
},
);
it("does not claim a mixed-version or unknown model group is a known version", () => {
const rows = [
deployment("mixed", "anthropic/claude-opus-5"),
@ -112,7 +144,7 @@ describe("deployedRouterModels", () => {
],
rows,
);
expect(models.map((model) => model.name)).toEqual(["claude-sonnet-5", "mixed"]);
expect(models.map((model) => model.name)).toEqual(["claude-sonnet-5", "mixed", "wildcard"]);
expect(models.every((model) => model.family === undefined)).toBe(true);
expect(recommendSolverPair(models, "balanced", "auto")).toBeNull();
});

View file

@ -146,24 +146,21 @@ const compareVersion = (left: readonly number[], right: readonly number[]): numb
const compareRelease = (left: ModelIdentity, right: ModelIdentity): number =>
compareVersion(left.version, right.version) || left.snapshot - right.snapshot;
export const routingModelGroups = (groups: readonly ModelGroup[]): ModelGroup[] =>
groups.filter((group) => group.mode !== "embedding");
export const deployedRouterModels = (
groups: readonly ModelGroup[],
deployments: readonly AutoRouterDeployment[],
): SolverModel[] =>
groups
.flatMap((group) => {
if (group.mode && group.mode !== "chat") return [];
routingModelGroups(groups)
.map((group) => {
const rows = deployments.filter((row) => row.model_name === group.model_group);
if (
rows.some(isAutoRouterDeployment) ||
group.model_group.includes("*") ||
group.model_group.startsWith("auto_router/")
)
return [];
const active = rows.filter((row) => !row.model_info?.blocked);
if (rows.length > 0 && active.length === 0) return [];
if (active.some((row) => row.model_info?.mode && row.model_info.mode !== "chat")) return [];
if (active.some((row) => row.litellm_params?.model?.includes("*"))) return [];
const isRouter = rows.some(isAutoRouterDeployment) || group.model_group.startsWith("auto_router/");
const hasWildcard =
group.model_group.includes("*") || active.some((row) => row.litellm_params?.model?.includes("*"));
const manualOnly = isRouter || hasWildcard || (rows.length > 0 && active.length === 0);
const identities = active.length > 0 ? active.map(identityOf) : [parseModel(group.model_group)];
const first = identities[0];
const homogeneous =
@ -173,19 +170,17 @@ export const deployedRouterModels = (
const sameLine = identity.family === first.family && identity.line === first.line;
return sameLine && compareRelease(identity, first) === 0;
});
const identity = homogeneous ? first : undefined;
const identity = homogeneous && !manualOnly ? first : undefined;
const providers =
active.length > 0
? active.map((row) => providerOf(row, group, identity?.family))
: [providerOf({}, group, identity?.family)];
return [
{
...identity,
name: group.model_group,
model: identity?.model ?? group.model_group,
provider: [...new Set(providers)].toSorted().join(", "),
},
];
return {
...identity,
name: group.model_group,
model: identity?.model ?? group.model_group,
provider: [...new Set(providers)].toSorted().join(", "),
};
})
.toSorted((a, b) => a.name.localeCompare(b.name));