From bcfc3d5ea948012c3c0bccee29bc43240b1b3b5f Mon Sep 17 00:00:00 2001 From: shivam Date: Thu, 10 Sep 2026 23:24:53 +0000 Subject: [PATCH] fix(proxy): honor explicit null to clear cache_control_injection_points on model update Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../model_management_endpoints.py | 8 +++- .../test_model_management_endpoints.py | 48 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index 94d2b773e14..b20f73b729b 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -129,6 +129,7 @@ if TYPE_CHECKING: from prisma import models as prisma_models router: Final = APIRouter() +CLEARABLE_LITELLM_PARAMS: Final = frozenset({"cache_control_injection_points"}) async def update_team(*args, **kwargs): @@ -758,11 +759,16 @@ def update_db_model(db_model: Deployment, updated_patch: updateDeployment) -> Pr # model_info fields like team_id or access groups. SPECIAL_MODEL_INFO_PARAMS are # mirrored between litellm_params and model_info by Deployment.__init__, so the # clear propagates to both blobs. + # CLEARABLE_LITELLM_PARAMS lists non-mirrored litellm_params that explicit null may clear. 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 not None: + continue + if field in SPECIAL_MODEL_INFO_PARAMS: merged_litellm_params.pop(field, None) merged_model_info.pop(field, None) + elif field in CLEARABLE_LITELLM_PARAMS: + merged_litellm_params.pop(field, None) if updated_patch.model_info: for field in updated_patch.model_info.model_fields_set: if field in SPECIAL_MODEL_INFO_PARAMS and getattr(updated_patch.model_info, field) is None: 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 5325e069813..1f7e67214ad 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 @@ -3573,6 +3573,54 @@ class TestUpdateDBModelClearPricing: assert info["cache_creation_input_token_cost"] == 0.000003 +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."""