diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx
index 892950600a5..ecc15453e84 100644
--- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx
+++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx
@@ -79,6 +79,7 @@ const { FALSY_PRESET } = vi.hoisted(() => ({
match_threshold: 0,
keyword_tier_rules: [{ keywords: ["foo"], tier: "SIMPLE" as const }],
escalation_keywords: [],
+ session_affinity: true,
},
},
}));
@@ -380,6 +381,7 @@ describe("AddAutoRouterTab", () => {
const payload = mockHandleAddAutoRouterSubmit.mock.calls.at(-1)?.[0];
expect(payload.complexity_router_config.match_threshold).toBe(0);
expect(payload.complexity_router_config.escalation_keywords).toEqual([]);
+ expect(payload.complexity_router_config.session_affinity).toBe(true);
});
it("offers no team selector to a proxy admin, who may create an unscoped router", () => {
@@ -474,4 +476,36 @@ describe("AddAutoRouterTab", () => {
session_affinity: true,
});
});
+
+ // handlePresetChange fully replaces complexityRouterConfig, so a field missing from the object
+ // it builds isn't "carried forward" from a prior manual edit, it's silently dropped to whatever
+ // the destructuring default resolves to downstream. Turn affinity on via Custom, then apply a
+ // preset whose own value is off, and confirm the preset's off wins in both the UI and the
+ // payload, the same authoritative-replace behavior every other preset field already has.
+ it("applies the preset's own session affinity instead of a previously toggled-on value", async () => {
+ const user = userEvent.setup();
+ mockFetchAvailableModels.mockResolvedValue(ALL_FAMILY_MODELS);
+
+ renderWithProviders();
+
+ openTemplateDropdown();
+ fireEvent.click(optionByLabel("Custom Configuration")!);
+ await user.click(screen.getByText("Advanced: Session Affinity"));
+ await user.click(await screen.findByRole("switch", { name: "Pin a session to its first model" }));
+ expect(screen.getByRole("switch", { name: "Pin a session to its first model" })).toBeChecked();
+
+ openTemplateDropdown();
+ await waitFor(() => expect(isOptionDisabled(optionByLabel("Anthropic Family")!)).toBe(false));
+ fireEvent.click(optionByLabel("Anthropic Family")!);
+
+ expect(screen.getByRole("switch", { name: "Pin a session to its first model" })).not.toBeChecked();
+
+ await user.type(screen.getByPlaceholderText(/smart_router/i), "affinity-then-preset-router");
+ await user.click(screen.getByRole("button", { name: /add auto router/i }));
+
+ await waitFor(() => expect(mockHandleAddAutoRouterSubmit).toHaveBeenCalled());
+ expect(mockHandleAddAutoRouterSubmit.mock.calls.at(-1)?.[0].complexity_router_config).toMatchObject({
+ session_affinity: false,
+ });
+ });
});
diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx
index 58718d8e8da..ea29e04d372 100644
--- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx
+++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx
@@ -171,6 +171,10 @@ const AddAutoRouterTab: React.FC = ({
tiers: config.tiers,
classifier_type: config.classifier_type,
classifier_llm_config: config.classifier_llm_config,
+ classifier_context_window_size: config.classifier_context_window_size,
+ classifier_context_per_turn_chars: config.classifier_context_per_turn_chars,
+ classifier_context_include_assistant_turns: config.classifier_context_include_assistant_turns,
+ session_affinity: config.session_affinity ?? DEFAULT_SESSION_AFFINITY,
adaptive: config.adaptive,
adaptive_weights: config.adaptive_weights,
tier_distance_penalty: config.tier_distance_penalty,