Cookbook: Building an Evaluation Pipeline
On this page
Using RAGEvaluator to score a RAGSystem's answers against a labeled test set.
Recipe: Evaluate a fixed question set with ground truth
from zerda.rag.core import RAGEvaluator, EvalSample
test_set = [
{"question": "What is the return window?",
"ground_truth": "30 days from purchase."},
{"question": "Can I return sale items?",
"ground_truth": "No, sale items are final."},
]
samples = []
for item in test_set:
retrieved = rag.retrieve(item["question"])
answer = rag.generate(item["question"])
samples.append(EvalSample(
question=item["question"],
answer=answer,
contexts=[chunk.text for chunk, _score in retrieved],
ground_truth=item["ground_truth"],
))
evaluator = RAGEvaluator(llm=judge_llm) # can be the same or a different LLM
report = evaluator.evaluate_dataset(samples, show_progress=True)
report.print_report()
Recipe: Regression-testing a config change
def score_config(config):
rag = RAGSystem(vector_db=vector_db, llm=llm, chunker=chunker,
context_manager=context_manager, config=config)
rag.add_documents(corpus)
samples = [build_sample(rag, q) for q in test_questions]
return evaluator.evaluate_dataset(samples).overall_mean()
baseline_score = score_config(RAGConfig())
reranked_score = score_config(RAGConfig(enable_reranking=True, rerank_mode="hybrid"))
print(f"Baseline: {baseline_score:.3f} | Reranked: {reranked_score:.3f}")
Recipe: Exporting results for a dashboard
import json
report = evaluator.evaluate_dataset(samples)
with open("eval_report.json", "w") as f:
json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
Recipe: Quick single-sample sanity check during development
from zerda.rag.core import RAGEvaluator
result = RAGEvaluator.quick_eval(
llm=judge_llm,
question="What is the refund policy?",
answer=rag.ask("What is the refund policy?"),
contexts=[c.text for c, _ in rag.retrieve("What is the refund policy?")],
)
print(result.summary())
Recipe: Rate-limiting evaluation against a throttled API
evaluator = RAGEvaluator(llm=judge_llm, batch_delay=1.0) # 1s pause between samples
report = evaluator.evaluate_dataset(large_sample_set)
Remember evaluate_dataset runs strictly sequentially (no thread pool), so batch_delay combined with a large sample set and 5 LLM calls per sample can add up — budget wall-clock time accordingly (see evaluate_dataset complexity).