Logo
Overview

Оценка качества ответов LLM: галлюцинации, метрики и guardrails

September 4, 2026
12 min read

Вы внедрили LLM в продукт. Модель генерирует требования, пишет SQL, составляет отчёты. Коллеги довольны. А потом вы замечаете: в трёх отчётах подряд фигурирует несуществующая таблица user_orders_archive_v2. Модель её придумала. Выглядело правдоподобно, никто не усомнился. Это не баг, это галлюцинация. И пока вы не построили пайплайн проверки — вы не знаете, сколько ещё таких «фактов» просочилось в прод.

Проблема не в том, что LLM иногда ошибается. Проблема в том, что без системной оценки качества вы узнаёте об ошибках последними — от пользователей. В этой статье разберём три слоя защиты: guardrails (формат и безопасность), детекцию галлюцинаций (фактчекинг) и LLM-as-Judge (семантическая оценка качества). Не теория — пайплайн, который можно собрать за неделю.

Почему «проверить глазами» не работает

Разовый просмотр ответа модели — это иллюзия контроля. Пока у вас 10 запросов в день — можно читать каждый. Когда интеграция работает на сотнях или тысячах запросов, ручная проверка становится узким горлом.

Есть три причины, почему глазами — плохо:

  1. Масштаб. Даже в среднем проекте LLM генерирует сотни ответов в день. Прочитать каждый — нерабочая стратегия.
  2. Правдоподобность. Галлюцинация не выглядит как явная ошибка. Она выглядит как уверенный, хорошо структурированный ответ с несуществующими деталями. Человек пропускает такие ошибки, особенно если не является экспертом в узкой теме.
  3. Субъективность. Что для одного аналитика «хороший ответ», для другого — «поверхностный». Без формальных критериев оценка — лотерея.

Поэтому нужен автоматизированный пайплайн. Не чтобы заменить человека, а чтобы отсеять 80% проблем до того, как ответ попадёт к пользователю.

Три слоя защиты: архитектура пайплайна

Пайплайн проверки ответа LLM — это конвейер из трёх слоёв. Каждый слой отсекает свой класс проблем:

СлойЧто проверяетИнструментОшибка первого рода (false positive)
GuardrailsФормат ответа (JSON-схема), запрещённые паттерны, утечка PIIRegex, JSON Schema, PresidioРедко — правила детерминированы
Детектор галлюцинацийСоответствие фактов в ответе фактам в контекстеNER + верификация, SelfCheckGPTСредне — модель может сомневаться в корректном факте
LLM-as-JudgeСемантическое качество: полнота, релевантность, полезностьВторая LLM с критериями оценкиВысоко — LLM-as-Judge тоже может ошибаться

Порядок важен. Дешёвый regex на первом слое отсекает грубые ошибки, не тратя токены. Дорогой LLM-as-Judge — на последнем, когда ответ уже прошёл формальную и фактологическую проверку.

100%
flowchart TD
  A["Запрос аналитика"] --> B["LLM: Claude / GPT / Gemini"]
  B --> C["Черновой ответ LLM"]
  C --> D["Guardrail: проверка формата"]
  D --> E{"Формат корректен?"}
  E -->|"Нет"| F["Отклонён / ретрай"]
  E -->|"Да"| G["Проверка фактов"]
  G --> H{"Факты подтверждены?"}
  H -->|"Нет"| I["Галлюцинация: пометить и залогировать"]
  H -->|"Да"| J["Метрики качества: LLM-as-Judge"]
  I --> K["Отправить на доработку / переформулировку"]
  J --> L{"Score > порог?"}
  L -->|"Нет"| K
  L -->|"Да"| M["Принять ответ → в систему"]
  B --> N["Логирование: latency, токены, модель"]

  style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style B fill:#7b68ee,stroke:#5a4db2,color:#fff
  style C fill:#e0e0e0,stroke:#999,color:#333
  style D fill:#f0a500,stroke:#c88400,color:#fff
  style E fill:#f0a500,stroke:#c88400,color:#fff
  style F fill:#e74c3c,stroke:#c0392b,color:#fff
  style G fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style H fill:#f0a500,stroke:#c88400,color:#fff
  style I fill:#e74c3c,stroke:#c0392b,color:#fff
  style J fill:#7b68ee,stroke:#5a4db2,color:#fff
  style K fill:#f0a500,stroke:#c88400,color:#fff
  style L fill:#f0a500,stroke:#c88400,color:#fff
  style M fill:#50c878,stroke:#3a9a5c,color:#fff
  style N fill:#e0e0e0,stroke:#999,color:#333

На диаграмме — три развилки проверки. Первая (жёлтая) — guardrail: если формат нарушен, ответ уходит на ретрай без дальнейших затрат. Вторая (красная) — детектор галлюцинаций: если факты не подтверждены, ответ помечается и логируется. Третья (фиолетовая) — LLM-as-Judge: семантическая оценка, ниже порога — на доработку. Зелёный узел — ответ прошедший все проверки. Серый блок внизу — логирование метрик, без которого невозможно понять, где именно пайплайн даёт сбой.

Отмечу: ретрай на первом слое (формат) дёшев. Ретрай на третьем (LLM-as-Judge) — дорог, потому что за ним стоит новый запрос к модели. Проектируйте пайплайн так, чтобы большинство проблем отсекалось на первых двух слоях.

Слой 1: Guardrails — формат, паттерны, безопасность

Guardrails — это детерминированные проверки. Никакого AI, только код. Они не оценивают смысл — они проверяют форму. И это их главное преимущество: предсказуемость и нулевая стоимость.

Проверка схемы ответа

Если от LLM ожидается JSON — проверяйте его JSON Schema. Это первый и самый дешёвый барьер. Нет смысла гонять через LLM-as-Judge ответ, который даже синтаксически не валиден.

import json
from 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, три слоя проверки и только потом попадает к пользователю.

100%
graph TB
  USER["Аналитик ставит задачу<br/>в продакшен-системе"]
  ORCH["Оркестратор проверок<br/>качества ответа"]
  LLM["LLM API<br/>Claude / GPT / Gemini"]
  FORMAT["Guardrail:<br/>проверка схемы ответа"]
  REGEX["Guardrail:<br/>запрещённые паттерны<br/>и PII"]
  HALL["Детектор галлюцинаций:<br/>NER + верификация"]
  JUDGE["LLM-as-Judge:<br/>оценка по критериям"]
  LOG["Мониторинг и логи:<br/>latency, score, retries"]
  CACHE["Кэш семантически<br/>похожих запросов"]
  RESULT["Валидный ответ<br/>→ в продукт"]

  USER -->|"промпт + контекст"| CACHE
  CACHE -->|"промах кэша"| LLM
  CACHE -->|"попадание"| RESULT
  LLM -->|"сырой ответ"| FORMAT
  FORMAT -->|"валидный JSON/текст"| REGEX
  FORMAT -->|"нарушена схема"| ORCH
  REGEX -->|"чисто"| HALL
  REGEX -->|"найден запрет"| ORCH
  HALL -->|"факты проверены"| JUDGE
  HALL -->|"галлюцинация"| ORCH
  JUDGE -->|"score >= порог"| RESULT
  JUDGE -->|"score < порог"| ORCH
  ORCH -->|"ретрей: новый запрос"| LLM
  LLM -.-> LOG
  ORCH -.-> LOG
  JUDGE -.-> LOG

  style USER fill:#e0e0e0,stroke:#999,color:#333
  style ORCH fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style LLM fill:#7b68ee,stroke:#5a4db2,color:#fff
  style FORMAT fill:#f0a500,stroke:#c88400,color:#fff
  style REGEX fill:#f0a500,stroke:#c88400,color:#fff
  style HALL fill:#e74c3c,stroke:#c0392b,color:#fff
  style JUDGE fill:#7b68ee,stroke:#5a4db2,color:#fff
  style LOG fill:#e0e0e0,stroke:#999,color:#333
  style CACHE fill:#50c878,stroke:#3a9a5c,color:#fff
  style RESULT fill:#50c878,stroke:#3a9a5c,color:#fff

На схеме — сквозной путь ответа от запроса аналитика до валидного результата. Зелёные узлы — оптимистичный путь (кэш-попадание, все проверки пройдены). Жёлтые — 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 uncertaintyStructured 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 — это дополнительный запрос. При росте нагрузки затраты растут нелинейно.

Стратегии контроля затрат:

  1. Семантический кэш — самый эффективный способ. Embedding запроса → поиск похожих в кэше → если cosine similarity > 0.95 — отдаём закэшированный ответ, минуя LLM и все проверки. Для типовых запросов аналитики («собери задачи спринта», «напиши ADR по шаблону») hit rate кэша может достигать 30–40%.

  2. Ранний возврат — если guardrail схемы отклонил ответ, не гоняйте его через детектор галлюцинаций и LLM-as-Judge. Ретрай сразу — это дешевле, чем полный цикл проверки заведомо плохого ответа.

  3. Легковесный Judge — для рутинных ответов используйте GPT-4o-mini вместо GPT-4o для LLM-as-Judge. Оценка будет чуть менее точной, но для отсева явно плохих ответов хватит.

  4. Батчинг — если позволяет latency, собирайте запросы в батчи и оценивайте через Judge одним вызовом (OpenAI Batch API, Anthropic Message Batches). Скидка 50% к стоимости.

  5. Пороги деградации — при пиковой нагрузке временно отключайте 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 длинный контекст. Там про выбор архитектурного подхода под задачу и бюджет.