Если вы делаете 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.