From eb733bdd270d0d4d6fb33793024e0eb2b8a37fdf Mon Sep 17 00:00:00 2001 From: yucheng Date: Wed, 30 Sep 2026 23:46:06 +0000 Subject: [PATCH] fix(guardrails): restore stored guardrail row verbatim on rejected patch Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/guardrails/guardrail_endpoints.py | 13 +------------ .../proxy/guardrails/test_guardrail_endpoints.py | 8 +++++--- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_endpoints.py b/litellm/proxy/guardrails/guardrail_endpoints.py index 8d7c1cfa944..e51de58f3c7 100644 --- a/litellm/proxy/guardrails/guardrail_endpoints.py +++ b/litellm/proxy/guardrails/guardrail_endpoints.py @@ -1286,18 +1286,7 @@ async def patch_guardrail( # the caller instead of a misleading 200. await GUARDRAIL_REGISTRY.update_guardrail_in_db( guardrail_id=guardrail_id, - guardrail=Guardrail( - guardrail_id=guardrail_id, - guardrail_name=existing_guardrail.get("guardrail_name") or "", - litellm_params=parse_tolerant_litellm_params( - existing_litellm_params, - existing_guardrail.get("guardrail_name") or "Unknown", - ), - guardrail_info=existing_guardrail.get( - "guardrail_info", - {}, # mutable-ok: Guardrail's own constructor takes a plain dict - ), - ), + guardrail=existing_guardrail, prisma_client=prisma_client, ) raise HTTPException( diff --git a/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py b/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py index dc4ab5eee08..d9d306602f2 100644 --- a/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py +++ b/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py @@ -1505,7 +1505,7 @@ async def test_patch_guardrail_rejects_explicit_unsupported_scope_and_rolls_back assert exc_info.value.status_code == 422 assert mock_guardrail_registry.update_guardrail_in_db.call_count == 2 restored_guardrail = mock_guardrail_registry.update_guardrail_in_db.call_args_list[-1].kwargs["guardrail"] - assert restored_guardrail["litellm_params"].logging_only_scope == "output" + assert restored_guardrail["litellm_params"] == stored_guardrail["litellm_params"] callback = handler.guardrail_id_to_custom_guardrail[stored_guardrail["guardrail_id"]] assert callback.logging_only_scope is None finally: @@ -1513,7 +1513,9 @@ async def test_patch_guardrail_rejects_explicit_unsupported_scope_and_rolls_back @pytest.mark.asyncio -async def test_patch_guardrail_rolls_back_invalid_stored_scope_tolerantly(mocker, monkeypatch, mock_guardrail_registry): +async def test_patch_guardrail_rejected_update_restores_invalid_stored_scope_verbatim( + mocker, monkeypatch, mock_guardrail_registry +): handler, stored_guardrail = _setup_patch_scope_guardrail( mocker, monkeypatch, @@ -1535,7 +1537,7 @@ async def test_patch_guardrail_rolls_back_invalid_stored_scope_tolerantly(mocker assert exc_info.value.status_code == 422 assert mock_guardrail_registry.update_guardrail_in_db.call_count == 2 restored_guardrail = mock_guardrail_registry.update_guardrail_in_db.call_args_list[-1].kwargs["guardrail"] - assert restored_guardrail["litellm_params"].logging_only_scope is None + assert restored_guardrail["litellm_params"] == stored_guardrail["litellm_params"] callback = handler.guardrail_id_to_custom_guardrail[stored_guardrail["guardrail_id"]] assert callback.logging_only_scope is None finally: