feat: allow multiple calls from tags

This commit is contained in:
Harshit28j
2026-03-07 11:24:18 +05:30
parent 497be5fb11
commit f18f4e3bbd
6 changed files with 124 additions and 9 deletions
@@ -497,7 +497,7 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \
Run guardrails based on the user-agent header. This is useful for running pre-call checks on OpenWebUI but only masking in logs for Claude CLI.
`default` can be a single mode string or a list of modes.
Both `default` and tag values can be a single mode string or a list of modes.
<Tabs>
<TabItem value="single" label="Single Default Mode">
@@ -545,6 +545,29 @@ guardrails:
default_on: true
```
</TabItem>
<TabItem value="tag-list" label="Multiple Tag Modes">
```yaml
model_list:
- model_name: gpt-3.5-turbo
litellm_params:
model: gpt-3.5-turbo
api_key: os.environ/OPENAI_API_KEY
guardrails:
- guardrail_name: "guardrails_ai-guard"
litellm_params:
guardrail: guardrails_ai
guard_name: "pii_detect"
mode:
tags:
"User-Agent: claude-cli": ["pre_call", "post_call"] # Run both pre and post call for claude-cli
default: "logging_only" # Default to logging only when no tags match
api_base: os.environ/GUARDRAILS_AI_API_BASE
default_on: true
```
</TabItem>
</Tabs>
@@ -669,7 +692,7 @@ guardrails:
Mode Specification
`default` accepts either a single string or a list of strings.
Both `default` and tag values accept either a single string or a list of strings.
```python
from litellm.types.guardrails import Mode
@@ -685,6 +708,12 @@ mode = Mode(
tags={"User-Agent: claude-cli": "logging_only"},
default=["pre_call", "post_call"]
)
# Multiple modes on a tag value
mode = Mode(
tags={"User-Agent: claude-cli": ["pre_call", "post_call"]},
default="logging_only"
)
```
### `guardrails` Request Parameter
@@ -50,8 +50,10 @@ class EnterpriseCustomGuardrailHelper:
break
if matched_mode is not None:
# Tag matched: only run if event_type matches the tag's mode value
# Tag matched: only run if event_type matches the tag's mode value(s)
if event_type is not None:
if isinstance(matched_mode, list):
return event_type.value in matched_mode
return event_type.value == matched_mode
return True
+13 -3
View File
@@ -231,8 +231,14 @@ class CustomGuardrail(CustomLogger):
event_hook, supported_event_hooks
)
elif isinstance(event_hook, Mode):
tag_values_flat: list = []
for v in event_hook.tags.values():
if isinstance(v, list):
tag_values_flat.extend(v)
else:
tag_values_flat.append(v)
_validate_event_hook_list_is_in_supported_event_hooks(
list(event_hook.tags.values()), supported_event_hooks
tag_values_flat, supported_event_hooks
)
if event_hook.default:
default_list = (
@@ -466,8 +472,12 @@ class CustomGuardrail(CustomLogger):
if isinstance(self.event_hook, list):
return event_type.value in self.event_hook
if isinstance(self.event_hook, Mode):
if event_type.value in self.event_hook.tags.values():
return True
for tag_value in self.event_hook.tags.values():
if isinstance(tag_value, list):
if event_type.value in tag_value:
return True
elif event_type.value == tag_value:
return True
if self.event_hook.default:
default_list = (
self.event_hook.default
+3 -1
View File
@@ -717,7 +717,9 @@ class BaseLitellmParams(
class Mode(BaseModel):
tags: Dict[str, str] = Field(description="Tags for the guardrail mode")
tags: Dict[str, Union[str, List[str]]] = Field(
description="Tags for the guardrail mode"
)
default: Optional[Union[str, List[str]]] = Field(
default=None, description="Default mode when no tags match"
)
+2 -2
View File
@@ -2625,8 +2625,8 @@ class StandardLoggingPayloadErrorInformation(TypedDict, total=False):
class GuardrailMode(TypedDict, total=False):
tags: Optional[Dict[str, str]]
default: Optional[str]
tags: Optional[Dict[str, Union[str, List[str]]]]
default: Optional[Union[str, List[str]]]
GuardrailStatus = Literal[
@@ -134,6 +134,78 @@ def test_custom_guardrail_with_mode_no_default(monkeypatch):
)
def test_custom_guardrail_with_mode_tag_value_list(monkeypatch):
"""Test Mode with tag value as a list of modes (e.g. tags: {"tag": ["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": ["pre_call", "post_call"]},
default="logging_only",
),
default_on=True,
)
# Tag matches → pre_call should fire (in tag's list)
assert (
cg.should_run_guardrail(
data={
"messages": [{"role": "user", "content": "test"}],
"litellm_metadata": {"tags": ["test_tag"]},
},
event_type=GuardrailEventHooks.pre_call,
)
is True
)
# Tag matches → post_call should fire (in tag's list)
assert (
cg.should_run_guardrail(
data={
"messages": [{"role": "user", "content": "test"}],
"litellm_metadata": {"tags": ["test_tag"]},
},
event_type=GuardrailEventHooks.post_call,
)
is True
)
# Tag matches → logging_only should NOT fire (not in tag's list)
assert (
cg.should_run_guardrail(
data={
"messages": [{"role": "user", "content": "test"}],
"litellm_metadata": {"tags": ["test_tag"]},
},
event_type=GuardrailEventHooks.logging_only,
)
is False
)
# No tag match → default fires for logging_only
assert (
cg.should_run_guardrail(
data={"messages": [{"role": "user", "content": "test"}]},
event_type=GuardrailEventHooks.logging_only,
)
is True
)
# No tag match → pre_call should NOT fire (not in default)
assert (
cg.should_run_guardrail(
data={"messages": [{"role": "user", "content": "test"}]},
event_type=GuardrailEventHooks.pre_call,
)
is False
)
def test_custom_guardrail_with_mode(monkeypatch):
monkeypatch.setattr(
"litellm.proxy.proxy_server.premium_user", True