Selbst wenn Input-Sanitization perfekt ist, kann das Modell immer noch problematische Inhalte generieren. Output Filtering ist die Sicherheitsebene, die Ausgaben prüft, BEVOR sie an den Nutzer gehen.
Diese Seite behandelt Content-Klassifikation, Guardrails-Bibliotheken und praktische Implementierung.
1. Output Filtering Architektur
┌────────────────────────────────────────────┐
│ LLM-Output (Raw) │
│ "The password for admin is [LEAKED_DATA]" │
└─────────────────────┬──────────────────────┘
↓
┌────────────────────────────────────────────┐
│ LAYER 1: Content Classification │
│ - Toxicity Score │
│ - Harm Category Detection │
│ - PII Detection │
└─────────────────────┬──────────────────────┘
↓
┌────────────────────────────────────────────┐
│ LAYER 2: Policy Check │
│ - Does output violate policies? │
│ - Severity assessment │
│ - Confidence scoring │
└─────────────────────┬──────────────────────┘
↓
┌────────────────────────────────────────────┐
│ LAYER 3: Action │
│ - SAFE: Return to user │
│ - CAUTION: Log + Allow │
│ - DANGEROUS: Block or Redact │
│ - UNKNOWN: Human review │
└────────────────────────────────────────────┘
2. Content Classification (Python)
2.1 PII-Erkennung
import re
from typing import List, Dict
from dataclasses import dataclass
@dataclass
class PII:
type: str # "email", "ssn", "phone", "credit_card", etc.
value: str
position: int
confidence: float
class PIIDetector:
"""Erkennt persönlich identifizierende Informationen"""
PATTERNS = {
"email": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
"ssn": r'\b\d{3}-\d{2}-\d{4}\b',
"credit_card": r'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b',
"phone": r'\b(?:\+1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})\b',
"ip_address": r'\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b',
"api_key": r'\b(?:sk|pk)-[A-Za-z0-9]{20,}\b',
"password_pattern": r'(?:password|passwd|pwd)\s*[:=]\s*[^\s]+',
"credit_card_visa": r'\b4[0-9]{12}(?:[0-9]{3})?\b',
"credit_card_amex": r'\b3[47][0-9]{13}\b',
}
def __init__(self):
self.compiled_patterns = {
key: re.compile(pattern, re.IGNORECASE)
for key, pattern in self.PATTERNS.items()
}
def detect(self, text: str) -> List[PII]:
"""
Erkennt PII in Text
Returns:
Liste von PII-Objekten
"""
pii_list = []
for pii_type, pattern in self.compiled_patterns.items():
for match in pattern.finditer(text):
pii = PII(
type=pii_type,
value=match.group(),
position=match.start(),
confidence=0.95 # High confidence für Regex-Matches
)
pii_list.append(pii)
return pii_list
def redact(self, text: str, replacement: str = "[REDACTED]") -> str:
"""
Redaktiert PII in Text
"""
redacted = text
pii_list = self.detect(text)
# Sortiere nach Position (rückwärts), damit Indices nicht verschoben werden
for pii in sorted(pii_list, key=lambda p: p.position, reverse=True):
redacted = redacted[:pii.position] + replacement + redacted[pii.position + len(pii.value):]
return redacted
def mask(self, text: str) -> str:
"""
Maskiert PII (z.B. partial masking)
"""
masked = text
pii_list = self.detect(text)
for pii in sorted(pii_list, key=lambda p: p.position, reverse=True):
if pii.type == "email":
# Zeige nur erste 2 Zeichen
mask_value = pii.value[:2] + "***@***"
elif pii.type == "ssn":
# Zeige nur letzte 4 Zeichen
mask_value = "***-**-" + pii.value[-4:]
elif pii.type == "credit_card":
# Zeige nur letzte 4 Zeichen
mask_value = "****-****-****-" + pii.value[-4:]
elif pii.type == "phone":
# Zeige nur letzte 4 Ziffern
mask_value = "***-***-" + pii.value[-4:]
else:
mask_value = "[" + pii.type.upper() + "]"
masked = masked[:pii.position] + mask_value + masked[pii.position + len(pii.value):]
return masked
# Verwendung:
detector = PIIDetector()
text = "Contact me at [email protected] or call (555) 123-4567. SSN: 123-45-6789"
pii_found = detector.detect(text)
print(f"Found {len(pii_found)} PII:")
for p in pii_found:
print(f" {p.type}: {p.value}")
print(f"\nRedacted: {detector.redact(text)}")
print(f"Masked: {detector.mask(text)}")
2.2 Toxicity-Klassifizierung
from dataclasses import dataclass
from enum import Enum
class ToxicityLevel(Enum):
SAFE = "safe"
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
@dataclass
class ToxicityResult:
level: ToxicityLevel
score: float # 0.0 - 1.0
toxic_spans: List[str]
categories: Dict[str, float] # z.B. {"insult": 0.8, "threat": 0.3}
class ToxicityClassifier:
"""Klassifiziert Text auf Toxizität"""
# Vereinfacht: Keyword-basiert (in Produktion: LLM-basiert oder ML-Modell)
TOXIC_KEYWORDS = {
"insult": ["stupid", "idiot", "dumb", "moron"],
"threat": ["kill", "die", "destroy", "harm"],
"hate": ["hate", "despise", "loathe"],
"abuse": ["abuse", "attack", "assault"],
}
SEVERITY_WEIGHTS = {
"insult": 0.3,
"threat": 0.9,
"hate": 0.8,
"abuse": 0.9,
}
def classify(self, text: str) -> ToxicityResult:
"""
Klassifiziert Toxizität
Returns:
ToxicityResult mit Score und Kategorien
"""
lower_text = text.lower()
categories = {}
toxic_spans = []
for category, keywords in self.TOXIC_KEYWORDS.items():
found = []
for keyword in keywords:
if keyword in lower_text:
found.append(keyword)
toxic_spans.append(keyword)
if found:
categories[category] = len(found) * self.SEVERITY_WEIGHTS[category]
else:
categories[category] = 0.0
# Berechne Gesamt-Score
total_score = max(categories.values()) if categories else 0.0
# Bestimme Level
if total_score == 0.0:
level = ToxicityLevel.SAFE
elif total_score < 0.3:
level = ToxicityLevel.LOW
elif total_score < 0.6:
level = ToxicityLevel.MEDIUM
elif total_score < 0.85:
level = ToxicityLevel.HIGH
else:
level = ToxicityLevel.CRITICAL
return ToxicityResult(
level=level,
score=total_score,
toxic_spans=list(set(toxic_spans)),
categories=categories
)
# Verwendung:
classifier = ToxicityClassifier()
test_texts = [
"Hello, how are you?",
"You are stupid and dumb",
"I will kill you",
]
for text in test_texts:
result = classifier.classify(text)
print(f"Text: {text}")
print(f" Level: {result.level.value}")
print(f" Score: {result.score:.2f}")
print()
3. NeMo Guardrails (NVIDIA)
NeMo Guardrails ist ein Framework für LLM-guardrails mit Colang (Custom Language).
3.1 Installation & Setup
# Installation
pip install nemo-guardrails
# Optional: mit LLM-Support
pip install nemo-guardrails[openai]
# Verify
python -c "from nemo.guardrails import RailsConfig; print('OK')"
3.2 Colang Guardrails Definieren
# guardrails.yaml
colang_version: "0.1"
# Definiere verfügbare Actions
actions:
- stop_chain
- log
- refrain
# Definiere Message Flows
flows:
user message:
- "user said something"
- run: check_pii
- if: $pii_detected
then: "pii violation"
- else: "continue"
pii violation:
message: "I detected PII in your message. Please remove it and try again."
continue:
- run: process_normally
check_pii:
if: user_input contains any ["email", "phone", "ssn"]
set: $pii_detected = True
else:
set: $pii_detected = False
# Optional: Log the violation
log: "PII detection triggered"
3.3 NeMo Guardrails in Python
from nemo.guardrails import RailsConfig, LLMRails
class NeMoGuardrailsFilter:
"""LLM-Output-Filtering mit NeMo Guardrails"""
def __init__(self, config_yaml: str):
"""
Args:
config_yaml: Pfad zur Guardrails-Config
"""
self.rails_config = RailsConfig.from_yaml(config_yaml)
self.rails = LLMRails(config=self.rails_config)
def process_output(self, llm_response: str, user_query: str) -> str:
"""
Verarbeitet LLM-Output durch Guardrails
Returns:
Gefilterte Ausgabe
"""
# Baue Messages
messages = [
{"role": "system", "content": "You are a safety filter"},
{"role": "user", "content": user_query},
{"role": "assistant", "content": llm_response}
]
# Führe durch Guardrails aus
filtered_response = self.rails.generate(messages=messages)
return filtered_response
# Verwendung:
filter = NeMoGuardrailsFilter(config_yaml="guardrails.yaml")
query = "What's my email?"
llm_response = "Your email is [email protected]"
filtered = filter.process_output(llm_response, query)
print(filtered) # Kann modifiziert sein wenn PII detected
4. Guardrails AI
Guardrails AI ist eine spezialisierte Bibliothek für Output-Validation mit declarativen Regeln.
4.1 Installation
pip install guardrails-ai
# Mit Validators
pip install guardrails-ai[validators]
4.2 Guardrails Definieren
from guardrails import Guard, Validator
from pydantic import BaseModel, Field
# Definiere Output-Schema
class SafeResponse(BaseModel):
message: str = Field(description="Safe response")
contains_pii: bool = Field(description="Does output contain PII?")
toxicity_level: str = Field(description="Toxicity level: safe/medium/high")
# Erstelle Guard mit Validatoren
guard = Guard.from_pydantic(
output_class=SafeResponse,
validators=[
# Validator gegen PII
Validator(
name="no_pii",
on="output.message",
register=True
),
# Validator gegen Toxizität
Validator(
name="no_toxic_language",
on="output.message",
register=True
),
]
)
# Verwende Guard
response = {
"message": "The weather is sunny",
"contains_pii": False,
"toxicity_level": "safe"
}
try:
validated = guard.validate(response)
print(f"Validation passed: {validated}")
except Exception as e:
print(f"Validation failed: {e}")
4.3 Custom Validators
from guardrails import Validator, register_validator
@register_validator(name="no_emails", data_type="string")
def no_emails(value: str) -> str:
"""
Validator der sicherstellt dass keine Emails in Output sind
"""
import re
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
if re.search(email_pattern, value):
raise ValueError(f"Output contains email addresses")
return value
@register_validator(name="max_length", data_type="string")
def max_length_validator(value: str, max_len: int = 1000) -> str:
"""Validator für maximale Länge"""
if len(value) > max_len:
raise ValueError(f"Output exceeds max length {max_len}")
return value
# Verwende Custom Validators
guard = Guard.from_pydantic(
output_class=SafeResponse,
validators=[
no_emails,
max_length_validator
]
)
5. LLM Guard (MIT-Licensed)
LLM Guard ist ein schneller, Python-basierter Output-Filter.
5.1 Installation
pip install llm-guard
# Mit allen Scanners
pip install llm-guard[all]
5.2 Verwendung
from llm_guard import scan_output
from llm_guard.output_scanners import (
PIIScanner,
ToxicityScanner,
PromptInjectionScanner,
BanTopicsScanner
)
class LLMGuardFilter:
"""Output-Filter mit LLM Guard"""
def __init__(self):
self.scanners = [
PIIScanner(), # Detektiert PII
ToxicityScanner(threshold=0.5), # Toxicity >= 0.5 blockiert
PromptInjectionScanner(), # Detektiert Injection-Versuche
BanTopicsScanner(topics=["weapons", "drugs", "violence"]),
]
def filter_output(self, output: str) -> dict:
"""
Filtert Output durch alle Scanner
Returns:
{
"safe": bool,
"results": {scanner_name: result}
}
"""
is_safe = True
results = {}
for scanner in self.scanners:
result = scan_output(output, scanner)
results[scanner.__class__.__name__] = result
if not result["safe"]:
is_safe = False
return {
"safe": is_safe,
"results": results
}
# Verwendung:
filter = LLMGuardFilter()
test_outputs = [
"The weather is nice today",
"Your email is [email protected]",
"I hate you, you're stupid",
]
for output in test_outputs:
result = filter.filter_output(output)
print(f"Output: {output}")
print(f"Safe: {result['safe']}")
print()
6. Complete Output Filtering Pipeline
from typing import Optional
class CompleteOutputFilter:
"""Komplette Output-Filtering-Pipeline"""
def __init__(self):
self.pii_detector = PIIDetector()
self.toxicity_classifier = ToxicityClassifier()
self.injection_detector = PromptInjectionDetector()
def filter(self, output: str, policy: dict = None) -> dict:
"""
Filtert Output durch alle Ebenen
Args:
output: LLM-Output
policy: Policy-Regeln (optional)
Returns:
{
"safe": bool,
"severity": "safe" | "caution" | "dangerous",
"actions": ["redact", "block", "log"],
"filtered_output": str,
"details": {...}
}
"""
if policy is None:
policy = {
"allow_pii": False,
"max_toxicity": 0.5,
"allow_injection": False,
}
details = {}
actions = []
filtered_output = output
# LAYER 1: PII Detection
pii_list = self.pii_detector.detect(output)
details["pii_found"] = [{"type": p.type, "count": 1} for p in pii_list]
if pii_list and not policy["allow_pii"]:
actions.append("redact")
filtered_output = self.pii_detector.redact(filtered_output)
details["pii_redacted"] = True
# LAYER 2: Toxicity Check
toxicity = self.toxicity_classifier.classify(output)
details["toxicity"] = {
"level": toxicity.level.value,
"score": toxicity.score,
"categories": toxicity.categories
}
if toxicity.score > policy["max_toxicity"]:
actions.append("block")
details["toxicity_blocked"] = True
# LAYER 3: Injection Detection
is_injection, patterns = self.injection_detector.detect(output)
details["injection_detected"] = is_injection
if is_injection and not policy["allow_injection"]:
actions.append("block")
details["injection_blocked"] = True
# LAYER 4: Determine Severity
if "block" in actions:
severity = "dangerous"
filtered_output = "[Output blocked due to policy violation]"
elif "redact" in actions:
severity = "caution"
else:
severity = "safe"
is_safe = severity == "safe"
return {
"safe": is_safe,
"severity": severity,
"actions": actions,
"filtered_output": filtered_output,
"original_output": output,
"details": details
}
# Verwendung:
filter = CompleteOutputFilter()
outputs = [
"The capital of France is Paris",
"Contact [email protected] or call (555) 123-4567",
"I hate you, you're stupid and should die",
]
for output in outputs:
result = filter.filter(output)
print(f"Original: {output}")
print(f"Safe: {result['safe']}")
print(f"Severity: {result['severity']}")
print(f"Filtered: {result['filtered_output']}")
print(f"Actions: {result['actions']}")
print()
7. Quellen und Links
- NeMo Guardrails: https://github.com/NVIDIA/NeMo-Guardrails
- Guardrails AI: https://www.guardrailsai.com/
- LLM Guard: https://github.com/laiyer-ai/llm-guard
- Presidio (PII Detection): https://github.com/microsoft/presidio
- Detoxify (Toxicity): https://github.com/unitaryai/detoxify
