From 696587c4ab5e48a656c0dfd87b487d8ca0f11059 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 18 Sep 2026 00:20:22 +0000 Subject: [PATCH] fix(ui): persist disabling cache control injection points on model update Turning Cache Control off on the model edit screen omitted the field from the PATCH body, which the backend reads as leave unchanged, so the stored cache_control_injection_points list survived the save. The dashboard now sends an explicit null when a stored list is being disabled, and update_db_model clears that field on null the same way it already clears the mirrored pricing fields. Omitted keys keep the stored value Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../model_management_endpoints.py | 4 +- .../test_model_management_endpoints.py | 48 +++++++++++++++++++ .../src/components/model_info_view.test.tsx | 4 +- .../src/components/model_info_view.tsx | 3 ++ 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index bcddb1f7ef0..d8e67a9a196 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -144,6 +144,8 @@ if TYPE_CHECKING: from prisma import types as prisma_types router: Final = APIRouter() +CLEARABLE_LITELLM_PARAMS: Final = frozenset({"cache_control_injection_points"}) +NULL_CLEARABLE_LITELLM_PARAMS: Final = frozenset((*SPECIAL_MODEL_INFO_PARAMS, *CLEARABLE_LITELLM_PARAMS)) async def update_team(*args, **kwargs): @@ -898,7 +900,7 @@ def update_db_model(db_model: Deployment, updated_patch: updateDeployment) -> Pr # clear propagates to both blobs. if updated_patch.litellm_params: for field in updated_patch.litellm_params.model_fields_set: - if field in SPECIAL_MODEL_INFO_PARAMS and getattr(updated_patch.litellm_params, field) is None: + if getattr(updated_patch.litellm_params, field) is None and field in NULL_CLEARABLE_LITELLM_PARAMS: merged_litellm_params.pop(field, None) merged_model_info.pop(field, None) elif ( diff --git a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py index e46b4fee61c..eb181fa0972 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py @@ -3864,6 +3864,54 @@ class TestModelInfoServerDerivedPricingFilter: assert written["access_groups"] == ["prod"] +class TestUpdateDBModelClearCacheControlInjectionPoints: + def test_explicit_null_removes_stored_injection_points(self): + from litellm.proxy.management_endpoints.model_management_endpoints import ( + update_db_model, + ) + from litellm.types.router import LiteLLM_Params, ModelInfo, updateLiteLLMParams + + db_model = Deployment( + model_name="haiku-cached", + litellm_params=LiteLLM_Params( + model="anthropic/claude-haiku-4-5", + cache_control_injection_points=[{"location": "message", "role": "system"}], + ), + model_info=ModelInfo(id="dep-cache-0"), + ) + patch = updateDeployment( + litellm_params=updateLiteLLMParams(cache_control_injection_points=None) + ) + + result = update_db_model(db_model=db_model, updated_patch=patch) + + params = json.loads(result["litellm_params"]) + assert "cache_control_injection_points" not in params + assert params["model"] == "anthropic/claude-haiku-4-5" + + def test_omitted_key_keeps_stored_injection_points(self): + from litellm.proxy.management_endpoints.model_management_endpoints import ( + update_db_model, + ) + from litellm.types.router import LiteLLM_Params, ModelInfo, updateLiteLLMParams + + db_model = Deployment( + model_name="haiku-cached", + litellm_params=LiteLLM_Params( + model="anthropic/claude-haiku-4-5", + cache_control_injection_points=[{"location": "message", "role": "system"}], + ), + model_info=ModelInfo(id="dep-cache-0"), + ) + patch = updateDeployment(litellm_params=updateLiteLLMParams(tpm=10)) + + result = update_db_model(db_model=db_model, updated_patch=patch) + + params = json.loads(result["litellm_params"]) + assert params["cache_control_injection_points"] == [{"location": "message", "role": "system"}] + assert params["tpm"] == 10 + + class TestGetModelInfoWithIdBlocked: """`ProxyConfig.get_model_info_with_id` must propagate the DB-level `blocked` column into the in-memory `model_info` dict so the router filter can read it.""" diff --git a/ui/litellm-dashboard/src/components/model_info_view.test.tsx b/ui/litellm-dashboard/src/components/model_info_view.test.tsx index f714b8e5c4a..5c4a6d368c1 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.test.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.test.tsx @@ -1785,7 +1785,7 @@ describe("ModelInfoView", () => { expect(payload.litellm_params.cache_control_injection_points).toEqual([{ location: "message", role: "user" }]); }); - it("drops the stored injection points when the operator turns the toggle off", async () => { + it("sends an explicit null when the operator turns the toggle off so the backend clears the stored points", async () => { withCachePoints([{ location: "message", role: "user" }]); const user = userEvent.setup(); await enterEditMode(user); @@ -1793,7 +1793,7 @@ describe("ModelInfoView", () => { await user.click(screen.getByRole("switch")); const payload = await save(user); - expect(payload.litellm_params).not.toHaveProperty("cache_control_injection_points"); + expect(payload.litellm_params.cache_control_injection_points).toBeNull(); }); it("adds a typed index as a string, matching what the deployment already stores", async () => { diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index 8730b7e4322..b48278eb2ac 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -352,8 +352,11 @@ export default function ModelInfoView({ } // Handle cache control settings + const hadInjectionPoints = Boolean(localModelData?.litellm_params?.cache_control_injection_points); if (values.cache_control && (values.cache_control_injection_points?.length ?? 0) > 0) { updatedLitellmParams.cache_control_injection_points = values.cache_control_injection_points; + } else if (hadInjectionPoints) { + updatedLitellmParams.cache_control_injection_points = null; } else { delete updatedLitellmParams.cache_control_injection_points; }