Your LLM app generates thousands of responses a day. Manually reviewing even a fraction of them is unsustainable. Traditional metrics like BLEU and ROUGE score surface-level text similarity, but they can't tell you whether a response is helpful, grounded in facts, or safe for production. LLM-as-a-Judge solves this by using one language model to evaluate another—automatically, at scale, and against criteria you define.
This article walks you through the concept, the known pitfalls, and a complete working example you can run today. By the end, you'll have a repeatable pattern for scoring your LLM outputs on both subjective quality (like helpfulness) and factual correctness, using Python, LangChain, and Claude.
What Is LLM-as-a-Judge?
LLM-as-a-Judge is an evaluation method where you prompt a capable language model to assess the output of another model (or itself) based on criteria you specify. Instead of writing brittle regex rules or relying on string-matching metrics, you give the judge a rubric—"Is this response helpful?", "Does it contradict the source document?", "Is the tone appropriate?"—and it returns a score and rationale.
The approach gained traction after GPT-4 demonstrated the ability to grade other models' outputs reliably, and has since become one of the most widely-used evaluation techniques alongside human review. Research shows that strong LLM judges can achieve roughly 80% agreement with human evaluators—comparable to the agreement rate between two human annotators.
There are two primary modes:
- Pointwise (single-output) evaluation scores one response at a time. This can be reference-free (judge the response on its own merits—clarity, helpfulness, tone) or reference-based (compare it against a gold-standard answer for correctness). This is the most common setup for production monitoring.
- Pairwise evaluation presents two responses side-by-side and asks the judge to pick the better one. This is how benchmarks like Chatbot Arena work—useful for model comparison, but less practical for continuous monitoring of a single system.
For most production use cases, pointwise evaluation is what you want. It's simpler to implement, easier to aggregate into dashboards, and maps directly to the question you're usually asking: "Is this response good enough to ship?"
Why Not Just Use Human Reviewers?
You absolutely should—for calibration. The question isn't "humans or LLM judges" but "how do I scale human-quality judgment without human-scale cost?"
Consider the math: if your system generates 10,000 responses per day and a human reviewer takes 2 minutes per response, that's 333 hours of review work daily. At scale, this is not feasible.
LLM-as-a-Judge lets you evaluate 100% of your traffic automatically, then route the flagged or low-scoring cases to human reviewers. The humans stay in the loop—they calibrate the judge, review edge cases, and catch the things automation misses. The judge handles the volume.
This hybrid approach—automated evaluation at scale, targeted human review for calibration and edge cases—is emerging as the standard pattern for production LLM monitoring.
The Five Biases That Can Break Your LLM Judge
LLM judges are powerful but imperfect. Before you build one, you need to understand the systematic biases that can silently corrupt your results.
- Position bias. In pairwise comparisons, judges tend to favor whichever response appears first (or last, depending on the model). Studies have shown up to 40% inconsistency in GPT-4 judgments when the order of responses is swapped. The fix: run every pairwise comparison twice with swapped positions, and only count results that are consistent across both orderings.
- Verbosity bias. Longer responses get higher scores, even when a shorter answer is more accurate and useful. A five-paragraph response that buries the correct answer in filler will often outscore a concise two-sentence answer. The fix: explicitly instruct the judge to reward conciseness, and use small integer scales (1–4 or 1–5) rather than continuous ranges.
- Self-enhancement bias. Models tend to rate their own outputs (or outputs from architecturally similar models) more favorably—roughly a 5–7% score inflation. The fix: use a judge from a different model family than the system being evaluated. If your app runs on one provider's model, consider judging with another.
- Authority bias. Responses that include citations, URLs, or academic-sounding references score higher—even when those citations are fabricated. The fix: include explicit instructions in the judge prompt to verify claims against provided context, and add hallucination-detection examples to the rubric.
- Domain gaps. Judge agreement with humans drops 10–15% in specialized domains like medicine, law, or finance, where the judge may lack domain expertise. The fix: for high-stakes domains, use LLM judges as a first-pass filter and route flagged outputs to domain experts.
Knowing these biases is half the battle. The other half is building your evaluation pipeline with mitigation strategies baked in from the start.
Building Your First LLM Judge: A Complete Example
Let's build a working evaluation pipeline that demonstrates both reference-free and reference-based scoring. We'll use Claude as both the system-under-test and the judge (a common and practical setup for getting started), with LangChain's evaluation utilities to handle the plumbing.
Prerequisites
You'll need:
- Python 3.9+
- An Anthropic API key (set as the
ANTHROPIC_API_KEYenvironment variable) - A few packages: pip install langchain langchain-anthropic
This runs in any local Jupyter environment or Google Colab. If using Colab, set your API key via Colab Secrets—never hardcode keys in shared notebooks.
The Setup
from langchain_anthropic import ChatAnthropic
from langchain.evaluation import load_evaluator
# Pin a versioned model and set temperature=0 for reproducible results.
# Swap for any supported Claude model (e.g., claude-haiku-4-5-20251001)
# depending on your access tier and cost/quality trade-off.
llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)
Two things matter here. First, pin your model version. Unversioned model aliases can silently change behavior when the provider updates. Second, set the temperature to 0. You want your judge to be deterministic—the same input should produce the same score every time.
Define Your Test Data
item = {
"question": "What is TTFT and why does it matter?",
"reference": (
"TTFT (Time-to-First-Token) measures the latency from when a user "
"sends a request to when the first token of the response is generated. "
"It matters because it directly affects perceived responsiveness—users "
"see output starting sooner, which is critical for streaming interfaces."
)
}
For this example, we'll use a single QA item. In production, you'd run this against a dataset of hundreds or thousands of examples.
Generate the Response
def predict(question: str) -> str:
"""System-under-test: a simple, deterministic call to Claude."""
return llm.invoke([
("system", "Answer concisely and accurately."),
("human", question)
]).content
prediction = predict(item["question"])
print(f"Prediction:\n{prediction}\n")
Set Up Two Judges
This is where it gets interesting. We create two evaluators that assess the same response from different angles:
# Judge 1: Reference-free (helpfulness)
# Scores the response on its own merits—no gold answer needed.
helpfulness_judge = load_evaluator(
"criteria",
llm=llm,
criteria={"helpfulness": "Is the answer practically useful, clear, and well-structured?"}
)
# Judge 2: Reference-aware (correctness)
# Checks factual alignment against the provided reference answer.
correctness_judge = load_evaluator(
"labeled_criteria",
llm=llm,
criteria={"correctness": "Is the answer factually correct given the reference?"}
)
The "criteria" evaluator is reference-free: it judges the response in isolation. The "labeled_criteria" evaluator is reference-aware: it compares the response against your gold-standard answer.
Run the Evaluation
# Reference-free: How helpful is this response?
result_help = helpfulness_judge.evaluate_strings(
prediction=prediction,
input=item["question"]
)
# Reference-aware: Is this response correct?
result_corr = correctness_judge.evaluate_strings(
prediction=prediction,
input=item["question"],
reference=item["reference"]
)
# Note: LangChain evaluator output keys can vary across versions—some return
# "reasoning", others "explanation". Using .get() with a fallback handles both
# gracefully. This is one more reason to pin your langchain versions.
help_score = result_help.get("score")
help_reasoning = result_help.get("reasoning") or result_help.get("explanation")
corr_score = result_corr.get("score")
corr_reasoning = result_corr.get("reasoning") or result_corr.get("explanation")
print(f"Helpfulness: {help_score} — {help_reasoning}")
print(f"Correctness: {corr_score} — {corr_reasoning}")
Understanding the Output
LangChain's built-in criteria evaluators default to a binary scale: 1 (criterion met) or 0 (not met), sometimes accompanied by a Y/N verdict. But this is configurable. Depending on your needs, you can define custom evaluators that use:
- Binary 0/1 — simple pass/fail, good for automated gates
- 1–5 Likert scale — useful for grading nuance in helpfulness or tone
- 0–10 numeric range — common in production dashboards for finer resolution
If you're mixing scores from different evaluator types (say, a binary safety check alongside a 1–5 helpfulness rating), normalize everything to a common range (e.g., 0.0–1.0) before aggregating. Document your scoring conventions early—this saves enormous headaches when the team grows.
A typical output from the code above looks like this:
Helpfulness: 1 — The response clearly defines TTFT, explains its
significance for user experience, and provides practical context
about streaming interfaces. Well-structured and concise.
Correctness: 1 — The core definition aligns with the reference
(latency from request to first token). Additional context about
streaming UX is accurate and non-contradictory.
The judge doesn't just return a number—it provides a rationale. This is invaluable for debugging. When a response scores poorly, the reasoning tells you why, which helps you fix the underlying prompt or retrieval step rather than guessing.
Wrapping Up
LLM-as-a-Judge isn't a silver bullet. It inherits the biases of the models it runs on, it costs real money per evaluation, and it can miss subtle domain-specific errors that a human expert would catch immediately. But when used correctly—with calibrated rubrics, known bias mitigations, and human-in-the-loop oversight—it's the most practical way to maintain quality across thousands of LLM-generated outputs daily.
The pattern is straightforward: define your criteria, build your judges, calibrate against humans, then run continuously. Start with the single-turn example in this article, extend it to your agent traces when you're ready, and treat evaluation as an ongoing discipline rather than a one-time test.
