Решения

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: start\ndata: {}\n\n"
        async for delta in llm_stream(sanitized, cfg):
            # POST
            pf = post_filter(delta)
            if not pf["ok"]:
                yield 'data: {"finish_reason":"policy"}\n\n'
                break
            yield f'data: {{"choices":[{{"delta":{{"content":"{pf["text"]}"}}}}]}}\n\n'
        yield "event: done\ndata: [DONE]\n\n"

    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‑тесты/ред‑тиминг и отслеживание регрессов.