AI Evaluation / Foundations
Using a model to grade model output at scale.
Reviewed by Yuvaraj
When you evaluate a summarizer, a chatbot, or a RAG system, there is usually no single correct string to compare against, a good answer can be phrased a hundred ways, so metrics like exact match or BLEU miss the point. LLM-as-judge uses a strong language model to read a candidate output and grade its quality against a rubric, standing in for a human rater at a fraction of the cost and latency. Used well, it lets you score thousands of open-ended responses per hour; used naively, it launders the judge's own biases into numbers that look objective. This lesson covers how a judge actually produces a score, why pairwise comparison usually beats pointwise scoring, the biases that quietly distort verdicts, and the calibration step that must happen before you trust any of it.
Reference-based metrics assume you have a gold string and can measure overlap with it: exact match for extractive QA, BLEU/ROUGE for translation and summarization, pass@k for code that runs against tests. The moment the task is open-ended, "was this explanation helpful?", "is this summary faithful to the source?", "which of these two chatbot replies is better?", overlap metrics break down, because two excellent answers can share almost no tokens.
A judge fills that gap. Mechanically, it is just a prompted LLM: you give it the task instructions, the input, a rubric, and one or more candidate outputs, and it returns either a score or a preference plus a short rationale. Two protocols dominate, and choosing between them is the first real design decision.
Pointwise (also called direct or absolute scoring) shows the judge one output and asks for a number against a rubric, for example a 1–5 Likert scale for helpfulness. Pairwise shows two outputs for the same input and asks which is better, with ties allowed. The distinction matters because LLMs, like people, are far better at relative judgments than absolute ones.
Ask about this lesson, or about anything in AI. Answers cite the lessons they draw on.
Finished this lesson?
Mark it complete to earn XP, keep your streak, and schedule a review.
Pairwise verdicts are local ("A beat B"). To turn a pile of them into a global leaderboard you fit an aggregation model. The Bradley–Terry model estimates a latent strength per system such that
which is the logistic form behind the Elo-style ratings reported by Chatbot Arena. Pointwise scoring needs no such machinery, which is part of its appeal for continuous monitoring, but that convenience is exactly why teams over-trust an uncalibrated absolute number.
Why relative beats absolute
Asking "is this answer a 4 or a 5?" forces the judge to invent a fixed internal scale and hold it steady across thousands of unrelated examples, something LLMs do badly. Asking "is A better than B?" only requires a comparison on the two things in front of it, which is a far easier and more stable task. When you can afford the extra calls, prefer pairwise.
A judge prompt has a predictable anatomy, and each part controls a specific failure mode:
Ask for the rationale before the verdict so the label is conditioned on the reasoning rather than rationalized after the fact, but treat that rationale as a debugging artifact, not proof the verdict is correct.
A judge is an LLM, so it inherits systematic biases. These are measurable and, mostly, mitigable, but only if you look for them.
| Bias | Symptom | Mitigation |
|---|---|---|
| Position bias | The answer shown first (or, for some models, second) wins more often than content justifies | Score both orders, (A, B) and (B, A); accept a winner only if it wins both, otherwise record a tie; report the swap-consistency rate |
| Verbosity bias | Longer, more elaborate answers score higher regardless of correctness | Instruct the judge to ignore length; length-match candidates where possible; explicitly penalize unsupported padding |
| Self-preference bias | The judge rates outputs from its own model family higher | Use a judge from a different family than the systems under test, or a panel of judges from multiple vendors |
| Rubric sensitivity | Small wording changes in the prompt shift scores noticeably | Freeze and version the rubric; add few-shot anchors; pin temperature to 0; require structured output |
| Central tendency / leniency | Pointwise scores pile up at 4–5 with little discrimination between systems | Prefer pairwise; or use anchored rubrics with a concrete descriptor for each score level |
Here is a concrete pairwise judge prompt. The rubric puts correctness first and explicitly instructs the judge to ignore length, a direct defense against verbosity bias.
System:
You are an impartial evaluator. Compare two AI assistant responses to the
same user question and decide which is more helpful and correct.
Judge on these criteria, in order of importance:
1. Correctness, factual and technical accuracy.
2. Relevance, directly addresses the user's actual problem.
3. Completeness, covers the root cause and a working fix.
4. Clarity, well organized and easy to follow.
Rules:
- Do NOT let response length affect your decision. A shorter, correct
answer beats a longer answer that is padded or partly wrong.
- Do NOT favor a particular position. Judge only on the criteria above.
- If the two responses are of equal quality, output "tie".
Return strict JSON: {"reason": "<one sentence>", "verdict": "A" | "B" | "tie"}
[USER QUESTION]
Why does my Python multiprocessing script hang when a worker puts a very
large object on a multiprocessing.Queue and the parent then calls join()?
[RESPONSE A]
multiprocessing.Queue sends objects through an OS pipe with a bounded
buffer. A large object does not fit the buffer, so the worker's put()
blocks in a background feeder thread until the parent reads the data. If
the parent calls join() before draining the queue, the worker can never
finish and the parent waits forever, a deadlock. Fix it by draining the
queue with get() in the parent before join(), or by using a sentinel value
so the consumer reads until the producer signals completion.
[RESPONSE B]
This is almost always caused by Python's Global Interpreter Lock, which
prevents two processes from running at once and serializes all your I/O.
Because the GIL is held while the large object sits in memory, join()
cannot acquire it and the program stalls. The most reliable fix is to
switch from multiprocessing to the threading module, which avoids pickling
overhead entirely, and to wrap your queue calls in try/except so the
interpreter can recover. You may also want to raise your system swap and
call gc.collect() before joining.
Response B is longer and more confident, but it is wrong: the GIL is per-process and does not span processes, threading would not fix a pipe-buffer deadlock (and reintroduces GIL contention), and swap/gc.collect() are irrelevant. A well-designed judge picks the shorter, correct answer:
{
"reason": "A correctly identifies the bounded pipe buffer and feeder-thread deadlock and gives a working drain-before-join fix, whereas B misattributes the hang to the GIL and proposes changes that do not address the buffer.",
"verdict": "A"
}
Now test for position bias by swapping the order. Re-run the exact same evaluation with the candidates reversed, so the correct answer is now in position 2:
{
"reason": "The response explaining the pipe buffer and feeder thread is technically correct; the GIL explanation is not.",
"verdict": "A"
}
The winner is stable under the swap, so this verdict is decisive and not an artifact of position. Had the judge instead picked "whatever is in position 1" both times, favoring B in the original order and A after the swap, that flip would expose position bias, and the correct move is to record the pair as a tie and count it against the judge's swap-consistency rate. In practice you always run both orderings and only credit a win when the two agree.
None of the above means anything until you know the judge agrees with humans. Before trusting a judge, build a human-labeled gold set of a few hundred representative examples, ideally labeled by two or more raters so you can measure human–human agreement as your ceiling, then run the judge on it and compare.
Raw agreement (the fraction of examples where the judge's label matches the human's) is the headline number, but it is inflated when one label dominates. Cohen's kappa corrects for chance:
where is the observed agreement and is the agreement you would expect if both raters labeled at random according to their observed marginal rates. Interpret against the human–human ceiling on the same task, not against 1.0: the MT-Bench study found GPT-4's agreement with human preferences was over 80%, on par with the agreement between two independent humans, which is the bar that actually matters. For pointwise judges, also check rank correlation (Spearman or Kendall) between judge scores and human scores, since a judge can be uniformly too generous yet still rank systems correctly.
Set an agreement threshold before you look at the results, and only promote a judge that clears it on held-out data.
Common mistakes