diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py index 55fbd54bc85..00adf83709f 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py +++ b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py @@ -10,8 +10,19 @@ import json import os import re from datetime import datetime -from typing import (TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Literal, - Optional, Pattern, Tuple, Union, cast) +from typing import ( + TYPE_CHECKING, + Any, + AsyncGenerator, + Dict, + List, + Literal, + Optional, + Pattern, + Tuple, + Union, + cast, +) import yaml from fastapi import HTTPException @@ -26,12 +37,20 @@ if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.types.utils import GenericGuardrailAPIInputs, GuardrailStatus -from litellm.types.guardrails import (BlockedWord, ContentFilterAction, - ContentFilterPattern, - GuardrailEventHooks, Mode) +from litellm.types.guardrails import ( + BlockedWord, + ContentFilterAction, + ContentFilterPattern, + GuardrailEventHooks, + Mode, +) from litellm.types.proxy.guardrails.guardrail_hooks.litellm_content_filter import ( - BlockedWordDetection, CategoryKeywordDetection, - ContentFilterCategoryConfig, ContentFilterDetection, PatternDetection) + BlockedWordDetection, + CategoryKeywordDetection, + ContentFilterCategoryConfig, + ContentFilterDetection, + PatternDetection, +) from .patterns import PATTERN_EXTRA_CONFIG, get_compiled_pattern @@ -306,10 +325,10 @@ class ContentFilterGuardrail(CustomGuardrail): action if action else category_config_obj.default_action ) - # Handle conditional categories (with identifier_words + inherit_from) - if ( - category_config_obj.identifier_words - and category_config_obj.inherit_from + # Handle conditional categories (with identifier_words + inherit_from OR identifier_words + additional_block_words) + if category_config_obj.identifier_words and ( + category_config_obj.inherit_from + or category_config_obj.additional_block_words ): self._load_conditional_category( category_name, @@ -364,64 +383,81 @@ class ContentFilterGuardrail(CustomGuardrail): categories_dir: str, ) -> None: """ - Load a conditional category that uses identifier_words + inherited block_words. + Load a conditional category that uses identifier_words + block_words. + + Supports two patterns: + 1. Inherit + additional: identifier_words + inherit_from + optional additional_block_words + 2. Standalone: identifier_words + additional_block_words (no inheritance) Args: category_name: Name of the category - category_config_obj: CategoryConfig object with identifier_words and inherit_from + category_config_obj: CategoryConfig object with identifier_words and either inherit_from or additional_block_words category_action: Action to take when match is found severity_threshold: Minimum severity threshold categories_dir: Directory containing category files """ - # Load the inherited category to get block words + block_words = [] inherit_from = category_config_obj.inherit_from - if not inherit_from: - return - # Remove .json or .yaml extension if included - inherit_base = inherit_from.replace(".json", "").replace(".yaml", "") + # Pattern 1: Load inherited category to get base block words + if inherit_from: + # Remove .json or .yaml extension if included + inherit_base = inherit_from.replace(".json", "").replace(".yaml", "") - # Find the inherited category file - inherit_yaml_path = os.path.join(categories_dir, f"{inherit_base}.yaml") - inherit_json_path = os.path.join(categories_dir, f"{inherit_base}.json") + # Find the inherited category file + inherit_yaml_path = os.path.join(categories_dir, f"{inherit_base}.yaml") + inherit_json_path = os.path.join(categories_dir, f"{inherit_base}.json") - if os.path.exists(inherit_yaml_path): - inherit_file_path = inherit_yaml_path - elif os.path.exists(inherit_json_path): - inherit_file_path = inherit_json_path - else: + if os.path.exists(inherit_yaml_path): + inherit_file_path = inherit_yaml_path + elif os.path.exists(inherit_json_path): + inherit_file_path = inherit_json_path + else: + verbose_proxy_logger.warning( + f"Category {category_name}: inherit_from '{inherit_from}' file not found at {categories_dir}" + ) + verbose_proxy_logger.debug( + f"Tried paths: {inherit_yaml_path}, {inherit_json_path}" + ) + return + + try: + # Load the inherited category + inherited_category = self._load_category_file(inherit_file_path) + + # Extract block words from inherited category that meet severity threshold + for keyword_data in inherited_category.keywords: + keyword = keyword_data["keyword"].lower() + severity = keyword_data["severity"] + if self._should_apply_severity(severity, severity_threshold): + block_words.append(keyword) + except Exception as e: + verbose_proxy_logger.error( + f"Error loading inherited category for {category_name}: {e}" + ) + return + + # Pattern 2 or supplement to Pattern 1: Add additional block words + if category_config_obj.additional_block_words: + block_words.extend(category_config_obj.additional_block_words) + + # Ensure we have block words before storing + if not block_words: verbose_proxy_logger.warning( - f"Category {category_name}: inherit_from '{inherit_from}' file not found at {categories_dir}" - ) - verbose_proxy_logger.debug( - f"Tried paths: {inherit_yaml_path}, {inherit_json_path}" + f"Category {category_name}: no block words found (check inherit_from or additional_block_words)" ) return - try: - # Load the inherited category - inherited_category = self._load_category_file(inherit_file_path) - - # Extract block words from inherited category that meet severity threshold - block_words = [] - for keyword_data in inherited_category.keywords: - keyword = keyword_data["keyword"].lower() - severity = keyword_data["severity"] - if self._should_apply_severity(severity, severity_threshold): - block_words.append(keyword) - - # Add additional block words specific to this category - if category_config_obj.additional_block_words: - block_words.extend(category_config_obj.additional_block_words) - - # Store the conditional category configuration - self.conditional_categories[category_name] = { - "identifier_words": category_config_obj.identifier_words, - "block_words": block_words, - "action": category_action, - "severity": "high", # Combinations are always high severity - } + # Store the conditional category configuration + self.conditional_categories[category_name] = { + "identifier_words": category_config_obj.identifier_words, + "block_words": block_words, + "action": category_action, + "severity": "high", # Combinations are always high severity + } + # Log different messages based on pattern + if inherit_from and category_config_obj.additional_block_words: verbose_proxy_logger.info( f"Loaded conditional category {category_name}: " f"{len(category_config_obj.identifier_words)} identifiers + " @@ -429,9 +465,17 @@ class ContentFilterGuardrail(CustomGuardrail): f"({len(category_config_obj.additional_block_words)} additional + " f"{len(block_words) - len(category_config_obj.additional_block_words)} from {inherit_from})" ) - except Exception as e: - verbose_proxy_logger.error( - f"Error loading inherited category for {category_name}: {e}" + elif inherit_from: + verbose_proxy_logger.info( + f"Loaded conditional category {category_name}: " + f"{len(category_config_obj.identifier_words)} identifiers + " + f"{len(block_words)} block words (from {inherit_from})" + ) + else: + verbose_proxy_logger.info( + f"Loaded conditional category {category_name}: " + f"{len(category_config_obj.identifier_words)} identifiers + " + f"{len(block_words)} block words (standalone)" ) def _load_category_file(self, file_path: str) -> CategoryConfig: @@ -1518,7 +1562,8 @@ class ContentFilterGuardrail(CustomGuardrail): @staticmethod def get_config_model(): - from litellm.types.proxy.guardrails.guardrail_hooks.litellm_content_filter import \ - LitellmContentFilterGuardrailConfigModel + from litellm.types.proxy.guardrails.guardrail_hooks.litellm_content_filter import ( + LitellmContentFilterGuardrailConfigModel, + ) return LitellmContentFilterGuardrailConfigModel