Перейти к основному содержимому

Инструкция по интеграции: PII Proxy Wrapper

Что это

PII Proxy — тонкая программная прослойка между вашим приложением и LLM-провайдером, которая автоматически обнаруживает и обезличивает персональные данные в запросах перед отправкой в нейросеть, а затем восстанавливает их в ответе.

Ваше приложение общается с прокси как с обычным OpenAI-совместимым API — формат запросов не меняется. Прокси берёт на себя всю логику PII-защиты.

Рисунок1

Проблема

При передаче пользовательских данных в публичные LLM вы теряете контроль над чувствительной информацией: данные уходят за периметр вашей инфраструктуры. PII Proxy решает эту проблему — модель видит только обезличенный текст. Персональные данные проходят через прослойку транзитно, но сама нейросеть их не получает.

Два сценария

Обезличивание и генерация — это два независимых шага, и каждый из них может выполняться как на стороне AIaaS, так и на вашей. Отсюда два рабочих сценария.

Сценарий 1. Обезличивание через AIaaS, модель — ваша

Прокси разворачивается на вашей стороне, а поиск и маскирование персональных данных выполняются вызовами к AIaaS: /ner, /mask и /unmask. Обезличенный текст вы отправляете в ту модель, которую выбрали сами — собственную, развёрнутую у вас, или любого внешнего провайдера.

В AIaaS при этом попадает только текст, передаваемый на обезличивание. Сам запрос к модели через наш контур не идёт.

Сценарий подходит, когда модель уже выбрана и менять её не планируется, а закрыть нужно именно риск утечки ПДн.

Сценарий 2. Обезличивание у вас, модель — в AIaaS

Вы разворачиваете у себя собственный NER-движок — например, ту же GLiNER — и логику маскирования и восстановления. В контур AIaaS приходит уже обезличенный запрос.

Персональные данные в этом случае не покидают ваш периметр вообще: ни в модель, ни в сервисы обезличивания. AIaaS видит только текст с плейсхолдерами.

Сценарий подходит организациям, которым нужен доступ к широкому каталогу моделей, но регламент запрещает передавать ПДн наружу даже на обработку.

Что выбрать

Сценарий 1Сценарий 2
Где обезличиваниеВ AIaaSУ вас
Где модельУ вас или у стороннего провайдераВ AIaaS
Что попадает в AIaaSТекст с ПДн — на обезличиваниеТолько обезличенный текст
Что нужно развернутьПрокси-обёрткуПрокси-обёртку и NER-движок
Правила детекцииОбщие для контураПолностью ваши
Доступ к каталогу моделей AIaaSНе используетсяИспользуется полностью

Есть и третий вариант, при котором ничего разворачивать не нужно: protected-маршруты — обезличивание и генерация выполняются в AIaaS, а от вас требуется только сменить значение поля model.

Как это работает

Ниже описан сценарий 1 — обезличивание через AIaaS с отправкой в вашу модель.

#ШагКудаЧто происходит
1NERPOST /v1/nerОбнаружение ПДн в тексте: ФИО, телефон, email, адрес, паспорт, даты
2MaskPOST /v1/maskЗамена найденных ПДн на плейсхолдеры вида ***N_LABEL***, получение mapping-словаря
3LLMВаш LLM-провайдерОбезличенный текст уходит в нейросеть. LLM не получает ПДн
4UnmaskPOST /v1/unmaskПлейсхолдеры в ответе LLM заменяются на исходные значения из mapping

Интеграция: полный код обёртки

import json
import requests


class PIIProxy:
"""
Прозрачная прокси-обёртка: ваш код → PII-очистка → LLM → PII-восстановление.
Интерфейс совместим с OpenAI /chat/completions.
"""

AINERGY_URL = "https://<адрес, выданный при подключении>"

def __init__(self, ainergy_token: str, llm_base_url: str, llm_api_key: str):
self.ainergy_headers = {
"Authorization": f"Bearer {ainergy_token}",
"X-AIN-USERNAME": "<ваш-username>",
"X-AIN-SOURCEID": "<ваш-source-id>",
"Content-Type": "application/json",
}
self.llm_base_url = llm_base_url.rstrip("/")
self.llm_headers = {
"Authorization": f"Bearer {llm_api_key}",
"Content-Type": "application/json",
}
# плейсхолдер → исходное значение, накапливается за сессию
self.mapping = {}

def chat_completion(self, openai_request: dict) -> dict:
user_text = self._user_text(openai_request["messages"])
if not user_text:
return self._call_llm(openai_request)

# 1. Найти персональные данные
ner = self._ainergy("/ner", {
"model": "itg.pii-detection.ner",
"text": user_text,
})
if not ner.get("entities"):
# ПДн не найдены — отправляем запрос как есть
return self._call_llm(openai_request)

# 2. Обезличить. entities передаётся из ответа /ner без изменений
mask = self._ainergy("/mask", {
"model": "itg.pii-detection.mask",
"text": user_text,
"entities": ner["entities"],
})
self.mapping.update(mask["mapping"])

# 3. В модель уходит только обезличенный текст
response = self._call_llm(
self._replace_user_text(openai_request, mask["text"])
)

# 4. Восстановить значения в ответе
content = response["choices"][0]["message"].get("content")
if content and self.mapping:
restored = self._ainergy("/unmask", {
"model": "itg.pii-detection.unmask",
"text": content,
"mapping": self.mapping,
})
response["choices"][0]["message"]["content"] = restored["text"]

return response

def _ainergy(self, path: str, payload: dict) -> dict:
resp = requests.post(f"{self.AINERGY_URL}/v1{path}",
headers=self.ainergy_headers,
json=payload, timeout=60)
resp.raise_for_status()
return resp.json()

def _call_llm(self, payload: dict) -> dict:
resp = requests.post(f"{self.llm_base_url}/chat/completions",
headers=self.llm_headers,
json=payload, timeout=120)
resp.raise_for_status()
return resp.json()

@staticmethod
def _user_text(messages: list):
"""Последнее текстовое сообщение пользователя."""
for message in reversed(messages):
if message.get("role") == "user" and isinstance(message.get("content"), str):
return message["content"]
return None

@staticmethod
def _replace_user_text(request: dict, text: str) -> dict:
"""Копия запроса, в которой текст пользователя заменён обезличенным."""
patched = json.loads(json.dumps(request))
for message in reversed(patched["messages"]):
if message.get("role") == "user" and isinstance(message.get("content"), str):
message["content"] = text
break
return patched

Обезличиваются только сообщения с ролью user. Системный промпт и история ассистента передаются без изменений. Словарь mapping накапливается за сессию, поэтому восстановление работает и тогда, когда модель упоминает данные из предыдущих сообщений.

Быстрый старт

proxy = PIIProxy(
ainergy_token="<ваш-AInergy-токен>",
llm_base_url="https://<адрес-вашего-LLM-провайдера>",
llm_api_key="<ваш-ключ-LLM>",
)

response = proxy.chat_completion({
"model": "openai/gpt-4.1",
"messages": [{
"role": "user",
"content": "Меня зовут Степан Сергеевич, email: stepa@example.com"
}]
})
print(response["choices"][0]["message"]["content"])

# Вывод: ответ LLM с восстановленными ФИО и email

Пример работы

Входные данные

Паспортная анкета из 17 полей: ФИО, дата рождения, паспорт, адрес, СНИЛС, ИНН, телефон, email, место работы, автомобиль и т.д.

Этап 1: NER — обнаружено 17 сущностей

PHONE_NUMBER +7 (900) 000-00-00 score=0.986

PHONE_NUMBER +7 (900) 111-11-11 score=0.993

EMAIL demo.person@example.com score=0.975

SURNAME Тестов score=0.459

CITY г. Демоград score=0.863

CITY г. Демоград score=0.936

CITY г. Демоград score=0.964

COUNTRY РФ score=0.973

STREET ул. Примерная / проспект Тестовый score=0.637 / 0.449

PASSPORT_NUMBER 000000 score=0.589

SNILS 000-000-000 00 score=0.487

JOB_TITLE специалист по тестовым данным score=0.896

JOB отдел проверки форматов score=0.636

DATE 01.01.2020 score=0.614

YEAR 2022 score=0.940

Этап 2: Mask — текст обезличен

До: ФИО: Иван Александрович Тестов, тел: +7 (900) 000-00-00, email: demo.person@example.com...

После: ФИО: ***NAME*** ***0_SURNAME***, тел: ***1_PHONE_NUMBER***, email: ***2_EMAIL***...

Этап 3: LLM — GPT-4.1 обрабатывает обезличенный текст

LLM видит: ФИО: ***0_SURNAME***... и не имеет доступа к реальным данным.

Этап 4: Unmask — ПДн восстановлены

Ответ LLM после восстановления:

ФИО: Иван Александрович Тестов

Дата рождения: 14.03.1991

Паспорт: серия 0000 номер 000000

Телефон: +7 (900) 000-00-00

E-mail: demo.person@example.com

...

Метрики цикла

ПараметрЗначение
Объём текста818 символов, 17 полей ПДн
Токенов на NER (raw)1 180
Найдено сущностей17
Mapping (плейсхолдеров)14
Токенов LLM (prompt+completion)733
End-to-end latency~5 секунд

Особенности эксплуатации

Типы обнаруживаемых сущностей

Модель надёжно определяет следующие категории ПДн:

  • EMAIL — адреса электронной почты (точность 97–99%)
  • PHONE_NUMBER — телефонные номера в любом формате (точность 97–99%)
  • CITY — названия городов (точность 86–96%)
  • COUNTRY — названия стран (точность 97%)
  • DATE — даты (точность ~60%)
  • YEAR — года (точность 94%)
  • JOB_TITLE — должности (точность 89%)
  • JOB — места работы (точность ~64%)
  • PERSON_NAME — имена и фамилии (точность 46–89%)

Дополнительная валидация

Для гарантированного покрытия полей со строгим форматом (ИНН, СНИЛС, паспортные серии) рекомендуется добавить правила в конфигурацию сервиса — файл configs/rules.yml. Это позволяет комбинировать AI-детекцию с детерминированными regex-паттернами без изменения клиентского кода.

Формат восстановленных данных

/unmask возвращает исходные значения как есть, без дополнительной разметки: ***0_SURNAME***Тестов. Обрамление восстановленных значений настраивается в конфигурации сервиса — если оно включено, значение вернётся в квадратных скобках. Проверить фактическое поведение можно одним вызовом /unmask с известным mapping.

Тарификация

Услуга PII-очистки тарифицируется в А-токенах по формуле:

А-токены = input_tokens × coefficient

Объём текстаRaw-токены~А-токенов (coeff=0.001)
1 предложение (ФИО + email + телефон)1300.13
Паспортная анкета (17 полей, 818 символов)1 1801.18
Развёрнутый текст (~5 000 символов)~5 000~5

Рекомендации по внедрению

Streaming (SSE)

При использовании потокового режима (stream=true) накапливайте чанки LLM-ответа, затем выполните один вызов unmask на полном тексте. Либо — unmask каждого чанка отдельно (работает, если mapping уже полный).

Tool calling / Function calling

Аргументы, которые LLM передаёт в вызовы функций (tool_calls), могут содержать ПДн. Прокси поддерживает их маскирование и восстановление.

RAG / Embeddings

Если вы векторизуете пользовательские запросы для RAG — обезличивайте их перед эмбеддингом и сохраняйте mapping для обратной замены в извлечённых чанках.

Конфиденциальность

Все вызовы PII API (NER, Mask, Unmask) выполняются внутри контура ITGLOBAL.COM. ПДн не покидают инфраструктуру облачного провайдера.