LLM-as-a-Judge
|
This content was generated with the assistance of AI and should be verified against the official documentation of each tool and framework before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Written on 2026-10-01 against openevals 0.2.0 and langsmith 0.14.2, MLflow 3.16.1, Spring AI 2.0.1 on Spring Boot 4.1.x
with Java 21, and LangChain 1.4.3 on Python 3.12; the full list is the
version baseline on the landing page. None of the snippets on this page was run
for this page: the openevals and MLflow snippets were written from their official documentation and source docstrings,
and the Spring AI snippet follows the reference page and the API already exercised on
Spring AI evaluation. Two details could not be confirmed from official pages that were
reachable on that date, and the text says so where they occur (the MLflow alignment workflow and the exact judge model names
you would choose). No benchmark numbers or vendor prices are quoted.
An LLM judge is a model asked to grade another model’s output against a rubric. It is the only practical way to score open-ended text at the volume a CI gate or a live-traffic sample needs, and it is also a measuring instrument with systematic errors. This page treats it as one: write it down precisely, measure its bias, calibrate it against humans, and report its agreement alongside every score it produces. The reasoning about why open-ended answers need judges at all is on Evaluation Methodology; pairwise rankings at scale are on Comparative Evaluation and Arenas. The framing here (judge as a measurement to be validated, the bias catalogue, and agreement statistics) follows the treatment of AI-as-a-judge in Chip Huyen’s AI Engineering, and the empirical findings on bias come from the MT-Bench paper listed in the References; the explanations and examples are our own.
Why a judge works, and when it does not
Judging is easier than generating. A judge does not have to compose an answer; it compares one with a rubric and a context. That asymmetry is why a modest model can grade a stronger one on narrow questions, and why a judge is cheaper than a human. It fails in predictable ways:
-
The rubric is ambiguous, so two runs read it differently ("is the answer helpful?" has no stable meaning).
-
The judge shares a blind spot with the model it grades, for instance the same factual error learned from the same data.
-
The judge prefers certain surface features regardless of quality (position, length, style, its own style).
-
The task needs ground truth the judge does not have (current facts, private documents, arithmetic), so the judge guesses.
The first rule follows: where a deterministic check exists, use it. A citation either points at a retrieved chunk or it does not; a JSON answer either parses or it does not; a code answer either compiles or it does not. Spend judge calls on the remainder.
Anatomy of a judge prompt
A reliable judge prompt has six parts. The order is not decoration: criteria and scale come before the material to grade, so the model reads the rubric first, and the verdict format comes last.
| Part | What it contains |
|---|---|
Role and task |
One sentence: what is being graded and from whose point of view ("You grade answers given by a documentation assistant"). |
Criterion |
One property per judge. Correctness and tone are two judges, not one prompt; combined criteria produce scores nobody can interpret. |
Scale and anchors |
A small scale (binary, or 1 to 4) with a written description of every level. Anchors are what make two runs, or two people, agree. |
Inputs |
The question, the retrieved context if the criterion is about grounding, the answer, and the reference answer if one exists. Delimit each with tags so the model cannot confuse them. |
Reasoning instruction |
Ask for a short justification before the verdict, so the verdict is conditioned on it. Whether to ask for the reasoning first or last is a design choice you should measure on your own data rather than assume. |
Output format |
A fixed, parseable shape: a single token, or a small JSON object enforced with structured output. |
A rubric for the DocsAssistant
The DocsAssistant (see the running scenario) answers questions about the documentation and cites passages. Its quality splits into criteria that need different inputs, so each becomes its own judge:
| Criterion | Inputs the judge needs | Scale and anchors |
|---|---|---|
Groundedness |
Question, retrieved context, answer |
Binary. Pass only if every factual claim in the answer is supported by the context; any unsupported claim fails. |
Correctness |
Question, answer, reference answer |
1 to 4. 4: same facts as the reference; 3: correct but omits a minor point; 2: partly wrong or missing a key point; 1: contradicts the reference. |
Refusal quality |
Question, context, answer |
Binary. Pass if, when the context lacks the answer, the assistant says so instead of guessing, and if it answers when the context does contain it. |
Tone and format |
Answer only |
Binary. Pass if the answer follows the style guide (short paragraphs, code in fenced blocks, no marketing language). |
Prefer binary or very small scales. A model asked for a 1 to 10 score clusters around a few favourite values, and the resulting numbers hide more noise than signal; a 1 to 4 scale with anchors keeps the information that is actually reproducible. The same applies to humans, which is why the calibration section below measures agreement on exactly the scale you ship.
Pointwise and pairwise judging
There are two shapes of judgement, and they answer different questions.
| Shape | Question it answers | Strengths and weaknesses |
|---|---|---|
Pointwise (single answer grading) |
"Does this answer meet the rubric?" Output: a label or a score per answer. |
Scales linearly with the number of answers and gives an absolute number to threshold in CI. Scores drift when the judge or its prompt changes, so absolute numbers are comparable only within one judge version. |
Pairwise (comparison) |
"Which of these two answers is better for this question?" Output: A, B or tie. |
Relative judgements are easier and more stable than absolute ones, which suits choosing between two prompts or two models. Cost grows with the number of pairs, it needs both candidates to exist, and it exposes position bias directly. |
Use pointwise judges for gates on a single candidate (is groundedness above the threshold?) and for monitoring live traffic, where there is no second candidate. Use pairwise judges to choose between two versions on the same inputs, and turn the outcomes into ratings with the methods on Comparative Evaluation and Arenas. A third shape, reference-guided grading, is pointwise with a reference answer in the prompt; it is the most reliable of the three when a trustworthy reference exists, because the judge compares two texts rather than recalling facts.
The MT-Bench paper by Zheng and colleagues names these modes (single-answer grading, pairwise comparison and reference-guided grading) and quantifies how strongly a strong judge model agrees with human preferences on its benchmark; read it for the numbers instead of trusting a rule of thumb copied from a blog.
Known biases and how to counter them
The same paper catalogues the systematic errors of LLM judges, and later work confirms and extends them. Treat each as a hypothesis to test on your own judge, not a constant of nature.
| Bias | Symptom | Mitigation |
|---|---|---|
Position bias |
In a pairwise prompt the judge favours the answer shown first (or last), whichever it is. |
Run each pair twice with the order swapped and count the pair only if both runs agree; otherwise record a tie or send it to a human. Report the flip rate (the share of pairs whose verdict changes with order). |
Verbosity bias |
Longer answers win regardless of quality. |
Say in the rubric that length is not a virtue and that extra unrequested detail counts against the answer; compare lengths of winners and losers on your data; add length-controlled examples to a validation set. |
Self-preference bias |
A judge scores outputs from its own model family higher. |
Judge with a different model family from the one that generated the answers; when comparing two vendors' models, use a judge from a third, or use more than one judge and report disagreement. |
Leniency and scale compression |
Most scores land in the top bucket; the metric cannot detect regressions. |
Use binary criteria with strict anchors ("any unsupported claim fails"); include known-bad examples in the validation set. |
Style and authority bias |
Confident, formatted or citation-heavy answers win even when wrong. |
Judge groundedness against the context, not against how it sounds; give the judge the context and the reference, not just the answer. |
Judge non-determinism |
The same input gets different scores across runs. |
Temperature zero and a fixed seed where the provider supports it; repeat borderline items; cache judge calls by hash of (prompt version, inputs) so that unchanged items are not re-scored. |
The position-swap protocol is easy to implement and worth making a default. For a pair and a judge that returns the preferred answer, the swapped verdict is . The pair has a consistent winner only when both orders pick the same underlying answer, and the flip rate over pairs is
where the superscript means the verdict is mapped back to the original labels before comparing. A flip rate near zero means the judge’s preference is driven by content; a high one means the judge cannot discriminate between the candidates, or has a strong position bias, and its verdicts on those pairs are noise.
Judges with openevals
openevals is the LangChain team’s package of prebuilt evaluators. Its create_llm_as_judge function builds a judge from a
prompt, a model, and a feedback key, and returns a callable that takes inputs, outputs, reference_outputs and any
further variables your prompt names. The documented parameters include prompt, feedback_key, model (a string of the form
provider:model_name) or judge (a LangChain chat model or an OpenAI client), continuous (a float score between 0 and 1
instead of a boolean), choices (a list of allowed numeric scores, exclusive with continuous), use_reasoning and
few_shot_examples. The result is a dictionary with key, score and comment. Prebuilt prompts such as
CORRECTNESS_PROMPT live in openevals.prompts. How these evaluators plug into LangSmith datasets, evaluate and the
pytest plugin is on LangChain evaluation; this section only shows the judge itself.
The DocsAssistant correctness judge, with the rubric from the table above as a custom prompt and a discrete 1 to 4 scale:
from openevals.llm import create_llm_as_judge
CORRECTNESS_RUBRIC = """You grade answers given by a documentation assistant.
Compare the answer with the reference answer. Judge facts only; ignore length and style.
Scale:
4 - same facts as the reference
3 - correct, but omits a minor point
2 - partly wrong, or misses a key point
1 - contradicts the reference
<question>
{inputs}
</question>
<answer>
{outputs}
</answer>
<reference_answer>
{reference_outputs}
</reference_answer>
Explain briefly, then give the score."""
correctness_judge = create_llm_as_judge(
prompt=CORRECTNESS_RUBRIC,
feedback_key="correctness",
choices=[1.0, 2.0, 3.0, 4.0],
model="ollama:llama3.1:8b", # a model family different from the one that wrote the answer
)
result = correctness_judge(
inputs="How do I invert a matrix?",
outputs="Use MatrixInverse on a square, non-singular matrix.",
reference_outputs="Call MatrixInverse; the matrix must be square and invertible.",
)
print(result["key"], result["score"], result["comment"])
Source: openevals README (LLM-as-judge). The model string ollama:llama3.1:8b
assumes the provider integration for Ollama is installed; the README documents the provider:model form and its OpenAI
example, and a LangChain chat model can be passed through judge instead if you prefer to construct it yourself.
A pairwise judge is the same function with a prompt that has two answers and a categorical outcome. The README documents
that extra prompt variables are supplied at call time, and that choices restricts the numeric score; encoding "A wins" and
"B wins" as two scores keeps the verdict machine-readable. Running the pair in both orders implements the position-swap
protocol:
from openevals.llm import create_llm_as_judge
PAIRWISE = """Decide which answer is better for the question, using only the criterion given.
Criterion: factual agreement with the reference. Ignore length, style and order of presentation.
Reply 1.0 if answer A is better, 0.0 if answer B is better.
<question>{inputs}</question>
<reference_answer>{reference_outputs}</reference_answer>
<answer_a>{answer_a}</answer_a>
<answer_b>{answer_b}</answer_b>"""
pairwise = create_llm_as_judge(
prompt=PAIRWISE,
feedback_key="a_beats_b",
choices=[0.0, 1.0],
model="ollama:llama3.1:8b",
)
def compare(question, reference, first, second):
"""Return 'first', 'second' or 'tie' after judging in both orders."""
forward = pairwise(inputs=question, reference_outputs=reference, answer_a=first, answer_b=second)
swapped = pairwise(inputs=question, reference_outputs=reference, answer_a=second, answer_b=first)
first_wins_forward = forward["score"] == 1.0
first_wins_swapped = swapped["score"] == 0.0 # 'first' is answer B in the swapped run
if first_wins_forward and first_wins_swapped:
return "first"
if not first_wins_forward and not first_wins_swapped:
return "second"
return "tie" # the verdict flipped with the order
Source: openevals README (LLM-as-judge, custom prompts and extra variables).
Passing custom variables such as answer_a follows the README’s statement that additional prompt variables are provided when
the evaluator is called; the repository’s other prompt helpers (for example prebuilt correctness or hallucination prompts)
are single-answer prompts, so the pairwise prompt above is ours, not a prebuilt one.
Judges with MLflow 3
MLflow 3 treats a judge as a scorer: an object that mlflow.genai.evaluate runs over a dataset, recording the verdicts and
the judge’s rationale next to traces and runs, so judge scores sit in the same experiment as the rest of the evaluation. The
make_judge function builds a judge from natural-language instructions. Its signature in the 3.x source takes name,
instructions, model (a URI of the form openai:/gpt-4), feedback_value_type, and optional description,
inference_params, base_url, extra_headers and generate_rationale_first. The instructions must contain at least one
of the template variables {{ inputs }}, {{ outputs }}, {{ expectations }}, {{ conversation }} or {{ trace }}, and
feedback_value_type accepts primitive types, Literal enums and some containers (Pydantic models are not supported).
MLflow also ships built-in scorers such as Correctness.
from typing import Literal
import mlflow
from mlflow.genai.judges import make_judge
grounded = make_judge(
name="grounded_in_docs",
instructions=(
"You grade answers given by a documentation assistant. The answer in {{ outputs }} "
"responds to the question in {{ inputs }}. The answer is grounded only if every factual "
"claim in it is supported by the reference material in {{ expectations }}. "
"Any unsupported claim means 'no'."
),
model="ollama:/llama3.1:8b", # URI form: provider:/model; check that your MLflow version lists the provider
feedback_value_type=Literal["yes", "no"],
)
dataset = [
{
"inputs": {"question": "How do I invert a matrix?"},
"outputs": "Use MatrixInverse on a square, non-singular matrix.",
"expectations": {"expected_response": "Call MatrixInverse; the matrix must be square and invertible."},
},
]
results = mlflow.genai.evaluate(data=dataset, scorers=[grounded])
print(results.metrics) # summary across rows
print(results.result_df) # per-row verdicts and rationales
Source: MLflow GenAI documentation; the signatures of make_judge and
mlflow.genai.evaluate (data, scorers, predict_fn, model_id) were read from the mlflow source on GitHub because the
documentation site answered automated fetches with HTTP 403 on 2026-10-01. The ollama:/ model URI is an assumption by
analogy with the documented openai:/gpt-4 form; confirm the provider prefix for your version before relying on it. With
predict_fn set, mlflow.genai.evaluate calls your application on each input and traces it, and the dataset then needs no
outputs column.
MLflow documents a workflow for aligning a judge with human feedback: Judge.align(traces, optimizer) takes traces that
carry human assessments and returns a new judge whose instructions have been adjusted, defaulting to an optimizer named
MemAlign in the source. I did not find a reachable official page that states the full workflow, so this page does not show
code for it; treat alignment as an automated version of the prompt revision loop in the calibration section below, and
validate any aligned judge on held-out labelled data before trusting it.
Judges in Spring AI
Spring AI ships two model-based evaluators in org.springframework.ai.chat.evaluation that implement the Evaluator
interface (one method, evaluate(EvaluationRequest), returning an EvaluationResponse with isPass(), a score and
feedback). RelevancyEvaluator asks whether the answer is in line with the context for the query, and
FactCheckingEvaluator asks whether a document supports a claim, which makes it a groundedness check for the DocsAssistant.
Both are pointwise, binary judges whose prompts you can replace; the full API, the strictness of the yes parsing and the
custom evaluators are on Spring AI evaluation, so only the judge-specific parts are shown
here.
import java.util.List;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.evaluation.FactCheckingEvaluator;
import org.springframework.ai.chat.evaluation.RelevancyEvaluator;
import org.springframework.ai.document.Document;
import org.springframework.ai.evaluation.EvaluationRequest;
import org.springframework.ai.evaluation.EvaluationResponse;
class DocsAssistantJudges {
private final RelevancyEvaluator relevancy;
private final FactCheckingEvaluator groundedness;
// the builder must wrap a model different from the one that generates answers
DocsAssistantJudges(ChatClient.Builder judgeClientBuilder) {
this.relevancy = RelevancyEvaluator.builder().chatClientBuilder(judgeClientBuilder).build();
this.groundedness = FactCheckingEvaluator.builder(judgeClientBuilder).build();
}
boolean judge(String question, List<Document> retrieved, String answer) {
EvaluationRequest request = new EvaluationRequest(question, retrieved, answer);
EvaluationResponse relevant = this.relevancy.evaluate(request);
EvaluationResponse grounded = this.groundedness.evaluate(request);
return relevant.isPass() && grounded.isPass();
}
}
Source: Spring AI: Model Evaluation. The builder form of
FactCheckingEvaluator (builder(clientBuilder).build()) follows the 2.0.1 API as exercised on the sibling page, where the
constructor shown in the reference page is not accessible.
Spring AI’s own LLM-as-a-judge guide describes the same two patterns as this page, direct assessment (point-wise) and
pairwise comparison, and recommends a separate evaluation model to avoid self-evaluation (narcissistic) bias; its worked
example is a recursive advisor that rates a response on a 1 to 4 scale and retries with the judge’s feedback, which is
the runtime self-refinement use of a judge and is covered on the sibling page. For fact checking, the evaluator has a
forBespokeMinicheck mode, which leads to the next topic.
Specialised judge models
A general chat model can judge, but a model trained for the job is often cheaper and more consistent on that job. Two families exist:
-
Fact-checking models. Bespoke-Minicheck, which Spring AI’s
FactCheckingEvaluatorsupports throughforBespokeMinicheck, is a small model that answers one question: does this document support this claim? It suits the groundedness criterion and nothing else. -
Rubric-following judge models. Open models fine-tuned on judgement data take a rubric and a pair or a single answer and return a score with feedback. Spring AI’s guide names
avcodes/flowaicom-flow-judge:q4as an example for the Ollama runtime, and the Prometheus family of papers describes a similar approach. Whether any specific one fits the DocsAssistant is an empirical question that the calibration below answers; I have not checked the current state of these model releases.
Three considerations decide between a specialised judge and a large general model. A small local judge keeps documentation text and user questions inside your network (see Running LLMs Locally for hosting it), costs little per call, and can be run on every pull request. A large model reads long contexts and subtle rubrics better. And any fine-tuned judge inherits the biases of its training data, so it needs the same bias tests and kappa measurement as a general model. A practical pattern is a two-tier setup: a cheap judge scores everything, and a stronger judge re-scores the items where the cheap judge is uncertain or where two cheap judges disagree.
G-Eval (Liu et al.) is the related idea of asking the judge to first generate evaluation steps from the criterion and then score, optionally weighting the score by token probabilities; libraries such as DeepEval implement it, and it improves granularity at the cost of extra calls.
Calibrating a judge against human labels
A judge score is a claim about quality; calibration is how you earn the right to use it. The procedure:
-
Label a validation set. Take 100 to 300 DocsAssistant (question, context, answer) triples, stratified over your intents and including known failures. Two people label them with the same rubric the judge uses. How to build and version such sets is on Building Eval Sets.
-
Measure human agreement first. If two humans disagree, the rubric is the problem and no judge can fix it; revise the anchors until they agree.
-
Run the judge with the production prompt, model and settings.
-
Measure judge-versus-human agreement with a chance-corrected statistic, the confusion matrix, and the per-class rates.
-
Read the disagreements, revise the rubric or add few-shot examples, and repeat. Freeze the validated prompt as a version.
Raw accuracy misleads whenever one label dominates: a judge that always says "grounded" scores 90% accurate if nine answers in ten are grounded. Cohen’s kappa corrects for the agreement expected by chance. With two raters (the judge and a human) labelling the same items into categories, let be the observed fraction of items on which they agree, and , the fraction of items each rater assigned to category . Then
where is the agreement expected if both raters labelled independently with their own base rates. is perfect agreement, is no better than chance, and negative values are worse than chance. A worked case: 100 answers, judge and human both say "grounded" on 80, both say "ungrounded" on 8, and disagree on 12 (judge says grounded 6 times when the human says not, and the reverse 6 times). Then , the judge says grounded on 86% and the human on 86%, so and . An 88% raw accuracy hides a kappa of about one half, because the prevalence of one class inflates chance agreement.
Common verbal scales (Landis and Koch’s) call 0.41 to 0.60 moderate and 0.61 to 0.80 substantial agreement, but the threshold that matters is the one your decision needs. A judge that gates releases should be near the kappa between two trained humans on the same rubric; a judge that only triages items for human review can be lower.
For ordered scales such as 1 to 4, a disagreement of one level is less serious than three. Weighted kappa replaces exact matching with disagreement weights (linear or quadratic in the distance between levels):
where is the observed count of items the judge put in level and the human in , and is the count expected by chance from the marginals. In Python, scikit-learn implements both:
from sklearn.metrics import cohen_kappa_score, confusion_matrix
human = ["yes", "yes", "no", "yes", "no", "yes", "yes", "no"] # the validation labels
judge = ["yes", "yes", "no", "no", "no", "yes", "yes", "yes"] # the judge's verdicts on the same items
kappa = cohen_kappa_score(human, judge)
print(f"kappa = {kappa:.2f}")
print(confusion_matrix(human, judge, labels=["yes", "no"]))
# ordered 1-4 scores: penalise far disagreements more than near ones
human_scores = [4, 3, 2, 4, 1, 3]
judge_scores = [4, 4, 2, 3, 1, 2]
print(cohen_kappa_score(human_scores, judge_scores, weights="quadratic"))
Source: scikit-learn: cohen_kappa_score. Eight items are far too few for a real estimate; the sample-size reasoning is on Building Eval Sets, and a kappa computed on 100 items has a wide confidence interval that you should report.
Correcting a prevalence estimate for judge error
Kappa tells you whether to trust the judge; it does not remove the judge’s errors from an aggregate such as "what share of production answers are grounded". If the judge has true-positive rate and true-negative rate measured on the validation set, and it reports a fraction of live answers as grounded, the corrected estimate of the true grounded share is the classical misclassification correction (known in epidemiology as the Rogan-Gladen estimator)
where the denominator must be positive, which only holds when the judge is better than a coin flip. If the judge catches 90% of grounded answers ( ) and 80% of ungrounded ones ( ) and flags 70% of live traffic as grounded, then . The correction widens the uncertainty, and it assumes the validation set resembles live traffic, so re-measure when the traffic changes. Using the corrected figure is how sampled online judging stays honest about its own error.
Putting judges in the pipeline
A validated judge becomes part of the suite described on the neighbouring pages:
-
Gates. Judge scores on the golden set enter the CI gate on Evaluation in CI. Pin the judge model version, the prompt version and the temperature, and cache judge calls by hash so only changed items are re-scored.
-
Retrieval-augmented answers. The reference-free RAG judges (groundedness, answer relevance, context relevance) and the agent judges are covered on RAG and Agent Evaluation; the retrieval metrics and RAGAS basics are on Evaluating RAG Systems, and hallucination detection as a runtime step is on Hallucination Detection and Correction.
-
Production sampling. Run the same judge, asynchronously, over a sample of live traces (Online Evaluation and Feedback).
-
Drift. A judge is a dependency. When the judge model is a hosted model that a vendor updates, its scores can shift without any change in your application; the periodic re-run of the validation set described on Drift and Regression Monitoring catches that.
-
Evaluation at scale for RAG. Batch strategies, sampling and cost control for large judged sets are described on Evaluation at Scale.
Judge prompts are versioned artefacts like any other (see Prompt and Model Management). Security testing of judges themselves, for example attempts to embed instructions in an answer that tell the judge to pass it, belongs to AI Red Teaming in the AI Security & Responsible AI sub-section; a minimal defence is to delimit the graded text with tags, tell the judge that the content inside them is data to be graded and not instructions, and include injection attempts in the validation set.
Judge checklist
-
One criterion per judge, binary or a small anchored scale; deterministic checks used wherever they exist.
-
Rubric, scale anchors, inputs and output format written down and versioned; temperature zero and a seed.
-
Judge model from a different family from the generator; pairwise runs done in both orders, flip rate reported.
-
Two human labellers agree on the validation set before the judge is measured against them.
-
Kappa (weighted for ordered scales), confusion matrix and per-class rates reported with their sample size.
-
Aggregates over live traffic corrected for the judge’s measured error.
-
Judge, prompt and model versions pinned; validation set re-run on a schedule and on any judge change.
References
-
Zheng et al., Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena (judge modes, position, verbosity and self-enhancement biases, agreement with humans)
-
Liu et al., G-Eval: NLG Evaluation using GPT-4 with Better Human Alignment
-
Shankar et al., Who Validates the Validators? Aligning LLM-Assisted Evaluation of LLM Outputs with Human Preferences
-
Cohen, J. (1960), A Coefficient of Agreement for Nominal Scales, Educational and Psychological Measurement 20(1)
-
Landis, J. R., and Koch, G. G. (1977), The Measurement of Observer Agreement for Categorical Data, Biometrics 33(1)
-
openevals: LangChain evaluators, including
create_llm_as_judge -
MLflow source repository (
mlflow/genai/judges/make_judge.py,mlflow/genai/evaluation/base.py) -
DeepEval documentation (G-Eval metric)