Если вы делаете RAG-поиск по документации или базе знаний, то рано или поздно упираетесь в проблему: хорошо найти — это еще не хорошо ответить.
База знаний, RAG, найденные чанки, LLM строит ответ. Но пользователь не знает ни про DCG, ни про Recall@10, ни про чанки вообще. Он видит только то, что написано в итоговом ответе. А проблемы начинаются именно здесь. Нашел нужные чанки — молодец. Но модель может их проигнорировать, ответить на другом языке, добавить что-то от себя или выдать уверенный текст с иероглифами посередине. И как потом доказать, что после правок стало лучше — тоже не очевидно.
В прошлой статье мы разбирали, как улучшали сам retrieval: чанкование, метаданные, гибридный поиск, реранкинг. Но после того как с поиском более-менее разобрались, встал другой вопрос — как вообще понять, хороший ли ответ получает пользователь?
Привет, меня зовут Дима, я делаю ИИ-функции в Gramax. В этой статье расскажу, как мы выстроили методику оценки ответов RAG-поиска. Заодно поделюсь, какая модель прямо сейчас дает лучший результат на наших задачах.
С чего все началось
Стартовая точка у нас была вполне обычная. Кое-что уже работало:
-
Собирать бенчмарк вопросов.
-
Хранить эталонные ответы.
-
Прогонять retrieval.
-
Смотреть, какие чанки попали в топ.
-
Считать метрики вроде recall и DCG.
Для оценки поиска этого хватало. Понимали, насколько система находит нужные фрагменты, как их ранжирует, не теряется ли полезное среди мусора. Но стоило перейти от поиска к финальному ответу — все сломалось. Потому что «нужные чанки попали в топ» и «пользователь получил нормальный ответ» — это совсем не одно и то же.
Модель в любой момент может:
-
Не использовать важный фрагмент из контекста.
-
Упростить ответ так, что потеряется половина сути.
-
Добавить лишние факты от себя.
-
Ответить красиво, уверенно и неправильно.
-
Неожиданно уйти в другой язык.
-
Выдать экзотические символы там, где их никто не звал.
Retrieval хороший, а продуктовый результат — нет. И это значит, что нужно оценивать не только то, что нашли, но и то, что в итоге написали. Первая наивная идея: давайте сравнивать ответы с эталоном. Логика простая: есть вопрос, есть эталонный ответ, есть то, что выдала модель. Берем и сравниваем.
Шаг 1. Подключили LLM-as-Judge
Нас интересовало не буквальное совпадение слов, а смысловое качество ответа. Поэтому быстро пришли к схеме LLM-as-Judge. Раз оценивать нужно по содержанию — пусть проверяет модель, которая умеет работать с текстом. Судье на вход: вопрос, эталон, ответ модели. На выходе — оценка.
Работало заметно лучше любых текстовых сравнений — судья хотя бы не штрафовал за другие формулировки, если по смыслу ответ верный. Но довольно быстро выяснилось, что и у этого подхода есть свой затык.
Главная проблема одной общей оценки: одно число вроде 75 из 100 — удобно для таблицы, но для работы бесполезно. Непонятно, почему 75, а не 100.
-
Ответ неполный?
-
Он не опирается на найденные чанки?
-
Есть фактическая ошибка?
-
Модель ответила не тем языком?
-
Она слишком много додумала от себя?
Одно число все смешивает в кашу. Хочешь улучшать систему — нужны отдельные оси.
Шаг 2. Разложили качество ответа на части
Одна общая оценка говорит, что ответ плохой, но не объясняет, где именно. Мы решили разбить качество на части и остановились на трех подметриках.
-
Coverage. Насколько ответ покрывает эталон по смыслу. Здесь важно не совпадение по словам, а то, донесла ли модель ключевые факты, которые ожидались в ответе. Если в эталоне было несколько важных пунктов, а модель упомянула только часть из них, значит coverage будет проседать.
-
Groundedness. Насколько ответ опирается на тот контекст, который был передан модели. Для RAG это особенно важно. Ответ может выглядеть убедительно, но на деле быть построен не по найденным чанкам, а скорее по «общим знаниям» модели. В таком случае система вроде бы использует базу знаний, но по факту начинает додумывать сама.
-
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-модель
-
Задержка и скорость ответа
Название вроде само по себе мало что говорит. Одна и та же модель в разных условиях дает разный результат.

Что в итоге выбрали для локального запуска
В общей таблице наверху, конечно, облачные 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. Ничего неожиданного — результаты либо слабее, либо без достаточного выигрыша, чтобы что-то менять. После облачного этапа круг сузился до тех, кого уже имело смысл разворачивать локально.
Финальная таблица по локальным кандидатам

Таблица получилась довольно наглядная.
-
Qwen3.5-9B — уже слабовата.
-
Qwen3.5-27B FP8 — по качеству хороша, но работает слишком медленно.
-
Gemma 4 31B — нормальный вариант, но без особых причин переходить именно на нее.
А вот Qwen3.5-35B-A3B-GPTQ-Int4 выбилась вперед: и качество лучше, и отвечает быстрее.
Итог
Локально оставляем Qwen3.5-35B-A3B-GPTQ-Int4.
На нашем бенчмарке она дала лучший баланс качества и скорости среди всего, что мы реально смогли развернуть и проверить локально.
Главный вывод, если совсем коротко: выбирать модель по общей таблице — плохая идея. Имеет смысл только то, как она ведет себя в твоем конкретном сценарии. У нас именно это и решило выбор.
Как будем с этим работать в будущем
Модели обновляются очень быстро, поэтому важно регулярно проверять их качество на наших задачах и понимать, какая модель лучше всего подходит для использования в Gramax.
Для этого мы подготовили сервис оценки, который позволяет:
-
Отслеживать результаты прогонов.

-
Сравнивать модели между собой и с прошлыми результатами.

-
Добавлять новые модели для тестирования.

-
Подробно разбирать ответы, найденный контекст и отдельные метрики по каждому вопросу.

В первой версии сервиса собраны метрики, которые сейчас наиболее важны для оценки качества поиска и ответов моделей. Дальше сервис будет развиваться: мы будем уточнять методику оценивания, добавлять новые показатели и корректировать логику проверки так, чтобы результаты максимально точно отражали качество работы LLM-моделей на задачах Gramax.
Открыто, бесплатно, и с сообществом
-
Смотрите наш сайт — https://gram.ax
-
Вступайте в комьюнити — https://t.me/gramax_chat
Автор: dimakpa


