Tworzenie automatycznego środowiska ewaluacyjnego
Uczestnicy utworzą powtarzalny potok ewaluacji, który uruchamia cały system RAG na zbiorze testowym, oblicza wszystkie metryki i generuje raport umożliwiający śledzenie postępów w czasie.
Tworzenie automatycznego środowiska ewaluacyjnego to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.
Czym jest narzędzie ewaluacyjne?
Narzędzie ewaluacyjne to powtarzalny, automatyczny potok, który uruchamia cały system RAG na ustandaryzowanym zbiorze testowym, oblicza wszystkie metryki i generuje raport. Kluczowe słowo to powtarzalny: za każdym razem, gdy zmieniają Państwo strategię dzielenia na fragmenty, model osadzeń, prompt lub LLM, uruchamiają Państwo ten sam mechanizm i porównują wyniki z wartością bazową. Dzięki temu tworzenie systemów RAG zmienia się z subiektywnego eksperymentowania w inżynierię opartą na danych.
Architektura narzędzia ewaluacyjnego
Dobrze zaprojektowane narzędzie ewaluacyjne ma cztery warstwy: zarządzanie danymi testowymi (wczytanie i wersjonowanie wzorcowego zbioru danych), wykonywanie potoku (przepuszczenie każdego testowego pytania przez cały potok RAG), obliczanie metryk (obliczenie wszystkich metryk wyszukiwania i generowania) oraz generowanie raportów (zapisanie wyników wraz z informacjami o wersji i przygotowanie porównania z poprzednią wartością bazową). Każdą warstwę należy przygotować tak, aby można ją było niezależnie testować i konfigurować.
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 metricsUruchamianie każdego przypadku testowego
Dla każdego pytania ze wzorcowego zbioru danych należy uruchomić kompletny potok RAG i zapisać wszystkie wyniki pośrednie: identyfikatory i wyniki pobranych fragmentów, sformatowany kontekst, wygenerowaną odpowiedź oraz liczbę tokenów. Przechowywanie tych wartości pośrednich jest niezbędne do debugowania błędów — gdy pytanie uzyska słaby wynik, można dokładnie sprawdzić, które fragmenty pobrano i dlaczego odpowiedź była błędna, bez ponownego uruchamiania kosztownego potoku.
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)
}Obliczanie wszystkich metryk w jednym przebiegu
Po zebraniu wyników wszystkich przypadków testowych należy obliczyć pełny zestaw metryk w jednym przebiegu po wynikach. Metryki wyszukiwania, obliczane na podstawie identyfikatorów fragmentów, należy oddzielić od metryk generowania, obliczanych za pomocą wywołań sędziowskiego LLM. Aby zmaksymalizować wydajność, należy grupować wywołania sędziowskiego LLM — ewaluacje wierności należy grupować i wysyłać równolegle za pomocą asyncio, zamiast wykonywać je sekwencyjnie. Należy rejestrować postęp, ponieważ obliczanie metryk generowania może zająć kilka minut dla ponad 100 przypadków testowych.
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 metricsZapisywanie wyników z informacjami o wersji
Każdy przebieg ewaluacji należy zapisać wraz z metadanymi wersji, aby można było porównywać wyniki między konfiguracjami. Należy uwzględnić hash commita git zawierającego kod, parametry konfiguracji (model osadzeń, rozmiar fragmentu, K, próg, model LLM), znacznik czasu oraz zrozumiały dla człowieka opis przebiegu. Wyniki należy przechowywać w pliku JSON Lines lub tabeli bazy danych. Tworzy to trwałą historię zmian systemu.
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))Porównywanie z wartością bazową
Po każdym przebiegu należy automatycznie porównać wyniki z poprzednią wartością bazową i oznaczyć regresje. Regresja występuje, gdy dowolna metryka spada o więcej niż określony próg (np. o 2 punkty procentowe). Należy wyświetlić tabelę różnic pokazującą zmiany metryk. Jeśli dowolna metryka ulegnie znaczącemu pogorszeniu, przebieg ewaluacji powinien zakończyć się niezerowym kodem wyjścia, co spowoduje zablokowanie wdrożenia tej zmiany przez potok 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) == 0Integracja z CI/CD
Narzędzie ewaluacyjne jest najbardziej użyteczne po zintegrowaniu go z potokiem CI/CD. Należy skonfigurować je tak, aby uruchamiało się automatycznie przy każdym pull requeście, który modyfikuje logikę dzielenia na fragmenty, konfigurację modelu osadzeń, szablony promptów lub parametry wyszukiwania. Potok przechodzi pomyślnie tylko wtedy, gdy wszystkie metryki spełniają minimalne progi i żadna metryka nie pogarsza się względem wartości bazowej głównej gałęzi. Zapobiega to wdrażaniu przypadkowych regresji jakości na produkcję.
# 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 }}Generowanie raportów zrozumiałych dla człowieka
Oprócz surowych plików z metrykami należy generować raport HTML lub Markdown zrozumiały dla człowieka, który zespół może przeglądać w komentarzach do pull requestów. Należy uwzględnić tabelę podsumowującą wszystkie metryki, listę nieudanych przypadków testowych zawierającą pytanie, oczekiwaną odpowiedź, wygenerowaną odpowiedź i pobrane fragmenty oraz wykres trendu pokazujący metryki z ostatnich 10 przebiegów. Raporty wizualne pomagają interesariuszom nietechnicznym zrozumieć, czy system się poprawia.
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)Śledzenie kosztu pojedynczego przebiegu ewaluacji
Przebiegi ewaluacji kosztują — wymagają wywołań API osadzeń, API LLM oraz sędziowskiego LLM. Należy śledzić koszt każdego przebiegu ewaluacji razem z metrykami jakości. Kompleksowa ewaluacja 100 przypadków testowych kosztuje zazwyczaj od 0,50 do 2,00 USD, zależnie od użytych modeli. Do wywołań sędziowskich należy używać tańszych modeli (GPT-4o-mini do oceny wierności), a droższe modele rezerwować do generowania. Szacowany koszt przebiegu należy uwzględnić w zapisanym raporcie, aby można było zaplanować koszty ewaluacji w cyklu wytwarzania.
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 totalHarmonogramowana ewaluacja do monitorowania produkcji
Oprócz ewaluacji CI/CD uruchamianej po zmianach w kodzie należy uruchamiać narzędzie zgodnie z harmonogramem na produkcji — codziennie lub co tydzień — testując je na rzeczywistych zapytaniach użytkowników pobranych z logów. Pozwala to wykrywać dryf danych: w miarę zmian korpusu dokumentów i wzorców zapytań użytkowników jakość systemu może się pogarszać bez żadnych zmian w kodzie. Należy planować cotygodniowe przebiegi ewaluacji, które pobierają próbkę 50 ostatnich zapytań użytkowników, oceniają je i automatycznie wysyłają podsumowanie jakości na kanał Slack zespołu.
# 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)Wizualizacja trendów metryk w czasie
Surowe liczby w pliku JSONL trudno szybko zinterpretować. Należy przygotować prostą wizualizację trendów, która przedstawia każdą metrykę z ostatnich 20 przebiegów ewaluacji na wykresie liniowym. Na osi X należy umieścić znacznik czasu przebiegu, a na osi Y — wynik metryki. Należy dodać poziomą linię oznaczającą minimalny akceptowalny próg. Gdy metryka spadnie poniżej tej linii, problem będzie od razu widoczny bez przeglądania surowych danych. Dobrze sprawdzą się narzędzia takie jak Matplotlib lub prosty dashboard internetowy (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}')Szybki test
Sprawdź swoje rozumienie zagadnień z zakresu inżynierii AI omówionych w tej lekcji.
Podsumowanie lekcji
W tej lekcji poznali Państwo: jak zbudować kompletne narzędzie ewaluacyjne obejmujące zarządzanie danymi testowymi, wykonywanie potoku, obliczanie metryk i generowanie raportów, jak porównywać przebiegi z wartością bazową i zatrzymywać CI/CD w przypadku regresji, jak generować raporty zrozumiałe dla człowieka do przeglądu przez zespół oraz jak uruchamiać harmonogramowane monitorowanie produkcji w celu wykrywania dryfu danych bez zmian w kodzie. Mają już Państwo kompletną podstawę do budowania i oceniania produkcyjnych systemów RAG.
Często zadawane pytania
Czy lekcja „Tworzenie automatycznego środowiska ewaluacyjnego” jest bezpłatna?
Tak — pełny tekst „Tworzenie automatycznego środowiska ewaluacyjnego” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Tworzenie automatycznego środowiska ewaluacyjnego”?
Uczestnicy utworzą powtarzalny potok ewaluacji, który uruchamia cały system RAG na zbiorze testowym, oblicza wszystkie metryki i generuje raport umożliwiający śledzenie postępów w czasie. Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?
Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Tworzenie automatycznego środowiska ewaluacyjnego”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?
Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Dlaczego ewaluacja ma znaczenie w RAG
- Metryki pobierania: hit rate, MRR i NDCG
- Metryki generowania: wierność i trafność odpowiedzi
- Tworzenie automatycznego środowiska ewaluacyjnego