LLM Guardrails w praktyce: co naprawdę działa
Kontroluj ryzyko, nie tylko model.
Modele językowe LLM są nieprzewidywalne. Halucynują, ujawniają dane, generują szkodliwe treści lub odmawiają spełnienia legalnych zapytań. Mechanizmy ochronne (guardrails) ograniczają zachowanie modelu, nie kosztem jego możliwości.
Kluczem jest wiedza, które mechanizmy ochronne są istotne, a które stanowią jedynie szum.
Mechanizmy ochronne nie służą do kontrolowania modelu. Służą do kontrolowania ryzyka.

Walidacja danych wejściowych
Najważniejszy mechanizm ochronny. Dobre dane wejściowe dają dobre wyjście, a złe dane wejściowe mogą również doprowadzić do iniekcji promptów w systemie.
Strategia 1: Sanityzacja promptów
Sanityzuj niebezpieczne wzorce na wczesnym etapie:
import re
class PromptSanitizer:
def __init__(self):
self.dangerous_patterns = [
r"ignore\s+previous\s+instructions",
r"system\s+prompt",
r"you\s+are\s+now\s+free",
r"break\s+out\s+of",
]
def sanitize(self, prompt: str) -> str:
for pattern in self.dangerous_patterns:
prompt = re.sub(pattern, "[REDACTED]", prompt, flags=re.IGNORECASE)
return prompt
To nie jest rozwiązanie odporne na błędy. Ataki celowe bywają kreatywne. Jednak pozwala to przechwytywać te najbardziej oczywiste, a te są najczęstsze.
Strategia 2: Limity długości danych wejściowych
Limity długości zapobiegają marnowaniu tokenów i przekraczaniu limitów czasu:
class InputValidator:
def __init__(self, max_length: int = 10000):
self.max_length = max_length
def validate(self, prompt: str) -> tuple[bool, str]:
if len(prompt) > self.max_length:
return False, f"Dane wejściowe za długie: {len(prompt)} > {self.max_length}"
return True, "OK"
Strategia 3: Filtrowanie treści
Filtrowanie treści blokuje naruszenia zasad. Wzorce zależą od Twojej dziedziny:
class ContentFilter:
def __init__(self):
self.blocked_topics = [
"violence", "hate speech", "self-harm",
"sexual content", "illegal activities",
]
def filter(self, prompt: str) -> tuple[bool, str]:
prompt_lower = prompt.lower()
for topic in self.blocked_topics:
if topic in prompt_lower:
return False, f"Zablokowane: {topic}"
return True, "OK"
Proste dopasowanie ciągów znaków jest szybkie, ale niedokładne. W produkcji użyj modelu klasyfikacyjnego — nawet małego, takiego jak qwen3-1.7b — do wykrywania naruszeń zasad. Jest to bardziej dokładne i trudniejsze do obejścia.
Filtrowanie danych wyjściowych
Wyjścia modelu również wymagają sprawdzenia. Struktura, treść i fakty.
Strategia 1: Walidacja odpowiedzi
Najpierw waliduj strukturę. Jeśli oczekujesz JSON, sprawdź JSON:
class ResponseValidator:
def __init__(self):
self.required_fields = ["answer", "confidence"]
def validate(self, response: dict) -> tuple[bool, str]:
for field in self.required_fields:
if field not in response:
return False, f"Brakujące pole: {field}"
return True, "OK"
Strategia 2: Filtrowanie treści
Filtruj szkodliwe treści:
class OutputFilter:
def __init__(self):
self.blocked_patterns = [
r"kill\s+someone",
r"bomb\s+recipe",
r"hate\s+speech",
r"self-harm",
]
def filter(self, response: str) -> tuple[bool, str]:
for pattern in self.blocked_patterns:
if re.search(pattern, response, re.IGNORECASE):
return False, f"Zablokowane: {pattern}"
return True, "OK"
Strategia 3: Weryfikacja faktów
Weryfikacja faktów jest trudniejsza. Nie możesz zweryfikować każdej tezy, więc wybierz te, które mają znaczenie:
class FactChecker:
def __init__(self):
self.known_facts = {
"capital of france": "Paris",
"population of usa": "330 million",
"speed of light": "299,792,458 m/s",
}
def check(self, claim: str) -> tuple[bool, str]:
claim_lower = claim.lower()
for fact, truth in self.known_facts.items():
if fact in claim_lower and truth not in claim_lower:
return False, f"Test faktów nieudany: {fact}"
return True, "OK"
Do prawdziwej weryfikacji faktów potrzebujesz potoku odzyskiwania danych. Porównuj twierdzenia z bazą wiedzy, a nie z zaszytym słownikiem.
Mechanizmy bezpieczeństwa
Strategia 1: Ograniczanie częstotliwości zapytań
Ograniczanie częstotliwości zapytań zapobiega nadużyciom:
import time
from collections import deque
class RateLimiter:
def __init__(self, max_requests: int = 10, window: int = 60):
self.max_requests = max_requests
self.window = window
self.requests = deque()
def allow(self) -> bool:
now = time.time()
while self.requests and self.requests[0] < now - self.window:
self.requests.popleft()
if len(self.requests) >= self.max_requests:
return False
self.requests.append(now)
return True
Strategia 2: Budżetowanie tokenów
Budżetowanie tokenów ogranicza koszty na żądanie:
class TokenBudget:
def __init__(self, max_tokens: int = 1000):
self.max_tokens = max_tokens
def validate(self, response: str) -> tuple[bool, str]:
token_count = len(response.split())
if token_count > self.max_tokens:
return False, f"Przekroczono limit tokenów: {token_count} > {self.max_tokens}"
return True, "OK"
Strategia 3: Zarządzanie oknem kontekstowym
Zarządzanie oknem kontekstowym zapobiega przepełnieniu:
class ContextManager:
def __init__(self, max_context: int = 4096):
self.max_context = max_context
self.context = []
def add(self, message: str):
self.context.append(message)
self.trim()
def trim(self):
while len(" ".join(self.context)) > self.max_context:
self.context.pop(0)
Ocinanie przesuwającego się okna jest proste, ale utrudnia dostęp do wcześniejszego kontekstu. Lepsze podejścia wykorzystują podsumowanie lub kompresję opartą na uwadze, ale zwiększają one opóźnienie.
Zgodność
Systemy korporacyjne wymagają mechanizmów ochronnych zapewniających zgodność. Dwa najważniejsze:
Wzorzec 1: Lokalność danych
Lokalność danych — zapewnij, że dane pozostają w wymaganych granicach geograficznych:
class DataResidency:
def __init__(self, allowed_regions: list[str]):
self.allowed_regions = allowed_regions
def validate(self, region: str) -> tuple[bool, str]:
if region not in self.allowed_regions:
return False, f"Region niedozwolony: {region}"
return True, "OK"
Wzorzec 2: Rejestrowanie audytowe
Rejestrowanie audytowe — rejestruj wszystkie interakcje z modelem:
import json
from datetime import datetime
class AuditLogger:
def __init__(self, log_file: str = "audit.log"):
self.log_file = log_file
def log(self, request: dict, response: dict):
entry = {
"timestamp": datetime.now().isoformat(),
"request": request,
"response": response,
}
with open(self.log_file, "a") as f:
f.write(json.dumps(entry) + "\n")
Dzienniki audytowe są krytyczne dla debugowania i zgodności. Zadbaj o to, aby były strukturalne, tylko do dopisywania i przechowywane w sposób bezpieczny.
Połączenie elementów
Wzorzec 1: Proste mechanizmy ochronne
Prosty potok mechanizmów ochronnych:
class SimpleGuardrails:
def __init__(self):
self.input_validator = InputValidator(max_length=10000)
self.output_filter = OutputFilter()
def process(self, prompt: str) -> str:
valid, message = self.input_validator.validate(prompt)
if not valid:
return f"Błąd: {message}"
response = self.call_model(prompt)
valid, message = self.output_filter.filter(response)
if not valid:
return f"Błąd: {message}"
return response
Wzorzec 2: Zaawansowane mechanizmy ochronne
Zaawansowane mechanizmy ochronne dodają sanityzację, ograniczanie częstotliwości zapytań i budżety tokenów:
class AdvancedGuardrails:
def __init__(self):
self.sanitizer = PromptSanitizer()
self.input_validator = InputValidator(max_length=10000)
self.content_filter = ContentFilter()
self.output_filter = OutputFilter()
self.rate_limiter = RateLimiter(max_requests=10)
self.token_budget = TokenBudget(max_tokens=1000)
def process(self, prompt: str) -> str:
prompt = self.sanitizer.sanitize(prompt)
valid, message = self.input_validator.validate(prompt)
if not valid:
return f"Błąd: {message}"
valid, message = self.content_filter.filter(prompt)
if not valid:
return f"Błąd: {message}"
if not self.rate_limiter.allow():
return "Błąd: Przekroczono limit częstotliwości zapytań"
response = self.call_model(prompt)
valid, message = self.output_filter.filter(response)
if not valid:
return f"Błąd: {message}"
valid, message = self.token_budget.validate(response)
if not valid:
return f"Błąd: {message}"
return response
Kiedy mechanizmy ochronne są ważne
Mechanizmy ochronne mają znaczenie, gdy budujesz systemy skierowane do użytkowników, obsługujesz poufne dane lub działasz w środowisku produkcyjnym. Mają one również znaczenie, gdy musisz spełniać wymogi zgodności — RODO (GDPR), HIPAA, SOC 2.
Nie mają znaczenia, gdy prototypujesz, używasz modeli wyłącznie do wewnętrznych narzędzi lub nie obsługujesz poufnych danych. Pomiń je, dopóki ich nie potrzebujesz.
Zawsze występuje kompromis między możliwościami a bezpieczeństwem. Więcej mechanizmów ochronnych oznacza mniej awarii, ale także mniejsze możliwości. Znajdź równowagę, która działa dla Twojego systemu.
Kompromisy
| Strategia | Bezpieczeństwo | Możliwości | Opóźnienie |
|---|---|---|---|
| Brak mechanizmów ochronnych | Najniższe | Najwyższe | Najniższe |
| Walidacja danych wejściowych | Wysokie | Średnie | Niskie |
| Filtrowanie danych wyjściowych | Wysokie | Średnie | Niskie |
| Mechanizmy bezpieczeństwa | Najwyższe | Najniższe | Najwyższe |
| Zgodność | Najwyższe | Najniższe | Najwyższe |
Powiązane
- Strategie routingu modeli — routing oparty na możliwościach, kosztach i opóźnieniach
- Optymalizacja kosztów w systemach LLM — budżetowanie tokenów, modele zapasowe, buforowanie
- Projektowanie wielomodelowych systemów — architektura dla wielu modeli
- Architektura LLM — filar projektowania systemów: routing, koszty, mechanizmy ochronne i orkiestracja