Guardrails: слои защиты для LLM в продакшне
87% топовых моделей до сих пор уязвимы к jailbreak, штрафы по EU AI Act — до €35M, а слоистые guardrails ловят 95% инцидентов. Разбираем, как строить защиту на каждом шве пайплайна: один guardrail — это не guardrail, а ощущение страховки.
СреднийDevOps с AI25 минNeMo Guardrails, Guardrails AI, Any validator library
1
Один guardrail — это не guardrail. Защита всегда слоистая
Ни одна проверка не ловит всё. Валидация на входе пропускает умные атаки — те, что маскируются под обычный текст. Валидация на выходе ловит их, но уже поздно: PII (персональные данные) утекли, модель выдала токсичный ответ, деньги потрачены на длинную генерацию, которую придётся выкинуть.
Аналогия простая: замок в двери, сигнализация, камера. Убрать один слой — остальные компенсируют. Убрать все кроме одного — надеяться на удачу. Defense-in-depth работает так же: каждый слой ловит свой класс проблем, и только сумма даёт реальную защиту. По публичным оценкам, один guardrail ловит 30-50% инцидентов; пять слоёв вместе — около 95%.
Слой 1 — Input validation
Jailbreak detection, PII scrub
Слой 2 — Hardening системного промпта
Instruction defense, role-lock
Слой 3 — Безопасность на уровне модели
Temperature, max tokens, stop-слова
Слой 4 — Output validation
Factuality, toxicity, PII leak
Слой 5 — Audit + мониторинг
Логи подозрительных паттернов, алерты
Если команда говорит «у нас настроен guardrail» — спросите, какой именно слой. Почти всегда один, и это не страховка, а ощущение страховки.
2
Где ставить проверки: не на выходе, а на каждом шве
Частая ошибка: все проверки собраны на выходе. Latency суммируется, атака уже обработана (дорого и опасно), ревалидация дублирует работу. И главное: если вход был ядовитым, модель уже среагировала — артефакты (токены в контексте, записи в логах) остались.
Правильная схема: проверка на каждой границе, где ненадёжные данные пересекают доверенный контур. Пользователь → приложение — быстрый input gate. Приложение → модель — проверка целостности системного промпта. Модель → приложение — глубокий output gate. Дешёвые проверки ловят типовые случаи рано; дорогие (factuality через retrieval) запускаются только на том, что уже прошло ранние ворота.
Пользователь
Input gate
Приложение
Проверка системного промпта
LLM
Output gate
Финальный фильтр
Пользователь
Gate на входе должен быть быстрым (regex, классификатор). Gate на выходе — глубоким (retrieval-grounded, external eval). Дорогое оставляйте на то, что уже прошло дешёвое.
3
Параллельно, не последовательно — иначе latency
Допустим, вы навесили 5 проверок: PII scrub 200 мс, toxicity 300 мс, factuality 400 мс, jailbreak echo 100 мс, policy match 100 мс. Последовательно — 1100 мс задержки до первого байта ответа. Пользователь уже закрыл вкладку.
Почти все проверки независимы: работают с одним и тем же сырым текстом. Параллельный запуск — и общая latency (задержка) равна самой медленной проверке, а не сумме. Первый fail завершает остальные (race-to-fail). Последовательно имеет смысл только там, где есть реальная зависимость данных — например, одна проверка нормализует текст, а следующая работает с нормализованной версией. Таких случаев мало.
❌ Последовательно
- 1100 мс общая задержка (сумма)
- Пользователь ждёт все проверки подряд
- Fail на 5-й — потратили время на 1-4
- Чем больше слоёв — тем хуже UX
✅ Параллельно
- ~400 мс = самая медленная проверка
- Первый fail завершает остальные
- Независимые слои не дружат с очередью
- Добавление слоя почти бесплатно
4
Что проверять: четыре категории, не одна
Guardrails — не одна абстрактная «безопасность», а четыре разных класса задач с разными инструментами и местом в пайплайне. Путать их — ставить регулярку туда, где нужен классификатор.
Jailbreak / prompt injection ловится на входе, дёшево — классификаторами и regex. PII — на обоих gate: на входе не даём утечь в логи модели, на выходе не даём попасть пользователю. Toxicity — только на выходе, её генерирует модель. Factuality — самая дорогая: нужна retrieval-инфраструктура, включают только там, где цена ошибки высокая. Не каждому приложению нужны все четыре: поддержка клиентов — jailbreak и toxicity обязательны; медицинский чат-бот — factuality первый приоритет.
| Категория | Что проверяет | Как | Где |
|---|---|---|---|
| Jailbreak / prompt injection | Пытается ли ввод обойти системные инструкции | Классификатор (fine-tuned) + regex | Input gate |
| PII leakage | Персональные данные в вводе или выводе | Regex (email, phone, SSN) + NER | Input + Output gate |
| Toxicity | Оскорбления, hate speech, NSFW | Классификатор (Perspective API, LlamaGuard) | Output gate |
| Factuality | Выдумывает ли модель факты | Retrieval grounding + citation check | Output gate (domain-specific) |
Начинайте с PII и jailbreak — самые дешёвые и самые частые инциденты. Factuality — последним: она требует retrieval-инфраструктуры и дорогая.
5
Blocked ≠ error: что показывать пользователю
Самое недооценённое решение во всей системе guardrails — не то, что проверять, а что показать, когда проверка сработала. Три плохих варианта, которые встречаются постоянно. Первый — «Error 500»: выглядит как баг, пользователь жмёт retry и эскалирует в поддержку, а это был честный блок. Второй — «Sorry, I can't help with that»: звучит как робот, пользователи распознают паттерн и подбирают формулировку, которая проходит. Третий — silent response: пользователь думает, что AI сломался, и теряет доверие.
Лучшие паттерны работают иначе. Объясните категорию мягко, без конкретики: «не могу помочь с этим запросом» — ок; «ваш ввод похож на jailbreak по классификатору Y с confidence 0.87» — катастрофа, это готовая инструкция обхода. Предложите переформулировку или эскалацию в человека-оператора для честных граничных случаев.
Главный баланс: false positive (заблокировали честного) бьёт по метрикам сильнее, чем false negative (атака проскочила). Причина — false positive уходит в поддержку и отзывы, а false negative без мониторинга никем не замечается. Поэтому: log 100% срабатываний, ревьюйте еженедельно, тюньте пороги в сторону разрешать-по-умолчанию.
если guardrail_fired(ввод):
тип = какой_сработал() # для логов, не для пользователя
сообщение:
"Не могу помочь с этим запросом. Попробуйте переформулировать
или обратитесь в поддержку: support@..."
логи:
record({ тип, ввод, user_id, timestamp })
# для anti-abuse: 5+ срабатываний за час → rate-limit пользователяLog 100% срабатываний, показывайте пользователю 0% деталей. Логи — для вашего eval, детали — для атакующего. Раскрытие причины блокировки = обучение обхода.
Результат
Слоистая система guardrails, где пять независимых проверок запускаются в нужных точках пайплайна и в параллель. Вы знаете, какие категории нужны именно вашему приложению, и понимаете, почему сообщение пользователю важнее, чем сам факт блокировки.