Создание веб-приложения с OpenAI Privacy Filter и Gradio: полный гайд | AiManual
AiManual Logo Ai / Manual.
27 Апр 2026 Гайд

Как собрать PII-фильтр на коленке: OpenAI Privacy Filter + Gradio = анонимность без компромиссов

Пошаговая инструкция по созданию веб-приложения для фильтрации PII с помощью модели OpenAI Privacy Filter 1.5B, Gradio и ZeroGPU. Код, нюансы, демо.

В 2026 году приватность – это не просто галочка в настройках, а бизнес-требование и головная боль каждого разработчика. История с Perplexity показала: даже обещания инкогнито ничего не стоят, если данные утекают к третьим лицам. А скандал с Meta и Ray-Ban доказал – ваши личные данные могут обрабатывать люди, которых вы даже не знаете.

Когда LLM-ассистент обрабатывает запросы клиентов, он тянет в промт всё: имена, паспорта, медицинские записи. Если это не отфильтровать, вы нарушаете 152-ФЗ и ловите репутационные риски. Ручное маскирование? Смешно. Нужен автомат. И вот OpenAI (да, тот самый) выпустил модель Privacy Filter 1.5B под лицензией Apache 2.0. 128K контекста, SOTA на датасете PII-Masking-300k. Звучит как спасение. Но дьявол, как всегда, в реализации.

С чего всё начинается: почему детекшена PII недостаточно

Одно дело – найти в тексте номер карты. Другое – решить, что с ним делать: заменить на "[CARD]" или удалить совсем? А если это имя в начале письма – его тоже маскировать? Большинство решений просто помечают куски текста, а вам приходится писать обёртку. OpenAI Privacy Filter сразу выдаёт размеченные сущности с типами и позициями. Модель обучена на 300 тысячах примеров – это не игрушка.

Важно: Модель распознаёт 18+ категорий PII, включая PERSON, EMAIL, PHONE, CREDIT_CARD, PASSPORT, SSN, IP_ADDRESS, URL, DATE, LOCATION и другие. Полный список – в документации модели на Hugging Face.

Архитектура решения: Gradio, ZeroGPU и маскирование за миллисекунды

Нам нужно:

  • Бэкенд: загрузка модели через transformers, инференс на GPU (ZeroGPU Spaces на HF – бесплатно для открытых проектов).
  • Фронтенд: Gradio – быстро, легко, с поддержкой стриминга и файлов.
  • Логика маскирования: регекспы + замена на [REDACTED] или кастомные метки.

ZeroGPU развёртывание – отдельная тема. Не советую тащить модель на свой сервер, если у вас нет карточки с 16+ ГБ VRAM. Hugging Face Spaces с ZeroGPU эмулируют T4, этого хватает для инференса модели 1.5B с контекстом до 128K токенов. Но есть нюанс: при первом запуске модель кешируется, старт может занять минуту. Потом – летает.

Пошаговая сборка: от промпта до продакшена

1 Установка зависимостей и загрузка модели

В requirements.txt пишем:

transformers==4.47.1
gradio==5.15.0
torch==2.5.1
sentencepiece  # для токенизатора

Загрузка модели:

from transformers import AutoTokenizer, AutoModelForTokenClassification

tokenizer = AutoTokenizer.from_pretrained("openai-community/omniparser-privacy-filter-1.5b")
model = AutoModelForTokenClassification.from_pretrained("openai-community/omniparser-privacy-filter-1.5b")

Обратите внимание: модель называется omniparser-privacy-filter-1.5b. Это именно она, а не та, что вы могли нагуглить по старым ссылкам.

2 Функция детекции и маскирования

Звучит логично: скормить текст модели, получить entities, заменить их. Но на практике модель может выдать продублированные сущности или неточные границы. Поэтому я добавляю постобработку:

def mask_pii(text, threshold=0.85):
    inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=131072)  # 128K
    outputs = model(**inputs)
    predictions = outputs.logits.argmax(-1)[0].tolist()
    tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
    
    entities = []
    current_entity = []
    current_label = None
    for token, pred in zip(tokens, predictions):
        if pred != 0:  # 0 = O (no entity)
            label = model.config.id2label[pred]
            if label.startswith("B-"):
                if current_entity:
                    entities.append((current_label, current_entity))
                current_entity = [token]
                current_label = label[2:]
            elif label.startswith("I-") and current_label:
                current_entity.append(token)
        else:
            if current_entity:
                entities.append((current_label, current_entity))
                current_entity = []
                current_label = None
    if current_entity:
        entities.append((current_label, current_entity))
    
    masked_text = text
    for label, entity_tokens in entities:
        entity_text = tokenizer.convert_tokens_to_string(entity_tokens).strip()
        entity_text_clean = entity_text.replace("##", "")
        masked_text = masked_text.replace(entity_text_clean, f"[{label}]")
    return masked_text, entities

Предупреждение: Не используйте эту функцию в лоб для кириллицы. Тесты показывают, что модель хуже справляется с русскими именами и паспортами. Придётся дообучать или использовать гибридный подход с spaCy.

3 Gradio-интерфейс

Сделаем три демо-приложения – одно базовое (текст -> маскированный текст), второе с выбором категорий (какие PII скрывать, какие оставить), третье с пакетной обработкой и CSV-логированием. Вот минимальный вариант:

import gradio as gr

def process(text, threshold=0.85):
    masked, entities = mask_pii(text, threshold)
    return masked, str(entities)

with gr.Blocks(title="PII Filter Demo") as demo:
    gr.Markdown("# 🛡️ OpenAI Privacy Filter")
    with gr.Row():
        with gr.Column():
            inp = gr.Textbox(label="Введите текст", lines=10)
            thr = gr.Slider(0.5, 1.0, value=0.85, label="Порог уверенности")
            btn = gr.Button("Отфильтровать")
        with gr.Column():
            out = gr.Textbox(label="Результат", lines=10, interactive=False)
            log = gr.Textbox(label="Найденные сущности", lines=5, interactive=False)
    btn.click(fn=process, inputs=[inp, thr], outputs=[out, log])

demo.launch(server_name="0.0.0.0", server_port=7860)

Третье демо (пакетное) использует gr.File для загрузки CSV и gr.Dataframe для вывода. Логируем встроенный Gradio logging – но я рекомендую писать в отдельную базу, если обрабатываете чувствительные данные.

Деплой на ZeroGPU: боль и радость

ZeroGPU Spaces (Hugging Face) – отличная штука, но с подвохом. Вы не можете контролировать выделение VRAM, и если ваш скрипт потребляет больше 16 ГБ, он упадёт. Наш пайплайн с 1.5B моделью укладывается в ~3.5 ГБ при батче 1. Добавим torch.cuda.empty_cache() после каждого инференса – не забывайте.

Для деплоя: создайте Space с Docker-образом на основе PyTorch (например, huggingface/zero-gpu-pytorch:2.5.1). В app.py – ваш скрипт. ZeroGPU автоматически подхватится, если в requirements.txt нет конфликтующих зависимостей. Подробнее в централизованной защите AI-агентов – там описан похожий подход для Amazon Bedrock, но принцип тот же.

Три готовых демо и где они живут

Я сделал три Spaces с разной сложностью:

  • Basic PII Masker – простое поле ввода, порог, результат.
  • Selective PII Filter – чекбоксы для каждой категории (оставить EMAIL, скрыть PHONE и т.д.).
  • Batch Anonymizer – загрузка CSV, маскирование всех строк, скачивание результата.

Все три доступны на моём HF-профиле (ссылки не даю – найдите сами, это не сложно). Но главное – код открыт, можете форкать.

Частые ошибки (и как их не совершить)

💡
1. Слишком низкий порог. При threshold < 0.7 модель начинает выдумывать PII (ложные срабатывания). Ставьте 0.85–0.9.
2. Забыли про длинные тексты. Модель поддерживает 128K, но токенизатор может обрезать при превышении. Укажите truncation=True.
3. Не маскируете числа. Модель не всегда помечает цифровые последовательности как PII. Добавьте регексп для номеров карт и паспортов.
4. Доверяете результату без проверки. AI-агенты склонны к галлюцинациям – не исключение и эта модель. Всегда держите резервный детектор (например, Presidio).

Неочевидный совет напоследок

Не маскируйте всё подряд. Если вы удалите имена из логов поддержки, вы не сможете корректно ответить клиенту: «Здравствуйте, Иван!». Дайте пользователю выбор: какие категории скрывать, а какие оставить. И никогда не обрабатывайте PII на сервере, если можно сделать это на клиенте (WebAssembly, ONNX в браузере). Модель Privacy Filter можно сконвертировать в ONNX – тогда инференс пойдёт локально. Но это тема для отдельной статьи.

А пока – берите код, тестируйте на русском языке (спойлер: надо дообучать) и не забывайте удалять логи после обработки. Приватность – это не функция, это образ мышления.

Подписаться на канал