fix: support list of modes in Mode.default for tag-based guardrails

This commit is contained in:
Harshit28j 2026-03-04 01:50:28 +05:30
parent 22e682b1e8
commit d661419109
3 changed files with 158 additions and 13 deletions

View File

@ -10,10 +10,15 @@ class EnterpriseCustomGuardrailHelper:
event_hook: Optional[
Union[GuardrailEventHooks, List[GuardrailEventHooks], Mode]
],
event_type: Optional[GuardrailEventHooks] = None,
) -> Optional[bool]:
"""
Assumes check for event match is done in `should_run_guardrail`
Returns True if the guardrail should be run by tag
Returns True if the guardrail should be run for this request and event_type.
Logic:
- If a request tag matches a Mode tag key, only run if event_type matches
the tag's value (the mode for that tag).
- If no request tag matches, fall back to default mode(s).
"""
from litellm.litellm_core_utils.litellm_logging import (
StandardLoggingPayloadSetup,
@ -36,11 +41,29 @@ class EnterpriseCustomGuardrailHelper:
proxy_server_request=proxy_server_request,
)
if request_tags and any(tag in event_hook.tags for tag in request_tags):
return True
elif event_hook.default and any(
tag in event_hook.default for tag in request_tags
):
# Check if any request tag matches a Mode tag key
matched_mode = None
if request_tags:
for tag in request_tags:
if tag in event_hook.tags:
matched_mode = event_hook.tags[tag]
break
if matched_mode is not None:
# Tag matched: only run if event_type matches the tag's mode value
if event_type is not None:
return event_type.value == matched_mode
return True
# No tag matched: fall back to default mode(s)
if event_hook.default is not None:
if event_type is not None:
default_list = (
event_hook.default
if isinstance(event_hook.default, list)
else [event_hook.default]
)
return event_type.value in default_list
return False
return False

View File

@ -420,7 +420,7 @@ class CustomGuardrail(CustomLogger):
"Setting tag-based guardrails is only available in litellm-enterprise. You must be a premium user to use this feature."
)
result = EnterpriseCustomGuardrailHelper._should_run_if_mode_by_tag(
data, self.event_hook
data, self.event_hook, event_type
)
if result is not None:
return result
@ -447,7 +447,7 @@ class CustomGuardrail(CustomLogger):
"Setting tag-based guardrails is only available in litellm-enterprise. You must be a premium user to use this feature."
)
result = EnterpriseCustomGuardrailHelper._should_run_if_mode_by_tag(
data, self.event_hook
data, self.event_hook, event_type
)
if result is not None:
return result

View File

@ -1,9 +1,5 @@
import datetime
import json
import os
import sys
import unittest
from unittest.mock import ANY, MagicMock, patch
sys.path.insert(
0, os.path.abspath("../..")
@ -12,6 +8,132 @@ from litellm.integrations.custom_guardrail import CustomGuardrail
from litellm.types.guardrails import GuardrailEventHooks, Mode
def test_custom_guardrail_with_mode_default_list(monkeypatch):
"""Test Mode with default as a list of modes (e.g. default: ["pre_call", "post_call"])"""
monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", True)
cg = CustomGuardrail(
guardrail_name="test_guardrail",
supported_event_hooks=[
GuardrailEventHooks.pre_call,
GuardrailEventHooks.post_call,
GuardrailEventHooks.logging_only,
],
event_hook=Mode(
tags={"test_tag": "logging_only"},
default=["pre_call", "post_call"],
),
default_on=True,
)
# No tag match → default fires for pre_call
assert (
cg.should_run_guardrail(
data={"messages": [{"role": "user", "content": "test"}]},
event_type=GuardrailEventHooks.pre_call,
)
is True
)
# No tag match → default fires for post_call
assert (
cg.should_run_guardrail(
data={"messages": [{"role": "user", "content": "test"}]},
event_type=GuardrailEventHooks.post_call,
)
is True
)
# No tag match → logging_only NOT in default list, should not fire
assert (
cg.should_run_guardrail(
data={"messages": [{"role": "user", "content": "test"}]},
event_type=GuardrailEventHooks.logging_only,
)
is False
)
# Tag matches → only logging_only should fire
assert (
cg.should_run_guardrail(
data={
"messages": [{"role": "user", "content": "test"}],
"litellm_metadata": {"tags": ["test_tag"]},
},
event_type=GuardrailEventHooks.logging_only,
)
is True
)
# Tag matches → pre_call should NOT fire (tag says logging_only)
assert (
cg.should_run_guardrail(
data={
"messages": [{"role": "user", "content": "test"}],
"litellm_metadata": {"tags": ["test_tag"]},
},
event_type=GuardrailEventHooks.pre_call,
)
is False
)
# Tag matches → post_call should NOT fire (tag says logging_only)
assert (
cg.should_run_guardrail(
data={
"messages": [{"role": "user", "content": "test"}],
"litellm_metadata": {"tags": ["test_tag"]},
},
event_type=GuardrailEventHooks.post_call,
)
is False
)
def test_custom_guardrail_with_mode_no_default(monkeypatch):
"""Test Mode with no default — guardrail only fires when tag matches"""
monkeypatch.setattr("litellm.proxy.proxy_server.premium_user", True)
cg = CustomGuardrail(
guardrail_name="test_guardrail",
supported_event_hooks=[
GuardrailEventHooks.pre_call,
GuardrailEventHooks.logging_only,
],
event_hook=Mode(
tags={"test_tag": "logging_only"},
),
default_on=True,
)
# No tag, no default → nothing fires
assert (
cg.should_run_guardrail(
data={"messages": [{"role": "user", "content": "test"}]},
event_type=GuardrailEventHooks.pre_call,
)
is False
)
assert (
cg.should_run_guardrail(
data={"messages": [{"role": "user", "content": "test"}]},
event_type=GuardrailEventHooks.logging_only,
)
is False
)
# Tag matches → only logging_only fires
assert (
cg.should_run_guardrail(
data={
"messages": [{"role": "user", "content": "test"}],
"litellm_metadata": {"tags": ["test_tag"]},
},
event_type=GuardrailEventHooks.logging_only,
)
is True
)
def test_custom_guardrail_with_mode(monkeypatch):
monkeypatch.setattr(
"litellm.proxy.proxy_server.premium_user", True