diff --git a/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py b/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py index f77f948a595..9b2117b7647 100644 --- a/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py +++ b/tests/test_litellm/proxy/guardrails/test_guardrail_endpoints.py @@ -1227,12 +1227,12 @@ async def test_patch_guardrail_endpoint( mock_in_memory_handler.sync_guardrail_from_db = mocker.Mock( side_effect=ValueError("on_flagged='inject_system_message' requires payload=True and breakdown=True") ) - mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) - mocker.patch( + mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) # test-quality-ok: reused pattern + mocker.patch( # test-quality-ok: reused pattern "litellm.proxy.guardrails.guardrail_endpoints.GUARDRAIL_REGISTRY", mock_guardrail_registry, ) - mocker.patch( + mocker.patch( # test-quality-ok: reused pattern "litellm.proxy.guardrails.guardrail_registry.IN_MEMORY_GUARDRAIL_HANDLER", mock_in_memory_handler, ) diff --git a/tests/test_litellm/proxy/guardrails/test_guardrail_registry.py b/tests/test_litellm/proxy/guardrails/test_guardrail_registry.py index 2b1dee32a2c..2c0735970d3 100644 --- a/tests/test_litellm/proxy/guardrails/test_guardrail_registry.py +++ b/tests/test_litellm/proxy/guardrails/test_guardrail_registry.py @@ -602,7 +602,7 @@ class TestReinitializeGuardrailRestoresOnFailure: try: handler.reinitialize_guardrail(_lakera_guardrail("lakera-restore-meta", on_flagged="block"), source="db") - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="requires payload=True and breakdown=True"): handler.reinitialize_guardrail( _lakera_guardrail("lakera-restore-meta", on_flagged="inject_system_message", breakdown=False), source="db", diff --git a/tests/test_litellm/proxy/guardrails/test_init_guardrails.py b/tests/test_litellm/proxy/guardrails/test_init_guardrails.py index 08108dea8a8..0f0b3b1875d 100644 --- a/tests/test_litellm/proxy/guardrails/test_init_guardrails.py +++ b/tests/test_litellm/proxy/guardrails/test_init_guardrails.py @@ -159,11 +159,17 @@ def test_initialize_presidio_forwards_analyze_chunk_size_bytes(): def test_init_guardrails_v2_skips_invalid_guardrail_instead_of_crashing_boot(): """ Regression: one guardrail with an invalid litellm_params combination (Lakera's - on_flagged="inject_system_message" with mode="during_call", which LakeraAIGuardrail's - __init__ rejects with ValueError) must not take down the entire proxy at startup. - init_guardrails_v2 previously had no try/except around initialize_guardrail, so this - ValueError propagated all the way through proxy_server.py's load_config and crashed - the whole process, including every other, correctly-configured guardrail in the list. + on_flagged="inject_system_message" with payload=False, which LakeraAIGuardrail's + __init__ rejects with ValueError since masking can't happen without payload data) + must not take down the entire proxy at startup. init_guardrails_v2 previously had + no try/except around initialize_guardrail, so this ValueError propagated all the + way through proxy_server.py's load_config and crashed the whole process, including + every other, correctly-configured guardrail in the list. + + mode="during_call" + on_flagged="inject_system_message" is deliberately NOT used + here anymore (maintainer finding on BerriAI/litellm#34940): that combination is + now accepted at construction time, since async_moderation_hook already degrades + it gracefully at runtime instead of needing a config-time rejection. """ from litellm.proxy.guardrails.guardrail_registry import IN_MEMORY_GUARDRAIL_HANDLER @@ -175,8 +181,9 @@ def test_init_guardrails_v2_skips_invalid_guardrail_instead_of_crashing_boot(): "guardrail_name": "broken_lakera_advisory", "litellm_params": { "guardrail": SupportedGuardrailIntegrations.LAKERA_V2.value, - "mode": "during_call", + "mode": "pre_call", "on_flagged": "inject_system_message", + "payload": False, "api_key": "fake-key", }, }, @@ -200,6 +207,39 @@ def test_init_guardrails_v2_skips_invalid_guardrail_instead_of_crashing_boot(): assert "healthy_presidio" in guardrail_names +def test_init_guardrails_v2_accepts_during_call_advisory_mode(): + """ + Maintainer finding on BerriAI/litellm#34940: on_flagged='inject_system_message' + with mode='during_call' must construct successfully now -- async_moderation_hook + already masks whatever's maskable and falls back to a log-only warning when the + advisory itself can't be delivered, so rejecting this combination at config time + disabled a guardrail that runtime already handles safely. + """ + from litellm.proxy.guardrails.guardrail_registry import IN_MEMORY_GUARDRAIL_HANDLER + + IN_MEMORY_GUARDRAIL_HANDLER.IN_MEMORY_GUARDRAILS.clear() + IN_MEMORY_GUARDRAIL_HANDLER.guardrail_id_to_custom_guardrail.clear() + + all_guardrails = [ + { + "guardrail_name": "during_call_advisory", + "litellm_params": { + "guardrail": SupportedGuardrailIntegrations.LAKERA_V2.value, + "mode": "during_call", + "on_flagged": "inject_system_message", + "api_key": "fake-key", + }, + }, + ] + + init_guardrails_v2(all_guardrails=all_guardrails) + + guardrail_names = { + guardrail["guardrail_name"] for guardrail in IN_MEMORY_GUARDRAIL_HANDLER.IN_MEMORY_GUARDRAILS.values() + } + assert "during_call_advisory" in guardrail_names + + def test_init_guardrails_v2_skips_guardrail_with_malformed_advisory_template(): """ Regression: a malformed advisory_system_message (missing the {reason} placeholder