Метрики оценки: relevance, faithfulness, answer relevancy, RAGAS фреймворк
Без оценки качества RAG-система — чёрный ящик. Вы не знаете, становится ли она лучше или хуже.
Вы изменили chunking-стратегию, заменили embedding-модель, переписали промпт. Стала ли система лучше? Без метрик вы гадаете. С метриками — знаете точно.
Оценка качества RAG отвечает на вопросы:
RAG-систему оценивают по трём аспектам:
| Аспект | Что оцениваем | Метрика |
|---|---|---|
| Retrieval | Насколько найденные чанки релевантны вопросу | Context Precision, Context Recall |
| Generation (faithfulness) | Соответствует ли ответ найденному контексту | Faithfulness |
| Generation (answer relevancy) | Насколько ответ релевантен вопросу | Answer Relevancy |
RAGAS (Retrieval Augmented Generation Assessment) — стандартный фреймворк для оценки RAG-систем.
pip install ragasДля оценки нужен тестовый набор: вопрос, правильный ответ, найденный контекст, сгенерированный ответ.
from datasets import Dataset
test_data = {
"question": [
"Как оформить отпуск?",
"Куда нести больничный лист?",
"Сколько дней удалёнки разрешено?"
],
"answer": [
"Заявление на отпуск подаётся через HR-портал за 2 недели.",
"Больничный лист нужно отнести в бухгалтерию в течение 3 дней.",
"Удалённая работа разрешена до 3 дней в неделю по согласованию.",
],
"contexts": [
["Отпуск оформляется через HR-портал. Заявление подаётся за 2 недели до начала."],
["Больничный лист нужно отправить в бухгалтерию в течение 3 дней."],
["Удалённая работа разрешена до 3 дней в неделю по согласованию с руководителем."]
],
"ground_truth": [
"Через HR-портал, заявление за 2 недели.",
"В бухгалтерию, в течение 3 дней.",
"До 3 дней в неделю."
]
}
dataset = Dataset.from_dict(test_data)Поля:
question — вопрос пользователяanswer — ответ, сгенерированный RAG-системойcontexts — список чанков, найденных retrieval (список списков, так как чанков может быть несколько)ground_truth — эталонный правильный ответ (для сравнения)from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
)
result = evaluate(
dataset=dataset,
metrics=[
faithfulness,
answer_relevancy,
context_precision,
context_recall,
],
)
print(result)RAGAS использует LLM (обычно GPT-4) для оценки каждого аспекта. Результат —_score от 0 до 1 для каждой метрики.
{'faithfulness': 0.85, 'answer_relevancy': 0.90, 'context_precision': 0.80, 'context_recall': 0.75}
| Метрика | Значение | Что означает |
|---|---|---|
| faithfulness | 0.85 | 85% утверждений в ответе подтверждаются контекстом — мало галлюцинаций |
| answer_relevancy | 0.90 | Ответ хорошо соответствует вопросу — не отклоняется от темы |
| context_precision | 0.80 | 80% найденных чанков релевантны вопросу — retrieval работает хорошо |
| context_recall | 0.75 | Retrieval нашёл 75% всей нужной информации — часть упущена |
Хорошие значения: все метрики выше 0.7. Отличные: выше 0.85.
Faithfulness измеряет, насколько ответ привязан к контексту. Если модель добавляет информацию от себя — faithfulness снижается.
from ragas.metrics import faithfulness
# Пример: ответ содержит информацию не из контекста
test_case = {
"question": ["Как оформить отпуск?"],
"answer": ["Заявление подаётся через HR-портал за 2 недели. Также нужно приложить копию паспорта."],
"contexts": [["Отпуск оформляется через HR-портал. Заявление подаётся за 2 недели до начала."]]
}
# «Копия паспорта» не упоминается в контексте — faithfulness будет ниже 1.0Здесь RAGAS обнаружит, что утверждение «нужна копия паспорта» не подтверждается контекстом, и снизит score.
Answer Relevancy оценивает, насколько ответ отвечает на вопрос — даже если контекст был релевантен, модель могла ответить не на то.
from ragas.metrics import answer_relevancy
# Ответ не релевантен вопросу
test_case = {
"question": ["Как оформить отпуск?"],
"answer": ["В компании есть HR-портал для различных процедур."],
"contexts": [["Отпуск оформляется через HR-портал. Заявление за 2 недели."]]
}Ответ «В компании есть HR-портал» техничесчески верен (правильный контекст), но не отвечает на конкретный вопрос «как оформить». Answer relevancy будет низким.
Доля релевантных чанков среди всех найденных. Если retrieval нашёл 5 чанков, но только 3 релевантны — precision = 0.6.
# Retrieval нашёл 5 чанков, но 2 нерелевантны
test_case = {
"question": ["Как оформить отпуск?"],
"answer": ["Через HR-портал за 2 недели."],
"contexts": [
[
"Отпуск через HR-портал, заявление за 2 недели.", # релевантный
"В офисе есть кофе-машина.", # нерелевантный
"Для отпуска нужно согласие руководителя.", # релевантный
"Парковка во дворе бесплатная.", # нерелевантный
"Международный отпуск — до 4 недель.", # релевантный
]
],
"ground_truth": ["Через HR-портал."]
}
# context_precision = 3/5 = 0.6Низкий precision = retrieval приносит много шума. Решение: улучшить embedding-модель, добавить reranking, настроить threshold.
Доля нужной информации, которую retrieval нашёл. Если для ответа нужно 4 факта, а retrieval нашёл только 3 — recall = 0.75.
# Для полного ответа нужен факт про 4 недели, но retrieval его не нашёл
test_case = {
"question": ["Какие сроки оформления отпуска?"],
"answer": ["Заявление за 2 недели."],
"contexts": [
["Отпуск оформляется через HR-портал. Заявление за 2 недели."]
],
"ground_truth": ["Заявление за 2 недели, международный отпуск — до 4 недель."]
}
# context_recall будет низким — не найден факт про 4 неделиНизкий recall = retrieval пропускает важную информацию. Решение: увеличить k, улучшить chunking, использовать query expansion.
Откуда взять ground_truth ответы? Есть несколько подходов:
Эксперт по предметой области пишет вопросы и правильные ответы. Точнее всего, но дорого по времени.
# Эксперт создал 20 вопросов с ответами для тестирования
manual_test_data = {
"question": ["Как оформить отпуск?", "Куда нести больничный?"],
"answer": [...], # ответы вашей RAG-системы
"contexts": [...],
"ground_truth": ["Через HR-портал за 2 недели.", "В бухгалтерию за 3 дня."]
}Использовать LLM для генерации вопросов и ответов на основе документов:
# Генерируем тестовые данные из документов
doc_contexts = [
"Отпуск оформляется через HR-портал. Заявление за 2 недели.",
"Больничный лист нужно отнести в бухгалтерию."
]
# Для каждого документа генерируем вопрос-ответ
test_questions = []
test_answers = []
for ctx in doc_contexts:
# Здесь был бы вызов LLM с промптом:
# «Сгенерируй вопрос и ответ на основе этого текста»
passОценка качества позволяет сравнивать разные конфигурации RAG:
# Конфигурация A: chunking по 200 слов, gpt-4o-mini
results_a = evaluate(dataset_a, metrics=[faithfulness, answer_relevancy])
# Конфигурация B: chunking по 500 слов, claude-3-haiku
results_b = evaluate(dataset_b, metrics=[faithfulness, answer_relevancy])
print(f"Конфиг A: faithfulness={results_a['faithfulness']:.2f}")
print(f"Конфиг B: faithfulness={results_b['faithfulness']:.2f}")Так вы объективно определяете, какая конфигурация лучше, а не полагаетесь на субъективные ощущения.
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
from datasets import Dataset
def evaluate_rag_system(test_cases, rag_pipeline):
"""
Оценивает RAG-систему на тестовом наборе.
test_cases: список {"question": str, "ground_truth": str}
rag_pipeline: функция, которая принимает question и возвращает {"answer": str, "contexts": list}
"""
questions = []
answers = []
contexts = []
ground_truths = []
for test_case in test_cases:
question = test_case["question"]
gt = test_case["ground_truth"]
# Запускаем RAG-систему
result = rag_pipeline(question)
questions.append(question)
answers.append(result["answer"])
contexts.append(result["contexts"])
ground_truths.append(gt)
dataset = Dataset.from_dict({
"question": questions,
"answer": answers,
"contexts": contexts,
"ground_truth": ground_truths
})
result = evaluate(
dataset=dataset,
metrics=[faithfulness, answer_relevancy, context_precision, context_recall]
)
return result
# Использование
def my_rag(question):
# Ваша RAG-система
return {"answer": "...", "contexts": ["..."]}
test_cases = [
{"question": "Как оформить отпуск?", "ground_truth": "Через HR-портал за 2 недели."}
]
results = evaluate_rag_system(test_cases, my_rag)
print(results)Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.