TL;DR
BLEU and ROUGE scores don't measure what you think they measure. A high BLEU score doesn't mean your LLM is good at language, it means your output matches a reference string. We'll explain why legacy metrics fail for modern LLMs, show you the alternatives (BERTScore, LLM-as-judge, G-Eval, human evaluation), when to use each, and how to build custom evaluation pipelines that actually measure what matters.
You've probably evaluated an LLM like this: generate outputs, compute BLEU score, celebrate if it's above 30.
If you have, your evaluation is lying to you.
BLEU scores measure n-gram overlap with a reference. That's not language quality, that's copy-paste fidelity. An LLM can have a stellar BLEU score while producing incoherent garbage, and vice versa.
This matters because a single point improvement in BLEU often comes from gaming the metric, not improving the model. You end up optimizing for BLEU instead of optimizing for actual quality.
Why Legacy Metrics Fail for LLMs
BLEU and ROUGE were designed for machine translation. The assumption: there's one correct way to translate a sentence. Output something close to that reference, and you're good.
LLMs generate diverse, creative outputs. A question can have many valid answers. A summary can be expressed in many ways. If your output doesn't match the reference word-for-word, BLEU penalizes you even if you're correct.
Real Example: BLEU Fails at Synonymy
Reference: "The cat sat on the mat"
Model A: "The cat sat on the mat" → BLEU = 100%
Model B: "The feline sat on the rug" → BLEU = 0%
But Model B is arguably better (more interesting vocabulary).
Another Example: BLEU Doesn't Understand Semantics
Question: "What's 2+2?"
Reference: "2 + 2 = 4"
Model A: "2 + 2 = 4" → BLEU = 100%
Model B: "The answer is four" → BLEU = ~20%
Model C: "2+2=4" → BLEU = ~40%
All three are correct. Only Model A gets credit.
BLEU scores are good at measuring translation quality (where outputs are constrained). They're terrible at measuring generation quality (where outputs are open-ended).
Understanding the Metric Landscape
Modern LLM evaluation has moved beyond BLEU. There are now multiple approaches, each with tradeoffs. Understanding when to use each is the key to building a reliable evaluation pipeline.
Metric 1: BERTScore (Semantic Similarity)
What It Is
BERTScore compares the contextual embeddings of your output and reference. Instead of counting matching n-grams, it measures semantic similarity. Two sentences saying the same thing in different words get high scores.
Pros
- Handles synonyms and paraphrasing
- Evaluates semantic correctness, not surface-level matching
- Works across languages with multilingual models
- Correlates reasonably well with human judgment
Cons
- Requires a reference (doesn't work for open-ended generation)
- Expensive (embedding computation for long documents)
- Still metric-gameable (can generate "correct semantically" but nonsensical text)
When to Use
Use BERTScore when you have reference answers and want to reward diverse but correct outputs.
import bert_score
predictions = ["The cat is sleeping", "The feline is resting"]
references = ["The cat is asleep"]
P, R, F1 = bert_score.score(predictions, references, lang="en")
# F1 scores: [0.91,0.87], Both correct, even though different
Metric 2: LLM-as-Judge (Learned Evaluation)
What It Is
Use another LLM (usually GPT-4 or Claude) to evaluate outputs. Ask it: "Is this answer correct? Why or why not?" Score based on its assessment.
Pros
- Handles open-ended evaluation (no reference needed)
- Understands nuance, context, and intent
- Can penalize hallucinations and factual errors
- Surprisingly correlates well with human judgment
Cons
- Expensive (API calls add up)
- Judge LLM has its own biases and limitations
- Can be gamed if model learns to match judge's style
- No transparency into scoring logic
When to Use
Use LLM-as-judge when you need nuanced evaluation and don't have references. It's industry standard for evaluating chat, summarization, and reasoning tasks.
from langchain.evaluation import EvaluatorChain
from langchain.chat_models import ChatOpenAI
evaluator = EvaluatorChain.from_llm_and_criteria(
llm=ChatOpenAI(model="gpt-4"),
criteria="helpfulness"
)
score = evaluator.evaluate_strings(
prediction="The capital of France is Paris",
input="What's the capital of France?"
)
# Returns: score, reasoning from GPT-4
Metric 3: G-Eval (Generative Evaluation)
What It Is
A framework where you define evaluation criteria and let an LLM score outputs step-by-step. More structured than LLM-as-judge, more flexible than static metrics.
Pros
- Customizable to your specific criteria
- Transparent reasoning (LLM explains its score)
- Handles complex, multi-dimensional evaluation
- Works for any task you can describe
Cons
- Still expensive (LLM API calls)
- Quality depends on how well you define criteria
- Can be inconsistent across evaluations
When to Use
Use G-Eval when you need custom evaluation logic that LLM-as-judge doesn't provide. Perfect for domain-specific tasks.
import anthropic
def evaluate_response(question: str, response: str, criteria: list[str]) -> dict:
client = anthropic.Anthropic()
prompt = f"""Evaluate this LLM response against the criteria.
Question: {question}
Response: {response}
Criteria:
{chr(10).join(f"- {c}" for c in criteria)}
For each criterion:
1. Assess how well the response meets it
2. Provide a score from 1-5
3. Explain your reasoning
Format your response as JSON with keys: criterion, score, reasoning"""
result = client.messages.create(
model="claude-3-sonnet",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
return json.loads(result.content[0].text)
Metric 4: Human Evaluation (Ground Truth)
What It Is
Have humans rate your outputs. It's slow and expensive, but it's ground truth.
Pros
- Most reliable evaluation method (if done right)
- Catches things automated metrics miss
- Can evaluate subjective qualities (tone, creativity)
- Provides actionable feedback for improvement
Cons
- Expensive ($1-5 per evaluation)
- Slow (days or weeks for large datasets)
- Inconsistent between raters (requires inter-rater agreement)
- Doesn't scale for continuous evaluation
When to Use
Use human evaluation as a validation gate. Evaluate a subset of outputs from all your metrics to see which correlates best with human judgment.
// Set up human evaluation through Toloka/Labeling/MTurk
const evaluationConfig = {
instructions: "Is this response helpful, accurate, and clear?",
examples: [
{ output: "Paris is the capital of France", rating: 5 },
{ output: "The capital is in the middle of the country", rating: 2 }
],
scale: [1,2,3,4,5],
minRaters: 3, // Get consensus
payPerEvaluation: 0.50
};
// Compare human ratings to automated metrics
const correlations = {
bleu: 0.42, // Weak correlation
bertscore: 0.68, // Moderate correlation
llmAsJudge: 0.79, // Strong correlation
humanEval: 1.00 // Ground truth
};
Building a Custom Evaluation Pipeline
to set up a production evaluation system that doesn't rely on BLEU:
Step 1: Define What Success Looks Like
// Not: "maximize BLEU"
// But: "generate responses that are correct, helpful, and clear"
const successCriteria = {
correctness: {
description: "Answer is factually accurate",
metric: "llmAsJudge",
threshold: 0.85
},
helpfulness: {
description: "Answer directly addresses the question",
metric: "llmAsJudge",
threshold: 0.80
},
clarity: {
description: "Answer is easy to understand",
metric: "llmAsJudge",
threshold: 0.75
},
hallucination: {
description: "No made-up information",
metric: "customGEval",
threshold: 0.95
}
};
Step 2: Implement Multi-Metric Evaluation
async function evaluateResponse(question: string, response: string) {
const results = {
timestamp: new Date(),
question,
response,
metrics: {}
};
// Run multiple metrics in parallel
const [bertscore, llmJudge, geval] = await Promise.all([
computeBERTScore(response, expectedReference),
runLLMAsJudge(question, response),
runGEval(question, response, customCriteria)
]);
results.metrics.bertscore = bertscore;
results.metrics.llmJudge = llmJudge;
results.metrics.geval = geval;
// Weighted aggregate score (you define the weights)
results.aggregateScore = (
bertscore * 0.2 + // 20% semantic similarity
llmJudge.score * 0.5 + // 50% LLM evaluation
geval.overallScore * 0.3 // 30% custom criteria
);
return results;
}
Step 3: Validate Against Human Judgment
// Periodically, sample outputs and get human evaluation
async function validateMetrics() {
const sample = await getSampleOutputs(100);
const humanRatings = await getHumanEvaluation(sample);
const correlations = {
bertscore: pearsonCorrelation(sample.map(s => s.metrics.bertscore), humanRatings),
llmJudge: pearsonCorrelation(sample.map(s => s.metrics.llmJudge.score), humanRatings),
geval: pearsonCorrelation(sample.map(s => s.metrics.geval.overallScore), humanRatings)
};
// Log which metric is most predictive of human judgment
console.log('Metric correlations with human judgment:', correlations);
// Alert if a metric diverges (something's wrong)
if (correlations.llmJudge < 0.70) {
alert('LLM-as-judge correlation has degraded. Investigate.');
}
}
Common Pitfalls When Switching Metrics
Pitfall 1: Replacing One Bad Metric with Another
Don't switch from BLEU to just LLM-as-judge. Use multiple metrics. A single metric can be gamed or fail on certain categories.
Pitfall 2: Forgetting to Validate
New metrics can feel "better" but still be wrong. Always validate against human judgment on a held-out test set before committing to a metric.
Pitfall 3: Ignoring Bias in the Judge LLM
If you use GPT-4 as a judge, your model is optimizing to be like GPT-4. It might produce correct outputs that GPT-4 doesn't recognize. Vary your judge or use multiple judges.
Pitfall 4: Not Tracking What Changed
When scores improve, understand why. Did the model actually improve, or did you just optimize for the metric? Log examples that improved and examples that got worse.
The Practical Recommendation
If you're evaluating an LLM production system, use this stack:
- For correctness: LLM-as-judge (GPT-4 or Claude) scoring "Is the answer factually correct?"
- For semantic quality: BERTScore (if you have references) or G-Eval with custom criteria
- For production safety: Custom rules (hallucination detection, prompt injection, jailbreak attempts)
- For validation: Human evaluation on a small sample every month
Ditch BLEU. It's been useful, but it's not measuring what you think it is.
The best evaluation metric is the one that correlates with what your users care about. Usually that's human judgment. Invest in understanding what your users actually value, then build metrics around that.
Building reliable LLM evaluation?
alt.qa provides evaluation infrastructure for LLM applications. Multi-metric pipelines, human validation, and continuous monitoring. No more BLEU scores.
Get evaluation infrastructure