Анонимизация PII до отправки в LLM: уроки из казахстанского финансового холдинга
Обратимый прокси, который анонимизирует входящие сообщения, дёргает upstream-модель и восстанавливает значения для пользователя. Что взлетело, что мы недооценили и когда такой проект — это инфраструктура, а не AI-практика.
Когда в холдинге появилась задача дать аналитикам и саппорту удобный интерфейс к LLM, мы быстро уперлись в одну стену: нельзя отправлять в чужой API строки, в которых есть реальные ИИН, номера карт и счета клиентов. Запрет был не «осторожный», а абсолютный — InfoSec и комплаенс просили показать, как именно мы это гарантируем.
Решение получилось скучным и потому работающим: обратимый прокси, который анонимизирует входящие сообщения, дёргает upstream-модель, потом восстанавливает исходные значения для пользователя. Стек — Python 3.10, FastAPI, Presidio analyzer/anonymizer, отдельный контейнер рядом с приложением. Никакой магии — ничего, что не делал бы пятилетней давности банковский шлюз. Просто на новом канале.
Дальше — четыре момента, которые в публичных туториалах обычно опускают.
Готовые библиотеки не знают ИИН
Presidio из коробки находит email и кредитки. Всё. Ни ИИН, ни БИН, ни локального формата телефона — нет. Пришлось написать собственные распознаватели: kz_iin, kz_bin, kz_phone, kz_id_card, ru_inn, ru_snils, ru_passport, bank_card, iban. Каждый — паттерн плюс контекст («иин», «жеке сәйкестендіру нөмірі» и т. д.).
И сразу первая ошибка: 12-значный паттерн ИИН ловит подряд номера заказов, складские артикулы, платёжные референсы — всё, в чём 12 цифр. Решение — проверка контрольной цифры по официальному алгоритму (семь разрядов даты, разряд века/пола, веса 1–11, при коллизии — второй проход с другими весами). Без этой проверки система превращалась в гиперактивного анонимизатора, который убивал бизнес-смысл сообщений.
Vault на запрос, а не глобальный
Любая анонимизация бесполезна, если её нельзя обратить — ответ модели должен вернуться пользователю с настоящими ИИН и фамилиями, иначе вся затея превращается в офлайн-генератор бессмыслицы. Маппинги «оригинал ↔ плейсхолдер» хранятся в потокобезопасном in-memory словаре с TTL 3600 секунд. Сессия привязана к request-id, ничего долгоживущего не остаётся.
Это важная деталь: первая версия использовала глобальный счётчик плейсхолдеров, и при параллельной нагрузке пользователь A иногда получал ответ с ИИН пользователя B. Урок очевидный, но мы наступили на него — просто потому, что синхронные тесты не вылавливали гонку.
OpenAI-совместимость — главное архитектурное решение
Мы выставили /v1/chat/completions с тем же контрактом, что у upstream-провайдера. Внутренние команды, у которых уже был код на openai-sdk, поменяли base_url — и всё. Ни новых клиентских библиотек, ни тренингов, ни «давайте обсудим архитектуру интеграции».
Звучит как мелочь, сэкономило недели. Если бы мы придумали свой REST-контракт, у нас лежал бы аккуратный, никем не используемый сервис.
Чего мы недооценили
Анонимизация теряет контекст. «Иван Петров просрочил платёж» и «[PERSON_1] просрочил платёж» — для LLM это разные тексты по информативности. Качество ответа на ряде задач просело на 5–10%. Для саппорта терпимо; для аналитики, где модель опирается на названия собственных брендов и продуктов, потребовался whitelist — список того, что не надо анонимизировать (свои компании, продукты, города), иначе ассистент перестаёт понимать, в какой компании он вообще работает.
Performance. Presidio добавляет 50–200 мс на запрос. Для чата незаметно, для bulk-обработки логов — уже больно. Пришлось делать отдельный путь со своим набором ограничений.
И главное — прокси не отменяет классификацию данных у источника. Если в CRM хранятся сканы паспортов в полях «комментарий», никакой Presidio это не вылечит. Он закрывает один канал утечки, не больше.
Когда это реально нужно
Стоит делать, если LLM получает реальные клиентские данные, регуляторика требует доказуемого отсутствия PII в исходящем трафике, или есть несколько внутренних команд и проще выставить общий шлюз, чем согласовывать политику с каждой отдельно. Не стоит переоценивать, если команда работает на синтетических данных, весь трафик идёт на on-prem-модель в том же VPC, или объём — десяток запросов в день и проще научить пользователей «не вставлять ИИН руками».
Сам по себе LLM-прокси — это не «AI-практика», а инфраструктурный кусок, который позволяет AI-практике существовать без боли с InfoSec. И это, пожалуй, самый честный фрейм для подобных проектов: вы покупаете не качество ответов, а право вообще их получать.