- BrainTools - https://www.braintools.ru -

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

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

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

В прошлой статье [1] мы разбирали, как улучшали сам retrieval: чанкование, метаданные, гибридный поиск, реранкинг. Но после того как с поиском более-менее разобрались, встал другой вопрос — как вообще понять, хороший ли ответ получает пользователь?

Привет, меня зовут Дима, я делаю ИИ-функции в Gramax [2]. В этой статье расскажу, как мы выстроили методику оценки ответов RAG-поиска. Заодно поделюсь, какая модель прямо сейчас дает лучший результат на наших задачах.

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

Стартовая точка у нас была вполне обычная. Кое-что уже работало:

  • Собирать бенчмарк вопросов.

  • Хранить эталонные ответы.

  • Прогонять retrieval.

  • Смотреть, какие чанки попали в топ.

  • Считать метрики вроде recall и DCG.

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

Модель в любой момент может:

  • Не использовать важный фрагмент из контекста.

  • Упростить ответ так, что потеряется половина сути.

  • Добавить лишние факты от себя.

  • Ответить красиво, уверенно и неправильно.

  • Неожиданно уйти в другой язык.

  • Выдать экзотические символы там, где их никто не звал.

Retrieval хороший, а продуктовый результат — нет. И это значит, что нужно оценивать не только то, что нашли, но и то, что в итоге написали. Первая наивная идея: давайте сравнивать ответы с эталоном. Логика [3] простая: есть вопрос, есть эталонный ответ, есть то, что выдала модель. Берем и сравниваем.

Шаг 1. Подключили LLM-as-Judge

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

Работало заметно лучше любых текстовых сравнений — судья хотя бы не штрафовал за другие формулировки, если по смыслу ответ верный. Но довольно быстро выяснилось, что и у этого подхода есть свой затык.

Главная проблема одной общей оценки: одно число вроде 75 из 100 — удобно для таблицы, но для работы бесполезно. Непонятно, почему 75, а не 100.

  • Ответ неполный?

  • Он не опирается на найденные чанки?

  • Есть фактическая ошибка [4]?

  • Модель ответила не тем языком?

  • Она слишком много додумала от себя?

Одно число все смешивает в кашу. Хочешь улучшать систему — нужны отдельные оси.

Шаг 2. Разложили качество ответа на части

Одна общая оценка говорит, что ответ плохой, но не объясняет, где именно. Мы решили разбить качество на части и остановились на трех подметриках.

  1. Coverage. Насколько ответ покрывает эталон по смыслу. Здесь важно не совпадение по словам, а то, донесла ли модель ключевые факты, которые ожидались в ответе. Если в эталоне было несколько важных пунктов, а модель упомянула только часть из них, значит coverage будет проседать.

  2. Groundedness. Насколько ответ опирается на тот контекст, который был передан модели. Для RAG это особенно важно. Ответ может выглядеть убедительно, но на деле быть построен не по найденным чанкам, а скорее по «общим знаниям» модели. В таком случае система вроде бы использует базу знаний, но по факту начинает додумывать сама.

  3. Factuality. Насколько ответ корректен по фактам относительно эталона. Это отдельная метрика, потому что ответ может хорошо опираться на контекст, но оставаться неполным. Или наоборот — быть полным, но содержать одну-две фактические ошибки. Бывает и так, что ответ звучит уверенно и гладко, но по сути в нем есть неточности.

Вместо одного числа — понятное разложение: где именно просело — в полноте, в опоре на контекст или в фактах.

Шаг 3. Поняли, что судье тоже нельзя верить на слово

Дальше встала задача сделать так, чтобы judge-модель оценивала предсказуемо — а не как придется.

Во-первых, мы начали передавать судье реальные чанки.

Не только вопрос, эталон и ответ модели, но и реальный контекст, который шел в генерацию, — явным нумерованным списком чанков. Нужно было, чтобы groundedness считался не по общему ощущению, а по конкретным выданным фрагментам. Судья должен смотреть не на «правдоподобно», а на «откуда это взято».

Во-вторых, мы зафиксировали формат ответа судьи.

Чтобы не разбирать каждый ответ руками, перевели judge-модель в JSON-режим. На выходе получали структурированный результат примерно такого вида:

{
  "coverage": 75,
  "groundedness": 100,
  "factuality": 75,
  "primary_language": "Русский",
  "language_ok": true,
  "issues": [
    "Ответ неполный",
    "Пропущен важный факт"
  ]
}

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

Шаг 4. Добавили то, что judge сам не всегда ловит

Не все стоит отдавать судье. Некоторые вещи надежнее проверять в коде напрямую. У нас таких было две.

Язык ответа

Если у нас русскоязычный сценарий, а модель отвечает по-английски — это уже провал, неважно насколько ответ точный по смыслу. Для пользователя это просто неверный результат. Вынесли проверку языка в отдельное поле language_ok и стали учитывать в итоговом скоре.

Иероглифы и другие CJK-символы

В тестах у некоторых моделей в ответах появлялись иероглифы — CJK-символы там, где им совершенно нечего делать. Это артефакт вывода, который портит ответ даже при правильном содержании.

На судью в этом случае не рассчитывали — сделали отдельную проверку в коде. Есть символы — фиксируем.

Шаг 5. Собрали итоговый скоринг, который можно воспроизводить

К этому моменту у нас сложилась уже нормальная схема. Берем три основные метрики:

  • factuality

  • groundedness

  • coverage

Дальше считаем итоговый балл по весам:

  • factuality — 0.40

  • groundedness — 0.35

  • coverage — 0.25

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

  • Если ответ не на том языке — снижаем балл.

  • Если в ответе нашли CJK-символы — тоже снижаем.

А потом приводим результат к шкале: 0 / 25 / 50 / 75 / 100.

Шкала грубая, да. Но зато понятная и воспроизводимая. На практике оказалось, что делать вид, будто 78.43 чем-то принципиально точнее 75 не стоит.

Шаг 6. Поняли, что без отдельного валидатора все это нормально не сравнить

На 3–5 моделях еще как-то справляешься вручную. Когда конфигураций становится больше, все разваливается. Потому что «модель» — это целый набор условий:

  • Сама модель

  • Квантизация

  • Локальный или облачный запуск

  • Провайдер API

  • Разная задержка

  • Разные judge-модели

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

  • Выбрать набор вопросов

  • Выбрать конфигурацию chat-модели

  • Прогнать один и тот же бенчмарк

  • Сохранить вопрос, эталон, ответ, реальные чанки, подметрики judge и итоговый балл

  • Потом сравнить все это в таблицах

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

Что показали реальные прогоны

Когда все заработало, перешли к сравнению. Быстро стало понятно два момента. Первое: разница между моделями реальная и хорошо видна в метриках. Не на уровне ощущений. Второе: универсального победителя нет. На результат влияет не только сама модель, но и все вокруг нее:

  • Размер модели

  • Архитектура

  • Квантизация

  • Локальный или облачный запуск

  • Конкретный провайдер

  • judge-модель

  • Задержка и скорость ответа

Название вроде само по себе мало что говорит. Одна и та же модель в разных условиях дает разный результат.

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

Что в итоге выбрали для локального запуска

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

Большие модели использовали как ориентир — они показывали потолок качества на нашем бенчмарке. А дальше уже смотрели на то, что реально можно запустить на своем железе.

Сразу стало понятно, что Qwen3.5-9B FP16, которую мы держали локально, уже не тянет. Qwen3.5-27B FP8 выглядела заметно лучше — и у GPT-5.1, и у DeepSeek V3.2 это было согласованно. Стало ясно: если менять локальную модель, смотреть надо как минимум в сторону 27B.

Кстати, иероглифы в ответах — это была только 9B. В облаке такого не было. Мелочь, но в рабочем сценарии неприятная.

Параллельно прогнали еще несколько кандидатов: Gemma 4 31B, Gemma 4 26B A4B, Qwen3-32B. Ничего неожиданного — результаты либо слабее, либо без достаточного выигрыша, чтобы что-то менять. После облачного этапа круг сузился до тех, кого уже имело смысл разворачивать локально.

Финальная таблица по локальным кандидатам

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

Таблица получилась довольно наглядная.

  • Qwen3.5-9B — уже слабовата.

  • Qwen3.5-27B FP8 — по качеству хороша, но работает слишком медленно.

  • Gemma 4 31B — нормальный вариант, но без особых причин переходить именно на нее.

А вот Qwen3.5-35B-A3B-GPTQ-Int4 выбилась вперед: и качество лучше, и отвечает быстрее.

Итог

Локально оставляем Qwen3.5-35B-A3B-GPTQ-Int4.

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

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

Как будем с этим работать в будущем

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

Для этого мы подготовили сервис оценки, который позволяет:

  • Отслеживать результаты прогонов.

    Как мы перестали мерить качество ответов RAG-поиска «на глаз» и начали нормально сравнивать - 3
  • Сравнивать модели между собой и с прошлыми результатами.

    Как мы перестали мерить качество ответов RAG-поиска «на глаз» и начали нормально сравнивать - 4
  • Добавлять новые модели для тестирования.

    Как мы перестали мерить качество ответов RAG-поиска «на глаз» и начали нормально сравнивать - 5
  • Подробно разбирать ответы, найденный контекст и отдельные метрики по каждому вопросу.

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

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

Открыто, бесплатно, и с сообществом

Автор: dimakpa

Источник [8]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/28832

URLs in this post:

[1] В прошлой статье: https://habr.com/ru/companies/gram_ax/articles/994782/

[2] Gramax: https://gram.ax/ru?utm_source=rag-validator

[3] Логика: http://www.braintools.ru/article/7640

[4] ошибка: http://www.braintools.ru/article/4192

[5] GitHub: https://github.com/Gram-ax/gramax

[6] GitVerse: https://gitverse.ru/gramax/gramax

[7] https://t.me/gramax_chat: https://t.me/gramax_chat

[8] Источник: https://habr.com/ru/companies/gram_ax/articles/1020248/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1020248

www.BrainTools.ru

Rambler's Top100