Разработчик заменил селф-хост LLM на PII-фильтр перед облачными моделями
Разработчик протестировал несколько GPU для селф-хоста LLM, но в итоге поставил между облачными моделями и своими данными open-source PII-фильтр. Теперь персональные данные и секреты заменяются плейсхолдерами до отправки запроса.
- Автор протестировал RTX PRO 6000 96GB, RTX4090 48GB, H200, RTX5070ti и V100 32GB для селф-хоста, но отказался от этой идеи
- Фильтр работает как прокси на пути запроса и ответа в режимах enforce и detect, заменяя персональные данные плейсхолдерами
- От LiteLLM с гардрейлом автор отказался: промежуточный прокси ломает общий префикс и экономия кэша промптов падает примерно на 90%

Разработчик на Хабре описал, как отказался от идеи полностью локальной LLM в пользу PII-фильтра перед облачными моделями. За два месяца он протестировал RTX PRO 6000 96GB, RTX4090 48GB, H200, купил RTX5070ti и V100 32GB и прогнал большое количество бенчмарков. Рост цен на железо и череда утечек персональных данных у провайдеров LLM заставили его искать другой подход.
Решением стал open-source guardrails-llm-filter от Cloud.ru под лицензией Apache-2.0. Он работает как прокси на пути запроса и ответа и умеет обрабатывать данные в обе стороны. Фильтр стоит между топовыми облачными моделями и всем, что им отправляется, поддерживает два режима: enforce заменяет персональные данные и секреты плейсхолдерами, detect только пишет в журнал найденное.
Почему не LiteLLM и не хук в клиенте
Первый фильтр был запущен 17 сентября. До этого работу выполнял хук внутри клиента, но за пять сессий накопились поломки: шторм потоков у ONNX, испорченные строки от глобальной работы с фильтрами через LLM, конфликт с кэшем промптов. Последнюю проблему в рамках API-клиента обойти не удалось: хук менял исходящий контекст, но не мог вернуть исходные значения в ответ, поэтому в ответах моделей оставались плейсхолдеры вида [EMAIL_01].
LiteLLM с гардрейлом был первым кандидатом, но автор отказался из-за тестов. Промежуточный прокси, который пересобирает запрос, ломает общий префикс, и экономия кэша промптов у провайдера падает примерно на 90%. При почти 1,5 млрд кэшированных токенов за три недели это оказалось слишком дорого.
Сбой 22 сентября и fail-open
22 сентября первая система фильтрации упала. Системный пользователь процесса был намеренно без домашней директории, а для go build нужен доступный на запись кэш модулей. Сборка не прошла, бинарник пропал, а правило пересборки в Ansible смотрело на изменения в репозитории, а не на сам файл. systemd пытался запустить несуществующий бинарник около четырёх часов, больше 2 600 раз. Обе облачные настройки провайдеров шли через этот фильтр, поэтому встал весь стандартный маршрут к моделям.
Фикс оказался простым: кэши и HOME переехали в каталог, которым владеет пользователь фильтра, а пересборка теперь запускается и тогда, когда файла нет. Также выяснилось, что режим detect стоял в двух местах — автор считал, что выставленный один раз глобально режим применяется и на reload/reboot. Теперь enforce стоит и в стартовом значении, и в применённых настройках с дополнительной проверкой Ansible при падении.
В системе реализован fail-open: если процесс работает, но не смог разобрать или замаскировать конкретное тело запроса, он отправляет запрос как есть и поднимает счётчики на +1. Иначе одна новая форма запроса превращала бы ежедневную работу в череду случайных блокировок. Цена этого выбора — возможная неотфильтрованная отправка, которая становится Major-инцидентом через монитор.
Мониторинг
В OneUptime появились два монитора. Heartbeat раз в пять минут получает push от проверки режима: недоступный процесс, crash-loop или режим, ушедший из enforce, выглядят как отсутствие сигнала, и отсутствие — уже инцидент. Метрики считают сумму за пять минут по счётчикам fail-open: ошибки маскирования, неподдерживающийся формат тела запроса и неизвестный формат, пропущенный дальше. В метриках только счётчики, типы данных и задержки, текстов запросов там нет.
После сегодняшнего рестарта с заменой GPU и полной перезагрузкой всех контейнеров основной экземпляр фильтра поднялся в enforce: включён, шесть типов данных, настройки режима прочитаны целиком. Локальные Gemma4 и Qwen3.8 находятся в stand-by режиме на ноутбуке и LXC с RTX5070ti. По словам автора, локальные модели пока до топовых облачных не дотягивают.
Для профиТехнические детали: архитектура, цифры, ссылки
- Фильтр: self-hosted guardrails-llm-filter от Cloud.ru, лицензия Apache-2.0.
- Режимы: enforce (замена плейсхолдерами) и detect (только журналирование).
- Причина отказа от LiteLLM с гардрейлом: промежуточный прокси ломает общий префикс, экономия кэша промптов падает примерно на 90%.
- Мониторинг: OneUptime, heartbeat раз в 5 минут, метрики fail-open за 5 минут.
- Протестированное железо: RTX PRO 6000 96GB, RTX4090 48GB, H200, RTX5070ti, V100 32GB.
- Локальные модели в stand-by: Gemma4 и Qwen3.8 на ноутбуке и LXC с RTX5070ti.
Вопросы и ответы
- Что делает PII-фильтр перед облачными моделями?
- Работает как прокси на пути запроса и ответа: в режиме enforce заменяет персональные данные и секреты плейсхолдерами, в режиме detect только пишет в журнал найденное.
- Почему автор отказался от LiteLLM с гардрейлом?
- Промежуточный прокси пересобирает запрос и ломает общий префикс, из-за чего экономия кэша промптов у провайдера падает примерно на 90%.
- Что происходит, если фильтр не смог замаскировать запрос?
- Срабатывает fail-open: запрос отправляется как есть, а счётчики fail-open поднимаются на +1, чтобы автор мог проанализировать и оттюнить систему.


