LLM-as-judge: how to use it without fooling yourself
LLM-as-judge can replace hours of human annotation — or silently validate garbage. Here's how to tell which one you're getting.
BuildPulse Team
June 10, 2026

The appeal is real, but so is the trap
You've built a RAG pipeline. You want to know if the answers are good. Writing deterministic assertions for "good" is impossible, human labeling is slow and expensive, and you have 10,000 test cases to evaluate. So you ask GPT-4 to rate the outputs. Fast, cheap, scalable — what's not to love?
The problem is that LLM-as-judge gives you a number that feels like a ground truth but is actually a reflection of whatever the judge model happens to prefer on a given Tuesday. I've watched teams optimize against an LLM judge for weeks, ship confidently, then get user complaints that contradicted every metric they'd collected. The eval wasn't measuring quality. It was measuring the judge's aesthetic preferences.
That doesn't mean you should abandon LLM-as-judge. It means you need to know what you're actually measuring.
The failure modes you need to internalize
Position bias
When you ask a judge model to compare two responses side by side, it systematically favors whichever response appears first. This isn't subtle — studies on GPT-4 as a judge have found it picks the first option at rates well above 50% when the options are actually equivalent. The model isn't lying; it's just doing next-token prediction, and position is a strong prior.
The fix is simple but most teams skip it: always run pairwise comparisons twice, swapping the order, and only declare a winner if the judge agrees in both orderings.
def robust_pairwise(judge, prompt, response_a, response_b):
score_ab = judge.compare(prompt, first=response_a, second=response_b)
score_ba = judge.compare(prompt, first=response_b, second=response_a)
# Normalize: score_ba is from B's perspective, flip it
if score_ab == "A" and score_ba == "B":
return "A" # consistent winner
if score_ab == "B" and score_ba == "A":
return "B" # consistent winner
return "tie" # inconsistent — don't count this as signal
If you're seeing a high "tie" rate from inconsistency, that's a sign your differences are too small for the judge to reliably detect — not that you should average the results.
Self-preference (model identity bias)
If you use GPT-4 to judge outputs from GPT-4 versus Claude, it will tend to prefer GPT-4's outputs. Same for Claude judging Claude. This isn't speculation; it shows up in the literature and it will show up in your evals if you don't control for it.
The implication: never use the same model family as both generator and judge when comparing systems. Use a genuinely different judge — if your app generates with Claude 3.5 Sonnet, consider GPT-4o as the judge, and vice versa. Better still, use multiple judges and look at agreement rates.
Also worth noting: when you're evaluating a single system's output against a rubric (not pairwise), self-preference is less of an issue. But if you're comparing two generations from the same model family, you're essentially asking someone to pick their own favorite child. The results are not trustworthy.
Verbosity bias
LLM judges reliably prefer longer, more elaborate answers — even when the shorter answer is more accurate, more useful, and actually better by any real-world standard. Ask an LLM judge to rate a crisp two-sentence answer versus a verbose five-paragraph one on the same question and watch it pick the paragraph version almost every time.
This creates a nasty optimization trap. If you tune your system against an LLM judge without controlling for length, you will train yourself toward bloated outputs. Your users will notice even if your eval doesn't.
Mitigation options:
- Make your rubric explicit about length-appropriateness: "A response that answers the question without unnecessary elaboration should score higher than a verbose response that repeats itself."
- Separately track a response length metric alongside your judge score. If length and score are correlated at r > 0.6, something is wrong.
- Run a sanity check: deliberately generate a long, rambling, repetitive version of a known-good answer and check whether the judge upgrades it. If it does, your rubric has a verbosity leak.
Prompt sensitivity
The framing of your judge prompt can swing scores by a surprising amount. "Rate this response from 1 to 10" produces different distributions than "Rate this response as poor, acceptable, or excellent." Asking the judge to explain its reasoning before giving a score (chain-of-thought) changes the scores. Even the order of criteria in your rubric matters.
This isn't a reason to avoid LLM judges; it's a reason to treat the judge prompt as a first-class artifact that you version, test, and don't change casually. Pin your judge prompt the same way you'd pin a dependency.
When LLM-as-judge is actually trustworthy
None of the above means it's useless. LLM-as-judge is genuinely reliable for certain things:
- Coarse-grained binary checks — did this response refuse a clearly harmful prompt? Does this output contain personally identifiable information? Is this obviously off-topic? Binary checks with clear criteria are where judges shine.
- Relative ranking within a narrow range — comparing two generations from very different approaches (e.g., a RAG pipeline versus a direct completion, or a 7B model versus a 70B model) gives meaningful signal even with the biases above.
- Catching regressions, not measuring absolute quality — if your judge score drops 15 points across a 500-question benchmark after a prompt change, something got meaningfully worse even if the absolute score is noisy.
- High-stakes criteria that are hard to formalize — "does this medical response recommend the user see a doctor for serious symptoms" is the kind of safety-relevant criterion where no deterministic check exists and the cost of getting it wrong justifies the noise.
The pattern: use LLM judges as a signal filter, not a measurement instrument. They're good at catching big problems and directional changes. They're bad at telling you your system is a 7.3 out of 10.
Calibrating against human labels
If you want to trust your judge, earn that trust the same way you'd earn trust in any test: check it against ground truth.
Here's a practical calibration process:
- Sample 100-200 cases from your eval set — ideally stratified across different question types, difficulty levels, and output lengths.
- Collect human labels on those cases. Two annotators per case, track inter-annotator agreement. If your annotators disagree more than 30% of the time, your rubric is ambiguous — fix the rubric before you fix the judge.
- Run your judge on the same cases and compute agreement with the human consensus label.
- Segment the disagreements. Don't just report an overall agreement rate. Look at where the judge is wrong: is it systematically disagreeing on short answers? On a particular topic category? On cases where the human labels themselves disagreed?
A reasonable bar: for a judge you're going to use in CI, you want >80% agreement with human labels on your specific task. Not 80% on LMSYS Arena benchmarks — 80% on your data, your rubric, your output style.
from sklearn.metrics import cohen_kappa_score
human_labels = [1, 1, 0, 1, 0, 0, 1, 1, 0, 1] # 1=good, 0=bad
judge_labels = [1, 1, 1, 1, 0, 0, 1, 0, 0, 1]
agreement = sum(h == j for h, j in zip(human_labels, judge_labels)) / len(human_labels)
kappa = cohen_kappa_score(human_labels, judge_labels)
print(f"Raw agreement: {agreement:.0%}") # 80%
print(f"Cohen's kappa: {kappa:.2f}") # accounts for chance agreement
Cohen's kappa is more honest than raw agreement because it adjusts for the baseline rate of correct guesses by chance. A kappa above 0.6 is generally considered substantial agreement; below 0.4 and you should be skeptical.
Re-run calibration whenever you change your judge model, your judge prompt, or the distribution of your eval set. A judge calibrated on your V1 product might be badly miscalibrated for V2.
Where to use deterministic checks instead
LLM judges are expensive, slow, and noisy compared to a simple assertion. A surprisingly large fraction of what teams reach for LLM judges to evaluate can be handled better with deterministic checks:
| What you want to check | Better approach |
|---|---|
| Does the response cite a specific source? | String match / regex |
| Is the output valid JSON matching a schema? | JSON schema validation |
| Does the response stay under N tokens? | len(tokenize(response)) |
| Does the response not contain a banned phrase? | String search |
| Does a SQL query parse and execute? | Actually run it |
| Does the generated code pass unit tests? | Actually run the tests |
Every one of those was a real case I've seen teams burn LLM judge calls on. Structure validation, format checks, and behavioral constraints that can be expressed as code should be expressed as code. Reserve the judge for the things that genuinely can't be — coherence, helpfulness, tone, factual accuracy without a ground-truth reference.
In CI, this also matters for speed and cost. Deterministic checks run in milliseconds and cost nothing. An LLM judge call on a 500-case eval suite at $0.01 per call is $5 — fine once, but if you're running it on every pull request, that's real money accumulating fast. At BuildPulse, we've found that teams who instrument their eval pipelines carefully tend to run them more often and catch regressions earlier; that positive loop only works if the evals are cheap enough to run without hesitation.
Putting it together
A useful mental model: think of your eval suite as a pyramid. At the base, a large layer of fast, free, deterministic checks that run on every commit. In the middle, LLM judge checks on a sampled subset — run on PRs, calibrated against human labels, with position-swapping for pairwise comparisons. At the top, periodic human review of edge cases and judge disagreements, used to recalibrate the layer below.
LLM-as-judge earns its place in that middle layer. It's genuinely irreplaceable for evaluating things that can't be expressed as code. But treated as a measurement instrument rather than a signal filter, it will tell you exactly what you want to hear — and that's the most dangerous kind of feedback there is.
Related posts