Как защитить данные и не "сломать" LLM: что скрывается внутри Guardrails Filter
Когда в компании появляется LLM, довольно быстро выясняется: одной политики "не отправляем персональные данные в модель" недостаточно. В реальных продуктах пользователи пишут телефоны, почты, ФИО, номера документов и делают это спонтанно - в переписке, в комментариях к заявкам, в подсказках для бота. Поэтому нужен отдельный слой, который обеспечивает защиту данных при использовании LLM и при этом не вмешивается в логику приложения. Именно такую роль и выполняет Guardrails Filter - промежуточный фильтр между вашим сервисом и моделью. Разбор подхода хорошо иллюстрирует текст защита данных при использовании LLM, а ниже - ключевые инженерные нюансы, из‑за которых "просто заменить данные" не работает.
Почему маскировка - лишь половина задачи
На бумаге всё выглядит прямолинейно: в фильтр приходит JSON‑запрос к LLM, внутри - история диалога и новое сообщение. Нужно найти чувствительные сущности и заменить их плейсхолдерами до отправки в модель, а потом, получив ответ, вернуть исходные значения пользователю.
Поиск обычно решается довольно уверенно - регулярками, словарями и NER‑распознаванием: телефон, e‑mail, паспортные данные, СНИЛС, ФИО. Модель вместо "+7 999..." видит условное `
Поэтому на практике нужен не плейсхолдер "типа данных", а плейсхолдер "конкретного значения" - с таблицей соответствий. Фильтр проверяет: встречалось ли это значение раньше в текущей переписке. Если да - присваивает тот же идентификатор; если нет - создаёт новый. Так один и тот же номер или e‑mail получает одинаковый маркер по всему диалогу, а демаскировка становится детерминированной.
LLM "не помнит" - и фильтру тоже приходится начинать заново
Есть неприятный момент, который многие недооценивают: модель не хранит контекст между запросами. То, что выглядит как "память чата", на деле - отправка всей истории сообщений в каждом запросе. Значит, Guardrails Filter не может ограничиться обработкой только последней реплики: перед каждым вызовом он снова прогоняет всю историю, заново строит таблицу соответствий, маскирует и лишь затем пропускает запрос в LLM. Это цена согласованности: иначе плейсхолдеры "поплывут", и восстановление станет ошибочным.
Для обычного ответа (когда LLM возвращает результат целиком) это ещё терпимо: получили текст, нашли плейсхолдеры, подставили значения, отдали пользователю. Но всё резко усложняется, как только включается стриминг.
Стриминг ломает наивную демаскировку
В SSE‑режиме ответ приходит кусками: иногда по букве, иногда по нескольким символам, иногда словом. И плейсхолдер тоже может "разрезаться" между чанками. Сегодня вы ждёте `
Поэтому фильтр вынужден накапливать небольшой буфер, склеивать события и только потом отдавать наружу. Да, появляется микрозадержка - зато текст идёт более крупными фрагментами, и шанс "поймать" целый плейсхолдер становится реальным. Размер буфера обычно завязан на максимально возможную длину маркера: фильтр проверяет, есть ли открывающая `<`, закрылся ли `>`, узнаётся ли ключ, и можно ли уже безопасно восстановить исходное значение, не рискуя "схлопнуть" лишний текст.
В таком режиме логика быстро разрастается: нужно корректно работать с неполными токенами, учитывать границы UTF‑8, не терять служебные поля, а также стабильно обрабатывать ситуации, когда модель внезапно генерирует символы, похожие на маркеры, но не являющиеся ими.
Tool calling: важнее сохранить структуру, чем "почистить" текст
Отдельная категория проблем - tool calling (function calling). Когда модель возвращает не только текст, а структуру: имя инструмента, аргументы в JSON, иногда - несколько вызовов подряд. Здесь критично не просто найти и скрыть ПДн, а не разрушить формат, который ожидает ваше приложение.
Если маскировать значения внутри аргументов неаккуратно, можно получить невалидный JSON, поломанную схему или "сдвиг" типов (строка превратилась в число, число - в строку). И тогда бот не выполнит действие, даже если сама модель всё сделала правильно. На практике приходится вводить правила: что можно маскировать в аргументах инструментов, что нельзя трогать (идентификаторы, коды, ключи интеграций), и как логировать ошибки так, чтобы в логах не всплыли исходные чувствительные данные.
Именно поэтому фильтрация данных для LLM API - это не "регэксп и replace", а полноценный парсер и валидатор поверх протоколов провайдера. Хороший пример того, как к этому подходят в продакшене, разбирается в материале фильтрация данных для LLM API - там видно, почему поддержка стрима и структурированных ответов превращается в самостоятельный модуль, а не в пару утилитных функций.
---
Что ещё важно учесть (добавлено)
Во многих компаниях Guardrails Filter становится частью более широкого контура: DLP для LLM решения дополняют фильтр политиками хранения, журналированием и контролем каналов утечки. Иначе можно идеально замаскировать запрос в модель, но затем "слить" всё в логи, метрики или трассировки. Поэтому продакшен‑подход - это минимизация чувствительных данных на каждом этапе: в запросе, в ответе, в телеметрии и в инструментах поддержки.
Ещё один слой - производительность. Маскировка "всей истории на каждый запрос" легко превращается в узкое место: регулярки и NER на десятках сообщений, плюс буферизация SSE, плюс восстановление - и вот уже задержка заметна пользователю. Выход обычно в кэшировании таблиц соответствий на сессию, аккуратной инкрементальной обработке и лимитах на длину контекста (что, к слову, совпадает с практикой оптимизации стоимости вызовов LLM).
Также стоит заранее продумать, как вы будете доказывать соблюдение правил: кому доступны сырые данные, где они могут появиться, как быстро можно отозвать доступ, как проводится аудит. Для безопасность LLM для бизнеса это не абстрактная формулировка, а вопрос ответственности: от внутренних регламентов до требований комплаенса и отраслевых стандартов.
Наконец, фильтр полезно строить так, чтобы он был заменяемым компонентом. Рынок развивается быстро: сегодня вы используете один API, завтра - другой, послезавтра добавляете локальную модель. Если контур защиты жёстко "прошит" под одного провайдера, миграция станет дорогой. Поэтому Guardrails Filter всё чаще проектируют как отдельный сервис с понятным контрактом и политиками, которые можно обновлять без остановки продукта.
И да - вопрос "Guardrails Filter для LLM купить или написать самим?" закономерен. Готовое решение экономит месяцы на отладке стриминга, tool calling и пограничных случаев. Самописный вариант даёт полный контроль, но потребует серьёзной экспертизы в протоколах, тестировании и безопасной эксплуатации. В обоих сценариях смысл один: защитный слой должен не мешать модели работать и не ломать пользовательский опыт, иначе его начнут обходить.
Если нужно, пришлите требования (провайдер LLM, формат сообщений, используете ли SSE и function calling, какие категории ПДн важны) - предложу архитектуру фильтра и набор тест-кейсов для проверки маскировки и демаскировки.
