| name | ml-llm-as-judge |
| description | Comprehensive guide to LLM-as-judge evaluation patterns including Prometheus 2 models, G-Eval framework, pairwise/pointwise/reference-guided methods, bias mitigation, and uncertainty quantification |
LLM-as-Judge Evaluation
Last Updated: 2025-10-26
When to Use This Skill
Use LLM-as-judge evaluation when:
- Subjective quality metrics: Evaluating helpfulness, coherence, creativity, tone
- Open-ended generation: Assessing essay writing, creative content, dialogue quality
- No ground truth: Tasks where reference answers don't exist or are insufficient
- Human preference alignment: Approximating human judgments at scale
- Rapid iteration: Quick feedback on prompt or model changes without human annotation
- Multi-dimensional evaluation: Assessing multiple quality aspects simultaneously
- Pairwise comparison: A/B testing between model outputs or prompt variations
- Cost-effective scaling: Replacing expensive human evaluation for certain tasks
Anti-pattern: Using LLM-as-judge for tasks with objective ground truth (use exact match, BLEU, etc. instead). Never rely solely on LLM judges without validation against human judgments.
Core Concepts
Evaluation Paradigms
1. Pointwise Evaluation
- Judge evaluates single output independently
- Assigns absolute score (e.g., 1-5 rating)
- Simple but prone to inconsistent scale usage
2. Pairwise Evaluation
- Judge compares two outputs (A vs B)
- Determines which is better or if tied
- More reliable than pointwise (relative comparison easier than absolute)
- Can suffer from position bias
3. Reference-Guided Evaluation
- Judge has access to reference answer
- Evaluates output quality against reference
- Useful for factual accuracy, task completion
LLM Judge Models (2024-2025)
Prometheus 2 (Fine-tuned Evaluators)
- Models: prometheus-7b-v2.0, prometheus-8x7b-v2.0
- Specialty: Fine-tuned specifically for evaluation tasks
- Strengths: Consistent scoring, explicit rubrics, reduced bias
- Variants: BGB (Best-of-n Greedy + Backtracking) for improved accuracy
- Open source: Full control and transparency
G-Eval (GPT-based)
- Framework: Uses GPT-4 for evaluation with chain-of-thought
- Strengths: High correlation with human judgments, flexible criteria
- Weaknesses: More expensive, potential GPT-4 biases
GPT-4 / Claude 3 Opus (General-purpose)
- Use case: Quick prototyping, high-quality evaluations
- Strengths: Strong reasoning, nuanced judgments
- Weaknesses: Expensive, API-dependent, potential biases
Llama-3-70B-Instruct / Mixtral-8x7B
- Use case: Cost-effective, self-hosted evaluation
- Strengths: Good performance for many tasks, lower cost
- Weaknesses: Lower quality than specialized models
Bias Mitigation Strategies
Position Bias: Judge favors first/second position in pairwise comparisons
- Solution: Swap positions and average scores
Verbosity Bias: Judge favors longer responses
- Solution: Include length-agnostic criteria, normalize by length
Self-Enhancement Bias: Judge favors outputs from same model family
- Solution: Use different model for judging than generation
Scale Inconsistency: Judge uses rating scale inconsistently
- Solution: Use pairwise comparisons or calibration sets
Uncertainty Quantification
Self-Consistency: Sample multiple judgments and measure agreement
Confidence Scoring: Ask judge to provide confidence level
Multi-Judge Ensemble: Use multiple judges and measure consensus
Implementation Patterns
Pattern 1: Prometheus 2 Pointwise Evaluation
When to use: Fine-tuned evaluator with explicit rubrics
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
class PrometheusEvaluator:
"""Prometheus 2 evaluator for pointwise assessment."""
def __init__(self, model_name="prometheus-eval/prometheus-7b-v2.0"):
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto",
)
def evaluate(
self,
instruction: str,
response: str,
reference_answer: str = None,
rubric: str = None,
) -> dict:
"""
Evaluate response using Prometheus format.
Args:
instruction: The task instruction
response: The response to evaluate
reference_answer: Optional reference answer
rubric: Evaluation rubric (5-point scale by default)
Returns:
Dict with score and feedback
"""
if rubric is None:
rubric = """
1: The response is completely incorrect or irrelevant.
2: The response has major errors or misses key points.
3: The response is acceptable but has some mistakes or lacks detail.
4: The response is good with minor issues.
5: The response is excellent, accurate, and comprehensive.
"""
if reference_answer:
prompt = f"""###Task Description:
An instruction (might include an Input inside it), a response to evaluate, and a score rubric representing a evaluation criteria are given.
1. Write a detailed feedback that assess the quality of the response strictly based on the given score rubric, not evaluating in general.
2. After writing a feedback, write a score that is an integer between 1 and 5. You should refer to the score rubric.
3. The output format should look as follows: \"Feedback: (write a feedback for criteria) [RESULT] (an integer number between 1 and 5)\"
###The instruction to evaluate:
###Response to evaluate:
###Reference Answer (Score 5):
###Score Rubrics:
###Feedback:"""
:
prompt =
inputs = .tokenizer(prompt, return_tensors=).to(.model.device)
torch.no_grad():
outputs = .model.generate(
**inputs,
max_new_tokens=,
temperature=,
do_sample=,
)
evaluation = .tokenizer.decode(outputs[], skip_special_tokens=)
feedback = evaluation.split()[-].strip()
re
score_match = re.search(, feedback)
score = (score_match.group()) score_match
{
: score,
: feedback.split()[].strip() score feedback,
: evaluation,
}
evaluator = PrometheusEvaluator()
result = evaluator.evaluate(
instruction=,
response=,
reference_answer=,
)
()
()
Pattern 2: G-Eval Framework
When to use: GPT-4 based evaluation with chain-of-thought
from openai import OpenAI
import json
class GEvalEvaluator:
"""G-Eval framework using GPT-4 with chain-of-thought."""
def __init__(self, model="gpt-4-turbo-preview"):
self.client = OpenAI()
self.model = model
def evaluate(
self,
task_description: str,
criteria: str,
response: str,
context: str = None,
min_score: int = 1,
max_score: int = 5,
) -> dict:
"""
Evaluate response using G-Eval with chain-of-thought.
Args:
task_description: What the task is
criteria: What to evaluate (coherence, fluency, etc.)
response: The response to evaluate
context: Optional context (e.g., prompt, reference)
min_score: Minimum score
max_score: Maximum score
Returns:
Dict with score, reasoning, and confidence
"""
prompt = f"""You will be given a task description and evaluation criteria. Your job is to evaluate the given response based on the criteria.
Task Description:
{task_description}
Evaluation Criteria:
{criteria}
{f"Context: {context}" if context else ""}
Response to Evaluate:
{response}
Please evaluate step-by-step:
1. First, identify the key aspects of the evaluation criteria
2. Analyze how well the response meets each aspect
3. Provide a final score from to
Your response MUST be valid JSON in this format:
{{
"reasoning": "Detailed step-by-step analysis",
"score": <integer from to >,
"confidence": <float from 0.0 to 1.0>
}}
"""
response = .client.chat.completions.create(
model=.model,
messages=[
{
: ,
: ,
},
{: , : prompt},
],
response_format={: },
temperature=,
)
result = json.loads(response.choices[].message.content)
result
() -> :
scores = []
reasonings = []
_ (num_samples):
result = .evaluate(task_description, criteria, response)
scores.append(result[])
reasonings.append(result[])
numpy np
{
: np.mean(scores),
: np.std(scores),
: np.(scores),
: np.(scores),
: scores,
: reasonings,
: - (np.std(scores) / np.mean(scores)) np.mean(scores) > ,
}
evaluator = GEvalEvaluator()
result = evaluator.evaluate(
task_description=,
criteria=,
response=,
context=,
)
()
()
uncertain_result = evaluator.evaluate_with_uncertainty(
task_description=,
criteria=,
response=,
num_samples=,
)
()
()
Pattern 3: Pairwise Comparison with Bias Mitigation
When to use: A/B testing with position bias correction
from openai import OpenAI
import json
class PairwiseJudge:
"""Pairwise comparison with position bias mitigation."""
def __init__(self, model="gpt-4-turbo-preview"):
self.client = OpenAI()
self.model = model
def compare(
self,
instruction: str,
response_a: str,
response_b: str,
criteria: str = "overall quality",
) -> dict:
"""
Compare two responses with bias mitigation.
Args:
instruction: The task instruction
response_a: First response
response_b: Second response
criteria: What to compare on
Returns:
Dict with winner and reasoning
"""
result_ab = self._single_comparison(
instruction, response_a, response_b, criteria, "A", "B"
)
result_ba = self._single_comparison(
instruction, response_b, response_a, criteria, "B", "A"
)
if result_ab["winner"] == result_ba["winner"]:
winner = result_ab["winner"]
confidence = "high"
elif result_ab[] == result_ba[] == :
winner =
confidence =
:
winner =
confidence =
{
: winner,
: confidence,
: result_ab[],
: result_ba[],
: result_ab[] != result_ba[] [result_ab[], result_ba[]],
}
() -> :
prompt =
response = .client.chat.completions.create(
model=.model,
messages=[
{
: ,
: ,
},
{: , : prompt},
],
response_format={: },
temperature=,
)
result = json.loads(response.choices[].message.content)
result
judge = PairwiseJudge()
result = judge.compare(
instruction=,
response_a=,
response_b=,
criteria=,
)
()
()
()
()
Pattern 4: Multi-Dimensional Evaluation
When to use: Assessing multiple quality aspects simultaneously
from typing import List, Dict
from openai import OpenAI
import json
class MultiDimensionalJudge:
"""Evaluate response across multiple dimensions."""
def __init__(self, model="gpt-4-turbo-preview"):
self.client = OpenAI()
self.model = model
def evaluate(
self,
instruction: str,
response: str,
dimensions: List[Dict[str, str]],
) -> dict:
"""
Evaluate response across multiple dimensions.
Args:
instruction: Task instruction
response: Response to evaluate
dimensions: List of {name, description, min_score, max_score} dicts
Returns:
Dict with scores per dimension and overall
"""
dimension_descriptions = []
for dim in dimensions:
dimension_descriptions.append(
f"- {dim['name']}: {dim['description']} (scale: {dim.get('min_score', 1)}-{dim.get('max_score', 5)})"
)
prompt = f"""Evaluate the following response across multiple dimensions.
Instruction:
{instruction}
Response:
Evaluation Dimensions:
For each dimension:
1. Provide a score within the specified range
2. Explain your reasoning
Return your evaluation as JSON:
{{
"dimensions": [
{{
"name": "dimension_name",
"score": <score>,
"reasoning": "explanation"
}},
...
],
"overall_score": <average score>,
"summary": "overall assessment"
}}
"""
response_eval = .client.chat.completions.create(
model=.model,
messages=[
{
: ,
: ,
},
{: , : prompt},
],
response_format={: },
temperature=,
)
result = json.loads(response_eval.choices[].message.content)
( dim dim dimensions):
weighted_score = (
dim_result[] * ((d[] d dimensions d[] == dim_result[]), )
dim_result result[]
) / (dim.get(, ) dim dimensions)
result[] = weighted_score
result
judge = MultiDimensionalJudge()
result = judge.evaluate(
instruction=,
response=,
dimensions=[
{
: ,
: ,
: ,
: ,
: ,
},
{
: ,
: ,
: ,
: ,
: ,
},
{
: ,
: ,
: ,
: ,
: ,
},
{
: ,
: ,
: ,
: ,
: ,
},
],
)
()
dim result[]:
()
()
()
()
Pattern 5: Expert-in-the-Loop Validation
When to use: Validating LLM judge against human experts
import pandas as pd
from sklearn.metrics import cohen_kappa_score
from typing import List, Dict
import json
class JudgeValidator:
"""Validate LLM judge against human annotations."""
def __init__(self, llm_judge):
"""
Args:
llm_judge: Any judge class with evaluate() method
"""
self.llm_judge = llm_judge
def validate_against_humans(
self,
test_cases: List[Dict],
human_annotations: List[int],
) -> dict:
"""
Validate LLM judge against human expert annotations.
Args:
test_cases: List of {instruction, response} dicts
human_annotations: List of human scores (same order as test_cases)
Returns:
Validation metrics
"""
llm_scores = []
for case in test_cases:
result = self.llm_judge.evaluate(
instruction=case["instruction"],
response=case["response"],
)
llm_scores.append(result["score"])
from scipy.stats import pearsonr, spearmanr
pearson_corr, pearson_p = pearsonr(llm_scores, human_annotations)
spearman_corr, spearman_p = spearmanr(llm_scores, human_annotations)
kappa = cohen_kappa_score(human_annotations, llm_scores)
mae = ((h - l) h, l (human_annotations, llm_scores)) / (human_annotations)
disagreements = [
{
: test_cases[i],
: human_annotations[i],
: llm_scores[i],
: (human_annotations[i] - llm_scores[i]),
}
i ((test_cases))
(human_annotations[i] - llm_scores[i]) >=
]
{
: pearson_corr,
: spearman_corr,
: kappa,
: mae,
: (disagreements),
: (disagreements) / (test_cases),
: disagreements[:],
}
() -> :
validation = .validate_against_humans(calibration_set, human_scores)
recommendations = []
validation[] < target_correlation:
recommendations.append(
)
validation[] > :
recommendations.append(
)
validation[] > :
recommendations.append(
)
recommendations:
recommendations.append(
)
.join(recommendations)
geval_evaluator GEvalEvaluator
judge = GEvalEvaluator()
validator = JudgeValidator(judge)
test_cases = [
{
: ,
: ,
},
]
human_scores = [, , , , , , , ]
validation_results = validator.validate_against_humans(test_cases, human_scores)
()
()
()
()
recommendations = validator.calibrate_judge(test_cases, human_scores)
()
Code Examples
Example 1: Production LLM-as-Judge Pipeline
from typing import List, Dict, Optional
from dataclasses import dataclass
import json
from datetime import datetime
@dataclass
class JudgmentResult:
"""Result from LLM judge."""
score: float
reasoning: str
confidence: float
judge_model: str
timestamp: str
metadata: Dict = None
class ProductionJudgePipeline:
"""Production-ready LLM-as-judge pipeline with logging and monitoring."""
def __init__(
self,
judge_model: str = "gpt-4-turbo-preview",
enable_uncertainty: bool = True,
enable_bias_mitigation: bool = True,
log_path: str = "judge_logs.jsonl",
):
self.judge = GEvalEvaluator(model=judge_model)
self.pairwise_judge = PairwiseJudge(model=judge_model)
self.enable_uncertainty = enable_uncertainty
self.enable_bias_mitigation = enable_bias_mitigation
self.log_path = log_path
def judge_single(
self,
task_description: str,
criteria: ,
response: ,
context: [] = ,
) -> JudgmentResult:
.enable_uncertainty:
result = .judge.evaluate_with_uncertainty(
task_description=task_description,
criteria=criteria,
response=response,
num_samples=,
)
judgment = JudgmentResult(
score=result[],
reasoning=result[][],
confidence=result[],
judge_model=.judge.model,
timestamp=datetime.now().isoformat(),
metadata={
: result[],
: result[],
},
)
:
result = .judge.evaluate(
task_description=task_description,
criteria=criteria,
response=response,
context=context,
)
judgment = JudgmentResult(
score=result[],
reasoning=result[],
confidence=result.get(, ),
judge_model=.judge.model,
timestamp=datetime.now().isoformat(),
)
._log_judgment(judgment, task_description, response)
judgment
() -> :
result = .pairwise_judge.compare(
instruction=instruction,
response_a=response_a,
response_b=response_b,
criteria=criteria,
)
._log_comparison(result, instruction, response_a, response_b)
result
() -> [JudgmentResult]:
results = []
i, example (examples):
judgment = .judge_single(
task_description=task_description,
criteria=criteria,
response=example[],
context=example.get(),
)
results.append(judgment)
(i + ) % == :
()
scores = [r.score r results]
avg_score = (scores) / (scores)
avg_confidence = (r.confidence r results) / (results)
()
()
()
()
results
():
log_entry = {
: ,
: task,
: response,
: judgment.score,
: judgment.confidence,
: judgment.judge_model,
: judgment.timestamp,
}
(.log_path, ) f:
f.write(json.dumps(log_entry) + )
():
log_entry = {
: ,
: instruction,
: response_a,
: response_b,
: result[],
: result[],
: result[],
: datetime.now().isoformat(),
}
(.log_path, ) f:
f.write(json.dumps(log_entry) + )
pipeline = ProductionJudgePipeline(
judge_model=,
enable_uncertainty=,
enable_bias_mitigation=,
)
judgment = pipeline.judge_single(
task_description=,
criteria=,
response=,
)
()
examples = [
{: },
{: },
]
batch_results = pipeline.batch_evaluate(
examples=examples,
task_description=,
criteria=,
)
Anti-Patterns
Anti-Pattern 1: Using LLM Judge for Objective Tasks
Wrong: Using judge for tasks with clear right/wrong answers
judgment = judge.evaluate(
task="What is 2+2?",
response="4",
)
Right: Use deterministic metrics
def evaluate_math(response, expected):
return {"score": 1.0 if response.strip() == expected.strip() else 0.0}
Anti-Pattern 2: Ignoring Position Bias
Wrong: Single pairwise comparison
winner = judge.compare(response_a, response_b)
Right: Swap positions and aggregate
result_ab = judge.compare(response_a, response_b)
result_ba = judge.compare(response_b, response_a)
if result_ab["winner"] == result_ba["winner"]:
winner = result_ab["winner"]
else:
winner = "tie"
Anti-Pattern 3: Not Validating Against Humans
Wrong: Blind trust in LLM judge
scores = [judge.evaluate(case) for case in test_cases]
Right: Validate on calibration set
validator = JudgeValidator(judge)
validation = validator.validate_against_humans(test_cases, human_scores)
if validation["pearson_correlation"] < 0.7:
print("WARNING: Low correlation with humans. Review calibration.")
Related Skills
llm-benchmarks-evaluation.md: Standard benchmarks for objective capability testing
llm-evaluation-frameworks.md: Arize Phoenix, Braintrust for production evaluation
rag-evaluation-metrics.md: RAGAS metrics combining LLM-as-judge with retrieval evaluation
custom-llm-evaluation.md: Domain-specific evaluation metrics and continuous evaluation
dspy-evaluation.md: DSPy metric functions and prompt optimization
Summary
LLM-as-judge provides scalable, flexible evaluation for subjective quality metrics:
Key Takeaways:
- Specialized models: Prometheus 2 (fine-tuned), G-Eval (GPT-4 CoT), general-purpose (Claude/GPT-4)
- Paradigms: Pointwise (absolute), pairwise (comparative), reference-guided (factual)
- Bias mitigation: Position swapping, verbosity normalization, multi-judge ensemble
- Uncertainty: Self-consistency sampling, confidence scoring, agreement metrics
- Validation: Always validate against human experts on calibration set
Best Practices:
- Use pairwise comparison when possible (more reliable than pointwise)
- Always mitigate position bias by swapping positions
- Quantify uncertainty with multi-sample evaluation
- Validate judge against human annotations before production use
- Use specialized models (Prometheus) for consistent, transparent evaluation
- Reserve LLM judges for subjective tasks (use deterministic metrics for objective tasks)
When to combine with other skills:
- Use
llm-benchmarks-evaluation.md for objective capability testing (MMLU, HumanEval)
- Use
llm-evaluation-frameworks.md to integrate judges with Phoenix/Braintrust pipelines
- Use
rag-evaluation-metrics.md for RAG-specific metrics with LLM-as-judge components
- Use
custom-llm-evaluation.md for domain-specific rubrics and safety evaluation