1. Главная
  2. Блог
  3. LLM-судья на страже качества: как вторая модель ловит галлюцинации до того, как их увидит клиент

LLM-судья на страже качества: как вторая модель ловит галлюцинации до того, как их увидит клиент

LLM-судья на страже качества: как вторая модель ловит галлюцинации до того, как их увидит клиент

Под вечер пятницы менеджер оптового поставщика электрики отправляет клиенту ответ, который за пару секунд собрал наш ИИ-агент: «Кабель ВВГнг-LS 3х2,5, артикул такой-то, остаток на складе есть, цена по вчерашней спецификации». Клиент подтверждает крупный заказ. В понедельник выясняется, что артикула, который назвала модель, в номенклатуре нет вообще. Есть очень похожий — отличается перестановкой двух цифр. Модель уверенно достроила номер, менеджер не перепроверил, потому что доверял агенту.

Метрики в Grafana в тот момент горели ровно. Latency в норме, ошибок нет, retry-цепочка не сработала. Всё выглядело идеально — кроме содержимого ответа. Тогда до меня окончательно дошло: классические LLM-ops-метрики видят только техническую сторону. Сколько токенов, сколько миллисекунд, какой процент 5xx. Они не отвечают на главный вопрос, ради которого мы вообще занимаемся созданием ИИ-агентов: а сам-то ответ правильный?

Почему обычные проверки не спасают

К тому моменту у нас уже жило несколько слоёв защиты. JSON Schema валидировала структуру. Регулярка сверяла артикулы с справочником. Snapshot-тесты отлавливали регрессы в промптах на релизе. Всё это ловило синтаксис и знакомые паттерны, но не ловило смысл.

Та пятничная галлюцинация прошла все фильтры насквозь: номер артикула — валидное шестизначное число, схема принята, регулярка не заругалась, потому что в справочнике есть похожий, релизные тесты давно прошли. Проблема была в семантике: агент придумал несуществующий SKU, потому что RAG вернул похожий чанк из старого каталога, а модель уверенно достроила недостающие цифры. Классическая беда искусственного интеллекта — правдоподобие вместо правды.

Классические подходы к контролю качества здесь буксуют. Ручная выборка — менеджер смотрит какой-то небольшой процент ответов, находит редкие ошибки и быстро выгорает. Gold-набор эталонных пар «запрос — ответ» устаревает быстрее, чем мы успеваем его перегенерировать: каталог живой, цены двигаются, промпты подкручиваются. На потоке в тысячи запросов в день ни один из этих подходов не даёт ни покрытия, ни доверия.

Идея: второй LLM как контролёр

Решение выглядит почти неприлично простым: пусть один ИИ-агент отвечает клиенту, а второй LLM оценивает этот ответ по критериям. Схема называется LLM-as-judge и в исследовательских работах последних двух лет показывает приличную корреляцию с человеческой оценкой — хуже эксперта, но кратно быстрее и дешевле.

Первая моя реакция была: «Мы что, теперь платим за модель дважды?» На практике — нет. Судья работает на более лёгкой и дешёвой модели, оценивает ответ по компактному чек-листу, а не генерирует длинный текст. Один запрос-судейство обходится в разы дешевле основной генерации, и в общем счёте за месяц это заметная, но не катастрофическая добавка.

Как устроен пайплайн

Основной ИИ-агент отвечает клиенту через GigaChat Pro с RAG по каталогу, актуальным ценам и остаткам. Ответ уходит клиенту в стриминге — быстро, чтобы не терять UX. Одновременно тот же ответ вместе с исходным запросом и извлечёнными RAG-фрагментами отправляется на асинхронную очередь судьи.

Судья — отдельный микросервис на FastAPI с Celery-воркерами. У него есть свой ИИ-агент API, который основной сервис дёргает после ответа клиенту. Промпт судьи фиксированный, шаблонный, без пространства для творчества. На выходе — строгий JSON с восемью полями оценки. Структура примерно такая, и мы навязываем её через JSON Schema:

{
  "grounded": true,
  "grounded_confidence": 0.92,
  "invented_facts": [],
  "contradicts_context": false,
  "sku_valid": true,
  "sku_flags": [],
  "tone_ok": true,
  "verdict": "pass"
}

Критериев восемь, разбиты на три группы. Фактологические: подкреплён ли ответ RAG-контекстом, не выдумал ли агент цифры, артикулы или сроки, не противоречит ли документу. Доменные: валиден ли SKU по справочнику 1С, попадает ли цена в разумный диапазон, соответствует ли остаток последнему снапшоту склада. Формальные: тон, длина, отсутствие подписи «С уважением» посреди сообщения — модель иногда так делает, если промпт неаккуратно склеен.

Если verdict — pass, ответ живёт своей жизнью. Если warn — попадает в очередь на утренний обзор менеджеру продаж. Если fail — уходит триггер: агент регенерирует ответ с усиленным RAG (top-k повышается с 6 до 15, добавляется явное «отвечай только на основе прикреплённого контекста, если данных нет — так и скажи»), а клиенту в интерфейсе появляется вежливое «уточняю по складу, минуту». Если и второй прогон fail — эскалация на живого менеджера.

Как выбирал модель для судьи

Первая версия судьи была на том же GigaChat Pro, что и основной агент. Работало, но арифметика ломалась быстро: удвоение счёта за токены, задержка очереди в пиковые часы уползала за несколько минут. И — неприятный сюрприз — судья, будучи той же модели, часто пропускал ошибки той же природы, что и основной ИИ-агент. Искусственный интеллект одного и того же типа склонен к одним и тем же слепым пятнам: один галлюцинирует про артикул, другой уверенно подтверждает.

Я прогнал три российские модели на выборке размеченных вручную ответов: GigaChat-Lite, YandexGPT-Lite и T-Lite. Смотрел recall на классе fail — сколько плохих ответов судья реально поймал — и precision, то есть сколько нормальных ответов он ошибочно зафлажил.

Победил GigaChat-Lite. Recall у него оказался заметно выше, чем у остальных, при сравнимой точности. Ключевое отличие — при разных промпт-стилях у первичного агента и у судьи он давал минимум согласованных промахов. Разные модели «думают» немного по-разному, и это работало на нас, а не против.

Дополнительно вынес часть проверок из LLM в детерминированный код. Валидацию SKU через справочник 1С, проверку цены на разумный коридор, сверку остатков — всё это делает Python, а не модель. Судья фокусируется на том, что нельзя формализовать: соответствует ли ответ смыслу вопроса, не выдуманы ли характеристики, не противоречит ли документу. Правило простое: если что-то можно проверить кодом — проверяй кодом, LLM держи как последний рубеж.

Отдельно скажу про сценарий, когда судью хочется поднять как локальный ИИ-агент — на своём железе, без внешнего API. Мы это тоже пробовали для клиента с чувствительными данными под 152-ФЗ: облачные вызовы там были не вариантом. Взяли open-source модель поменьше, обернули в тот же контракт JSON Schema, получили заметно ниже качество на редких классах ошибок, но по хребтовым фактологическим проверкам работало нормально. Инженерный вывод: локальный судья реален, если готов вложиться в оценку и подбор промптов.

Ловушка: судья тоже галлюцинирует

Первый месяц в проде показал странность. Судья иногда флажил fail на идеальные ответы. Начал разбирать — три сценария.

Первый: судья не видел, что цитата из документа реально там есть, потому что RAG-контекст в его промпт я обрезал слишком коротко ради экономии токенов. Расширил окно — ложные срабатывания заметно просели.

Второй — судья слишком буквально понимал «grounded». Если агент правильно сказал «в наличии, отгрузка со склада на этой неделе», а в RAG было «остаток такой-то, отгрузка в течение трёх дней», судья считал переформулировку выдумкой. Пришлось в промпте явно разрешить синонимию и допустимые перефразировки — с примерами того, что можно, и того, что нельзя.

Третий сценарий редкий, но опасный. Судья иногда подделывал JSON-структуру: возвращал verdict: "pass" при перечисленных ошибках в invented_facts. Модель уверенно противоречит сама себе, но синтаксически ответ валидный. Мы добавили простую детерминированную проверку: если список ошибок непуст, вердикт принудительно переопределяется в fail. Модель может быть сколько угодно уверенной — правила выше её мнения.

Ещё один момент, про который стоит предупредить сразу. Прокси, через который у нас ходят запросы к внешним моделям, режет соединение по таймауту в 100 секунд. Для основного агента это редко становится проблемой, а для судьи в пик — становится, потому что очередь копится. Пришлось разносить воркеров и подкручивать backpressure, чтобы длинные джобы не забивали пул.

Что показал первый месяц

За календарный месяц через судью прошёл весь трафик агента. Ложных срабатываний оказалось немного, реальных пойманных галлюцинаций — заметно больше, чем нам приносила ручная выборка за то же время. Часть из них — из категории, которая привела бы к прямым финансовым последствиям: выдуманные артикулы, неверные цены, обещанные сроки поставки, которые склад не мог выполнить. Одна пойманная сделка такого рода перекрывает счёт за всю судейскую инфраструктуру за месяц с большим запасом.

Отдельно — сдвиг в восприятии внутри команды продаж. Раньше менеджеры выборочно проверяли ответы агента и уставали от постоянной подозрительности. Сейчас они видят зелёный значок «проверено» на большинстве ответов и красный на небольшой доле — и знают, куда сконцентрировать внимание. Доверие к ИИ-агенту выросло именно потому, что появилась внятная граница, где ему доверять нельзя.

Что важно понимать перед внедрением

LLM-судья не заменяет ни качество RAG, ни валидацию по справочникам, ни человеческий обзор. Он добавляет ещё один слой, который ловит то, что не ловят предыдущие. Судья на той же модели, что и основной агент, — почти бесполезен, надо разносить. Промпт судьи должен быть короче и жёстче промпта агента, иначе он сам начнёт творить.

И главное — судья должен быть асинхронным. Синхронный судья на критическом пути удваивает задержку ответа клиенту, а в стриминговых сценариях это убивает UX. Асинхронная очередь с регенерацией через пару секунд при fail — компромисс, который клиент не замечает.

Есть что обсудить: где у вас в системе сейчас проходит граница между «модель ответила быстро» и «модель ответила правильно»? И кто отвечает за вторую половину?

Похожие статьи

Хотите так же?

Обсудим, как это внедрить у вас

Опишите задачу — подберём ИИ-решение под ваш процесс и бюджет: что сделать агентом, какие нужны данные и интеграции, с чего начать пилот.