Pavan Rangani

HomeBlogRAG Evaluation Frameworks: Measuring Quality with RAGAS and TruLens

RAG Evaluation Frameworks: Measuring Quality with RAGAS and TruLens

By Pavan Rangani · March 21, 2026 · AI & ML

RAG Evaluation Frameworks: Measuring Quality with RAGAS and TruLens

RAG Evaluation Frameworks for Production Quality

RAG evaluation frameworks solve a critical problem: how do you know if your retrieval-augmented generation pipeline is actually working well? Without systematic measurement, RAG systems degrade silently — retrieving irrelevant documents, hallucinating despite context, or quietly missing key information that lives in the knowledge base. Because the failure modes are subtle, a pipeline can look healthy in a demo and still produce wrong answers for a fifth of real user questions. RAGAS and TruLens provide automated metrics that measure every component of the pipeline, from retrieval precision to answer faithfulness, so quality becomes a number you can track rather than a feeling.

This guide covers setting up automated evaluation, understanding the key metrics (faithfulness, relevance, correctness), building evaluation datasets, and integrating quality checks into your CI/CD pipeline. Moreover, you will learn to catch regressions before they reach users and to improve your system with data-driven insights rather than guesswork. The goal is not a one-time score but a continuous feedback loop that survives prompt changes, model upgrades, and document churn.

Understanding RAG Quality Dimensions

RAG quality is not a single metric — it is a combination of retrieval quality, generation quality, and end-to-end correctness. Each dimension requires a different evaluation approach, and crucially, each can fail independently. A perfect retriever paired with a careless generator still hallucinates; a faithful generator fed irrelevant context still answers the wrong question.

RAG evaluation frameworks quality dimensions
The three pillars of RAG quality: retrieval, generation, and end-to-end correctness
RAG Quality Metrics

RETRIEVAL QUALITY:
├── Context Precision — Are retrieved docs relevant?
├── Context Recall — Are all needed docs retrieved?
└── Context Relevance — How focused is the context?

GENERATION QUALITY:
├── Faithfulness — Does answer stick to retrieved context?
├── Answer Relevance — Does answer address the question?
└── Hallucination — Does answer include unsupported claims?

END-TO-END:
├── Answer Correctness — Is the final answer right?
├── Answer Similarity — How close to ground truth?
└── Latency — Is response time acceptable?

How to Read the Metrics Together

The real diagnostic power comes from reading these numbers as a matrix rather than in isolation. For instance, when faithfulness is high but answer correctness is low, the generator is faithfully summarizing the wrong documents — the bug lives in retrieval, not generation. Conversely, when context recall is strong but faithfulness drops, the right documents reached the model and it ignored them, which usually points to a prompt or context-window problem.

A common pattern in production teams is to follow a “diagnostic ladder”: fix retrieval metrics first, because no amount of prompt engineering rescues an answer built on missing context. Only once context precision and recall clear their thresholds should you tune generation. Otherwise you spend weeks optimizing prompts against a retriever that never surfaces the relevant chunk in the first place. This ordering matters because retrieval errors propagate downstream and contaminate every generation metric.

Evaluating with RAGAS

RAGAS (Retrieval Augmented Generation Assessment) is the most widely adopted open-source framework for this job. It uses an LLM-as-judge approach to compute metrics without requiring human annotations for most cases, which makes it practical to run over hundreds of questions on every commit. The docs recommend pinning the judge model and temperature so scores stay comparable across runs.

from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
    context_entity_recall,
    answer_correctness,
)
from datasets import Dataset

# Prepare evaluation dataset
eval_data = {
    "question": [
        "What is the maximum connection pool size for PostgreSQL?",
        "How do I configure SSL for Redis connections?",
        "What retry policy should I use for transient failures?",
    ],
    "answer": [
        "The default max pool size is 100, but for production we recommend...",
        "Configure SSL by setting the tls parameter in your Redis client...",
        "Use exponential backoff with jitter for transient failures...",
    ],
    "contexts": [
        ["PostgreSQL connection pooling docs...", "HikariCP configuration..."],
        ["Redis TLS configuration guide...", "Certificate setup..."],
        ["Retry patterns documentation...", "Circuit breaker guide..."],
    ],
    "ground_truth": [
        "The maximum connection pool size defaults to 100 connections...",
        "SSL for Redis requires configuring TLS certificates and...",
        "Exponential backoff with jitter is recommended for...",
    ],
}

dataset = Dataset.from_dict(eval_data)

# Run evaluation with all metrics
results = evaluate(
    dataset=dataset,
    metrics=[
        faithfulness,
        answer_relevancy,
        context_precision,
        context_recall,
        answer_correctness,
    ],
)

# Print results
print(results)
# {'faithfulness': 0.92, 'answer_relevancy': 0.88,
#  'context_precision': 0.85, 'context_recall': 0.90,
#  'answer_correctness': 0.87}

# Detailed per-question analysis
df = results.to_pandas()
print(df[['question', 'faithfulness', 'answer_relevancy']].to_string())

The per-question dataframe is where the value lives. Aggregate scores hide the long tail, so teams typically sort by the lowest faithfulness rows and inspect those transcripts manually. That triage almost always reveals a cluster — a document category that chunks badly, an embedding that confuses two similar product names, or a prompt that truncates context. Fixing the cluster moves the aggregate far more than chasing average scores blindly.

Integrating TruLens for Production Monitoring

Moreover, TruLens provides real-time evaluation and monitoring for live RAG systems. Whereas RAGAS shines for offline batch scoring against a golden set, TruLens wraps the running pipeline and evaluates every response, tracking quality over time and surfacing degradation as it happens. This matters because production drift — new documents, changed user behavior, a silently upgraded model — rarely shows up in a static test set.

from trulens.core import TruSession
from trulens.apps.custom import TruCustomApp, instrument
from trulens.providers.openai import OpenAI as TruOpenAI
from trulens.core import Feedback
import numpy as np

# Initialize TruLens session
session = TruSession(database_url="sqlite:///trulens_eval.db")

# Define feedback functions
provider = TruOpenAI()

# Faithfulness — does the response stick to context?
f_faithfulness = (
    Feedback(provider.groundedness_measure_with_cot_reasons)
    .on_input_output()
    .aggregate(np.mean)
)

# Relevance — is the response relevant to the question?
f_relevance = (
    Feedback(provider.relevance_with_cot_reasons)
    .on_input_output()
    .aggregate(np.mean)
)

# Context relevance — are retrieved docs relevant?
f_context_relevance = (
    Feedback(provider.context_relevance_with_cot_reasons)
    .on_input()
    .on(TruCustomApp.select_context())
    .aggregate(np.mean)
)

# Instrument your RAG pipeline
class ProductionRAGPipeline:
    def __init__(self):
        self.retriever = VectorStoreRetriever()
        self.llm = ChatModel()

    @instrument
    def retrieve(self, query: str) -> list[str]:
        return self.retriever.search(query, top_k=5)

    @instrument
    def generate(self, query: str, context: list[str]) -> str:
        prompt = f"Context: {' '.join(context)}\n\nQuestion: {query}"
        return self.llm.complete(prompt)

    @instrument
    def query(self, question: str) -> str:
        context = self.retrieve(question)
        return self.generate(question, context)

# Wrap with TruLens monitoring
rag = ProductionRAGPipeline()
tru_rag = TruCustomApp(
    rag,
    app_name="production-rag",
    app_version="v2.1",
    feedbacks=[f_faithfulness, f_relevance, f_context_relevance],
)

# Every call is now evaluated
with tru_rag as recording:
    response = rag.query("How do I configure database connection pooling?")

# Launch dashboard to visualize metrics
session.run_dashboard()
RAG pipeline evaluation dashboard with metrics
Monitoring RAG quality metrics in production with TruLens dashboard

One practical caution: running an LLM judge on every production request adds latency and token cost. As a result, most teams sample — evaluating perhaps 5–10% of live traffic, or running feedback functions asynchronously after the response is already returned to the user. The app_version field is also worth using deliberately, because it lets you compare scores across deployments and prove that a prompt change actually improved quality rather than just feeling better.

Building Evaluation Datasets

Quality evaluation requires quality test data. Additionally, the best datasets combine synthetic generation with human curation for maximum coverage, since synthetic questions provide breadth while human-written ones capture the messy, ambiguous phrasing real users actually type.

from ragas.testset.generator import TestsetGenerator
from ragas.testset.evolutions import simple, reasoning, multi_context
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

# Generate synthetic evaluation dataset from your documents
generator = TestsetGenerator.from_langchain(
    generator_llm=ChatOpenAI(model="gpt-4o"),
    critic_llm=ChatOpenAI(model="gpt-4o"),
    embeddings=OpenAIEmbeddings(),
)

# Load your knowledge base documents
from langchain_community.document_loaders import DirectoryLoader
loader = DirectoryLoader("./docs/", glob="**/*.md")
documents = loader.load()

# Generate test set with different complexity levels
testset = generator.generate_with_langchain_docs(
    documents=documents,
    test_size=50,
    distributions={
        simple: 0.4,       # Straightforward questions
        reasoning: 0.3,    # Multi-step reasoning
        multi_context: 0.3, # Requires multiple documents
    },
)

# Export for review and manual curation
test_df = testset.to_pandas()
test_df.to_csv("evaluation_dataset.csv", index=False)
print(f"Generated {len(test_df)} evaluation questions")

The distribution weights deserve thought rather than being copied blindly. A documentation search bot lives mostly on simple lookups, so weighting simple questions higher mirrors reality. A research assistant, by contrast, leans on multi-context reasoning, so that bucket should dominate. The key discipline is to freeze a golden subset — questions you have manually verified — and never regenerate it, because a stable golden set is what makes regression comparisons trustworthy over months.

CI/CD Quality Gates

# .github/workflows/rag-quality.yml
name: RAG Quality Gate
on:
  pull_request:
    paths: ['src/rag/**', 'prompts/**', 'config/retrieval.yml']

jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install ragas datasets langchain-openai

      - name: Run RAG Evaluation
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: |
          python scripts/evaluate_rag.py \
            --dataset eval/golden_dataset.json \
            --output eval_results.json

      - name: Check Quality Thresholds
        run: |
          python -c "
          import json
          results = json.load(open('eval_results.json'))
          thresholds = {
              'faithfulness': 0.85,
              'answer_relevancy': 0.80,
              'context_precision': 0.75,
          }
          failures = []
          for metric, threshold in thresholds.items():
              score = results.get(metric, 0)
              if score < threshold:
                  failures.append(f'{metric}: {score:.3f} < {threshold}')
          if failures:
              print('Quality gate FAILED:')
              for f in failures: print(f'  - {f}')
              exit(1)
          print('Quality gate PASSED')
          "

Two failure modes plague these gates in practice. First, LLM-judge scores carry inherent variance, so a threshold set too close to the current score will flake — a margin a few points below your observed baseline is more stable than demanding a round number. Second, evaluation runs cost real API spend, so gating only on changes to src/rag/** and prompts/** (as the paths filter does) avoids burning tokens on unrelated commits. For teams worried about non-determinism, running the gate twice and averaging is a cheap way to dampen noise.

When NOT to Use Automated RAG Evaluation

Automated frameworks use LLMs to judge LLM outputs, which introduces correlated bias: the judge shares blind spots with the system under test. For high-stakes domains — medical, legal, financial — automated metrics should supplement, never replace, human review. Furthermore, metrics like faithfulness have their own failure modes and may miss subtle factual errors that a domain expert would catch instantly.

There are also cost and latency ceilings. If your pipeline serves millions of requests and you cannot afford a judge call per response, full automated evaluation in production is unrealistic; sampling and offline batch scoring become the pragmatic compromise. Consequently, use automation for regression testing and continuous monitoring at scale, but keep a human evaluation loop for accuracy validation on the questions that matter most. As a result, the strongest strategy combines automated metrics for breadth with periodic expert review for depth. For deeper context on the retrieval side, see our guides on RAG architecture patterns and advanced chunking strategies.

Data quality monitoring and evaluation metrics
Combining automated and human evaluation for comprehensive RAG quality assurance

Key Takeaways

Use RAGAS for batch evaluation with metrics like faithfulness, relevance, and correctness, and use TruLens for production monitoring with real-time feedback tracking. Build golden datasets that combine synthetic generation with human curation, fix retrieval before generation, and wire quality gates into CI/CD so regressions surface in pull requests rather than in front of users.

Key Takeaways

  • Read metrics as a matrix — faithfulness and correctness together pinpoint whether retrieval or generation is at fault
  • Fix retrieval quality first; prompt tuning cannot rescue answers built on missing context
  • Sample production traffic for live evaluation to control judge latency and token cost
  • Freeze a golden subset so regression comparisons stay trustworthy over time
  • Keep humans in the loop for high-stakes domains where judge bias is unacceptable

For related AI topics, explore our guide on RAG architecture patterns and LLM prompt engineering techniques. The RAGAS documentation and TruLens documentation provide comprehensive framework references.

In conclusion, RAG evaluation frameworks turn RAG quality from an opaque guess into a measurable, defensible engineering discipline. By applying the patterns in this guide — diagnostic metrics, curated golden datasets, sampled production monitoring, and CI quality gates — you can build retrieval-augmented systems that stay accurate as prompts, models, and documents evolve. Start with the fundamentals, iterate on your evaluation set, and continuously measure results so quality improves with every release.

← Back to all articles