Как мы перестали мерить качество ответов RAG-поиска «на глаз» и начали нормально сравнивать

Как мы перестали мерить качество ответов RAG-поиска «на глаз» и начали нормально сравнивать

Если вы делаете RAG-поиск по документации или базе знаний, рано или поздно сталкиваетесь с проблемой: хорошо найти — это ещё не значит хорошо ответить.

База знаний, RAG, найденные чанки, LLM формирует ответ. Но пользователь не видит чанки, DCG или Recall@10. Он видит только итоговый текст. А здесь и начинаются сложности. Даже если нужные фрагменты найдены, модель может их проигнорировать, ответить на другом языке, добавить «от себя» или выдать уверенный, но бессмысленный текст. И доказать, что после правок стало лучше, — отдельная задача.

Ранее мы улучшали retrieval: чанкование, метаданные, гибридный поиск, реранкинг. Но когда поиск стал стабильным, встал вопрос: как оценить качество самого ответа?

С чего всё началось

Изначально у нас была базовая система:

  • Сбор бенчмарка вопросов.
  • Хранение эталонных ответов.
  • Прогон retrieval.
  • Анализ топ-чанков.
  • Подсчёт метрик вроде recall и DCG.

Этого хватало для оценки поиска. Но как только переходили к финальному ответу — всё переставало работать. Потому что «нужные чанки в топе» и «пользователь получил хороший ответ» — не одно и то же.

Проблемы генерации

Модель может:

  • Проигнорировать важный фрагмент из контекста.
  • Слишком упростить ответ, потеряв суть.
  • Добавить вымышленные факты.
  • Ответить уверенно и неправильно.
  • Переключиться на другой язык.
  • Выдать иероглифы или другие посторонние символы.

Retrieval может быть идеальным, а ответ — неприемлемым. Значит, нужно оценивать не только найденное, но и сгенерированное.

Шаг 1. LLM-as-Judge

Первая идея — сравнить ответ модели с эталоном. Но нас интересовало не буквальное совпадение, а смысл. Поэтому мы перешли к схеме LLM-as-Judge: на вход — вопрос, эталон, ответ модели; на выходе — оценка.

Это работало лучше, чем текстовые метрики: модель-судья не штрафовала за разные формулировки, если смысл был верным. Но у подхода была проблема: одна общая оценка (например, 75 из 100) не объясняла, почему ответ не идеален.

Шаг 2. Разбили качество на подметрики

Мы выделили три ключевых аспекта:

  • Coverage — насколько ответ охватывает эталон по смыслу. Если в эталоне было несколько ключевых пунктов, а модель упомянула только часть, coverage падает.
  • Groundedness — насколько ответ опирается на предоставленный контекст. Важно, чтобы модель не додумывала «по себе», а использовала найденные чанки.
  • Factuality — корректность фактов относительно эталона. Ответ может быть полным и обоснованным, но содержать ошибки.

Теперь вместо одного числа — понятная картина: где именно система проседает.

Шаг 3. Сделали судью предсказуемым

Чтобы judge-модель оценивала стабильно, мы внесли два изменения:

  • Передавали судье не только ответ, но и реальные чанки — в виде нумерованного списка. Это позволило точно проверять groundedness.
  • Перевели judge в JSON-режим. Теперь на выходе — структурированные данные: оценки по каждой метрике, комментарии. Это упростило автоматизацию и сравнение.

Шаг 4. Добавили проверки, которые судья может пропустить

Некоторые ошибки лучше ловить в коде:

  • Язык ответа. Если сценарий русскоязычный, а модель отвечает по-английски — это провал. Мы добавили флаг language_ok и включили его в итоговый скор.
  • CJK-символы. У некоторых моделей в ответах появлялись иероглифы. Это артефакт, портящий восприятие. Мы добавили отдельную проверку на наличие таких символов.

Шаг 5. Собрали итоговый скоринг

Мы объединили три метрики в один балл с весами:

  • Factuality — 0.40
  • Groundedness — 0.35
  • Coverage — 0.25

Фактическая корректность важнее всего, затем — опора на контекст, затем — полнота. Далее — штрафы:

  • Неверный язык — снижение балла.
  • Наличие CJK-символов — снижение балла.

Результат приводится к шкале: 0 / 25 / 50 / 75 / 100. Шкала грубая, но понятная и воспроизводимая. Точность вроде 78.43 не даёт реального преимущества.

Шаг 6. Создали валидатор для честного сравнения

С ростом числа конфигураций ручное сравнение стало невозможным. «Модель» — это не только архитектура, но и квантизация, провайдер, задержка, judge-модель и т.д.

Мы создали rag-validator — сервис, где можно:

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

Только с таким стендом сравнение стало объективным.

Результаты тестов

Мы увидели:

  • Разница между моделями реальна и хорошо отражается в метриках.
  • Универсального победителя нет. Результат зависит от всей цепочки: модель, квантизация, запуск, провайдер, judge.

Одна и та же модель в разных условиях ведёт себя по-разному.

Выбор для локального запуска

Облачные Qwen3.5 MoE лидируют, но нам нужна модель, которую можно запустить локально в пределах 40 ГБ VRAM без потерь в качестве.

Qwen3.5-9B FP16 оказалась слабой. Qwen3.5-27B FP8 — лучше, но медленная. Gemma 4 31B и 26B A4B — без существенного выигрыша.

Лидером стала Qwen3.5-35B-A3B-GPTQ-Int4: высокое качество и хорошая скорость.

Это лучший баланс среди всех локально разворачиваемых моделей.

Главный вывод

Выбирать модель по общей таблице — плохая идея. Важно, как она работает в вашем сценарии. Именно это и определило наш выбор.

Что дальше

Модели быстро обновляются. Мы будем регулярно тестировать их на своём бенчмарке.

Наш сервис оценки позволяет:

  • Отслеживать результаты.
  • Сравнивать модели и версии.
  • Добавлять новые кандидаты.
  • Детально анализировать ответы, контекст и метрики по каждому вопросу.

Методика будет развиваться: уточним метрики, добавим новые показатели и улучшим проверку, чтобы результаты точнее отражали качество LLM в задачах Gramax.

Читать оригинал