feat(proxy/utils.py): support model level guardrails on stream event

enables guardrails to work with streaming
This commit is contained in:
Krrish Dholakia
2025-07-25 15:37:27 -07:00
parent d1b63566ac
commit e5d68e5222
4 changed files with 80 additions and 8 deletions
+1
View File
@@ -129,6 +129,7 @@ class CustomGuardrail(CustomLogger):
self,
requested_guardrails: Union[List[str], List[Dict[str, DynamicGuardrailParams]]],
) -> bool:
for _guardrail in requested_guardrails:
if isinstance(_guardrail, dict):
if self.guardrail_name in _guardrail:
File diff suppressed because one or more lines are too long
-7
View File
@@ -10,13 +10,6 @@ model_list:
model: openai/gpt-4o
guardrails:
- guardrail_name: "presidio-pii"
litellm_params:
guardrail: presidio # supported values: "aporia", "bedrock", "lakera", "presidio"
mode: "pre_call"
presidio_language: "en" # optional: set default language for PII analysis
pii_entities_config:
PERSON: "BLOCK" # Will mask credit card numbers
- guardrail_name: azure-text-moderation
litellm_params:
guardrail: azure/text_moderations
+78 -1
View File
@@ -1077,6 +1077,8 @@ class ProxyLogging:
Covers:
1. /chat/completions
"""
from litellm.proxy.proxy_server import llm_router
response_str: Optional[str] = None
if isinstance(response, (ModelResponse, ModelResponseStream)):
response_str = litellm.get_response_string(response_obj=response)
@@ -1088,9 +1090,15 @@ class ProxyLogging:
# Main - V2 Guardrails implementation
from litellm.types.guardrails import GuardrailEventHooks
## CHECK FOR MODEL-LEVEL GUARDRAILS
modified_data = _check_and_merge_model_level_guardrails(
data=data, llm_router=llm_router
)
if (
callback.should_run_guardrail(
data=data, event_type=GuardrailEventHooks.post_call
data=modified_data,
event_type=GuardrailEventHooks.post_call,
)
is not True
):
@@ -3012,6 +3020,74 @@ def _to_ns(dt):
return int(dt.timestamp() * 1e9)
def _check_and_merge_model_level_guardrails(
data: dict, llm_router: Optional[Router]
) -> dict:
"""
Check if the model has guardrails defined and merge them with existing guardrails in the request data.
Args:
data: The request data dict
llm_router: The LLM router instance to get deployment info from
Returns:
Modified data dict with merged guardrails (if any model-level guardrails exist)
"""
if llm_router is None:
return data
# Get the model ID from the data
metadata = data.get("metadata") or {}
model_info = metadata.get("model_info") or {}
model_id = model_info.get("id", None)
if model_id is None:
return data
# Check if the model has guardrails
deployment = llm_router.get_deployment(model_id=model_id)
if deployment is None:
return data
model_level_guardrails = deployment.litellm_params.get("guardrails")
if model_level_guardrails is None:
return data
# Merge model-level guardrails with existing ones
return _merge_guardrails_with_existing(data, model_level_guardrails)
def _merge_guardrails_with_existing(data: dict, model_level_guardrails: Any) -> dict:
"""
Merge model-level guardrails with any existing guardrails in the request data.
Args:
data: The request data dict
model_level_guardrails: Guardrails defined at the model level
Returns:
Modified data dict with merged guardrails in metadata
"""
modified_data = data.copy()
metadata = modified_data.setdefault("metadata", {})
existing_guardrails = metadata.get("guardrails", [])
# Ensure existing_guardrails is a list
if not isinstance(existing_guardrails, list):
existing_guardrails = [existing_guardrails] if existing_guardrails else []
# Ensure model_level_guardrails is a list
if not isinstance(model_level_guardrails, list):
model_level_guardrails = (
[model_level_guardrails] if model_level_guardrails else []
)
# Combine existing and model-level guardrails
metadata["guardrails"] = list(set(existing_guardrails + model_level_guardrails))
return modified_data
def get_error_message_str(e: Exception) -> str:
error_message = ""
if isinstance(e, HTTPException):
@@ -3194,6 +3270,7 @@ def is_valid_api_key(key: str) -> bool:
- Length between 20 and 100 characters
"""
import re
if not isinstance(key, str):
return False
if 3 <= len(key) <= 100: