Using LLM to Evaluate LLM Outputs
Why LLM judges work and where they fail compared to human evaluation.
Using LLM to Evaluate LLM Outputs is a free AI Prompt Engineering lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Prompt Engineering learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why Use an LLM as a Judge?
Traditional evaluation metrics — BLEU, ROUGE, exact match — work for structured outputs but fail for nuanced qualities like helpfulness, accuracy, tone, and creativity.
Human evaluation captures nuance but is slow and expensive. LLM-as-judge offers a middle path: automated evaluation that understands semantic meaning, context, and subjective quality — at scale and low cost.
Why LLM Judges Work
LLM judges succeed because they share the same language understanding as the model being evaluated. They can assess:
- Whether a response is factually accurate, not just lexically similar to a reference
- Whether a response is helpful for the stated purpose
- Whether the tone matches requirements
- Whether a summary captures the key points
These are qualities that simple string-matching metrics cannot measure.
A Simple LLM Judge
The most basic LLM judge: ask the model to rate a response on a numeric scale with a brief justification. This is the foundation all more advanced judge patterns build on.
import anthropic
import json
client = anthropic.Anthropic(api_key='sk-ant-...')
def simple_llm_judge(question, response, criterion):
judge_prompt = (
f'Rate the following response on {criterion} from 1 to 5.\n\n'
f'Question: {question}\n'
f'Response: {response}\n\n'
f'Return JSON: {{"score": <1-5>, "reason": "<one sentence>"}}'
)
r = client.messages.create(
model='claude-opus-4-5',
max_tokens=100,
messages=[{'role': 'user', 'content': judge_prompt}]
)
try:
result = json.loads(r.content[0].text)
return result['score'], result['reason']
except Exception:
return None, r.content[0].text
score, reason = simple_llm_judge(
question='What is recursion in programming?',
response='Recursion is when a function calls itself.',
criterion='clarity and completeness'
)
print(f'Score: {score}/5 — {reason}')Where LLM Judges Fail: Position Bias
Position bias: When presented with two responses (A and B), LLM judges prefer whichever appears first — regardless of quality. Studies show this affects 60-70% of pairwise comparisons when using a naive judge prompt.
This means the order you present options changes the judge's verdict.
import anthropic
client = anthropic.Anthropic(api_key='sk-ant-...')
def demonstrate_position_bias(question, response_a, response_b):
def ask_judge(first, second, order):
prompt = (
f'Question: {question}\n\n'
f'Response 1: {first}\n\n'
f'Response 2: {second}\n\n'
f'Which response is better? Reply with 1 or 2.'
)
r = client.messages.create(
model='claude-opus-4-5',
max_tokens=10,
messages=[{'role': 'user', 'content': prompt}]
)
choice = r.content[0].text.strip()
# Map back to original labels
if order == 'AB':
return 'A' if choice == '1' else 'B'
else: # BA
return 'B' if choice == '1' else 'A'
result_ab = ask_judge(response_a, response_b, 'AB')
result_ba = ask_judge(response_b, response_a, 'BA')
print(f'Order A-B: Judge picked {result_ab}')
print(f'Order B-A: Judge picked {result_ba}')
if result_ab != result_ba:
print('Position bias detected: different results!')
return result_ab, result_baWhere LLM Judges Fail: Verbosity Bias
Verbosity bias: LLM judges tend to rate longer, more detailed responses higher — even when a concise response is objectively better. A response that uses 400 words to say what 50 words could say often gets a higher score than the concise version.
Mitigate by explicitly instructing the judge to penalize unnecessary length.
import anthropic
client = anthropic.Anthropic(api_key='sk-ant-...')
def length_aware_judge(question, response):
judge_prompt = (
f'Evaluate this response for quality. Be aware of verbosity bias: '
f'do NOT score longer responses higher just because they are longer.\n\n'
f'Question: {question}\n'
f'Response: {response}\n\n'
f'Evaluate on:\n'
f'1. Accuracy (does it correctly answer the question?)\n'
f'2. Conciseness (does it avoid unnecessary filler?)\n'
f'3. Helpfulness (does it serve the user well?)\n\n'
f'Penalize responses that add filler, repetition, or irrelevant information.\n'
f'Score each 1-5 and provide an overall score. Return JSON.'
)
r = client.messages.create(
model='claude-opus-4-5',
max_tokens=200,
messages=[{'role': 'user', 'content': judge_prompt}]
)
print(r.content[0].text)Where LLM Judges Fail: Self-Preference
Self-preference bias: When Claude judges two responses, it tends to prefer Claude-like responses. When GPT-4 judges, it prefers GPT-4-like responses. This is a systematic bias that affects all LLM judges.
Mitigation: use multiple different models as judges and aggregate their scores. Disagreement signals a borderline case requiring human review.
import anthropic
import openai
anthropic_client = anthropic.Anthropic(api_key='sk-ant-...')
openai_client = openai.OpenAI(api_key='sk-...')
def multi_model_judge(question, response):
judge_prompt = (
f'Rate this response 1-10 for overall quality.\n'
f'Q: {question}\nA: {response}\n'
f'Reply with only a number.'
)
# Judge 1: Claude
r_claude = anthropic_client.messages.create(
model='claude-opus-4-5',
max_tokens=10,
messages=[{'role': 'user', 'content': judge_prompt}]
)
score_claude = float(r_claude.content[0].text.strip())
# Judge 2: GPT-4o
r_gpt = openai_client.chat.completions.create(
model='gpt-4o',
max_tokens=10,
messages=[{'role': 'user', 'content': judge_prompt}]
)
score_gpt = float(r_gpt.choices[0].message.content.strip())
avg = (score_claude + score_gpt) / 2
print(f'Claude judge: {score_claude}, GPT judge: {score_gpt}, Average: {avg}')
if abs(score_claude - score_gpt) > 2:
print('WARNING: High disagreement — consider human review')
return avgScoring Inflation
Scoring inflation: LLM judges tend to give high scores (4-5 out of 5) to most responses, compressing the distribution and making it hard to distinguish good from excellent. Responses that would be 3/5 often score 4-4.5/5.
Fix: use a rubric that forces calibration, or use relative (pairwise) rather than absolute scoring.
# Anti-inflation judge prompt with explicit score anchors
CALIBRATED_JUDGE_PROMPT = (
'Rate this response 1-5 using these STRICT score definitions:\n'
'1 = Completely wrong, harmful, or completely off-topic\n'
'2 = Partially relevant but contains significant errors or omissions\n'
'3 = Correct and addresses the question but lacks depth or precision\n'
'4 = Correct, reasonably complete, and clearly expressed\n'
'5 = Exceptional: correct, complete, insightful, and concise\n\n'
'Only give 5 if the response is genuinely outstanding.\n'
'Give 3 for any adequate-but-not-impressive response.\n\n'
'Question: {question}\n'
'Response: {response}\n\n'
'Score (1-5) and one-sentence reason:'
)
# Compare to non-anchored prompt which tends to cluster at 4-5
print('Anchored rubrics force the judge to use the full scale')When LLM Judges Work Best
LLM judges are most reliable when:
- The criteria are clear and well-defined
- The response quality difference is large (obviously good vs obviously bad)
- The domain is within the judge model's knowledge
- You're evaluating subjective qualities (tone, helpfulness) human raters also disagree on
They're least reliable when evaluating recent knowledge, highly technical domains, or subtle factual errors that require specialist knowledge to detect.
When Human Evaluation Is Required
Keep humans in the loop for:
- Evaluating responses in specialized domains (medical, legal, security)
- Establishing ground-truth calibration for your LLM judge
- High-stakes decisions where LLM judge errors have real consequences
- Novel tasks the judge model has little training signal on
- Detecting subtle factual errors that require domain expertise
def triage_for_human_review(question, response, llm_score, confidence_threshold=0.7):
"""
Route low-confidence or high-stakes evaluations to human review.
"""
# Route to human if judge is uncertain
if llm_score is None:
return 'human_review', 'LLM judge failed to produce a score'
# Route to human for borderline scores (near decision boundaries)
if 2.5 <= llm_score <= 3.5:
return 'human_review', f'Borderline score {llm_score} — needs human judgment'
# Route to human for domain-specific high-risk content
HIGH_RISK_KEYWORDS = ['medication', 'legal advice', 'financial advice', 'security']
if any(kw in question.lower() for kw in HIGH_RISK_KEYWORDS):
return 'human_review', 'High-risk domain — human verification required'
# Else: LLM score is sufficient
return 'auto_accept', f'Score {llm_score} — LLM judgment sufficient'
routing, reason = triage_for_human_review('What medication should I take?', 'Take aspirin.', 4.5)
print(f'{routing}: {reason}')Building an Evaluation Pipeline
A practical LLM-as-judge pipeline: generate responses → run LLM judge → triage borderline cases to humans → aggregate scores → report quality metrics. Log everything for audit trails.
import anthropic
import json
client = anthropic.Anthropic(api_key='sk-ant-...')
def evaluate_batch(examples, product_under_test, criteria):
results = []
for ex in examples:
response = product_under_test(ex['question'])
score, reason = simple_llm_judge(ex['question'], response, criteria)
results.append({
'question': ex['question'],
'response': response,
'score': score,
'reason': reason,
'needs_review': score is None or 2.5 <= (score or 0) <= 3.5
})
# Summarize
valid_scores = [r['score'] for r in results if r['score'] is not None]
avg_score = sum(valid_scores) / len(valid_scores) if valid_scores else 0
review_count = sum(1 for r in results if r['needs_review'])
print(f'Average score: {avg_score:.2f}/5')
print(f'Cases needing review: {review_count}/{len(results)}')
return results, avg_score
# results, avg = evaluate_batch(test_cases, my_product, 'helpfulness')Reference-Free vs Reference-Based Evaluation
LLM judges can operate in two modes:
- Reference-based: The judge compares the response to a known correct answer. High accuracy but requires labeled data.
- Reference-free: The judge evaluates the response on its own merits (is it consistent? helpful? well-written?). More flexible but less precise on factual accuracy.
Use reference-based evaluation when you have gold-standard answers. Use reference-free for open-ended tasks like summarization, tone evaluation, or creative writing quality.
Knowledge Check: Position Bias
What is position bias in LLM-as-judge evaluation, and what does it cause?
Recap: LLM-as-Judge Evaluation
LLM judges understand nuance, semantic accuracy, and subjective quality — things traditional metrics cannot measure. They fail on: position bias (prefer first option), verbosity bias (prefer longer answers), self-preference (prefer their own style), and scoring inflation (cluster at 4-5/5). Mitigate with: randomizing response order, explicit anti-verbosity instructions, anchored rubrics that define each score level, and using multiple different models as judges. Route borderline scores and high-risk domains to human evaluators. Use LLM judges for scale; use humans for calibration and high-stakes decisions.
Frequently asked questions
Is the “Using LLM to Evaluate LLM Outputs” lesson free?
Yes — the full text of “Using LLM to Evaluate LLM Outputs” is free to read here on the web, and the AI Prompt Engineering course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Prompt Engineering course, upgrade to CoddyKit PRO.
What will I learn in “Using LLM to Evaluate LLM Outputs”?
Why LLM judges work and where they fail compared to human evaluation. You practise AI Prompt Engineering with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start AI Prompt Engineering?
No prior experience is required. AI Prompt Engineering on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Using LLM to Evaluate LLM Outputs” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this AI Prompt Engineering lesson?
Yes. Every AI Prompt Engineering lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Using LLM to Evaluate LLM Outputs
- Rubric-Based Scoring Prompts
- Comparative Judging: A vs B
- Calibration and Bias in LLM Judges