ASDRP · Agentic RAG Track · Module 09
Score the answer, not just the retrieval — and understand the judge behind every score.
What we'll cover
Motivation
A retriever can be perfect and the generator still hallucinate.
A generator can be fluent and still miss the point entirely.
Generator Metric 1
The judge LLM:
Generator Metric 1 — Code
from ragas import evaluate
from ragas.metrics import Faithfulness
results = evaluate(
dataset=eval_dataset,
metrics=[Faithfulness()],
llm=judge_llm, # temperature=0 for deterministic scores
embeddings=ragas_embeddings,
)
df = results.to_pandas()
print(df[["user_input", "faithfulness"]])
# faithfulness: 0.0 – 1.0 (higher is better)
score = await Faithfulness(llm=judge_llm).single_turn_ascore(samples[0])
print(score) # e.g. 0.88
nest_asyncio.apply() must be called first so await works inside a Jupyter event loop.
Generator Metric 2
The judge generates n synthetic questions that the answer could be answering, then measures how similar they are to the original question using embeddings.
Generator Metric 3
| Mode | Question asked |
|---|---|
precision | Are the answer's claims correct? |
recall | Were all reference points covered? |
f1 (default) | Harmonic mean of both |
Generator Metric 3 — Code
from ragas.metrics import FactualCorrectness
# Default: F1 (harmonic mean of precision and recall)
results_f1 = evaluate(
dataset=eval_dataset,
metrics=[FactualCorrectness()],
llm=judge_llm, embeddings=ragas_embeddings,
)
# Precision only — are the answer's claims accurate?
fc_precision = FactualCorrectness(llm=judge_llm, mode="precision")
# Recall only — did the answer cover all reference points?
fc_recall = FactualCorrectness(llm=judge_llm, mode="recall")
scores_p = await fc_precision.single_turn_ascore(samples[0])
scores_r = await fc_recall.single_turn_ascore(samples[0])
print(f"Precision: {scores_p:.2f} Recall: {scores_r:.2f}")
Track 3 — LLM-as-Judge
Every RAGAS generator metric sends a structured prompt to a judge LLM — a second model that reads the question, context, answer, and optional reference, then returns a score.
temperature=0Track 3 — LLM-as-Judge
| Bias | What happens | Mitigation |
|---|---|---|
| Verbosity bias | Judge prefers longer, more elaborate answers even if they contain errors | Evaluate concise and verbose answers on separate benchmarks; penalise hallucination explicitly |
| Position bias | In A/B comparisons the judge favours whichever candidate appears first | Randomise candidate order; average scores from both orderings |
| Self-preference | A model rates its own outputs higher than outputs from other models | Always use a different model as judge and generator |
nemotron-3-super:cloud (T=0.3),
judge = gemma4:31b-cloud (T=0.0).
Different models, different temperatures — by design.
Putting it all together
from ragas import evaluate
from ragas.metrics import Faithfulness, ResponseRelevancy, FactualCorrectness
results = evaluate(
dataset=eval_dataset,
metrics=[
Faithfulness(),
ResponseRelevancy(),
FactualCorrectness(),
],
llm=judge_llm,
embeddings=ragas_embeddings,
)
df = results.to_pandas()
print(df[["user_input","faithfulness","answer_relevancy","factual_correctness"]])
Interpretation
| Metric | Compares | Typical failure | Higher is better? |
|---|---|---|---|
| Faithfulness | Answer ↔ Context | Hallucination (claims not in context) | Yes |
| Response Relevancy | Answer ↔ Question | Answer drifts off-topic | Yes |
| Factual Correctness | Answer ↔ Reference | Wrong facts or missing key points | Yes |
Recap
Run the notebook: 09_generator_metrics.ipynb · Next: Module 10 — Reranking (Cohere rerank-v3.5, before/after lift)