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

Валидация входных данных
Самый важный ограничитель. Плохие входные данные приводят к плохим результатам, а также могут привести к инъекции промптов в вашу систему.
Стратегия 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: Идентификация, Делегирование и Аудиторские Следы.
Компромиссы
| Стратегия | Безопасность | Возможности | Задержка |
|---|---|---|---|
| Без ограничителей | Низкая | Высокая | Низкая |
| Валидация ввода | Высокая | Средняя | Низкая |
| Фильтрация вывода | Высокая | Средняя | Низкая |
| Механизмы безопасности | Высшая | Низкая | Высшая |
| Соответствие требованиям | Высшая | Низкая | Высшая |
Связанные материалы
- Стратегии маршрутизации моделей — маршрутизация на основе возможностей, стоимости и задержки
- Оптимизация затрат для систем LLM — бюджетирование токенов, резервные модели, кэширование
- Проектирование многомодельных систем — архитектура для нескольких моделей
- Архитектура LLM — базовый элемент системного дизайна: маршрутизация, стоимость, ограничители и оркестрация