How to Evaluate LLM Output Quality: Metrics, Tools, and Frameworks in 2026

TL;DR — LLM evaluation: layered stack, no single metric. DeepEval: "LLM-as-Judge uses language model as evaluator. G-Eval for custom criteria, DAGMetric for multi-step, built-in RAG metrics for grounding and retrieval." AgentsCamp: "Faithfulness is hallucination detector. Low context recall = retriever problem. High recall but low faithfulness = generation problem. LLM-as-judge has biases — position, length, self-preference." HLD Handbook: "Layered stack: deterministic checks, reference-free RAG metrics, LLM-as-judge, human calibration. CI gates: faithfulness >= 0.90, answer relevance >= 0.85. GPT-4 judge matches human 80%+ on MT-Bench." AITestingGuide: "Four metrics cover most production needs: faithfulness, answer relevancy, context precision, context recall. All automatable with CI/CD thresholds." Learn more with RAG vs fine-tuning, choose LLM, cost estimation, and monitoring.

AgentsCamp frames the challenge: "There is no single LLM metric — you pick one that matches the task. Use exact match and F1 for closed tasks like extraction and routing, retrieval metrics (recall@k, MRR, NDCG) for the retriever, RAG metrics (faithfulness, answer relevance, context precision/recall) for grounded answers, and a calibrated LLM-as-judge or human preference for open-ended generation."

HLD Handbook provides the production answer: "LLM evaluation is hard because outputs are open-ended, multiple answers are correct, and models are stochastic. No single metric solves it. The production answer is a layered stack: deterministic checks (schema, exact match) at the base, reference-free RAG metrics (Ragas faithfulness, context precision) in the middle, LLM-as-judge at the top, and human calibration to keep the judge honest."

LLM Evaluation Architecture

flowchart TD subgraph TaskTypes["Task Type Classification"] Closed["Closed Tasks
extraction, routing
exact match, F1
regex validation"] Retrieval["Retrieval Tasks
RAG retriever quality
recall@k, MRR, NDCG
context precision/recall"] Generation["Open Generation
chat, summarization
LLM-as-judge
human preference"] Structured["Structured Output
JSON, SQL, code
schema validation
format correctness"] end subgraph Metrics["Evaluation Metrics"] RefBased["Reference-Based
BLEU: precision, translation
ROUGE: recall, summarization
BERTScore: semantic similarity
Exact match: structured"] RefFree["Reference-Free
faithfulness: grounded?
answer relevance: on-topic?
context precision: right chunks?
context recall: all facts?"] Judge["LLM-as-Judge
G-Eval: custom criteria
pairwise: model comparison
rubric: discrete scale
DAG: multi-step logic"] Human["Human Evaluation
ground truth calibration
taste, tone, safety
domain correctness
compliance audits"] end subgraph Tools["Evaluation Tools"] Ragas["Ragas
RAG metrics: faithfulness
answer relevance
context precision/recall
decomposes RAG quality"] DeepEvalT["DeepEval
pytest-style harness
GEval, DAGMetric
built-in RAG metrics
CI/CD integration"] Promptfoo["promptfoo
prompt regression testing
model comparison
CLI-based eval"] LangSmith["LangSmith
production observability
trace evaluation
online monitoring"] end subgraph CI["CI/CD Integration"] Golden["Golden Eval Set
100-500 cases
expected outputs
run on every PR"] Gates["Quality Gates
faithfulness >= 0.90
answer_relevance >= 0.85
cost under budget
wall-clock under 15 min"] Flake["Flake Handling
temperature 0
fixed seeds
N=3 runs with median
non-deterministic providers"] end subgraph Diagnostics["Diagnostic Power"] RetrieverIssue["Retriever Problem
low context recall
low context precision
fix: embedding, chunking, ranking"] GeneratorIssue["Generator Problem
high context recall
low faithfulness
fix: prompt, model, temperature"] BothIssue["Both Issues
low recall + low faithfulness
fix retriever first
then generator"] end Closed --> RefBased Retrieval --> RefFree Generation --> Judge Structured --> RefBased RefBased --> Ragas RefFree --> Ragas RefFree --> DeepEvalT Judge --> DeepEvalT Judge --> Promptfoo Human --> LangSmith Ragas --> Golden DeepEvalT --> Golden Golden --> Gates Gates --> Flake RefFree --> RetrieverIssue RefFree --> GeneratorIssue RefFree --> BothIssue style TaskTypes fill:#4169E1,color:#fff style Metrics fill:#39FF14,color:#000 style Tools fill:#2D1B69,color:#fff style CI fill:#FF6B6B,color:#fff

Evaluation Metrics Comparison

Metric Type What It Measures Reference Needed Best For
Faithfulness Reference-free Claims grounded in context No (needs context) RAG hallucination detection
Answer relevance Reference-free Response addresses question No RAG, chatbot
Context precision Reference-free Right chunks ranked high No (needs context) Retriever quality
Context recall Reference-based All needed facts in context Yes (expected output) Retriever completeness
BLEU Reference-based N-gram precision Yes Translation
ROUGE Reference-based N-gram recall Yes Summarization
BERTScore Reference-based Semantic similarity Yes Paraphrase detection
Exact match Reference-based String equality Yes Extraction, routing
LLM-as-judge Either Subjective quality Either Open generation, comparison
Human eval Either Taste, tone, safety Either Calibration, compliance

Implementation

from dataclasses import dataclass
from typing import Optional
from enum import Enum

class EvalType(Enum):
    REFERENCE_BASED = "reference_based"
    REFERENCE_FREE = "reference_free"
    LLM_JUDGE = "llm_judge"
    HUMAN = "human"

@dataclass
class LLMEvaluationGuide:
    """LLM evaluation metrics and tools guide."""

    def get_ragas_eval(self) -> str:
        """Ragas RAG evaluation."""
        return (
            "# === RAGAS EVALUATION ===\n"
            "# pip install ragas\n"
            "\n"
            "from ragas import evaluate\n"
            "from ragas.metrics import (\n"
            "    faithfulness,\n"
            "    answer_relevancy,\n"
            "    context_precision,\n"
            "    context_recall)\n"
            "from datasets import Dataset\n"
            "\n"
            "# Prepare eval dataset\n"
            "eval_data = Dataset.from_dict({\n"
            "    'question': [\n"
            "        'What is RAG?',\n"
            "        'How does fine-tuning work?'],\n"
            "    'answer': [\n"
            "        'RAG retrieves context...'\n"
            "        'Fine-tuning updates...'],\n"
            "    'contexts': [\n"
            "        ['RAG is retrieval...'],\n"
            "        ['Fine-tuning trains...']],\n"
            "    'ground_truth': [\n"
            "        'RAG pulls external...'\n"
            "        'Fine-tuning adjusts...']\n"
            "})\n"
            "\n"
            "# Run evaluation\n"
            "result = evaluate(\n"
            "    eval_data,\n"
            "    metrics=[\n"
            "        faithfulness,\n"
            "        answer_relevancy,\n"
            "        context_precision,\n"
            "        context_recall])\n"
            "\n"
            "# Result: faithfulness=0.92,\n"
            "#   answer_relevancy=0.88,\n"
            "#   context_precision=0.85,\n"
            "#   context_recall=0.90\n"
            "\n"
            "# Diagnostic:\n"
            "#   low context_recall =\n"
            "#     retriever problem\n"
            "#   high recall but\n"
            "#     low faithfulness =\n"
            "#     generation problem"
        )

    def get_deepeval_eval(self) -> str:
        """DeepEval with CI/CD gates."""
        return (
            "# === DEEPEVAL CI/CD ===\n"
            "# pip install deepeval\n"
            "\n"
            "from deepeval import (\n"
            "    assert_test, evaluate)\n"
            "from deepeval.metrics import (\n"
            "    FaithfulnessMetric,\n"
            "    AnswerRelevancyMetric,\n"
            "    ContextualRelevancyMetric,\n"
            "    ContextualRecallMetric,\n"
            "    GEval)\n"
            "from deepeval.test_case import (\n"
            "    LLMTestCase)\n"
            "import pytest\n"
            "\n"
            "# Define thresholds\n"
            "faithfulness_metric = (\n"
            "    FaithfulnessMetric(\n"
            "        threshold=0.90))\n"
            "relevancy_metric = (\n"
            "    AnswerRelevancyMetric(\n"
            "        threshold=0.85))\n"
            "context_precision = (\n"
            "    ContextualRelevancyMetric(\n"
            "        threshold=0.80))\n"
            "context_recall = (\n"
            "    ContextualRecallMetric(\n"
            "        threshold=0.85))\n"
            "\n"
            "# Custom G-Eval for tone\n"
            "tone_metric = GEval(\n"
            "    criteria='professional and'\n"
            "      'helpful tone',\n"
            "    evaluation_params=[\n"
            "        'actual_output'],\n"
            "    threshold=0.80)\n"
            "\n"
            "@pytest.mark.parametrize(\n"
            "    'query,answer,context,'\n"
            "      'expected', eval_cases)\n"
            "def test_rag_quality(\n"
            "    query, answer, context,\n"
            "    expected):\n"
            "    '''CI/CD gate for RAG.''' \n"
            "    test_case = LLMTestCase(\n"
            "        input=query,\n"
            "        actual_output=answer,\n"
            "        retrieval_context=context,\n"
            "        expected_output=expected)\n"
            "    assert_test(\n"
            "        test_case,\n"
            "        [faithfulness_metric,\n"
            "         relevancy_metric,\n"
            "         context_precision,\n"
            "         context_recall,\n"
            "         tone_metric])\n"
            "\n"
            "# Run: pytest test_eval.py\n"
            "#   --deepeval\n"
            "# Exit non-zero on regression"
        )

    def get_llm_judge(self) -> str:
        """LLM-as-judge implementation."""
        return (
            "# === LLM-AS-JUDGE ===\n"
            "import httpx, json\n"
            "\n"
            "async def llm_judge(\n"
            "    query: str,\n"
            "    response: str,\n"
            "    criteria: str,\n"
            "    context: str = None) -> dict:\n"
            "    '''LLM-as-judge evaluation.''' \n"
            "    judge_prompt = f'''\\n"
            "    You are an expert judge.\n"
            "    Evaluate the response.\n"
            "    \n"
            "    Criteria: {criteria}\n"
            "    Query: {query}\n"
            "    Response: {response}\n"
            "    ''' \n"
            "    if context:\n"
            "        judge_prompt += (\n"
            "          f'Context: {context}\\n')\n"
            "    judge_prompt += '''\\n"
            "    Score 1-5:\n"
            "    1=very poor, 5=excellent\n"
            "    Return JSON: {\"score\": N,\n"
            "      \"reason\": \"...\"}\n"
            "    ''' \n"
            "    \n"
            "    async with httpx.AsyncClient(\n"
            "        timeout=60.0\n"
            "    ) as client:\n"
            "        resp = await client.post(\n"
            "            'http://localhost:11434'\n"
            "            '/api/chat',\n"
            "            json={\n"
            "                'model':'qwen2.5:32b',\n"
            "                'messages':[{\n"
            "                  'role':'user',\n"
            "                  'content':judge_prompt}],\n"
            "                'format':'json',\n"
            "                'options': {\n"
            "                  'temperature': 0}})\n"
            "        result = json.loads(\n"
            "            resp.json()\n"
            "            ['message']['content'])\n"
            "        return result\n"
            "\n"
            "# Pairwise comparison\n"
            "async def pairwise_judge(\n"
            "    query: str,\n"
            "    response_a: str,\n"
            "    response_b: str):\n"
            "    '''Compare two responses.''' \n"
            "    # Randomize order to\n"
            "    #   reduce position bias\n"
            "    import random\n"
            "    if random.random() > 0.5:\n"
            "        a, b = response_b, response_a\n"
            "        swapped = True\n"
            "    else:\n"
            "        a, b = response_a, response_b\n"
            "        swapped = False\n"
            "    \n"
            "    prompt = f'''\\n"
            "    Query: {query}\n"
            "    Response A: {a}\n"
            "    Response B: {b}\n"
            "    Which is better?\n"
            "    Return: {{\"winner\": \"A\"}}\n"
            "    ''' \n"
            "    # ... call LLM judge\n"
            "    # Un-swap if needed"
        )

    def get_production_tips(self) -> dict:
        """Production tips."""
        return {
            "no_single_metric": "No single LLM metric. Match metric to task: closed=exact match, retrieval=recall@k, RAG=faithfulness, open=LLM judge.",
            "layered_stack": "Layered stack: deterministic checks at base, RAG metrics in middle, LLM-as-judge at top, human calibration.",
            "faithfulness_first": "Faithfulness is most important metric — catches hallucinations. supported_claims / total_claims.",
            "diagnostic_split": "Low context recall = retriever problem. High recall but low faithfulness = generation problem.",
            "judge_biases": "LLM-as-judge biases: position, length, self-preference. Mitigate: low temp, randomize order, validate against humans.",
            "gpt4_judge": "GPT-4 as judge matches human agreement 80%+ on MT-Bench. But drifts when provider updates snapshots.",
            "ci_gates": "CI gates: faithfulness >= 0.90, answer_relevance >= 0.85, cost under budget, wall-clock under 15 min.",
            "flake_handling": "Flake handling: temperature 0, fixed seeds, N=3 runs with median for non-deterministic providers.",
            "human_calibration": "Human-label 200 cases once, calibrate LLM judge, run automated in CI, human audit quarterly.",
            "2026_playbook": "2026 playbook: LLM-as-judge 80%, automated CI/CD gates, human for calibration and compliance.",
        }

LLM Evaluation Checklist

  • [ ] Classify task type: closed (extraction, routing), retrieval (RAG), open generation (chat), structured (JSON, code)
  • [ ] Match metric to task: no single LLM metric works for all tasks
  • [ ] Closed tasks: exact match, F1 score, regex validation
  • [ ] Retrieval tasks: recall@k, MRR, NDCG for retriever quality
  • [ ] RAG tasks: faithfulness, answer relevance, context precision, context recall
  • [ ] Open generation: LLM-as-judge with rubric or pairwise comparison
  • [ ] Structured output: schema validation, format correctness, JSON parse check
  • [ ] Faithfulness: extracts atomic claims, checks each against retrieved context, score = supported_claims / total_claims
  • [ ] Faithfulness is the hallucination detector — most important RAG metric
  • [ ] Answer relevance: generates questions from answer, scores cosine similarity to original query
  • [ ] Context precision: checks whether relevant chunks rank high in retrieval
  • [ ] Context recall: checks whether ground-truth answer's claims are all present in context
  • [ ] Low context recall = retriever problem (fix embedding, chunking, ranking)
  • [ ] High context recall but low faithfulness = generation problem (fix prompt, model, temperature)
  • [ ] Low recall + low faithfulness = fix retriever first, then generator
  • [ ] BLEU: n-gram precision, built for translation, punishes valid paraphrases
  • [ ] ROUGE: n-gram recall, standard for summarization, ROUGE-L uses longest common subsequence
  • [ ] BERTScore: semantic similarity via contextual embeddings, catches paraphrases, correlates better with humans
  • [ ] All three (BLEU, ROUGE, BERTScore) are reference-based — need expected output
  • [ ] LLM-as-judge: use strong LLM to score another LLM's output against criteria
  • [ ] G-Eval for custom subjective criteria, DAGMetric for multi-step scoring logic
  • [ ] Pairwise preference: judge picks better of two outputs — more reliable than absolute scores
  • [ ] Rubric/pointwise scoring: judge rates on small discrete scale (1-5)
  • [ ] LLM-as-judge biases: position bias, length bias, self-preference (favors own model family)
  • [ ] Mitigate judge biases: explicit rubric, small discrete scale, randomized answer order, low temperature
  • [ ] Validate judge against human labels before trusting scores — re-check periodically
  • [ ] GPT-4 as judge matches human agreement 80%+ on MT-Bench
  • [ ] Judge drifts silently when provider updates model snapshots — re-validate after updates
  • [ ] Use Ragas for RAG evaluation: faithfulness, answer relevance, context precision, context recall
  • [ ] Use DeepEval for pytest-style CI/CD integration with metric thresholds
  • [ ] Use promptfoo for prompt regression testing and model comparison
  • [ ] Use LangSmith for production observability and online monitoring
  • [ ] Build golden eval set: 100-500 cases with expected outputs
  • [ ] Run evaluation on every PR or model change
  • [ ] CI/CD gates: faithfulness >= 0.90, answer_relevance >= 0.85, cost under budget, wall-clock under 15 min
  • [ ] Flake handling: temperature 0, fixed seeds, N=3 runs with median for non-deterministic providers
  • [ ] Exit non-zero on regression — fail the build
  • [ ] Human evaluation: use for ground truth calibration, taste, tone, safety, domain correctness, compliance
  • [ ] Human-label 200 cases once, calibrate LLM judge, run automated in CI, human audit quarterly
  • [ ] Human cost: $5-50 per evaluation, minutes per case
  • [ ] Automated cost: milliseconds, pennies per case
  • [ ] 2026 playbook: LLM-as-judge 80%, automated CI/CD gates, human for calibration and compliance
  • [ ] Production monitoring: page on faithfulness drops, not just GPU utilization
  • [ ] Wire eval into CI with golden set, observe production with OpenTelemetry GenAI semantic conventions
  • [ ] Evaluate both retriever and generator — most teams only test generator and miss retrieval failures
  • [ ] Read RAG vs fine-tuning for knowledge approach
  • [ ] Read choose LLM for model selection
  • [ ] Read cost estimation for budget forecasting
  • [ ] Read monitoring for production observability
  • [ ] Test: golden eval set passes all metric thresholds
  • [ ] Test: CI/CD gate fails on intentional regression
  • [ ] Test: LLM-as-judge scores correlate with human labels (>80% agreement)
  • [ ] Test: flake handling produces consistent results across N=3 runs
  • [ ] Test: faithfulness catches injected hallucinations
  • [ ] Test: context recall catches missing retrieval
  • [ ] Document eval set, metric thresholds, judge prompt, calibration results, CI integration

FAQ

What are the most important LLM evaluation metrics for RAG systems?

Faithfulness, answer relevance, context precision, and context recall. DeepEval: "FaithfulnessMetric: whether actual_output is grounded in retrieved context. AnswerRelevancyMetric: whether actual_output answers the input. ContextualRelevancyMetric: whether retrieved chunks are relevant. ContextualRecallMetric: whether retrieval captured facts needed by expected answer." AgentsCamp: "Faithfulness is your hallucination detector — scored by LLM judge checking each claim against passages. Answer relevance — does response address the question? Context precision — is retrieved context on-point? Context recall — does retrieved context contain everything needed? Low context recall is retriever problem; high context recall but low faithfulness is generation problem." AITestingGuide: "Four reference-free metrics cover most production testing needs: faithfulness, answer relevancy, context precision, context recall. Can all be automated with thresholds in CI/CD." Metrics: (1) Faithfulness: supported_claims / total_claims. (2) Answer relevance: cosine similarity of generated questions to original query. (3) Context precision: relevant chunks ranked high. (4) Context recall: all needed facts in context.

How does LLM-as-a-judge evaluation work?

Use a strong LLM to score another LLM's output against criteria. DeepEval: "LLM-as-a-Judge uses a language model as evaluator. Instead of exact string matching, BLEU, ROUGE, or manual review, give LLM judge the interaction and ask it to score against criteria. Common for RAG, agents, chatbots, summarization, code generation, prompt regression testing." HLD Handbook: "GPT-4 as judge matches human agreement at over 80% on MT-Bench. But drifts silently when provider updates model snapshots." AgentsCamp: "LLM-as-judge scales subjective scoring but has biases: position bias (favoring first answer), length bias (preferring longer), self-preference (favoring own model family). Make reliable with explicit rubric, small discrete scale, randomized order, low temperature, validation against human labels." LLM-as-judge: (1) G-Eval for custom criteria. (2) Pairwise preference for model comparison. (3) Rubric/pointwise scoring on discrete scale. (4) Biases: position, length, self-preference. (5) Mitigate: low temperature, randomize order, validate against humans. (6) GPT-4 judge matches human 80%+ on MT-Bench.

What is the difference between BLEU, ROUGE, and BERTScore?

BLEU measures precision (translation), ROUGE measures recall (summarization), BERTScore measures semantic similarity. HLD Handbook: "BLEU: modified n-gram precision with brevity penalty. Built for machine translation. Punishes valid paraphrases. ROUGE: recall-oriented n-gram overlap. Standard for summarization. ROUGE-L uses longest common subsequence. BERTScore: per-token cosine similarity in contextual BERT embeddings. Correlates better with human judgment than BLEU/ROUGE because it scores meaning, not surface form." TECHSY: "BLEU measures n-gram precision — word sequences matching reference. ROUGE measures recall — how much reference content appears in output. BERTScore uses contextual embeddings to measure semantic similarity, catching paraphrases that BLEU and ROUGE miss." Differences: (1) BLEU: precision, translation, punishes paraphrases. (2) ROUGE: recall, summarization, n-gram overlap. (3) BERTScore: semantic similarity, catches paraphrases, correlates better with humans. (4) All reference-based — need expected output. (5) For open-ended generation, use LLM-as-judge instead. (6) For structured output, use exact match or regex.

How do you integrate LLM evaluation into CI/CD pipelines?

Use pytest-style harnesses with metric thresholds that fail builds on regression. HLD Handbook: "CI integration: pytest-style harnesses (DeepEval, Ragas, promptfoo, Inspect AI) that wrap judge calls, compute aggregates, exit non-zero on regression. Typical gate: faithfulness >= 0.90, answer relevance >= 0.85, cost-per-eval under budget, wall-clock under 15 minutes. For flake handling: temperature 0, fixed seeds, N=3 runs with median." AITestingGuide: "SDETs set numeric thresholds on metrics — faithfulness above 0.8 — to pass or fail builds automatically in CI/CD." TECHSY: "Use LLM-as-judge for 80% of evals, automated metrics for CI/CD gates, human review for calibration and compliance. That is the 2026 playbook." CI/CD: (1) Golden eval set: 100-500 cases with expected outputs. (2) Run on every PR or model change. (3) Gates: faithfulness >= 0.90, answer_relevance >= 0.85. (4) Temperature 0, fixed seeds for reproducibility. (5) N=3 runs with median for non-deterministic providers. (6) Exit non-zero on regression. (7) Wall-clock under 15 minutes.

When should you use human evaluation vs automated metrics?

Use humans to calibrate and settle judgments automated metrics cannot. Use automated metrics for CI/CD and scale. AgentsCamp: "Use humans to establish ground truth and settle judgments automated metrics cannot — taste, tone, safety nuance, domain correctness. Efficient pattern: human-label few hundred cases once, use that set to calibrate LLM judge or train automated checks, then run cheap automated metrics in CI and reserve fresh human review for periodic audits and ambiguous cases." TECHSY: "Human evaluation costs $5-50 per evaluation, takes minutes instead of milliseconds. Use for calibrating LLM-as-judge, compliance audits, validating edge cases. Combine both — automated metrics catch most regressions cheaply, humans review flagged edge cases." AITestingGuide: "Automated LLM evaluation uses metrics and LLM-as-judge to test at scale with no human in loop. Human evaluation uses people for subjective qualities. Best practice combines both." When: (1) Human: calibrate LLM judge (few hundred cases), compliance audits, taste/tone/safety, domain correctness, ambiguous cases. (2) Automated: CI/CD gates, production monitoring, regression testing, scale. (3) Pattern: human-label 200 cases, calibrate judge, run automated in CI, human audit quarterly. (4) Human cost: $5-50 per evaluation. (5) Automated: milliseconds, pennies. (6) 2026 playbook: LLM-as-judge 80%, automated CI/CD gates, human for calibration.


Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →