
Всем привет, меня зовут Максим. Я автор Telegram‑канала Максим Максимов // IT, AI . В этой статье расскажу о том, как я реализовал свою идею создать guardrails, который будет простым, работать быстро, но в то же время готовым к внедрению в производство.
Поехали!
Что такое Guardrails
Для начала давайте поймём, что такое Guardrails. Это система, роль которой — защищать другие системы от нежелательных данных или действий. Чаще всего Guardrails выступает как прослойка между системами и проверяет данные, которыми они обмениваются.
Guardrails может работать как с выходными данными системы, так и с входными.
Сценарии использования могут быть разные. Например, ваша платформа использует LLM внешних провайдеров, куда не хотелось бы отправлять персональные данные пользователей (152-ФЗ). В этой цепочке Guardrails будет ловить такие данные в тексте и, например, блокировать такие запросы либо же маскировать их.
Так как Guardrails является дополнительным звеном в работе, это может увеличивать время обработки запросов пользователей, что не очень хотелось бы. Также при использовании тяжеловесных моделей для работы с данными (например LLM) могут требоваться мощности, которые есть не у всех.
Поэтому у меня возникла идея сделать такой Guardrails, который бы работал быстро и не требовал большого количества ресурсов. При этом я понимал, что готовые аналоги есть и их можно просто взять и использовать — но мне было интересно сделать своё: пройти путь от идеи, самому продумать архитектуру, произвести разработку и довести Guard до продакшен состояния. А значит, столкнуться с проблемами по пути и решить их. Во время реализации я ориентировался на открытые open-source решения.
Идея lite‑guardrails
Рассмотрим идею легковесного Guardrails, на которой я остановился.
Чтобы добиться легковесности и скорости, я решил не использовать никакие модели вроде BERT или LLM. Вместо этого использовались классические методы работы с текстом. Это решение, конечно, влечёт за собой некоторые проблемы — о них расскажу далее.
Основной функционал Guardrails разделён на три модуля:
PII — позволяет находить персональные данные в тексте, чтобы защитить их от утечек вовне и соблюдать 152-ФЗ.
NSFW — находит в тексте небезопасный контент. В моей реализации — русский мат. Используется там, где нужно отклонять подобного рода запросы.
Relevant — находит мусор в тексте (эмодзи, текст только из знаков пунктуации, …), а также смолток («Привет», «Как дела», «Пока», …). Может использоваться как фильтр нерелевантных запросов, либо же при работе с LLM роутить такие запросы на более дешёвые модели.
Во время разработки я ориентировался только на русскоязычный текст. Но сама реализация Guardrails позволяет расширять его и на другие языки.
Далее пройдемся по самому интересному — процессу разработки. Рассмотрим сам процесс и в целом поймём, как работает lite‑guardrails. Во время работы над проектом разработку заметно упрощал Claude Code: с ним я в большей степени выступал архитектором и ревьюером кода, а также продакт‑менеджером проекта.
Основная бизнес логика
В этой части рассмотрим работу каждого модуля Guardrails, а также проведённые эксперименты и сравнения.
Задача этого модуля — обнаружение персональных данных в тексте.
В качестве персональных данных были выделены следующие сущности:
Номер телефона
Номер телефона
Банковская карта
Банковская карта
Данные сущности были выбраны в связи с тем, что их не так сложно находить в тексте при помощи регулярных выражений.
Телефон, email и URL детектируются чисто регулярными выражениями.
Например, регулярка для номеров:
(?:\+7|8)[\s\‑]*\(?\s*\d{3}\s*\)?[\s\‑]*\d{3}[\s\‑]*\d{2}[\s\‑]*\d{2}
Ловит номера в формате:
+79161234567
+79161234567
8 916 123 45 67
8 916 123 45 67
+7 (916) 123–45-67
+7 (916) 123–45-67
Для остальных сущностей после регулярки применяется алгоритмическая валидация. ИНН и СНИЛС проверяются контрольной суммой (взвешенная сумма по модулю 11 и 101 соответственно):
Банковская карта валидируется алгоритмом Луна (Luhn). Каждая вторая цифра номера удваивается (а если получилось больше 9 — из результата вычитают 9), затем все цифры суммируются. Если сумма кратна 10 — номер прошёл проверку.
IP — проверкой диапазона октетов (0–255), а паспорт РФ — структурной проверкой (код региона, год бланка, отсев одинаковых цифр и монотонных последовательностей).
Вышеперечисленные проверки были добавлены для того, чтобы уменьшить количество ложных срабатываний, так как, например, в тексте могут попадаться схожие наборы чисел, которые не являются паспортом или ИНН.
Помимо модуля обнаружения персональных данных, разработан функционал их анонимизации и деанонимизации в тексте. Также это называют псевдонимизацией.
Он позволяет при отправке данных во внешние системы (например в LLM) маскировать персональные данные в тексте и заменять их на теги ([PERSON_1], [PHONE_1], …), хранить реальные данные в контуре Guardrails и отправлять в LLM уже маскированный текст. Далее LLM генерирует ответ, используя теги. Когда мы получаем ответ от LLM, текст проходит этап демаскирования — вместо тегов мы подставляем реальные персональные данные. Таким образом, персональные данные не уходят вовне (удовлетворяя 152-ФЗ), а пользователь получает корректный ответ.
Как можно заметить, при работе данного функционала требуется хранилище для извлечённых персональных данных — о его реализации расскажу далее.
Конечно, не всегда это работает идеально: бывают ошибки, например, когда LLM путается в тегах или пишет их неправильно.
Это модуль, который позволяет находить небезопасный контент в тексте. На данный момент он ориентирован на обнаружение русского мата, но также может быть расширен и на другие категории.
Работает он следующим образом: заранее собирается словарь из нежелательных слов, которые детектор должен находить. Далее во время работы модуля идёт поиск этих слов в тексте, и если слово найдено — происходит срабатывание.
Для ускорения поиска используется не полнотекстовый поиск, а алгоритм Ахо‑Корасик, который позволяет искать нежелательные слова быстрее, чем наивный полнотекстовый поиск. Данный алгоритм работает по принципу конечного автомата, обрабатывая текст за линейное время.
За простотой данной реализации скрываются недостатки — модуль ищет NSFW только по словам, но не по смыслу. То есть если в тексте будет нежелательный контент, но в формулировках, которых нет в словаре, — он такое пропустит. Отсутствие понимания семантики — это цена скорости и неиспользования моделей для работы с текстом.
Это модуль, который определяет, содержит ли сообщение осмысленный запрос или это смолток (приветствия, благодарности, прощания), оффтоп или бессмысленный «мусор».
Заранее собираются словари фраз, сгруппированные по категориям (приветствие, благодарность, прощание и так далее). Во время работы модуля текст приводится к нижнему регистру, и в нём ищутся совпадения с шаблонами всех категорий.
Ключевая идея — не просто найти шаблонную фразу, а оценить, какую долю сообщения она занимает. Для этого считается покрытие текста по буквам: сколько букв сообщения попало в найденные фразы. Если совпадения есть и покрытие превышает порог (80%), значит, сообщение почти целиком состоит из смолтока/оффтопа и помечается как нерелевантное; иначе в тексте есть содержательная часть, и оно считается релевантным. Такой подход позволяет отфильтровать чистый смолток («привет, спасибо, пока»), но не блокировать нормальный запрос, в котором вежливое приветствие идёт перед сутью.
Дополнительно работает отдельная проверка — фильтр «мусора»: если в тексте нет ни букв, ни цифр (одни символы или эмодзи), он сразу помечается нерелевантным.
Сравнение с другими решениями
Данные модули были протестированы на бенчмарках, чтобы понять их качество.
Для оценки модуля PII я взял русскоязычный бенчмарк на детекцию персональных данных.
Метрики вышли следующие:
В бенчмарке намеренно встречаются опечатки в сущностях и лишние знаки пунктуации, чтобы классические регулярные выражения не могли поймать PII. На таких примерах наш модуль PII допускает ошибки (это видно по низкому Recall), но простые чёткие примеры он ловит хорошо (высокий Precision).
Для модуля NSFW я также взял открытый бенчмарк для оценки детекторов NSFW в тексте.
В данном случае нашему NSFW‑детектору ещё тяжелее. Большая часть ошибок связана с тем, что бенчмарк ориентирован на семантическое понимание контента, а наш детектор — только на наличие триггерных слов в тексте.
Для модуля Relevant я не нашёл подходящего бенчмарка, поэтому оценить его на внешних данных не удалось. В любом случае вывод будет тот же — модуль отлавливает смолток, ориентируясь только на наличие фраз из словаря в тексте.
Отдельно я замерил скорость. Взял ноутбук без видеокарты, только CPU, и прогнал одни и те же тексты через несколько NER‑моделей, которые ловят персональные данные, и через свой guard. Результат на графике выше: небольшая BERT‑модель (piiranha) обрабатывает один текст примерно за 186 мс, GLiNER — за 226 мс, а lite‑guardrails на том же железе тратит меньше 0.1 мс на текст. Разница в тысячи раз. Причина простая — модели каждый раз гоняют нейросеть, и без GPU это медленно, а мой guard работает на регулярках с проверкой контрольных сумм, где считать почти нечего, поэтому он отвечает практически мгновенно.
По качеству lite‑guardrails, конечно, уступает языковым моделям (таким как BERT или, например, gpt‑oss‑guard) — он не понимает смысл и ошибается на искаженных данных. Зато он хорошо ловит простые чёткие случаи, не требует ресурсов и работает за доли миллисекунды. Поэтому такое решение стоит брать, когда нет свободных мощностей, а хочется поставить что‑то быстрое и лёгкое.
Для того чтобы другие системы могли удобно работать с Guardrails, я решил сделать его отдельным web‑сервисом. Для этого использовал FastAPI — популярный и удобный фреймворк для быстрой разработки API. Во время реализации я старался придерживаться слоистой архитектуры. Код модулей вынесен в слой бизнес‑логики, поверх которого располагаются внешний API и работа с данными.
Работает это следующим образом:
Для каждого модуля сделан эндпоинт, который могут вызывать внешние сервисы. Помимо одиночных вызовов добавлен функционал пакетной обработки.
Также, чтобы увеличить пропускную способность, я использую воркеры (workers) uvicorn при разворачивании сервиса. Так как при обработке запросов у нас по большей части CPU‑bound задачи, ускорить работу помогает распараллеливание по воркерам.
Работает это так: при запуске сервера с несколькими воркерами на каждом ядре CPU размещается копия FastAPI‑приложения. Запросы распределяются между этими копиями и обрабатываются параллельно, что и повышает пропускную способность сервера. При этом имеется единая база данных, с которой эти воркеры взаимодействуют.
Как мы помним, для модуля PII функционал деанонимизации требует наличия хранилища, которое будет сохранять и отдавать персональные данные при анонимизации и деанонимизации.
В lite‑guardrails это реализовано так. При вызове пользователем ручки деанонимизации на первом шаге происходит обнаружение персональных данных в тексте. После этого создаётся уникальный идентификатор запроса, а сами персональные данные заменяются на теги. Таким образом, нам нужно хранить данные в формате: ключ — идентификатор, а значение — теги и соответствующие им реальные персональные данные. Для этого использован Redis: он хранит данные в формате ключ‑значение и позволяет достаточно быстро сохранять и получать их при запросе.
Также был разработан функционал администрирования и настройки модулей Guardrails (UI будет показан ниже). Для хранения данных используется PostgreSQL: в ней хранятся ключи пользователей, настройки модулей (регулярные выражения, словари) и логи.
Интеграция с LiteLLM
Если в работе используется litellm, есть возможность удобно внедрить этот guardrails, чтобы он работал при запросах к внешним LLM.
Для этого в litellm есть возможность внедрить свой кастомный guardrails. Нужно реализовать Python‑класс со структурой:
Реализуем два хука: async_pre_call_hook отрабатывает до отправки запроса в LLM (проверяем и при необходимости блокируем/анонимизируем ввод), а async_post_call_success_hook — после ответа модели (деанонимизируем и проверяем вывод). В pre‑call PII заменяется на теги (<EMAIL_1>…), чтобы реальные данные не ушли в LLM, а в post‑call эти теги разворачиваются обратно — пользователь видит оригинальные значения.
Далее этот класс добавляется в YAML‑конфиг litellm:
Если всё сделано правильно, после перезапуска в UI litellm будет видно, что добавленный guard работает.
После этого при использовании LLM через litellm будет работать guardrails. Например, анонимизация персональных данных перед отправкой во внешнюю LLM.
Панель администратора
Во время разработки я предполагал, что с Guardrails будут работать несколько клиентов или сервисов. Для того чтобы было удобно это отслеживать и администрировать, была разработана отдельная админ‑панель. Он разрабатывался с использованием React.
Админка имеет следующий функционал:
Здесь размещена небольшая статистика по использованию каждого модуля: количество и процент срабатываний, а также данные по использованию Guardrails по каждому ключу.
Настройка модулей
Имеется модуль для добавления регулярных выражений для PII. Можно включить\отключить регулярные выражения, добавить новые или отредактировать старые.
Для модуля NSFW есть возможность включать/отключать словари, добавлять новые, а также редактировать старые, чтобы добавить или удалить слова, которые детектор должен отлавливать. Модуль Relevant работает схожим образом.
Таким образом, при добавлении или изменении правил можно настраивать работу guard — какие сущности он отлавливает и на какие слова триггерится.
Настройка API ключей
Здесь можно создать API ключ для клиента, настроить ему RPM, а также отслеживать по логам и метрикам его использование Guardrails.
Логи запросов
На страничке с логами можно посмотреть историю запросов в Guardrails, а также срабатывания. Имеется фильтр, по которому можно отфильтровать данные по метаданным, например по API‑ключу.
На этой страничке можно быстро опробовать и продемонстрировать работу Guardrails на своих данных прямо внутри админки.
SDK для взаимодействия с сервисом
Для того чтобы было удобно взаимодействовать с Guardrails из своего приложения, был разработан SDK для двух языков.
На Python:
Имеется синхронный так и асинхронный клиент.
Также разработан SDK на JavaScript:
Для того чтобы настроить более продвинутое observability, я добавил возможность интеграции с Prometheus: есть ручка /metrics, которая отдаёт метрики в нужном формате, а также заранее подготовленный YAML‑файл с конфигурацией для развёртывания Prometheus.
Также заранее подготовлена конфигурация и сформирован dashboard для Grafana, который можно развернуть вместе с Prometheus.
Либо же если эти сервисы уже развернуты — можно просто использовать ручку /metrics для извлечения метрик.
Для упрощения развёртывания используется Docker — вся конфигурация сервисов собрана в docker-compose .
Чтобы развернуть lite‑guardrails, достаточно клонировать репозиторий, создать файл окружения и выполнить запуск:
После этого поднимаются основные сервисы (API + движок, Admin UI, PostgreSQL, Redis) — можно начинать пользоваться Guardrails.
Для Prometheus и Grafana в docker-compose.yml заведён профиль monitoring . Если при развёртывании нужно поднять и мониторинг, запуск выполняется так (флаг профиля идёт до up ):
Эта команда поднимет все сервисы, включая мониторинг.
CI в Github Actions
Также для повышения качества кодовой базы и уменьшения багов был добавлен CI‑workflow в GitHub Actions, в котором производится автоматическая проверка линтинга, запуск автотестов и проверка зависимостей.
Pipe выглядит следующим образом
Также был добавлен CD‑workflow для автоматической публикации документации (о ней расскажу ниже). Для неё тоже использовался GitHub Actions, который публикует страницу с документацией при пуше в репозиторий.
Также для удобного погружения в работу инструмента я решил сформировать документацию.
Для этого я использовал крутой инструмент MkDocs, который позволяет достаточно просто собрать документацию из Markdown‑файлов, а после разместить их как страницы веб‑сайта.
Даже поиск из коробки есть
Для размещения документации я использовал GitHub Pages. Это функционал GitHub, который позволяет публиковать простые сайты, используя его же в качестве хостинга. Он отлично подошёл для документации.
Для того чтобы сформировать сайт с MkDocs, достаточно создать.md файлы с документацией.
Далее на основе документации настроить YAML‑файл, в котором помимо темы, шрифтов и так далее указать путь к.md‑файлам документации.
Для удобства, как сказано выше, создан CD‑workflow, который автоматически деплоит документацию на сайт при пуше в GitHub.
На этом всё! В статье я рассказал о своём ходе мыслей при формировании идеи гуарда, его разработке, а также настройке CI и мониторинга.
Делитесь в комментариях своими идеями по улучшению и доработке lite‑guardrails.
Ссылка на GitHub lite‑guardrails — https://github.com/maksimov‑m/lite‑guardrails
Подписывайтесь на мой Telegram‑канал , в котором я также рассказываю интересные вещи об IT и AI технологиях.