Similarity Metrics (Embedding, BLEU/ROUGE — and Their Limits)
Introduction
The last lesson, Functional Correctness & Exact Match, hit a wall: strict == can't see that "thirty days" means 30 days, or that two differently-worded summaries are both fine. When the output isn't verifiable and exact match is too rigid, you move up the ladder to similarity metrics — graders that score how close an output is to a reference, on a continuous scale instead of pass/fail.
There are two families. Lexical metrics (BLEU, ROUGE) count overlapping words. Semantic metrics (embeddings, BERTScore) compare meaning in vector space. Both are genuinely useful — and both come with a sharp limit you must understand before you trust a single one of their numbers. This lesson is the family, how each works, and exactly where each one lies to you.

Lexical Overlap: BLEU & ROUGE
The classic metrics measure n-gram overlap — how many word-sequences the candidate and reference share. They're a mirror image of each other:
- BLEU (Bilingual Evaluation Understudy) is precision-oriented: of the n-grams I generated, what fraction appear in the reference? It adds a brevity penalty so you can't game it by emitting one safe word. It's the long-standing default for machine translation.
- ROUGE (Recall-Oriented Understudy for Gisting Evaluation) is recall-oriented: of the n-grams in the reference, what fraction did I cover? That makes it the default for summarization, where coverage matters more than exact phrasing.
Under the hood, both are just counting:
from collections import Counter
def ngrams(text, n=1):
toks = text.lower().split()
return Counter(tuple(toks[i:i + n]) for i in range(len(toks) - n + 1))
def overlap(cand, ref, n=1):
c, r = ngrams(cand, n), ngrams(ref, n)
shared = sum((c & r).values()) # clipped n-gram matches
bleu_precision = shared / max(1, sum(c.values())) # BLEU: of MY n-grams, how many are in the ref?
rouge_recall = shared / max(1, sum(r.values())) # ROUGE: of the REF's n-grams, how many did I cover?
return round(bleu_precision, 2), round(rouge_recall, 2)
overlap("returns accepted within 30 days", "you have 30 days to return it")
# -> (0.40, 0.29) a CORRECT paraphrase, yet only "30" and "days" line upThat's the whole idea: BLEU asks “is what I said in the reference?”, ROUGE asks “did I say what's in the reference?” Both are fast, deterministic, cheap, and interpretable — and, as the result hints, both are about to disappoint you.
The Lexical Trap: Blind to Meaning
Notice what just happened: "returns accepted within 30 days" and "you have 30 days to return it" are the same answer, yet they share almost no n-grams, so BLEU/ROUGE score them low. That's a false fail — and it's the core weakness of lexical metrics: they measure surface form, not meaning. They're blind to synonyms, paraphrases, and word order, which is why they correlate only weakly with human judgment on open-ended text. (METEOR was invented partly to patch this, adding stemming and synonym matching for better correlation — but it's still fundamentally lexical.)
Worse, the failure runs both ways. Because they only count word overlap, you can add a few negation words to a reference and produce a sentence with the opposite meaning that still scores a high BLEU — almost every n-gram is preserved. Lexical metrics can fail a correct answer and pass a wrong one. That's a shaky foundation to gate a release on.
Semantic Similarity: Embeddings & BERTScore
To get past surface form, compare meaning instead of words. Embed both texts into vectors (the same embedding machinery from the RAG container) and measure their cosine similarity — paraphrases land close together, so "returns within 30 days" ≈ "you have 30 days to return it" even with no shared words.
BERTScore does this at the token level: it embeds each token in context (so “bank” differs in “river bank” vs “bank account”), matches candidate tokens to reference tokens by cosine similarity, and aggregates into precision/recall/F1. BLEURT goes further — a learned metric, fine-tuned to predict human ratings. These semantic and learned metrics correlate notably better with human judgment than n-gram overlap, and they fix the paraphrase problem outright.
This feels like the answer. It mostly isn't — and the reason is the most important idea in this lesson.
See It: Does the Metric Agree with the Truth?
Put all three graders side by side. For each pair below, you'll see the exact-match, n-gram, and embedding scores — then reveal whether the candidate is actually a good answer, and watch which graders got fooled.

The pattern is unmistakable. Embeddings rescue the paraphrase that n-grams failed — a real upgrade. But the negation, the wrong number, and the fluent hallucination sail past both with high scores. Every grader here answers “how similar?” — and none of them answers “is it right?”
The Deeper Trap: Similar ≠ Correct
Here is the limit that catches every metric in this lesson: similarity is not correctness. A high BLEU, ROUGE, or BERTScore tells you an output resembles the reference — not that it's true. Two failure modes make this concrete:
- The antonymy problem. In embedding space, “safe” and “not safe” (or “best” and “worst”) are neighbours — they appear in the same contexts. So a negation flip, which inverts the meaning, barely moves the cosine score. The embedding shrugs at the one word that matters.
- Fluent falsehoods. A confident, well-formed wrong answer (“completed in 1925” for a fact that's 1889) is semantically coherent and lexically close — so it scores high. The metric "doesn't distinguish confident falsehoods from accurate information."
Watch all four rungs of the ladder grade the same negation — and only the last one catch it:
from anthropic import Anthropic
client = Anthropic()
# The grader LADDER for one (candidate, reference) pair -- each rung looser & smarter:
cand = "The medication is not safe for children."
ref = "The medication is safe for children."
exact = cand.strip().lower() == ref.strip().lower() # -> False
bleu = overlap(cand, ref)[0] # -> 0.83 almost all words shared!
cos = cosine(embed(cand), embed(ref)) # -> 0.94 antonyms are neighbours!
# ...all three say "very similar" -- but the candidate means the OPPOSITE.
# For CORRECTNESS (not similarity), escalate to a judge -- Section 3:
verdict = client.messages.create(
model="claude-opus-4-8", max_tokens=10,
system="Reply CORRECT or WRONG: does the candidate match the MEANING of the reference?",
messages=[{"role": "user", "content": f"Reference: {ref}\nCandidate: {cand}"}]
).content[0].text # -> "WRONG" it caught the negationExact match, BLEU, and embedding cosine all call the negated sentence “very similar.” Only the LLM judge, asked about meaning and correctness rather than overlap, returns WRONG. That's the boundary of this lesson — and the whole reason LLM-as-a-Judge (the next section) exists.
Two More Gotchas: Reference-Dependence & Anisotropy
Even setting truth aside, two practical traps bite:
- They're reference-based. Every metric here needs a gold answer to compare against, and implicitly assumes there's one right answer. For open-ended tasks with many valid responses (the lesson of Why Evaluating LLMs Is Uniquely Hard), that assumption breaks — your output can be excellent and simply unlike your single reference.
- Cosine is hard to threshold. Embedding spaces are anisotropic — vectors cluster in a narrow cone, so cosine scores bunch up (often everything lands around 0.7–0.9). Is 0.82 a pass? There's no clean answer, the band shifts with every embedding model, and even unrelated sentences can score deceptively high. A raw cosine number is far mushier than it looks.
These don't make the metrics useless — but they're why a similarity score is a signal, never a verdict.
So When DO You Use Them?
Don't throw them out — place them correctly. Similarity metrics are fast, free, deterministic, and interpretable, which earns them real jobs:
- Machine translation → BLEU, where good references exist and adequacy is largely lexical.
- Summarization coverage → ROUGE, to check you included the key content.
- Cheap regression signals → a quick "did this output drift far from the known-good reference?" check in CI, run on every change.
- One signal among many → combined with deterministic checks and (where needed) a judge — never the sole gate.
Think of it as a ladder, the one running through this whole section: exact match → n-gram (BLEU/ROUGE) → embedding / learned (BERTScore/BLEURT) → LLM judge. Each rung is looser, smarter, more expensive, and less interpretable than the last. Start at the bottom; climb only as far as the task forces you. Most teams should reach for similarity metrics as a cheap early signal — and reach for a judge the moment correctness, not resemblance, is what they actually need to measure.
🧪 Try It Yourself
Make the limits undeniable with a two-minute experiment on a pair from your own system:
- Take a (candidate, reference) pair and compute a rough word-overlap (even by hand: shared words ÷ total) and, if you can, an embedding cosine. Note both numbers.
- Make a correct paraphrase of the candidate — reword it completely without changing the meaning. Recompute. Watch the n-gram score crash while the meaning is unchanged. (False fail.)
- Make a negation or wrong-number flip — insert a not, or change one number — so the answer is now wrong. Recompute. Watch both scores stay high. (False pass.)
- Ask yourself: could either number, alone, have caught step 3? If not, you've just proven why similarity ≠ correctness — and why you'll need a judge for the things that actually matter.
Keep the pair; it's a perfect demo for the next time someone proposes gating a release on BLEU.
Mental-Model Corrections
- “A high BLEU/ROUGE means a good output.” No — they measure lexical overlap, not quality. A correct paraphrase scores low; a negated sentence scores high. Weak correlation with human judgment on open-ended text.
- “Embeddings fix the problem.” They fix the paraphrase problem (a real upgrade), but inherit the antonymy problem — negations and antonyms stay close in vector space — and happily pass fluent falsehoods.
- “High similarity = correct.” The central trap of the whole lesson. Similarity is resemblance to a reference; correctness is truth. No metric here measures truth.
- “These are objective ground-truth metrics.” They're reference-based — they need a gold answer and assume there's only one. For open-ended tasks, that assumption fails.
- “A cosine of 0.82 clearly means it's good.” Cosine is hard to threshold — anisotropic spaces make scores cluster, and the band shifts per model. Treat it as a fuzzy signal, not a verdict.
- “BLEU and ROUGE are obsolete.” No — they're still the right, cheap tool for MT, summarization coverage, and fast regression signals. Just never the sole gate, and never for correctness.
Key Takeaways
- When exact match is too strict, climb to similarity metrics — continuous scores of how close an output is to a reference.
- Lexical (BLEU/ROUGE) count n-gram overlap: BLEU = precision (MT), ROUGE = recall (summarization). Fast and deterministic, but blind to meaning — they fail correct paraphrases and can pass negated nonsense.
- Semantic (embeddings, BERTScore, BLEURT) compare meaning via cosine of contextual embeddings — they fix paraphrases and correlate better with humans, but inherit the antonymy problem and pass fluent falsehoods.
- The one idea to keep: similar ≠ correct. Every metric here measures resemblance, not truth; none catches a negation, a wrong number, or a confident hallucination reliably.
- They're also reference-based (assume one right answer) and cosine is hard to threshold (anisotropic clustering). Treat their output as a signal, not a verdict.
- Use the ladder: exact → n-gram → embedding/learned → LLM judge, each looser, smarter, and costlier. Similarity metrics are great cheap signals for MT, summarization, and regression — but the moment you need correctness, escalate to a judge (next section).