Evaluate LLM Outputs with Metrics
Learn to evaluate LLM outputs with metrics in this hands-on Applied AI engineering tutorial. Step-by-step guidance, practical exercises, and troubleshooting tips.
Focus: evaluate llm outputs with metrics
You've built a brilliant LLM-powered feature — summarization, extraction, even a RAG pipeline — but how do you know the output is actually good? One "looks fine" sample isn't evidence. Subjective eyeballing doesn't scale, breaks in production, and hides regressions until users complain. This lesson gives you the toolbelt to evaluate LLM outputs with metrics — objective, repeatable numbers you can put in CI, compare across model versions, and trust. You'll move from gut feeling to evidence-driven AI engineering.
The problem this lesson solves
Every LLM call is a probabilistic system. The same prompt can produce excellent, mediocre, or flat-out wrong results. Without metrics, you're flying blind. The pain is real:
- Manual review doesn't scale. Reviewing 100 outputs takes 30 minutes. At 10,000 daily calls, you're sunk.
- Minor regressions silently destroy quality. A prompt tweak that improves one case might degrade another. You won't notice without a regression harness.
- Cost/quality trade-offs are invisible. A cheaper model might be 95% as good — but you can't prove it without numbers.
- No shared language. "The output feels okay" is not a requirement you can ship against. Metrics give you a common vocabulary.
When you evaluate LLM outputs with metrics, you create an early-warning system for quality. A PR that changes a prompt or model now comes with measured before/after numbers. Our goal: a repeatable, automated harness you can trust.
Core concept / mental model
Think of LLM evaluation like a quality-control checklist in manufacturing. You wouldn't ship a car without measuring dimensions and torque specs. LLM outputs need similar inspection.
Definition: evaluating LLM outputs with metrics means converting the quality of freeform text into quantifiable scores using defined measurement strategies. Those strategies fall into two camps:
1. Statistical metrics (deterministic)
These compare generated text to a reference using string-based algorithms. They're fast, free, and reproducible. Examples: exact match (EM), BLEU, ROUGE, chrF. They measure overlap, n-grams, or edit distance. But they miss semantics — synonyms, rephrasing, and deep meaning.
2. LLM-as-judge (semantic)
A separate, often more powerful LLM scores the output against rubrics — faithfulness, coherence, relevance. It understands meaning but costs tokens, latency, and bias. It's the modern standard for sophisticated evaluation.
Think of EM / ROUGE as a ruler — precise but limited. LLM-as-judge is a human expert — nuanced but slower and more expensive. The skill is choosing the right tool for the job.
💡 Pro tip: Start with statistical metrics for smoke tests, then layer in LLM-judge for quality-critical paths. The cheap check catches the obvious first.
How it works step by step
Any evaluation harness follows the same pipeline, regardless of metric. Let's walk it:
- Define the task and criteria. What does "good" mean for your use case? For a summarization task: faithfulness (does it stay true to source?), relevance, conciseness. For extraction: exact value correctness, formatting.
- Create a golden dataset. Collect (n) inputs — ideally 20–50 for a baseline — with human-written reference outputs. This is your ground truth.
- Generate model outputs. Run your LLM on the dataset with a fixed temperature (0 or low) for determinism.
- Compute metrics. Apply your chosen metrics to compare each prediction to the reference (or judge for LLM-as-judge).
- Aggregate. Average the scores across the dataset. This gives you a single quality number per metric.
- Set thresholds. Define a pass/fail line. For example, ROUGE-L ≥ 0.60 means acceptable.
- Automate and monitor. Wire this into CI so every prompt/model change runs the harness and blocks regressions.
Cause → effect: better inputs (ground truth, clear rubric) → more trustworthy metrics → safer model changes.
Hands-on walkthrough
Let's build a working evaluation script in Python. We'll use evaluate from Hugging Face — a clean library that gives us common metrics out of the box.
Setup: pip install evaluate datasets (and openai if you want the LLM-judge part).
Statistical metrics with BLEU and ROUGE
Here's a complete script to compare two candidate summaries against a reference:
from evaluate import load
# Load the ROUGE metric once
rouge = load("rouge")
references = [
"The cat sat on the mat and looked at the sun.",
"AI models need careful evaluation to prevent silent regressions."
]
predictions = [
"A cat on a mat watching the sun.",
"Careful evaluation prevents silent regressions in AI models."
]
# Compute ROUGE-1 and ROUGE-L
results = rouge.compute(
predictions=predictions,
references=references,
use_aggregator=True
)
print("ROUGE-1 F1:", round(results["rouge1"], 3))
print("ROUGE-L F1:", round(results["rougeL"], 3))
Expected output:
ROUGE-1 F1: 0.889
ROUGE-L F1: 0.889
The high F1 reflects strong n-gram overlap — but it doesn't capture the semantic shift ("sun" missing from prediction 1). This is why statistical metrics are necessary but not sufficient.
LLM-as-judge with prompt-based scoring
Now let's use an LLM to score the same summaries for faithfulness and relevance. We'll write a small judge using the openai client:
import os
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def judge_output(source, generation, rubric, criteria):
prompt = f"""You are a strict evaluator. Given the source text and a model generation,
rate the generation on a scale of 1–5 for each criterion: {', '.join(criteria)}.
Source: {source}
Generation: {generation}
Rubric: {rubric}
Return a single JSON object with keys as criteria and integer values."""
resp = client.chat.completions.create(model="gpt-4o", messages=[
{"role": "user", "content": prompt},
], temperature=0)
return resp.choices[0].message.content
# Example
source = "The cat sat on the mat and looked at the sun."
generation = "A cat on a mat watching the sun."
rubric = "1 = totally wrong, 5 = perfectly faithful and relevant."
print(judge_output(source, generation, rubric, ["faithfulness", "relevance"]))
Expected output (illustrative):
{"faithfulness": 5, "relevance": 5}
Note: you must parse the LLM's JSON response robustly (using json.loads with fallbacks). For a production harness, enforce a JSON schema via function calling.
A complete batch evaluation loop
Here's a reusable pattern:
from evaluate import load
import json
# Golden dataset: list of (input, reference)
GOLDEN = [
("The stock jumped 5% on strong earnings.", "Stock rose 5% after better-than-expected earnings report."),
("Company X acquired Y for $2B.", "X bought Y for two billion dollars.")
]
# Mock generation (in reality, your LLM call)
def generate(text):
return text.split("on ")[0] + "after " + text.split("on ")[1] # hacky example
references = [ref for _, ref in GOLDEN]
predictions = [generate(inp) for inp, _ in GOLDEN]
# Compute metrics
bleu = load("bleu")
rouge = load("rouge")
print("BLEU:", bleu.compute(predictions=predictions, references=references))
print("ROUGE-L:", rouge.compute(predictions=predictions, references=references))
⚠️ Remember: Never evaluate on the training data or your small test set only. Always set aside a held-out golden set you never tune against.
Compare options / when to choose what
The metric landscape is wide. Here's a practical cheat sheet:
| Metric | Type | Pros | Cons | Use when |
|---|---|---|---|---|
| Exact Match | Statistical | Simple, zero cost | Too strict, fails on synonyms | FAQs, code snippets, IDs |
| ROUGE | Statistical | Good for summaries, word-overlap | Ignores semantics | Summarization, headline gen |
| BLEU | Statistical | Industry-standard for MT | Prefers precision, weak for creative text | Translation, short text gen |
| chrF | Statistical | Handles morphology | Less known | Multilingual |
| LLM-as-judge | Semantic | Understands meaning, customizable | Cost, latency, bias | High-stakes QA, long-form content |
| Human eval | Semantic | Gold standard | Slow, expensive | Final acceptance tests |
Variations to note:
- Embedding similarity (e.g., cosine similarity of
text-embedding-3-small) is a middle ground — faster than LLM-judge, more semantic than n-grams. - LLM-as-judge with pairwise comparison (A vs B) often outperforms absolute scoring — ask which output is better.
- Custom rubric scoring — one LLM call that returns scores for multiple criteria (faithfulness, coherence, etc.) streamlines evaluation.
Decision rules
- Need speed and zero cost? → statistical
- Need semantic correctness? → LLM-judge
- Can afford human review? → hybrid: statistical to filter, human to verify
Troubleshooting & edge cases
Even the best harness hits snags. Here are the common ones and how to fix them.
💡 Pro tip: Always version your evaluation dataset alongside your code. A change in golden set makes historical metrics incomparable.
1. Empty or near-empty predictions
If your LLM returns empty strings, evaluate can throw ValueError. Fix: filter out empty predictions or set a default.
# Robust filtering
predictions = [p if p.strip() else "[NO OUTPUT]" for p in predictions]
# or
non_empty = [(p, r) for p, r in zip(predictions, references) if p.strip() and r.strip()]
2. Mismatched lengths
predictions and references must have same length. Check before computing:
assert len(predictions) == len(references), f"{len(predictions)} != {len(references)}"
3. LLM-judge returning malformed JSON
Set response_format={"type": "json_object"} in the API call, and wrap parsing in try/except:
import json
try:
scores = json.loads(response_text)
except json.JSONDecodeError:
print("Judge failed; retrying or falling back to heuristic.")
scores = {"faithfulness": 0, "relevance": 0}
4. Scores too high or too low to differentiate
If all scores cluster near 0.9 (or 0.1), your metric is not discriminating. Symptoms: your LLM is too similar (try a different model), or your rubric is too coarse. Fix: use a 10-point scale, or switch to pairwise comparison.
5. Non-determinism in generation
Run with temperature=0 to reduce variance, but note complete determinism isn't guaranteed. For stable evaluation, average over multiple runs (seed-based sampling).
What you learned & what's next
Solid progress. Recap what you now command:
- You can explain why metrics matter — objectivity, regression detection, and cost/quality trade-offs.
- You built a golden dataset and computed ROUGE/BLEU with the
evaluatelibrary. - You implemented an LLM-as-judge with a custom rubric.
- You can compare metrics and choose the right one for the job.
Key takeaways to carry forward:
- Metrics turn subjectivity into engineering. Every prompt change should be measured.
- Statistical metrics are fast but shallow; LLM-judges are deep but costly. Use both layers.
- Ground truth quality beats metric complexity. Garbage in, garbage out.
- Automate evaluation in CI to catch regressions early.
- Always set thresholds; otherwise metrics are just numbers.
- Beware judge bias; use diverse datasets and consider human oversight.
Your next step in the Applied AI engineering path is tracking experiments with MLflow — where you'll log these metrics systematically, compare runs, and manage model lifecycles. You're building a professional LLM engineering toolkit.
✅ Check off: You've achieved both learning objectives — understanding and hands-on application. Now go measure something!
Practice recap
Try building a mini evaluation harness for a simple "headline generation" task. Create 10 golden pairs, generate outputs with your favorite model (temperature 0), compute ROUGE and then a simple LLM-judge rubric on 3 criteria. Compare the two approaches and note which ones flag a specific semantic error you plant. This hands-on comparison cements the trade-offs you just learned.
Common mistakes
- Using only one metric (e.g., ROUGE) to judge quality — misleading on semantic tasks; combine statistical + LLM-judge.
- Forgetting to set
temperature=0during evaluation, causing unstable outputs and unreliable scores. - Comparing metrics across different versions of your golden dataset — always version your evaluation set.
- Ignoring empty predictions / mismatched list lengths — crashes the
evaluatelibrary; filter first. - Trusting LLM-as-judge JSON output without robust parsing — handle
json.JSONDecodeErrorand retry.
Variations
- Use embedding-based cosine similarity as a fast semantic check —
sentence-transformersor OpenAI embeddings. - Implement pairwise LLM-judge (A vs B) for more stable relative ranking instead of absolute scoring.
- Adopt a hybrid flow: statistical metrics as a cheap filter, LLM-judge for borderline cases, human review for final acceptance.
Real-world use cases
- CI gate in a RAG pipeline — block PRs if average ROUGE-L drops below 0.65 on a golden set.
- A/B testing two LLM vendors — use LLM-as-judge with a rubric to score 100 samples for faithfulness and tone.
- Monitoring a summarization API in production — daily batch evaluation alerts when faithfulness dips under 4.0/5.0.
Key takeaways
- Metrics turn subjective LLM quality into objective, comparable numbers.
- Statistical metrics (BLEU, ROUGE) are fast and free but miss semantics; LLM-as-judge understands meaning at a cost.
- A golden dataset with reference outputs is the foundation of trustworthy evaluation.
- Set thresholds and automate evaluation in CI to catch regressions early.
- Always handle edge cases: empty outputs, mismatched lengths, malformed judge responses.
- Combine metrics strategically — cheap filters + deep semantic checks for production-grade quality.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.