LLM Guardrails на практике: что действительно работает

Управляйте риском, а не только моделью.

Содержимое страницы

Языковые модели (LLM) непредсказуемы. Они галлюцинируют, утекают данные, генерируют вредоносный контент или отказывают в выполнению легитимных запросов. Ограничители (guardrails) constraining поведение модели без ущерба для её возможностей.

Ключевой момент заключается в том, чтобы знать, какие ограничения действительно важны, а какие являются лишь шумом.

Ограничители — это не про контроль над моделью. Это про управление рисками.

Ограничители LLM на практике

Валидация входных данных

Самый важный ограничитель. Плохие входные данные приводят к плохим результатам, а также могут привести к инъекции промптов в вашу систему.

Стратегия 1: Санитизация промптов

Своевременная санитизация опасных паттернов:

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

Это не панацея. Вредоносные входные данные могут быть креативными. Но это ловит очевидные случаи, а они являются наиболее распространенными.

Стратегия 2: Ограничение длины ввода

Ограничения длины предотвращают расход токенов и таймауты:

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"Input too long: {len(prompt)} > {self.max_length}"
        return True, "OK"

Стратегия 3: Фильтрация контента

Фильтрация контента блокирует нарушения политик. Паттерны здесь зависят от вашей предметной области:

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

Простое сопоставление строк быстро, но неточно. Для продакшена используйте модель классификатора — даже небольшую, такую как qwen3-1.7b, — для обнаружения нарушений политик. Это более точно и сложнее для обхода.

Фильтрация выходных данных

Вывод модели также нуждается в проверке. Структура, контент и факты.

Стратегия 1: Валидация ответа

Сначала валидируйте структуру. Если вы ожидаете JSON, проверьте наличие 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"Missing field: {field}"
        return True, "OK"

Стратегия 2: Фильтрация контента

Фильтрация вредоносного контента:

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

Стратегия 3: Проверка фактов

Проверка фактов сложнее. Вы не можете валидировать каждое утверждение, поэтому выбирайте те, которые важны:

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"Fact check failed: {fact}"
        return True, "OK"

Для реальной проверки фактов вам нужна конвейерная система извлечения данных. Проверяйте утверждения против базы знаний, а не жестко закодированного словаря.

Механизмы безопасности

Стратегия 1: Ограничение частоты запросов

Ограничение частоты запросов предотвращает злоупотребления:

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

Стратегия 2: Бюджетирование токенов

Бюджетирование токенов ограничивает затраты на один запрос:

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"Token limit exceeded: {token_count} > {self.max_tokens}"
        return True, "OK"

Стратегия 3: Управление окном контекста

Управление окном контекста предотвращает переполнение:

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)

Обрезка скользящим окном проста, но приводит к потере раннего контекста. Более совершенные подходы используют суммаризацию или сжатие на основе внимания, но это добавляет задержку.

Соответствие требованиям (Compliance)

Корпоративным системам нужны ограничители соответствия требованиям. Два самых важных:

Паттерн 1: Резидентность данных

Резидентность данных — убедитесь, что данные остаются в пределах требуемых географических границ:

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 not allowed: {region}"
        return True, "OK"

Паттерн 2: Аудиторское логирование

Аудиторское логирование — логируйте все взаимодействия с моделью:

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")

Аудиторские логи критически важны для отладки и соответствия требованиям. Делайте их структурированными, доступными только для добавления и храните их безопасно.

Совмещение всего вместе

Паттерн 1: Простые ограничители

Простой конвейер ограничителей:

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"Error: {message}"

        response = self.call_model(prompt)

        valid, message = self.output_filter.filter(response)
        if not valid:
            return f"Error: {message}"

        return response

Паттерн 2: Продвинутые ограничители

Продвинутые ограничители добавляют санитизацию, ограничение частоты запросов и бюджетирование токенов:

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"Error: {message}"

        valid, message = self.content_filter.filter(prompt)
        if not valid:
            return f"Error: {message}"

        if not self.rate_limiter.allow():
            return "Error: Rate limit exceeded"

        response = self.call_model(prompt)

        valid, message = self.output_filter.filter(response)
        if not valid:
            return f"Error: {message}"

        valid, message = self.token_budget.validate(response)
        if not valid:
            return f"Error: {message}"

        return response

Когда ограничители важны

Ограничители важны, когда вы создаете системы, ориентированные на пользователя, обрабатываете конфиденциальные данные или работаете в продакшене. Они также важны, когда у вас есть требования соответствия — GDPR, HIPAA, SOC 2.

Они не важны, когда вы прототипируете, используете модели только для внутренних инструментов или не обрабатываете конфиденциальные данные. Пропустите их, пока они вам не понадобятся.

Компромисс всегда заключается в балансе между возможностями и безопасностью. Больше ограничителей означают меньше сбоев, но и меньше возможностей. Найдите баланс, который подходит для вашей системы.

Ограничители покрывают разговор с моделью, но они останавливаются в точке, где агент вызывает инструмент или передает работу другому агенту. Как только вы развернете инструменты MCP или агенты A2A, вам также понадобится протокольная безопасность — идентификация, авторизация с ограниченной областью действия, лимиты делегирования и аудиторские следы того, кто может действовать от чьего имени. Этот слой описан в Безопасность агентов A2A и MCP: Идентификация, Делегирование и Аудиторские Следы.

Компромиссы

Стратегия Безопасность Возможности Задержка
Без ограничителей Низкая Высокая Низкая
Валидация ввода Высокая Средняя Низкая
Фильтрация вывода Высокая Средняя Низкая
Механизмы безопасности Высшая Низкая Высшая
Соответствие требованиям Высшая Низкая Высшая

Связанные материалы

Подписаться

Получайте новые материалы про системы, инфраструктуру и AI engineering.