Ochronne mechanizmy dla LLM w praktyce: co naprawdę działa

Kontroluj ryzyko, nie tylko model.

Page content

Modele językowe (LLM) są nieprzewidywalne. Mogą halucynować, wyciekać dane, generować szkodliwe treści lub odmawiać spełnienia legalnych żądań. Mechanizmy ochronne (guardrails) ograniczają zachowanie modelu, nie tracąc przy tym jego możliwości.

Kluczem jest wiedza, które mechanizmy ochronne są istotne, a które to tylko szum.

Mechanizmy ochronne nie służą do kontrolowania modelu. Służą do kontrolowania ryzyka.

Mechanizmy ochronne LLM w praktyce

Walidacja wejścia

To najważniejszy mechanizm ochronny. Słabe dane wejściowe dają słabe dane wyjściowe, a złe dane wejściowe mogą również zaatakować Twoje systemy poprzez injekcję promptów.

Strategia 1: Sanitizacja promptów

Sanitizuj 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 ataki. Dane wejściowe od przeciwników bywają kreatywne. Lecz ten mechanizm łapie te najbardziej oczywiste, a one są też najczęstsze.

Strategia 2: Limitowanie długości wejścia

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 polityki. Wzorce zależą od Twojej dziedziny:

class ContentFilter:
    def __init__(self):
        self.blocked_topics = [
            "przemoc", "mowa nienawiści", "samookaleczanie",
            "treści o charakterze seksualnym", "działania nielegalne",
        ]

    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"Blokowane: {topic}"
        return True, "OK"

Proste dopasowanie ciągów znaków jest szybkie, ale niedokładne. W środowisku produkcyjnym użyj modelu klasyfikacyjnego – nawet małego, takiego jak qwen3-1.7b – do wykrywania naruszeń polityki. Jest to bardziej dokładne i trudniejsze do obejścia.

Filtrowanie wyjścia

Wyjście modelu również wymaga 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"Blokowane: {pattern}"
        return True, "OK"

Strategia 3: Weryfikacja faktów

Weryfikacja faktów jest trudniejsza. Nie można zweryfikować każdej twierdzenia, dlatego 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"Weryfikacja faktów nie powiodła się: {fact}"
        return True, "OK"

Do prawdziwej weryfikacji faktów potrzebny jest potok odzyskiwania danych (retrieval pipeline). Sprawdzaj twierdzenia przeciwko bazie wiedzy, a nie przeciwko utwardzonemu słownikowi.

Mechanizmy bezpieczeństwa

Strategia 1: Limitowanie częstotliwości (Rate Limiting)

Limitowanie częstotliwości 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 z oknem ślizgowym (sliding window) jest proste, ale powoduje utratę wczesnego kontekstu. Lepsze podejścia wykorzystują podsumowanie lub kompresję opartą na uwadze (attention), ale zwiększają one opóźnienie.

Zgodność (Compliance)

Systemy korporacyjne wymagają mechanizmów ochronnych zgodności. Dwa najważniejsze to:

Wzorzec 1: Rezydencja danych

Rezydencja danych – upewnij się, ż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: Logowanie audytowe

Logowanie 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. Powinny być strukturalne, tylko do dołączania (append-only) i przechowywane bezpiecznie.

Połączenie w całość

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ą sanitizację, limitowanie częstotliwości 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"

        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ą istotne

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ć wymagania zgodności – RODO (GDPR), HIPAA, SOC 2.

Nie mają one 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 istnieje kompromis między możliwościami a bezpieczeństwem. Więcej mechanizmów ochronnych oznacza mniej awarii, ale też mniejsze możliwości. Znajdź balans, który działa dla Twojego systemu.

Mechanizmy ochronne obejmują rozmowę z modelem, ale zatrzymują się w momencie, gdy agent wywołuje narzędzie lub przekazuje pracę innemu agentowi. Po wdrożeniu narzędzi MCP lub agentów A2A potrzebujesz również bezpieczeństwa protokołu – tożsamości, autoryzacji o zakresie, limitów delegowania i śladów audytowych dotyczących tego, kto może działać w imieniu kogo. Ta warstwa jest omawiana w Bezpieczeństwo agentów A2A i MCP: Tożsamość, Delegowanie i Ślady Audytowe.

Kompromisy

Strategia Bezpieczeństwo Możliwości Opóźnienie
Brak mechanizmów ochronnych Najniższe Najwyższe Najniższe
Walidacja wejścia Wysokie Średnie Niskie
Filtrowanie wyjścia Wysokie Średnie Niskie
Mechanizmy bezpieczeństwa Najwyższe Najniższe Najwyższe
Zgodność Najwyższe Najniższe Najwyższe

Powiązane

Subskrybuj

Otrzymuj nowe wpisy o systemach, infrastrukturze i inżynierii AI.