Вы внедрили LLM в продукт. Модель генерирует требования, пишет SQL, составляет отчёты. Коллеги довольны. А потом вы замечаете: в трёх отчётах подряд фигурирует несуществующая таблица user_orders_archive_v2. Модель её придумала. Выглядело правдоподобно, никто не усомнился. Это не баг, это галлюцинация. И пока вы не построили пайплайн проверки — вы не знаете, сколько ещё таких «фактов» просочилось в прод.
Проблема не в том, что LLM иногда ошибается. Проблема в том, что без системной оценки качества вы узнаёте об ошибках последними — от пользователей. В этой статье разберём три слоя защиты: guardrails (формат и безопасность), детекцию галлюцинаций (фактчекинг) и LLM-as-Judge (семантическая оценка качества). Не теория — пайплайн, который можно собрать за неделю.
Почему «проверить глазами» не работает
Разовый просмотр ответа модели — это иллюзия контроля. Пока у вас 10 запросов в день — можно читать каждый. Когда интеграция работает на сотнях или тысячах запросов, ручная проверка становится узким горлом.
Есть три причины, почему глазами — плохо:
- Масштаб. Даже в среднем проекте LLM генерирует сотни ответов в день. Прочитать каждый — нерабочая стратегия.
- Правдоподобность. Галлюцинация не выглядит как явная ошибка. Она выглядит как уверенный, хорошо структурированный ответ с несуществующими деталями. Человек пропускает такие ошибки, особенно если не является экспертом в узкой теме.
- Субъективность. Что для одного аналитика «хороший ответ», для другого — «поверхностный». Без формальных критериев оценка — лотерея.
Поэтому нужен автоматизированный пайплайн. Не чтобы заменить человека, а чтобы отсеять 80% проблем до того, как ответ попадёт к пользователю.
Три слоя защиты: архитектура пайплайна
Пайплайн проверки ответа LLM — это конвейер из трёх слоёв. Каждый слой отсекает свой класс проблем:
| Слой | Что проверяет | Инструмент | Ошибка первого рода (false positive) |
|---|---|---|---|
| Guardrails | Формат ответа (JSON-схема), запрещённые паттерны, утечка PII | Regex, JSON Schema, Presidio | Редко — правила детерминированы |
| Детектор галлюцинаций | Соответствие фактов в ответе фактам в контексте | NER + верификация, SelfCheckGPT | Средне — модель может сомневаться в корректном факте |
| LLM-as-Judge | Семантическое качество: полнота, релевантность, полезность | Вторая LLM с критериями оценки | Высоко — LLM-as-Judge тоже может ошибаться |
Порядок важен. Дешёвый regex на первом слое отсекает грубые ошибки, не тратя токены. Дорогой LLM-as-Judge — на последнем, когда ответ уже прошёл формальную и фактологическую проверку.
На диаграмме — три развилки проверки. Первая (жёлтая) — guardrail: если формат нарушен, ответ уходит на ретрай без дальнейших затрат. Вторая (красная) — детектор галлюцинаций: если факты не подтверждены, ответ помечается и логируется. Третья (фиолетовая) — LLM-as-Judge: семантическая оценка, ниже порога — на доработку. Зелёный узел — ответ прошедший все проверки. Серый блок внизу — логирование метрик, без которого невозможно понять, где именно пайплайн даёт сбой.
Отмечу: ретрай на первом слое (формат) дёшев. Ретрай на третьем (LLM-as-Judge) — дорог, потому что за ним стоит новый запрос к модели. Проектируйте пайплайн так, чтобы большинство проблем отсекалось на первых двух слоях.
Слой 1: Guardrails — формат, паттерны, безопасность
Guardrails — это детерминированные проверки. Никакого AI, только код. Они не оценивают смысл — они проверяют форму. И это их главное преимущество: предсказуемость и нулевая стоимость.
Проверка схемы ответа
Если от LLM ожидается JSON — проверяйте его JSON Schema. Это первый и самый дешёвый барьер. Нет смысла гонять через LLM-as-Judge ответ, который даже синтаксически не валиден.
import jsonfrom jsonschema import validate, ValidationError
schema = { "type": "object", "properties": { "requirements": { "type": "array", "items": { "type": "object", "properties": { "id": {"type": "string", "pattern": "^FR-\\d{3}$"}, "actor": {"type": "string"}, "priority": {"enum": ["critical", "high", "medium", "low"]} }, "required": ["id", "actor", "priority"] } } }, "required": ["requirements"]}
def guardrail_schema(llm_response: str) -> dict | None: try: data = json.loads(llm_response) validate(instance=data, schema=schema) return data except (json.JSONDecodeError, ValidationError) as e: log_guardrail_failure("schema", str(e)) return NoneТри строчки — и вы отсекли ответы с кривой структурой. Для Structured Output-режима (описанного в отдельной статье) этот шаг почти избыточен — но только «почти». Даже Structured Output иногда ломается на длинных ответах с обрезанным контекстом. Guardrail схемы — ваша страховка.
Запрещённые паттерны и PII
Второй guardrail — regex-сканирование на запрещённые конструкции и персональные данные. Например, ответ LLM не должен содержать:
\{\{ ... \}\}— неразрешённые плейсхолдеры шаблонизатора- Фрагменты системного промпта («ты — полезный ассистент…»)
- PII: email, телефон, паспортные данные, номера карт
import re
FORBIDDEN_PATTERNS = [ (r'\{\{.*?\}\}', "неразрешённый плейсхолдер"), (r'ты[ —].*?ассистент', "утечка системного промпта"),]
PII_PATTERNS = [ (r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', "email"), (r'\+7\s?\(?\d{3}\)?\s?\d{3}[-\s]?\d{2}[-\s]?\d{2}', "телефон"),]
def guardrail_patterns(text: str) -> list[str]: violations = [] for pattern, label in FORBIDDEN_PATTERNS + PII_PATTERNS: if re.search(pattern, text): violations.append(f"Нарушение: {label}") return violationsЭтот guardrail должен срабатывать до того, как ответ попадёт в лог, не говоря уже о фронтенде. Утечка PII через LLM — не гипотетический риск, а реальный инцидент, который был у нескольких крупных компаний в 2025 году.
Слой 2: Детекция галлюцинаций — фактчекинг ответа
Guardrails проверили форму. Теперь проверяем содержание. Галлюцинация — это когда модель генерирует правдоподобный, но фактически неверный текст. Два основных подхода к детекции:
NER + верификация (для структурированных фактов)
Подход: извлекаем из ответа именованные сущности (даты, имена, названия таблиц, суммы), а затем сверяем их с источником — контекстом запроса.
def extract_claims(response: str) -> list[dict]: """ Извлекает утверждения из ответа LLM. Возвращает список \{"text": "...", "type": "entity/date/number"\}. """ claims = [] # Извлечение названий таблиц БД table_pattern = re.findall(r'`(\w+)`', response) for t in table_pattern: claims.append({"text": t, "type": "table_name"})
# Извлечение чисел с единицами измерения number_pattern = re.findall(r'\b(\d+(?:\.\d+)?)\s*(?:руб|сек|мс|ms|записей)', response) for n in number_pattern: claims.append({"text": n, "type": "metric"})
return claims
def verify_claims(claims: list[dict], source_context: str) -> dict: """ Проверяет, присутствует ли каждое утверждение в контексте-источнике. Возвращает \{"verified": [...], "hallucinated": [...]\}. """ result = {"verified": [], "hallucinated": []} for claim in claims: if claim["text"].lower() in source_context.lower(): result["verified"].append(claim) else: result["hallucinated"].append(claim) return resultПодход простой, но имеет ограничение: он ловит только те галлюцинации, которые можно сверить с явным текстом контекста. Если модель придумала правдоподобную дату, которой нет в контексте — найдём. Если модель перефразировала реальный факт до неузнаваемости — можем пропустить.
SelfCheckGPT и семантическая проверка
Более продвинутый подход: вместо сверки с контекстом — генерируем несколько вариантов ответа на один запрос и сравниваем их между собой. Если в трёх из трёх ответов фигурирует «таблица user_orders» — скорее всего, она реальна. Если в двух есть, а в третьем нет — подозрительно.
SelfCheckGPT (Manakul et al., 2023) делает ровно это: несколько сэмплов → сравнение противоречий → оценка консистентности. Минус: дорого. Каждый сэмпл — это отдельный запрос к LLM. Для продакшена используют гибрид: NER-проверку для быстрого отсева и SelfCheckGPT для сомнительных случаев.
Когда галлюцинация — не баг
Уточню: не каждое расхождение с контекстом — проблема. Модель может корректно обобщить три разрозненных факта в один вывод, и этот вывод не будет дословно присутствовать в контексте. Хороший детектор галлюцинаций различает:
- Экстраполяцию — логический вывод из фактов (допустимо).
- Галлюцинацию — выдуманный факт без опоры на контекст (недопустимо).
Практически это решается вторым проходом LLM c промптом: «Вот контекст, вот ответ. Найди утверждения в ответе, которые не подтверждаются контекстом. Не считай ошибкой обобщения и выводы. Верни список только тех утверждений, которым нет опоры в контексте.»
Слой 3: LLM-as-Judge — семантическая оценка качества
Формат проверен. Факты проверены. Осталось понять, насколько ответ полезен, полон и релевантен. Вот тут в дело вступает LLM-as-Judge.
Идея проста: одна LLM генерирует ответ, другая LLM его оценивает по заранее заданным критериям. Звучит как рекурсия, но на практике работает. Важно не использовать ту же модель для оценки — Claude оценивает ответы GPT объективнее, чем GPT оценивает сам себя (это подтверждено в нескольких исследованиях, включая MT-Bench от LMSYS).
Критерии оценки
LLM-as-Judge оценивает ответ по 3–5 критериям, каждый — по шкале от 1 до 5:
| Критерий | Что оценивает | Пример низкого score |
|---|---|---|
| Полнота | Все ли аспекты запроса покрыты | Ответил про API, но проигнорировал вопрос про безопасность |
| Релевантность | Ответ по делу или ушёл в сторону | Запрос про REST, ответ про GraphQL |
| Фактологическая точность | Нет ли противоречий внутри самого ответа | В начале: «всего 5 эндпоинтов», в конце: «шестой эндпоинт — …» |
| Полезность | Можно ли сразу использовать ответ | Ответ верный, но без примеров — придётся додумывать |
| Тон и стиль | Соответствует ли корпоративному tone of voice | Сленг в ответе для банковского отчёта |
Промпт для LLM-as-Judge выглядит так:
Ты — эксперт по оценке качества ответов AI-ассистента.Оцени ответ по шкале 1–5 по каждому из критериев ниже.Верни строгий JSON без комментариев.
Запрос пользователя: {user_query}Контекст (источник фактов): {context}Ответ модели: {model_response}
Критерии:- completeness (1–5): все ли аспекты запроса покрыты- relevance (1–5): ответ по делу, без отклонений от темы- internal_consistency (1–5): нет ли противоречий внутри ответа- actionability (1–5): можно ли использовать ответ без додумывания
Формат ответа:\{ "scores": \{ "completeness": <int>, "relevance": <int>, "internal_consistency": <int>, "actionability": <int> \}, "overall": <float>, "reasoning": "<краткое обоснование>"\}Важный нюанс: ответ LLM-as-Judge — тоже JSON, и его тоже нужно прогонять через guardrail схемы. Мета-проверка. Иначе вы рискуете, что оценщик сам сгенерирует невалидный ответ, и пайплайн упадёт уже на стадии оценки.
Пороги и эскалация
Для каждого критерия определяется минимально допустимый score. Например:
completeness < 3→ авторетрей (ответ неполный)relevance < 2→ отклонить, вернуть пользователю запрос на уточнениеinternal_consistency < 4→ эскалация человеку (противоречия — потенциально серьёзная проблема)
Суммарный порог для пропуска ответа — overall >= 3.5. Ниже — на ретрай, после трёх ретраев — эскалация человеку.
Полный пайплайн: как это выглядит в продакшене
Собираем три слоя вместе. Запрос проходит через кэш, LLM, три слоя проверки и только потом попадает к пользователю.
На схеме — сквозной путь ответа от запроса аналитика до валидного результата. Зелёные узлы — оптимистичный путь (кэш-попадание, все проверки пройдены). Жёлтые — guardrails, детерминированные проверки без AI. Красный — детектор галлюцинаций, потенциально блокирующий ответ. Фиолетовые — LLM (основная модель и LLM-as-Judge). Синий — оркестратор, который принимает решение о ретрае или эскалации. Серый — логирование всех событий.
Обратите внимание на зелёный блок «Кэш семантически похожих запросов» — это не просто premature optimization. Если один и тот же запрос приходит повторно (а в продуктах аналитики это случается часто: «собери задачи спринта» — типовой запрос), нет смысла гонять его через весь пайплайн. Кэш с семантическим поиском (embeddings + cosine similarity) экономит latency и деньги.
Что логировать и зачем
Без метрик пайплайн оценки качества — чёрный ящик. Вы не знаете, на каком слое теряется больше всего ответов, какая модель галлюцинирует чаще и при каких условиях. Минимальный набор метрик:
| Метрика | Что показывает | Пороговое значение |
|---|---|---|
guardrail_reject_rate | Доля ответов, отсечённых на первом слое | < 5% — нормально для Structured Output; > 15% — проблема с промптом |
hallucination_rate | Доля ответов с обнаруженными галлюцинациями | < 3% — хорошо; > 10% — модель не подходит для задачи |
judge_score_avg | Средний score LLM-as-Judge | > 3.8 — ответы полезны; < 3.0 — нужно менять промпт или модель |
retry_rate | Доля запросов, ушедших на ретрай | < 10% — нормально; > 25% — пайплайн дорожает экспоненциально |
p50_latency / p95_latency | Время от запроса до финального ответа | p95 < 5 сек — комфортно для пользователя |
cost_per_accepted_response | Средняя стоимость (в токенах), делённая на долю принятых ответов | Растёт — пайплайн стал дороже без прироста качества |
Все эти метрики должны быть на дашборде. Если hallucination_rate вырос с 2% до 7% после обновления версии модели — это сигнал к откату, и вы узнаете об этом через Grafana, а не от разгневанных пользователей.
Сравнение моделей: какая меньше галлюцинирует
Конкретные цифры — опора для выбора модели под задачу. Ниже данные из открытых бенчмарков (HaluEval, TruthfulQA, SimpleQA) и практики. Цифры актуальны на сентябрь 2026.
| Модель | Hallucination rate (HaluEval) | Self-reported uncertainty | Structured Output | Примечание |
|---|---|---|---|---|
| Claude 3.5 Sonnet / 4 | ~2.8% | Отказывается отвечать при неуверенности | Да | Лучший выбор для фактологически критичных задач |
| GPT-4o | ~3.5% | Уверенно генерирует даже при незнании | Да | Хорош для творческих задач, требует строгого guardrail |
| Gemini 2.5 Pro | ~4.2% | Склонен к избыточной детализации | Частично | Силён в аналитике, но чаще придумывает детали |
| GPT-4o-mini | ~6.1% | Низкая цена, но выше процент галлюцинаций | Да | Оправдан для черновиков, не для финальных ответов |
| Локальные (Llama 3.1 70B) | ~8–12% | Сильно зависит от квантования | Нет | Приватность ценой качества |
Важный вывод из таблицы: разница между лидерами (Claude и GPT-4o) по hallucination rate — меньше процента. Но для продукта с 10 000 запросов в день это ~50–100 галлюцинаций ежедневно. Поэтому даже топ-моделям нужен пайплайн проверки. Подробнее о выборе модели под конкретную задачу — в сравнении AI-моделей для аналитика.
Отдельная история — локальные модели. Ollama с Llama 3.1 70B даёт приватность, но hallucination rate в 8–12% означает, что каждый десятый ответ содержит выдуманный факт. Без пайплайна проверки использовать локальные модели в продакшене — русская рулетка.
Что делать, если пайплайн дорожает
Три слоя проверки увеличивают latency и стоимость. Каждый ретрай — это новый запрос к LLM. Каждый LLM-as-Judge — это дополнительный запрос. При росте нагрузки затраты растут нелинейно.
Стратегии контроля затрат:
-
Семантический кэш — самый эффективный способ. Embedding запроса → поиск похожих в кэше → если cosine similarity > 0.95 — отдаём закэшированный ответ, минуя LLM и все проверки. Для типовых запросов аналитики («собери задачи спринта», «напиши ADR по шаблону») hit rate кэша может достигать 30–40%.
-
Ранний возврат — если guardrail схемы отклонил ответ, не гоняйте его через детектор галлюцинаций и LLM-as-Judge. Ретрай сразу — это дешевле, чем полный цикл проверки заведомо плохого ответа.
-
Легковесный Judge — для рутинных ответов используйте GPT-4o-mini вместо GPT-4o для LLM-as-Judge. Оценка будет чуть менее точной, но для отсева явно плохих ответов хватит.
-
Батчинг — если позволяет latency, собирайте запросы в батчи и оценивайте через Judge одним вызовом (OpenAI Batch API, Anthropic Message Batches). Скидка 50% к стоимости.
-
Пороги деградации — при пиковой нагрузке временно отключайте LLM-as-Judge (самый дорогой слой) и полагайтесь на guardrails + детектор галлюцинаций. Качество просядет, но сервис останется жив.
Напомню: пайплайн оценки качества — это не «сделал и забыл». Это живая система, которая требует мониторинга и настройки порогов под конкретную модель и задачу.
Заключение
Внедрение LLM в продукт без пайплайна оценки качества — это как запуск самолёта без приборной панели. Вы летите, двигатели работают, пассажиры вроде довольны. Но вы не знаете, сколько из того, что говорит автопилот, — правда, а сколько — красивая выдумка.
Три слоя — guardrails, детектор галлюцинаций, LLM-as-Judge — дают вам приборную панель. Guardrails отсекают очевидный брак за нулевую стоимость. Детектор галлюцинаций ловит фактические ошибки. LLM-as-Judge оценивает смысл и полезность. Вместе они превращают LLM из «чёрного ящика, который иногда привирает» в предсказуемый компонент системы.
И да — когда в следующий раз модель скажет вам, что таблица user_orders_archive_v2 существует, вы узнаете об этом из Grafana, а не от пользователя. Это и есть зрелость.
Если вы проектируете AI-функцию и стоите перед выбором: дообучать модель (fine-tuning) или положиться на качественный RAG с проверками — загляните в сравнение RAG vs Fine-tuning vs длинный контекст. Там про выбор архитектурного подхода под задачу и бюджет.