/
Вопросы и ответы
/
RAG
/

Как оценивать качество RAG

Как оценивать качество RAG

11 часов назад

Никита Вихров

Ответы

0

Как оценивать качество RAG

«Кажется работает» — не метрика. RAG-система может выдавать уверенные ответы, которые неправильны в 30% случаев, и ты не узнаешь об этом без систематической оценки. Разберём как это измерять и автоматизировать.


Три ключевых метрики

Context Recall — нашёл ли поиск нужные документы? Если правильный ответ есть в базе, но поиск его не вернул — всё остальное не важно.

Context Precision — насколько найденные документы релевантны? Если из 5 найденных документов только 1 полезен — контекст засорён и модель может запутаться.

Answer Faithfulness — не выдумала ли модель что-то сверх найденных документов? Ответ должен опираться на контекст, а не на «знания» модели.


Автоматическая оценка через LLM

Золотые метки — дорого и медленно. Быстрый вариант: оцениваем качество другой моделью.

from anthropic import Anthropic
import json

client = Anthropic()


def evaluate_context_precision(question: str, retrieved_docs: list) -> float:
    """Какая доля найденных документов реально полезна для ответа?"""

    docs_text = "\n\n".join([f"[{i+1}] {d['content']}" for i, d in enumerate(retrieved_docs)])

    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=512,
        messages=[{
            "role": "user",
            "content": f"""Вопрос: {question}

Найденные документы:
{docs_text}

Для каждого документа оцени: помогает ли он ответить на вопрос? (true/false)
Отвечай только JSON-массивом булевых значений, например: [true, false, true]"""
        }]
    )

    relevance = json.loads(response.content[0].text)
    return sum(relevance) / len(relevance)


def evaluate_answer_faithfulness(question: str, context: str, answer: str) -> float:
    """Опирается ли ответ на контекст или модель выдумывает?"""

    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=512,
        messages=[{
            "role": "user",
            "content": f"""Вопрос: {question}

Контекст (документация):
{context}

Ответ системы:
{answer}

Оцени каждое утверждение в ответе: подтверждается ли оно контекстом?
Выведи JSON: {{"score": 0.0-1.0, "issues": ["список проблем если есть"]}}
1.0 — всё подтверждается, 0.0 — ответ полностью выдуман."""
        }]
    )

    result = json.loads(response.content[0].text)
    return result["score"]


def evaluate_context_recall(question: str, context: str, reference_answer: str) -> float:
    """Содержит ли найденный контекст информацию для правильного ответа?"""

    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=256,
        messages=[{
            "role": "user",
            "content": f"""Вопрос: {question}
Правильный ответ: {reference_answer}
Найденный контекст: {context}

Можно ли дать правильный ответ, опираясь только на найденный контекст?
Выведи JSON: {{"score": 0.0-1.0}}
1.0 — контекст полностью достаточен, 0.0 — нужной информации нет."""
        }]
    )

    result = json.loads(response.content[0].text)
    return result["score"]

Тестовый датасет — основа оценки

Без тестовых данных оценивать нечего. Собери датасет из реальных вопросов:

# Структура тестового датасета
test_dataset = [
    {
        "question": "Как откатить миграцию базы данных?",
        "reference_answer": "Используй команду alembic downgrade -1 для отката на одну миграцию назад",
        "relevant_doc_ids": ["doc_42", "doc_17"]  # какие документы должны быть найдены
    },
    {
        "question": "Что значит статус 429?",
        "reference_answer": "429 Too Many Requests — превышен rate limit. Подожди минуту и повтори запрос.",
        "relevant_doc_ids": ["doc_8"]
    },
    # ... ещё 50-100 вопросов
]


def run_evaluation(rag_system, test_dataset: list) -> dict:
    """Прогоняет датасет и считает агрегированные метрики"""

    precision_scores = []
    recall_scores = []
    faithfulness_scores = []

    for test_case in test_dataset:
        question = test_case["question"]

        # Получаем ответ от RAG
        retrieved_docs, answer = rag_system.answer_with_docs(question)
        context = "\n".join([d["content"] for d in retrieved_docs])

        # Считаем метрики
        precision = evaluate_context_precision(question, retrieved_docs)
        recall = evaluate_context_recall(question, context, test_case["reference_answer"])
        faithfulness = evaluate_answer_faithfulness(question, context, answer)

        precision_scores.append(precision)
        recall_scores.append(recall)
        faithfulness_scores.append(faithfulness)

        print(f"Q: {question[:50]}...")
        print(f"  Precision: {precision:.2f} | Recall: {recall:.2f} | Faithfulness: {faithfulness:.2f}")

    return {
        "context_precision": sum(precision_scores) / len(precision_scores),
        "context_recall": sum(recall_scores) / len(recall_scores),
        "answer_faithfulness": sum(faithfulness_scores) / len(faithfulness_scores),
    }


results = run_evaluation(my_rag, test_dataset)
print(f"\n=== ИТОГ ===")
print(f"Context Precision:    {results['context_precision']:.2f}")
print(f"Context Recall:       {results['context_recall']:.2f}")
print(f"Answer Faithfulness:  {results['answer_faithfulness']:.2f}")

Как интерпретировать результаты

Низкий Context Recall → поиск не находит нужные документы. Чини chunking, embedding-модель или порог релевантности.

Низкий Context Precision → поиск находит много лишнего. Уменьши top_k, добавь re-ranking или подними порог similarity.

Низкий Answer Faithfulness → модель выдумывает сверх контекста. Сделай system prompt строже, явно запрети отвечать без источника.


Автоматизация в CI

# .github/workflows/rag-eval.yml
- name: Evaluate RAG quality
  run: python evaluate_rag.py --dataset tests/rag_dataset.json --threshold 0.75

# Если метрики ниже порога — пайплайн падает
# Деплоим только когда качество подтверждено

Хорошая RAG-система — это не только правильный код, но и процесс: тестовый датасет, метрики, автоматическая проверка при каждом изменении. Без этого ты деплоишь вслепую.


Как выстраивать такой процесс в реальном проекте — разбирается на курсе «ИИ для разработчиков» на Хекслете. Весь материал — практика на живом проекте, а не абстрактные схемы. Автор курса Кирилл Мокевнин — разработчик с 18-летним опытом и основатель Хекслета.

11 часов назад

Никита Вихров

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845