From ff942c3a74e3d5a40e44f97111fffae56f29c06e Mon Sep 17 00:00:00 2001 From: moe-berri Date: Sat, 5 Sep 2026 11:05:50 -0700 Subject: [PATCH] fix(auto-router compression): surface a stored model-only policy in the edit form The backend treats either compression key on its own as an authoritative policy, but hydrate returned the untouched inherit state whenever the routing key was absent. A config carrying only auto_router_model_compression was therefore invisible in the form, and picking a routing value then overwrote the stored model hop. Only neither key set now reads as untouched, and an absent key on either hop hydrates as no compression for that hop rather than same-as-the-other. --- .../buildAutoRouterCompression.test.ts | 15 ++++++++++++++ .../add_model/buildAutoRouterCompression.ts | 20 ++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/ui/litellm-dashboard/src/components/add_model/buildAutoRouterCompression.test.ts b/ui/litellm-dashboard/src/components/add_model/buildAutoRouterCompression.test.ts index ea6eaf99106..20d6af50d18 100644 --- a/ui/litellm-dashboard/src/components/add_model/buildAutoRouterCompression.test.ts +++ b/ui/litellm-dashboard/src/components/add_model/buildAutoRouterCompression.test.ts @@ -96,6 +96,21 @@ describe("hydrateAutoRouterCompression", () => { expect(rebuilt.auto_router_model_compression).not.toBe("headroom-a"); }); + it("surfaces a stored model-only policy instead of reading as untouched", () => { + // Regression: the backend treats either key alone as an authoritative policy, so a + // model-only config that hydrated to the inherit state was invisible in the form, + // and the next save overwrote the stored model hop with the routing value. + const state = hydrateAutoRouterCompression({ auto_router_model_compression: "headroom-b" }); + expect(state).toEqual({ routing: "none", sameAsRouting: false, model: "headroom-b" }); + }); + + it("round-trips a model-only policy without changing either hop", () => { + const stored = { auto_router_model_compression: "headroom-b" }; + const rebuilt = buildAutoRouterCompressionParams(hydrateAutoRouterCompression(stored)); + expect(rebuilt.auto_router_model_compression).toBe("headroom-b"); + expect(rebuilt.auto_router_routing_compression).toBe("none"); + }); + it("round-trips through buildAutoRouterCompressionParams", () => { const original = { auto_router_routing_compression: "headroom-a", auto_router_model_compression: "none" }; const rebuilt = buildAutoRouterCompressionParams(hydrateAutoRouterCompression(original)); diff --git a/ui/litellm-dashboard/src/components/add_model/buildAutoRouterCompression.ts b/ui/litellm-dashboard/src/components/add_model/buildAutoRouterCompression.ts index 47d0e3db7e4..6f401a12865 100644 --- a/ui/litellm-dashboard/src/components/add_model/buildAutoRouterCompression.ts +++ b/ui/litellm-dashboard/src/components/add_model/buildAutoRouterCompression.ts @@ -50,14 +50,20 @@ export const hydrateAutoRouterCompression = (litellmParams: { auto_router_routing_compression?: string | null; auto_router_model_compression?: string | null; }): AutoRouterCompressionState => { - const routing = litellmParams.auto_router_routing_compression ?? undefined; - if (routing === undefined) return DEFAULT_AUTO_ROUTER_COMPRESSION; + const storedRouting = litellmParams.auto_router_routing_compression ?? undefined; + const storedModel = litellmParams.auto_router_model_compression ?? undefined; - // An absent model key is no model-hop compression, not same-as-routing: the backend - // reads it as None (policy_from_litellm_params). Hydrating it as same-as-routing - // would make re-saving an unrelated edit write the routing guardrail onto the model - // hop and silently start compressing the model call. - const model = litellmParams.auto_router_model_compression ?? NO_COMPRESSION; + // Only neither key set means the section was never touched. The backend treats + // either key on its own as an authoritative policy (policy_from_litellm_params), so + // reading a model-only config as untouched would hide it from the form and let the + // next save overwrite the stored model hop. + if (storedRouting === undefined && storedModel === undefined) return DEFAULT_AUTO_ROUTER_COMPRESSION; + + // An absent key on either hop is no compression for that hop, not same-as-the-other: + // the backend reads it as None. Hydrating it as same-as-routing would make re-saving + // an unrelated edit write one hop's guardrail onto the other. + const routing = storedRouting ?? NO_COMPRESSION; + const model = storedModel ?? NO_COMPRESSION; const sameAsRouting = model === routing; return { routing, sameAsRouting, model: sameAsRouting ? undefined : model }; };