AI Engineer Dojo Contents
Chapter 5

LLM-as-Judge

When there's no answer key and "good" is a matter of judgment, you ask a capable model to do the grading. It's the technique an evaluation engineer uses most, and without checks it will hand you confident numbers that are wrong.

Case study

PatchProof grades its grader

A dev-tools startup built PatchProof, which reads a pull request and posts a verdict: merge or needs work. There's no answer key for a code review, so they graded PatchProof with an LLM judge that scored each review from 1 to 5. After a model upgrade the average rose to 4.1, and the team was ready to launch. Then they checked the judge.

Two staff engineers labeled 100 real pull requests on their own and settled their disagreements. Against those labels the judge agreed 91% of the time overall, but only 58% on the 24 security-related changes, which were the reviews that mattered most. Swapping the order of the two reviews being compared flipped 8 of 40 verdicts. The new model also wrote longer explanations, and the judge rewarded the length: same correctness, score up from 3.5 to 4.2.

The team switched to a yes-or-no "safe to merge" rubric that said not to reward length, asked for a short reason before the verdict, and ran every comparison in both orders. Agreement on security changes rose to 84%. The launch was held until the security reviews improved.

Here's the problem in miniature. A support bot replies, "I've refunded your order; you'll see it in 3 to 5 business days." Is that good? There's nothing to diff it against. It depends on whether it's accurate, on-brand, complete and safe, and you have ten thousand replies a day. So you give each reply and a rubric to a strong model and ask it to grade. The rest of this chapter is about making that grade something you can trust.

Pointwise and pairwise

A pointwise judge takes one answer and a rubric and returns a score or label. That's useful for tracking a metric over time. A pairwise judge takes two answers and picks the better one. For "did my change help?" pairwise usually works better, because models, like people, compare more reliably than they score in the abstract. Suppose quality really did improve a little between two prompts. A 1-to-5 judge may give both a 4, and you'd conclude nothing changed. Head to head, the same judge picks the new version 7 times out of 10.

Name to know: G-Eval

If an interviewer asks how you'd build a judge, G-Eval is the technique to mention. You turn the rubric into explicit evaluation steps and have the model score through them instead of giving an unexplained verdict. The original paper used chain-of-thought; in production, asking for a short written reason or a structured result per criterion is easier to audit. DeepEval and similar frameworks include it.

Known biases, with numbers

Position bias. Judges tend to prefer whichever answer they read first. One team found the judge chose the first answer on 9 of 50 close pairs, an 18% bias rate. The fix is to run both orders and only keep verdicts that agree.

Verbosity bias. A short, correct reply scored 3.6 out of 5, and a padded reply with the same facts scored 4.3. If your rubric doesn't say "don't reward length," you'll slowly optimize toward waffle.

Self-preference. Judges tend to like outputs in their own model family's style. If the judge and the system are the same model, be skeptical of "the new version is better" until you've checked.

Score compression. On a 1-to-10 scale, almost everything lands between 6 and 8. That's the best argument for small label sets like yes/no or pass/fail.

Who checks the judge?

Compare the judge with human labels, and look hardest at the difficult cases where decisions get made. A judge that agrees 95% on easy cases and 55% on hard ones can report a comfortable 87% overall and still be useless to you. Report agreement on the hard slice.

Try it · ~20 minFree · mock judge

Build a pairwise judge and measure its position bias

Write a pairwise judge, then measure its position bias by swapping the order of each pair. It runs free on a mock; set USE_REAL_API = True with a key and the same code grades with a real model. You're aiming for one number, "flipped on N pairs," and a decision about whether you'd trust the raw verdicts.

Lab code · runs free (mock judge by default)

USE_REAL_API = False        # free mock by default; True needs a key

if USE_REAL_API:
    import anthropic
    from pydantic import BaseModel
    client = anthropic.Anthropic()
    class Verdict(BaseModel):
        reason: str        # reasoning BEFORE the label
        winner: str        # "A" or "B"
    def compare(question, a, b):
        r = client.messages.parse(
            model="claude-opus-4-8", max_tokens=400,
            messages=[{"role": "user", "content":
                f"Pick the more accurate, NOT-padded reply. Reason, then winner."
                f"\n\nQ:{question}\n\nA:\n{a}\n\nB:\n{b}"}],
            output_format=Verdict)
        return r.parsed_output.winner
else:
    # Mock with a built-in tilt: it leans toward slot A on close calls
    def compare(question, a, b):
        return "A" if len(a) >= len(b) else "B"   # length/position-ish bias

def robust_compare(question, x, y):
    """Run both orderings; trust only when they agree."""
    x_as_A = (compare(question, x, y) == "A")   # x in slot A
    x_as_B = (compare(question, y, x) == "B")   # x in slot B
    if x_as_A == x_as_B:
        return "x" if x_as_A else "y"
    return "biased"                             # flipped with position

PAIRS = [   # (question, reply_x, reply_y) — close in quality
    ("refund?", "Refunded; 3-5 days.", "Refunded today, arrives in 3-5 days."),
    ("hours?",  "9 to 5, Mon-Fri.",    "We're open 9-5 Monday through Friday."),
    ("reset?",  "Use 'Forgot password'.", "Click the 'Forgot password' link."),
]
flips = sum(robust_compare(q, x, y) == "biased" for q, x, y in PAIRS)
print(f"position-biased on {flips}/{len(PAIRS)} pairs ({flips/len(PAIRS):.0%})")
Worked solution

The mock picks the longer reply no matter which slot it's in, so its verdict follows the answer and robust_compare returns a clear winner every time. Now change the mock so it leans on position instead:

    def compare(question, a, b):
        return "A"        # always pick the first slot — pure position bias

Run it again and every pair comes back "biased": 3 of 3, because the winner changes as soon as you swap the order. One judge call gives you a verdict. Two calls give you a verdict and a check on it, and the pairs that disagree can go to a person. Real models usually flip on something like 15 to 20% of close pairs, and the only way to know your number is to measure it.

Running case · Meridian × Remi

Remi's helpfulness dashboard reads 4.2, up from 3.8 after a prompt change. Meridian checks the judge against 120 replies that people have labeled. It agrees 88% overall but only 56% on the hard refund cases, and most of the gain comes from longer replies, which score 3.7 before padding and 4.4 after, with the same facts. A pairwise run with swapped order shows no real change. The billing launch is held.

If you're starting out

"How would you know your LLM judge is right?" comes up in almost every evaluation interview, including entry-level ones. A strong answer has three parts: compare it with human labels on the hard cases, correct for the known biases (swap the order, tell it not to reward length, use a small label set), and check again whenever the rubric or the model changes.

This lab makes a good portfolio piece if you extend it. Run a real judge on a public set of paired answers, report the flip rate, and show how the swap-and-agree wrapper changes the results. Few new grads show a measured bias rate, and it's the kind of detail reviewers remember.

If you run the team

Your team will use a model judge because it can grade thousands of open-ended answers cheaply. The risk is a precise-looking dashboard number that nobody has checked. If quality has sat at "8.4 out of 10" for months, ask one question: how often does the judge agree with people on the difficult cases? If no one has measured it, the 8.4 is something a model produced, and it might just reward long answers.

A good answer sounds like: "We had people grade 100 cases, and the judge agrees 88% on the ambiguous ones. We run comparisons in both orders and check again after every model change." Be wary of "it's a very strong model," and of setups where the same model is both the system and its own judge with nothing to cross-check it.

Quiz · Chapter 5

  1. A pointwise judge gives v1 and v2 the same 4.0, but pairwise prefers v2 70% of the time. Most likely:
  2. Swapping answer order flips the winner on 20% of pairs. What does that mean, and what's the fix?
  3. A judge agrees 95% on easy cases and 55% on hard ones; the set is 80% easy, so it reports 87%. What's wrong with "87%"?
  4. Why prefer a yes/no "grounded" label over a 1-to-10 faithfulness score?
  5. The system you grade gets a new model, and your validated judge now disagrees with people more often. What first?
Back Continue

The AI Evaluation Engineer · aiengineerdojo.com