Создание автоматизированной системы оценки
Создайте воспроизводимый конвейер оценки, который запускает всю систему RAG на тестовом наборе, вычисляет все метрики и формирует отчёт, позволяющий отслеживать улучшения со временем.
«Создание автоматизированной системы оценки» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.
Что такое контур оценки?
Контур оценки — это воспроизводимый автоматизированный конвейер, который запускает всю RAG-систему на стандартизированном наборе проверок, рассчитывает все метрики и создаёт отчёт. Ключевое слово здесь — воспроизводимый: каждый раз, когда вы изменяете стратегию разбиения на фрагменты, модель эмбеддингов, запрос или LLM, вы запускаете один и тот же контур и сравниваете результаты с базовой версией. Благодаря этому разработка RAG превращается из субъективного перебора вариантов в инженерную работу на основе данных.
Архитектура контура оценки
Хорошо спроектированный контур оценки состоит из четырёх уровней: управление проверочными данными (загрузка и версионирование эталонного набора данных), выполнение конвейера (проведение каждого проверочного вопроса через весь RAG-конвейер), расчёт метрик (вычисление всех метрик извлечения и генерации) и формирование отчёта (сохранение результатов с информацией о версии и создание сравнения с предыдущей базовой версией). Каждый уровень должен независимо поддаваться проверке и настройке.
class RAGEvaluationHarness:
def __init__(self, retriever, llm_client, config):
self.retriever = retriever
self.llm_client = llm_client
self.config = config # chunk_size, top_k, model, threshold, etc.
self.results = []
def run(self, golden_dataset):
for item in golden_dataset:
result = self._evaluate_single(item)
self.results.append(result)
metrics = self._compute_metrics()
self._save_report(metrics)
return metricsЗапуск каждого проверочного случая
Для каждого вопроса из эталонного набора данных запустите полный RAG-конвейер и сохраните все промежуточные результаты: идентификаторы и оценки извлечённых фрагментов, отформатированный контекст, сгенерированный ответ и количество токенов. Сохранение этих промежуточных значений необходимо для отладки сбоев: если вопрос получил низкую оценку, вы сможете точно проверить, какие фрагменты были извлечены и почему ответ оказался неверным, не запуская заново дорогостоящий конвейер.
import time
def _evaluate_single(self, item):
start = time.perf_counter()
query_vector = embed_query(item['question'])
chunks = self.retriever.retrieve(query_vector, top_k=self.config['top_k'])
filtered_chunks = filter_by_score(chunks, self.config['threshold'])
context = format_context(filtered_chunks)
answer_result = generate_answer(item['question'], context, self.llm_client)
latency_ms = (time.perf_counter() - start) * 1000
return {
'question': item['question'],
'expected_answer': item['answer'],
'generated_answer': answer_result['answer'],
'retrieved_chunk_ids': [c['id'] for c in filtered_chunks],
'retrieved_scores': [c['score'] for c in filtered_chunks],
'relevant_chunk_ids': item['relevant_chunk_ids'],
'context_texts': [c['text'] for c in filtered_chunks],
'tokens_used': answer_result['tokens_used'],
'latency_ms': round(latency_ms)
}Расчёт всех метрик за один проход
После сбора результатов всех проверочных случаев вычислите полный набор метрик за один проход по результатам. Отделите метрики извлечения (рассчитываемые по идентификаторам фрагментов) от метрик генерации (рассчитываемых с помощью вызовов оценивающей LLM). Объединяйте вызовы оценивающей LLM в пакеты для максимальной эффективности: группируйте проверки достоверности и отправляйте их параллельно с помощью asyncio, а не последовательно. Записывайте ход выполнения, поскольку расчёт метрик генерации для более чем 100 проверочных случаев может занимать несколько минут.
def _compute_metrics(self):
# Retrieval metrics (no LLM calls needed)
hit_rates = []
mrr_scores = []
for r in self.results:
retrieved = r['retrieved_chunk_ids']
relevant = set(r['relevant_chunk_ids'])
hit = any(rid in relevant for rid in retrieved)
hit_rates.append(1.0 if hit else 0.0)
for rank, rid in enumerate(retrieved, 1):
if rid in relevant:
mrr_scores.append(1.0 / rank)
break
else:
mrr_scores.append(0.0)
metrics = {
'hit_rate_at_5': sum(hit_rates) / len(hit_rates),
'mrr': sum(mrr_scores) / len(mrr_scores),
'mean_latency_ms': sum(r['latency_ms'] for r in self.results) / len(self.results),
'mean_tokens': sum(r['tokens_used'] for r in self.results) / len(self.results)
}
return metricsСохранение результатов с информацией о версии
Каждый запуск оценки следует сохранять с метаданными версии, чтобы можно было сравнивать результаты разных конфигураций. Включите хеш коммита git с кодом, параметры конфигурации (модель эмбеддингов, размер фрагмента, K, порог, модель LLM), временную метку и понятное человеку описание запуска. Храните результаты в файле JSON Lines или в таблице базы данных. Так вы создадите постоянную историю развития системы.
import json
import subprocess
from datetime import datetime
def _save_report(self, metrics):
git_hash = subprocess.check_output(
['git', 'rev-parse', '--short', 'HEAD']
).decode().strip()
report = {
'run_id': datetime.utcnow().strftime('%Y%m%d_%H%M%S'),
'git_commit': git_hash,
'config': self.config,
'metrics': metrics,
'n_test_cases': len(self.results),
'timestamp': datetime.utcnow().isoformat()
}
with open('eval_history.jsonl', 'a') as f:
f.write(json.dumps(report) + '\n')
print(f'Saved evaluation run: {report["run_id"]}')
print(json.dumps(metrics, indent=2))Сравнение с базовой версией
После каждого запуска автоматически сравнивайте результаты с предыдущей базовой версией и отмечайте регрессии. Регрессией считается любое падение метрики более чем на заданный порог (например, на 2 процентных пункта). Выводите таблицу различий с изменениями метрик. Если какая-либо метрика значительно ухудшилась, запуск оценки должен завершиться с ненулевым кодом выхода, из-за чего конвейер CI/CD заблокирует развёртывание этого изменения.
def compare_to_baseline(current_metrics, baseline_file='best_eval.json'):
import json
from pathlib import Path
if not Path(baseline_file).exists():
print('No baseline yet. Saving current as baseline.')
Path(baseline_file).write_text(json.dumps(current_metrics, indent=2))
return True
baseline = json.loads(Path(baseline_file).read_text())
regressions = []
print('\nMetric comparison (current vs baseline):')
for metric, current_val in current_metrics.items():
baseline_val = baseline.get(metric, 0)
delta = current_val - baseline_val
status = 'OK' if delta >= -0.02 else 'REGRESSION'
print(f' {metric}: {current_val:.3f} vs {baseline_val:.3f} ({delta:+.3f}) {status}')
if status == 'REGRESSION':
regressions.append(metric)
return len(regressions) == 0Интеграция в CI/CD
Контур оценки наиболее эффективен при интеграции в ваш конвейер CI/CD. Настройте его автоматический запуск при каждом запросе на слияние, изменяющем логику разбиения на фрагменты, конфигурацию модели эмбеддингов, шаблоны запросов или параметры извлечения. Конвейер проходит проверку только в том случае, если все метрики достигают минимальных порогов и ни одна метрика не ухудшилась по сравнению с базовой версией основной ветки. Это предотвращает попадание случайных ухудшений качества в рабочую среду.
# GitHub Actions workflow (eval.yml)
# on:
# pull_request:
# paths:
# - 'rag/**'
# - 'prompts/**'
# - 'config/**'
# jobs:
# evaluate:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - name: Install dependencies
# run: pip install -r requirements.txt
# - name: Run evaluation harness
# run: |
# python eval/run_harness.py \
# --test-set eval/golden_dataset.json \
# --config config/rag_config.yaml \
# --fail-on-regression
# env:
# OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}Формирование понятных человеку отчётов
Помимо файлов с исходными значениями метрик создавайте понятный человеку отчёт в формате HTML или Markdown, который команда сможет просматривать в комментариях к запросу на слияние. Включите сводную таблицу всех метрик, список не пройденных проверок с вопросом, ожидаемым ответом, сгенерированным ответом и перечнем извлечённых фрагментов, а также график изменения метрик за последние 10 запусков. Визуальные отчёты помогают нетехническим заинтересованным сторонам понять, улучшается ли система.
def generate_markdown_report(metrics, failed_cases, run_id):
lines = [
f'# RAG Evaluation Report — {run_id}\n',
'## Summary Metrics',
'| Metric | Score | Target |',
'|--------|-------|--------|',
f'| Hit Rate@5 | {metrics["hit_rate_at_5"]:.1%} | > 80% |',
f'| MRR | {metrics["mrr"]:.3f} | > 0.70 |',
f'| Mean Latency | {metrics["mean_latency_ms"]:.0f}ms | < 500ms |',
'',
f'## Failed Cases ({len(failed_cases)} failures)'
]
for case in failed_cases[:10]: # show first 10
lines += [
f'**Q:** {case["question"]}',
f'**Expected:** {case["expected_answer"]}',
f'**Generated:** {case["generated_answer"]}\n'
]
return '\n'.join(lines)Отслеживание стоимости каждого запуска оценки
Запуски оценки стоят денег: они обращаются к API эмбеддингов, API LLM и оценивающей LLM. Отслеживайте стоимость каждого запуска оценки вместе с метриками качества. Полная оценка 100 проверочных случаев обычно стоит от $0.50 до $2.00 в зависимости от используемых моделей. Используйте более дешёвые модели для вызовов оценщика (GPT-4o-mini для оценки достоверности), а дорогие модели оставляйте для генерации. Включайте расчётную стоимость запуска в сохранённый отчёт, чтобы учитывать оценку в бюджете цикла разработки.
def estimate_run_cost(results, config):
# Embedding cost
embed_tokens = sum(len(r['question'].split()) * 1.3 for r in results)
embed_cost = (embed_tokens / 1_000_000) * 0.02 # $0.02/1M tokens
# Generation cost
total_gen_tokens = sum(r['tokens_used'] for r in results)
gen_cost = (total_gen_tokens / 1_000_000) * 5.0 # gpt-4o approx
# Judge cost (faithfulness evals)
judge_cost = len(results) * 0.001 # ~$0.001 per eval with gpt-4o-mini
total = embed_cost + gen_cost + judge_cost
print(f'Evaluation cost estimate: ${total:.2f}')
print(f' Embedding: ${embed_cost:.3f}')
print(f' Generation: ${gen_cost:.3f}')
print(f' Judgment: ${judge_cost:.3f}')
return totalПлановая оценка для мониторинга рабочей среды
Помимо оценки изменений кода в CI/CD, запускайте контур по расписанию в рабочей среде — ежедневно или еженедельно — на реальных запросах пользователей, выбранных из журналов. Это позволяет обнаружить дрейф данных: по мере изменения корпуса документов и сдвига закономерностей в запросах пользователей качество системы может ухудшиться без каких-либо изменений кода. Запланируйте еженедельные запуски оценки: выбирайте 50 недавних запросов пользователей, оценивайте их и автоматически отправляйте сводку о качестве в канал Slack вашей команды.
# Example scheduled evaluation (cron job or scheduled cloud function)
import random
def sample_production_queries(query_log_file, n=50):
with open(query_log_file) as f:
all_queries = [json.loads(line) for line in f]
sample = random.sample(all_queries, min(n, len(all_queries)))
# Convert to golden dataset format (without expected answers — use LLM judge)
return [
{'question': q['user_question'], 'relevant_chunk_ids': []}
for q in sample
]
# Run weekly evaluation against production queries
if __name__ == '__main__':
prod_queries = sample_production_queries('/var/log/rag_queries.jsonl')
harness = RAGEvaluationHarness(retriever, llm_client, config)
metrics = harness.run(prod_queries)
send_slack_digest(metrics)Визуализация изменений метрик во времени
Исходные числа в файле JSONL трудно быстро интерпретировать. Создайте простую визуализацию изменений, которая отображает каждую метрику на линейном графике за последние 20 запусков оценки. Используйте временную метку запуска на оси X, а оценку метрики — на оси Y. Проведите горизонтальную линию на уровне минимально допустимого порога. Когда метрика опускается ниже этой линии, проблему сразу видно, и не нужно просматривать исходные данные. Для этого хорошо подходят такие инструменты, как Matplotlib, или простая веб-панель (Grafana, Streamlit).
import json
import matplotlib.pyplot as plt
from pathlib import Path
def plot_metric_trends(history_file='eval_history.jsonl', metric='hit_rate_at_5'):
records = [
json.loads(line)
for line in Path(history_file).read_text().strip().split('\n')
]
timestamps = [r['timestamp'][:10] for r in records[-20:]]
scores = [r['metrics'].get(metric, 0) for r in records[-20:]]
plt.figure(figsize=(10, 4))
plt.plot(timestamps, scores, marker='o', label=metric)
plt.axhline(y=0.80, color='r', linestyle='--', label='Min threshold')
plt.title(f'{metric} over last 20 evaluations')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig(f'eval_trend_{metric}.png')
print(f'Saved trend chart for {metric}')Быстрая проверка
Проверьте своё понимание концепций инженерии ИИ из этого урока.
Итоги урока
В этом уроке вы узнали: как построить полноценный контур оценки с управлением проверочными данными, выполнением конвейера, расчётом метрик и формированием отчётов; как сравнивать запуски с базовой версией и останавливать CI/CD при регрессиях; как создавать понятные человеку отчёты для проверки командой; и как организовать плановый мониторинг рабочей среды для обнаружения дрейфа данных без изменений кода. Теперь у вас есть полноценная основа для создания и оценки производственных RAG-систем.
Изучай Python с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 30
- Уроки
- 120
Часто задаваемые вопросы
Урок «Создание автоматизированной системы оценки» бесплатный?
Да — полный текст урока «Создание автоматизированной системы оценки» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.
Чему я научусь в уроке «Создание автоматизированной системы оценки»?
Создайте воспроизводимый конвейер оценки, который запускает всю систему RAG на тестовом наборе, вычисляет все метрики и формирует отчёт, позволяющий отслеживать улучшения со временем. Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Engineering Academy?
Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Создание автоматизированной системы оценки»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Engineering Academy?
Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Зачем нужна оценка RAG
- Метрики поиска: доля попаданий, MRR и NDCG
- Метрики генерации: достоверность и релевантность ответа
- Создание автоматизированной системы оценки