← К портфолио RAG Evaluation Harness — retrieval and generation metrics preview
Ситуация
RAG-демо легко показать, но не измерить: непонятно, ошибка в поиске или в генерации, и воспроизводится ли результат вообще
Задача
Собрать пайплайн с офлайн-харнессом оценки, который разделяет метрики ретрива и генерации и работает без API-ключей
Действия
TF-IDF-индекс на корпусе концепт-документов, cosine top-k, детерминированный экстрактивный ответчик; Claude подключается флагом --llm, харнесс тот же
Результат
recall@5 и MRR = 1.00 на 20 вопросах; correctness 0.37 у офлайн-базлайна (faithfulness ≡1.00 — инвариант экстрактива); ретрив и генерация оцениваются раздельно
Стек
Pythonscikit-learnpandas / NumPyTF-IDFpytestuv
Исходники Смотреть на GitHub → Обновлено: Oct 10, 2026
Содержание
  1. Ситуация
  2. Задача
  3. Действия
  4. Запуск
  5. Результат
  6. Ограничения
  7. Документация

RAG Evaluation Harness — retrieval and generation metrics

▶ Схема потока данных — самодостаточный HTML, открывается в браузере — без установки и сборки

Ситуация

RAG-пайплайн легко показать на одном удачном вопросе и почти невозможно измерить: непонятно, где именно теряется качество — в поиске документов или в генерации ответа, — и воспроизводится ли результат вообще без внешних сервисов. Без офлайн-харнесса любое «стало лучше» остаётся впечатлением, а не числом.

Задача

От меня требовалось собрать RAG end-to-end и отдельно измерить каждый слой: метрики ретрива (recall@k, MRR, precision@k) на размеченном наборе вопросов и метрики генерации (faithfulness, answer correctness). Харнесс должен работать без API-ключей и оставаться тем же при подключении LLM.

Действия

Корпус — 12 коротких документов по аналитике (A/B-тесты, CUPED, uplift, retention, churn, RFM, воронка, LTV, атрибуция, когорты, DAU/MAU, оконные функции SQL), нарезанных на чанки и проиндексированных TF-IDF с 1–2-граммами.

  • Retrieval — cosine top-k по TF-IDF-матрице; зависимостей минимум, полностью офлайн. Эмбеддинг-бэкенд можно подключить, не трогая код оценки.
  • Generation — Claude, заземлённый на найденном контексте, если задан ключ; иначе детерминированный экстрактивный фолбэк возвращает самое релевантное предложение из top-документа. Оба пути возвращают одну и ту же запись Generation, поэтому оценка воспроизводима офлайн.
  • Evaluation — 20 размеченных вопросов (data/eval.jsonl) с gold doc id и эталонным ответом. Ретрив: recall@k, MRR, precision@k. Генерация: faithfulness (доля токенов ответа, заземлённых в контексте) и answer correctness (Jaccard-пересечение с эталоном).
Метрика Значение
recall@5 1.00
MRR 1.00
precision@5 0.20
faithfulness (инвариант, экстрактив) 1.00
correctness 0.37

Запуск

uv run --with scikit-learn --with pandas --with numpy python run.py query "How is the CUPED theta coefficient computed?"
uv run --with scikit-learn --with pandas --with numpy python run.py eval
# с реальной LLM (нужен ANTHROPIC_API_KEY):
uv run --with scikit-learn --with pandas --with numpy --with anthropic python run.py eval --llm
uv run --with scikit-learn --with pandas --with numpy --with pytest pytest -q

Разбор по вопросам: reports/retrieval_per_question.csv, reports/generation_per_question.csv.

Результат

Ретрив на этом корпусе идеален: recall@5 и MRR = 1.00, gold-документ стоит на ранге 1 для всех 20 вопросов — TF-IDF хватает при таком масштабе и хорошем разделении тем. Офлайн-экстрактивный базлайн заземлён по построению — faithfulness ≡1.00, потому что ответ берётся из контекста, — и набирает 0.37 correctness по пересечению токенов.

Главная ценность — не числа сами по себе, а то, что харнесс разводит слои. С --llm и ключом качество генерации растёт, а метрики ретрива не меняются, поэтому видно, какой именно слой улучшать. Это тот же принцип, что и в A/B: сначала измерь, потом меняй.

Ограничения

Faithfulness здесь — прокси заземлённости (токены ответа в контексте), а не проверка истинности: ответ может опираться на найденный контекст и всё равно быть неверным. Correctness по Jaccard чувствителен к формулировке — низкий балл не всегда ошибка. Корпус мал (12 документов, 20 вопросов), поэтому recall@5 = 1.00 — свойство этого набора, а не обещание на реальных данных; при росте корпуса метрику нужно пересчитать.

Документация