Guardrails для LLM: фильтрация, PII и аудит

Задача страницы. Дать практический план, как защитить LLM‑сервисы: входная/выходная фильтрация, PII‑санитайзинг, защита от prompt‑injection, контроль инструментов (function calling), аудит и метрики. Подходит для чатов, RAG, агентных систем и API‑сервисов.

TL;DR

  • Стройте конвейер Pre → Policy → LLM → Post → Audit.
  • На входе: классификация запросов, маскирование PII, детектор jailbreak/injection.
  • Внутри: строгий JSON‑контракт для инструментов и allow‑list функций.
  • На выходе: PII‑редакция, фильтры категорий, лимиты длины/тайм‑ауты, стриминг с heartbeat.

Логируйте события, а не сырые данные: маскируйте PII, хэшируйте идентификаторы. Связанные разделы: /solutions/security/, /solutions/llm-inference/agents/, /solutions/llm-inference/streaming/, /solutions/llm-inference/observability/, /solutions/monitoring-logging/.

Угрозы и цели

Угрозы

  • Утечки и обработка персональных данных (PII).
  • Prompt‑injection/jailbreak: инструкции, заставляющие модель игнорировать политику.
  • Инструменты: опасные сайд‑эффекты (запись в БД, внешние API) без валидации.
  • Токсичный контент/несоблюдение правил продукта.
  • Отсутствие трейсинга и воспроизводимости инцидентов.

Цели

  • Минимизировать риск утечек и нарушений политик.
  • Сохранить UX (стриминг, низкая латентность) при разумной цене.
  • Обеспечить аудит, воспроизводимость и метрики качества/безопасности.

Архитектура Guardrails: Pre → Policy → LLM → Post → Audit

[Request]

└─ Pre-Filter → Policy/Prompt Hardening → LLM/Tools → Post-Filter/PII Redact → Audit/Logs

  • Pre‑Filter: классификация темы/рисков, PII‑маскирование, проверка длины, rate‑limit.
  • Policy/Prompt Hardening: системные правила, JSON‑режим, ограничения инструментов.
  • LLM/Tools: сервинг в выбранном стеке (vLLM/TGI/…); строгое function calling.
  • Post‑Filter: проверка ответа, редакция PII/категорий, обрезка, тайм‑ауты.
  • Audit: безопасные логи событий, метрики.

См. также: /solutions/llm-inference/, /solutions/llm-inference/multi-model/.

Входные фильтры: PII, категории, injection ### Классификация/правила

  • Лёгкий классификатор или эвристики → метки category = {general, code, finance, medical, unknown}, risk = {low, medium, high}.
  • Для high — ужесточайте лимиты: max_tokens, включайте строгие шаблоны, раздельные пулы (см. /solutions/llm-inference/multi-model/).

Детектор PII (маскирование до LLM)

  • E‑mail, телефоны (+7 / международный формат), паспорт/карта (проверка Luhn), адреса, ИНН и т. п.
  • Заменяйте на токены‑маркеры: <EMAIL_1>, <PHONE_1>, <CARD_1>; храните оригиналы отдельно (если нужно использовать инструмент).

Пример маскировки (упрощённо, Python):

import re
RE_EMAIL = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}")
RE_PHONE = re.compile(r"(+?d[d-s()]{7,}d)")
RE_CARD = re.compile(r"b(?:d[ -]*?){13,19}b")
def luhn_ok(s):
 digits = [int(c) for c in re.sub(r"D", "", s)]
 if len(digits) < 13: return False
 checksum = 0
 dbl = False
 for d in digits[::-1]:
 checksum += (d*2 - 9) if dbl and d > 4 else (d*2 if dbl else d)
 dbl = not dbl
 return checksum % 10 == 0
def sanitize(text):
 m = {}
 def repl_email(mo): 
 i = len(m.get('email', [])); m.setdefault('email', []).append(mo.group(0)); return f"<EMAIL_{i}>"
 def repl_phone(mo): 
 i = len(m.get('phone', [])); m.setdefault('phone', []).append(mo.group(0)); return f"<PHONE_{i}>"
 def repl_card(mo):
 raw = mo.group(0)
 if not luhn_ok(raw): return raw
 i = len(m.get('card', [])); m.setdefault('card', []).append(raw); return f"<CARD_{i}>"
 text = RE_EMAIL.sub(repl_email, text)
 text = RE_PHONE.sub(repl_phone, text)
 text = RE_CARD.sub(repl_card, text)
 return text, m # text без PII + маппинг для инструментов

Prompt‑injection/jailbreak

  • Ищите паттерны: «ignore previous», «disregard instructions», вставки системных ролей, попытки раскрытия ключей/политик, вложенный HTML/Markdown‑линки с «магическими» инструкциями.
  • Для подозрительных — отбраковка/уточнение или строгий шаблон ответа.

Жёсткий режим инструментов (function calling)

  • Allow‑list функций; JSON Schema для аргументов; валидация и тайм‑ауты на исполнение.
  • Сайд‑эффектные инструменты (запись/платёж) — только после явного подтверждения политики с контекстом.
  • В ответах модели запрещён произвольный текст: только JSON с tool_name и arguments. Подробно: /solutions/llm-inference/agents/.

Валидация аргументов (псевдокод):

def call_tool(name, args):
 spec = TOOLS_REGISTRY[name] # содержит JSON Schema
 validate(args, spec.schema) # выбросить ошибку при несоответствии
 return execute(name, args, timeout=8.0) # с таймаутом и retry/backoff

Пост‑фильтрация ответов

  • PII‑редакция: повторно маскируйте PII в тексте ответа, если передача наружу запрещена.

  • Категории: токсичность/ненависть/насилие — блок/перефраз.

  • Обрезка: принудительный max_tokens/max_chars, трим по фразе/предложению.

  • Политики домена: юридические, мед, фин — выводить безопасные шаблоны или краткие инструкции вместо конкретных рецептов.

  • Стриминг: при срабатывании пост‑фильтра отправляйте корректное завершение потока (сигнал finish_reason="policy"). См. /solutions/llm-inference/streaming/. Лимиты, тайм‑ауты, деградация

  • Лимиты длины: max context, max_tokens, лимит на вложения RAG.

  • Тайм‑ауты: prefill, межтокенный idle, общий.

  • Деградация: при перегрузе — снижение max_tokens, переключение в short‑pool, фоллбек‑модель. См. /solutions/llm-inference/multi-model/.

Логи и аудит: события вместо «сырья»

Принципы

  • Логируйте события и метаданные, а не полные тексты.
  • Маскируйте PII и секреты на входе; используйте хэш‑идентификаторы с солью.
  • Шифруйте хранение и ограничивайте доступ (RBAC). См. /solutions/security/.

Схема события (JSON)

{
 "ts": "2025-08-22T12:34:56Z",
 "tenant": "acme",
 "user_hash": "u_9f3c...",
 "model": "llama3-8b@int8",
 "route": "short_pool",
 "risk": "low",
 "input_meta": {"len": 512, "pii": ["EMAIL","PHONE"]},
 "policy": {"json_only": true, "max_tokens": 256},
 "tools": [{"name":"search","ok":true,"latency_ms":120}],
 "stream": {"ttft_ms":180, "tbt_ms":25, "ttlt_ms":1200},
 "output_meta": {"len": 420, "pii_redacted": true, "finish_reason": "stop"},
 "decision": {"blocked": false, "reason": null},
 "trace_id": "tr_abc123"
}

Метрики и дашборды

Минимум:

  • TTFT/TBT/TTLT и p50/p95/p99.
  • Blocked/flagged rate по категориям.
  • PII hit rate (доли типов), false‑pos/false‑neg семплы.
  • Ошибки инструментов и тайм‑ауты, средняя глубина agent‑цикла.
  • KV‑кэш и GPU‑метрики для связи с деградациями.

См. /solutions/llm-inference/observability/, /solutions/monitoring-logging/.

Тесты и red‑teaming

  • Набор негативов: jailbreak‑фразы, обфускации, Unicode‑трюки, длинные «ядовитые» промпты.
  • Мутабельность: автоматическая генерация вариантов (перестановки, синонимы, разные кодировки).
  • Nightly: регрессы по блок‑рейтам и качеству; отбор 1–5% трафика в оффлайн‑оценку.

Связанные разделы: /solutions/llm-training/eval/.

Интеграция со стримингом и API

  • SSE/WS: события policy_start, policy_violation, tool_start, tool_result, done.
  • Cancel должен немедленно завершать генерацию и освобождать ресурсы.
  • Явно маркируйте финал: finish_reason = "stop" | "length" | "policy" | "cancelled".

См. /solutions/llm-inference/streaming/.

Экономика: влияние guardrails на стоимость

Фильтры и сокращение контекста напрямую уменьшают L_in/L_out, тем самым:

T ≈ (L_in / P) + (L_out / (D / B)) + overhead

Cost_per_1M ≈ (GPU_hour_price × Num_GPU) / (TPS_decode × 3600 / 1e6)

Экономьте через: квантизацию (/solutions/llm-inference/quantization/), лимиты длины, short‑pool, prefix‑кэш (см. /solutions/llm-inference/vllm/), отказ/перефраз токсичного контента на ранней стадии.

Шаблон конвейера (FastAPI, упрощённо)

from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from typing import Dict
app = FastAPI()
def classify(text)->Dict: ...
def detect_injection(text)->bool: ...
def post_filter(text)->Dict: ... # {"ok":True, "text":..., "reason":...}
@app.post("/v1/chat/completions_stream")
async def serve(req: Request):
 body = await req.json()
 user_msg = body["messages"][-1]["content"]
 # PRE
 sanitized, mapping = sanitize(user_msg) # PII → <TOKENS>
 meta = classify(sanitized)
 if detect_injection(sanitized):
 return {"blocked": True, "reason": "injection"}
 # POLICY (лимиты/JSON-режим/инструменты)
 cfg = {"max_tokens": min(256, body.get("max_tokens", 256)), "json_only": False}
 # LLM STREAM (обёртка над vLLM/TGI/…)
 async def stream():
 yield "event: startndata: {}nn"
 async for delta in llm_stream(sanitized, cfg):
 # POST
 pf = post_filter(delta)
 if not pf["ok"]:
 yield 'data: {"finish_reason":"policy"}nn'
 break
 yield f'data: {{"choices":[{{"delta":{{"content":"{pf["text"]}"}}}}]}}nn'
 yield "event: donendata: [DONE]nn"
 return StreamingResponse(stream(), media_type="text/event-stream",
 headers={"Cache-Control":"no-cache","X-Accel-Buffering":"no"})

Как запускать это в cloudcompute.ru

Чек‑лист перед продом- Входные фильтры: PII‑маскирование, категория/риск, детектор injection.

  • Политики: системный промпт, JSON‑контракт инструментов, allow‑list.
  • Лимиты/тайм‑ауты: max context, max_tokens, prefill/idle/overall, cancel.
  • Пост‑фильтры: PII‑редакция, категории, обрезка, finish_reason.
  • Аудит: события, маски PII, шифрование, RBAC, дашборды.
  • Деградация и фоллбеки: short‑pool, меньшая модель, снижение лимитов.
  • Nightly‑тесты/ред‑тиминг и отслеживание регрессов.

Навигация по разделу «Инференс LLM»

/solutions/llm-inference//solutions/llm-inference/vllm//solutions/llm-inference/tgi//solutions/llm-inference/tensorrt-llm//solutions/llm-inference/sglang//solutions/llm-inference/llama-cpp//solutions/llm-inference/agents//solutions/llm-inference/streaming//solutions/llm-inference/observability//solutions/monitoring-logging//solutions/security//solutions/llm-inference/quantization/

Готовы запустить?

Запустить GPU-сервер