Уверен, на Хабре найдётся немало статей, посвященных оценке качества RAG систем. Тема по-прежнему остаётся актуальной, потому что даже готовые библиотеки вроде RAGAS не очень-то работают из коробки, требуют навыков программирования и некоторой квалификации. При этом сам процесс оценки - повторение достаточно простых операций и мне всегда хотелось переложить его на AI-ассистента.
Повод попробовать появился неожиданно: свободное время и курс по Claude Code на Stepik. Для финала как раз нужен был проект, посвященный автоматизации реальной рутинной задачи, желательно без кода (курс про вайб-воркинг, для непрограммистов). Конечно, я сразу вспомнил про задачу оценки качества. Дальше - честная история со всеми проблемами. Забегая вперёд, скажу, что совсем без программирования не вышло. Но, может, это профессиональная деформация.
Шаг первый и первый фейл
Как начать? Я взял собственный готовый бенчмарк, таблицу с кейсами «вопрос; ответ; контекст» и отдал Клоду. Он быстро понял, что там русский язык UTF-8, но не определил разделитель полей, так как я использую точку с запятой в CSV, привычка. Дал подсказку. На что он сразу предупредил что будет гигантский расход токенов, а ещё ему нужен ключ, чтобы использовать SDK Anthropic. Ключа у меня нет, Клод предложил использовать эвристики на регулярных выражениях. Мы попробовали на одной метрике. Результат - 100% качество, что конечно жёсткая неправда. А ещё была путаница в чанках контекста, которые были склеены в один блок текста в бенчмарке.
Шаг второй. Внешняя модель
Регулярные выражения помогают только в одном случае, если в вопросе, контексте и ответе есть какой-то код, аббревиатура или шаблон, который легко извлечь. В других случаях нужен LLM-судья. И я решил его сделать на внешней модели. Выбрал gpt-4o-mini от прокси-провайдера, она часто используется в примерах для библиотеки RAGAS.
В RAGAS много метрик, не все из них полезны я планировал использовать только три. Они оценивают не качество ответа, а качество контекста, который система передаёт в модель.
Метрики
Точность контекста, Context Precision
оценивает, насколько высоко в выдаче RAG ранжированы чанки, полезные для правильного ответа. Чем выше полезный чанк, тем выше метрика.
Релевантность контекста, Context Relevance
оценивает, насколько высоко ранжированы чанки, релевантные вопросу пользователя. В отличие от Precision, не требует эталонного ответа.
Полнота контекста, Context Recall
оценивает, какая доля значимых фактов из эталонного ответа подтверждается найденным контекстом
Context Precision и Context Recall - метрики RAGAS. Context Relevance, если не ошибаюсь, нет. Она обычно получается выше чем Precision, нужно настраивать промпт, зато «не подсматривает в ответ».
В качестве первой пробной метрики решил взять Context Precision. Провёл пару экспериментов, Клод без проблем запускает скрипты из командной строки (пришлось сделать виртуальное окружение на Python). И главное - он не против внешних моделей для рутины, даже пишет, что это штатный режим, верим.
А вот проблема с парсингом контекста, если все чанки в одной ячейке таблицы, никуда не делась. Я попробовал вставлять в контекст разделители между чанками, оказалось не очень-то и надежно. Тогда появилась идея оставить в бенчмарке только вопрос и ответ, а контекст получать из RAG системы в процессе оценки каждого кейса. Это не только упростило подготовку бенчмарка, теперь я легко мог менять количество чанков в контексте.
Шаг третий. Сервис RAG и танцы вокруг MCP
План: сделать простенькую RAG систему с векторным поиском, добавить Model Context Protocol и далее использовать в субагентах. За пару часов я собрал её на FastAPI, LangChain и ChromaDB, загрузил туда тестовый контекст, всё проверил и решил добавить MCP. С библиотекой fastapi_mcp - пять минут работы.
Оказалось, не всё так просто. Клод упорно отказывался находить MCP сервер. Я прямо указывал на .mcp.json в проектной папке, на что он отвечал, что вероятно RAG система запущена после старта сессии, и что надо всё перезапустить. Ситуация повторялась снова и снова. Решил, что нужен какой-то тестер, нашёл MCP inspector (нужен Node.js).
Инспектор помог понять следующее - ни с каким видом транспорта (SSE, HTTP) моя система на запросы не отвечает, а вот если включить MCP Proxy, всё сразу начинает работать и в консоли появляется заветное 202 Accepted. Значит проблема в транспорте, который я использую. Ещё несколько экспериментов и решение было найдено, спасибо Google!
Решение
1. В блок импорта основного скрипта api.py добавить
from fastapi.middleware.cors import CORSMiddleware
2. Перед инструкциями app.include вставить блок кода
app.add_middleware( CORSMiddleware, allow_origins=["*"], # Or specify your Web UI's origin allow_credentials=True, allow_methods=["GET", "POST", "OPTIONS"], allow_headers=["*"], )
3. Использовать mcp.mount() вместо mount_sse() или mount_http(), хотя это устаревшая инструкция.
Полагаю, что есть ещё варианты, которые решают проблему. Код сервера со всем необходимым - в репозитории. А README к нему, как и к этому проекту, подготовил Клод. Вот уж точно полезный навык.
Шаг четвертый. «Шумные» субагенты
Сделал два субагента — один для Context Precision, он «видит» из бенчмарка вопрос, ответ и получает контекст из RAG системы, второй — для Context Relevance. Этот, в отличие от первого, не видит ответа и оценивает контекст только по вопросу.
Клод без каких либо проблем справился с этими агентами. Увы, они оказались слишком шумными, контекстное окно засорялось прямо на глазах, по 1-2k на один кейс бенчмарка (у меня 48 кейсов и два варианта вопросов — попроще и посложнее). Клод предложил для субагентов добавить инструкцию «Do not narrate your steps between tool calls», это не очень помогло в плане токенов.
Шаг пятый. Легкий навык и инструменты
Единственное полезное, что я взял из предыдущего шага - формат отчета. По каждому кейсу мне нужна была следующая информация:
{ "case_id": 0, "question": "question text", "answer": "answer text", "context": ["chunk_1", "chunk_2", "chunk_3", "chunk_4"], "labels": [1, 0, 1, 1], "explanation": ["explanation_1", "explanation_2", "explanation_3", "explanation_4"], "score": 0.875 }
Уточнив у Клода, в каком виде ему удобнее было бы с такой структурой работать, выяснил, что лучше всего подходит JSONL, текст, где каждая строка - отдельный JSON. На том и порешили.
Далее план был следующий — сделать инструмент с командной строкой (eval_context.py), который бы выполнял всю основную работу. Проходил последовательно по кейсам, запускал тесты и записывал результаты в отчёт. А навык всё запускал и управлял параметрами оценки.
Чтобы упростить код я взял свою готовую библиотеку CPCR_lib.py, это тоже обёртка для LLM-судьи, которая умеет выносить вердикт для оценок по всем трём метрикам.

Нужный класс импортируется в eval_context.py, так мы экономим токены на генерации промптов. Они не попадают в контекстное окно.
MCP в этой версии используется только как необязательная проверка доступности RAG системы. Оставил для будущих релизов. Все запросы — через REST API, что нормально для инструмента на Python. Если у кого-то получилось подключить FastAPI через MCP без дополнительных усилий, поделитесь, пожалуйста, рецептом в комментариях.
Объяснил Клоду изменения в архитектуре и попросил три вещи:
1. подготовить навык для работы с инструментом eval_context.py
2. сделать HTML вьювер для отчетов с учетом особенностей Recall (контекст склеен в один кусок,
а вместо чанков оцениваются факты)
3. подготовить дашборд для оценок по всем трём метрикам.
Навык
--- name: rag-evaluation description: Запускает инструменты оценки качества RAG-системы, когда пользователь просит оценить RAG, запустить метрику, или произносит слова: «оцени», «запусти оценку», «рассчитай метрику», «Context Precision», «Context Relevance», «Context Recall». --- # RAG Evaluation Skill Этот skill запускает оценку качества RAG-системы по выбранной метрике. ## Trigger Используй этот skill когда пользователь просит оценить RAG, запустить метрику, или произносит слова: «оцени», «запусти оценку», «рассчитай метрику», «Context Precision», «Context Relevance», «Context Recall». ## Процедура ### 1. Собери параметры Используй инструмент `AskUserQuestion` — задай все четыре вопроса одновременно с вариантами для клика: 1. **Метрика** — `precision` / `relevance` / `recall` 2. **Benchmark файл** — `benchmark_simple.csv` / `benchmark_hard.csv` 3. **max_k** — `3` / `5` / `7` 4. **Количество кейсов** — `5` / `10` / `все` ### 2. Запусти скрипт ```powershell venv\Scripts\python.exe eval_context.py --scenario <scenario> --benchmark benchmarks/<file> --max-k <max_k> --cases <cases> ``` Если пользователь не указал `--cases`, не передавай этот параметр (обработаются все кейсы). ### 3. Сообщи результат После завершения скрипта сообщи пользователю: - Mean метрику - Путь к JSONL файлу - Путь к Markdown отчёту
AskUserQuestion - внутренний инструмент Клод, даёт пользователю выбор из вариантов и превращает это в текстовые инструкции.
Прошу не удивляться PowerShell, проект сделан на windows-машине.
Универсальный вьювер для отчетов: Кейсы детализируются. Есть вердикт по каждому чанку.

Дашборд для трех метрик. Красотища :) Можно посмотреть вклад каждого кейса в общую оценку.

Подвожу итог. Оценка качества для одной метрики в расчёте на 10 кейсов занимает примерно 2-3 минуты (RAG работает на CPU). Расход токенов минимальный, в районе 500-800 на метрику. В качестве судьи можно использовать локальную модель. Рекомендация: модель не хуже 27b.
Оценку по всем трем метрикам и всем кейсам в проекте я делал несколько раз. Суммарно на LLM-судью получилось около 2М токенов (примерно 60 рублей). И ещё около 415k токенов в Claude Code на разработку: в районе 400k на все предыдущие шаги и 15k на финальную часть вместе с тестами. В плане работы, если бы делал то же самое в Python-ноутбуках - написание скриптов, отладку кода, прогон кейсов, подготовка отчетов, анализ результатов - полагаю, это заняло бы неделю на весь бенчмарк.
Ссылки:
Репозиторий проекта
Репозиторий naive-rag-mcp сервера
Документация на fastapi_mcp
Метрики в RAGAS
Откуда взялся мой бенчмарк (статья)
Комментарии (4)

Spyman
18.06.2026 14:28Как по мне - сейчас самая интересная тема, как раз тема оценки эффективности инструментов накрученных поверх llm. Хорошая статья. А ваши задачи без rag не решаются? Например положить все данные просто файлом рядом с моделью если это возможно. Интересно было бы ещё протестировать тут сценарий rag против cli в контексте затрат токенов. У меня в части задач выходило, что модель куда эффективнее сама искал необходимую часть документа базовыми unix инструментами, чем получая избыточные данные от rag, которые ей принудительно приходилось читать полностью.

khmelkoff Автор
18.06.2026 14:28Спасибо. Отличный вопрос!
А ваши задачи без rag не решаются? Например положить все данные просто файлом рядом с моделью если это возможно.
В API Anthropic есть система кеширования, кэш живёт 5 минут. Если мы работаем с одним и тем же документом, то его можно закешировать и сэкономить на токенах. Качество ответов будет выше, чем у простейшей RAG системы. Картинка с дашбордом как раз этот эффект показывает. Дело в том, что бенчмарк синтетический. Я его сделал на gemma4:31b. Она получала тему для вопроса, но видела весь документ целиком, поэтому RAG уступает по качеству там, где в метрике используется "правильный" ответ.
модель куда эффективнее сама искал необходимую часть документа базовыми unix инструментами, чем получая избыточные данные от rag
Если RAG - просто векторный поиск, так скорее всего и будет. Сравнительно новые архитектуры предполагают использование в RAG агентов и LLM-судьи. В зависимости от его решения может адаптивно меняться количество чанков в выдаче, переформулироваться и дополняться вопрос, или вообще отключаться выдача, если LLM-судья решил, что для этого вопроса RAG не нужен.
alexchizik
Все то же самое пишется за пару часов на чистом Python без всяких субагентов и псевдоинновационного вайб-воркинга, который здесь явно послужил лишь оправданием для накрутки охватов)
khmelkoff Автор
Здравствуйте. Там всё на питоне и написано. инструменты, библиотека, rag система. ещё немного маркдауна, html и JavaScript. Статья в том числе и про то, как всё-равно пришлось написать код.