Skip to main content
Back to Blog
Artificial Intelligence

Evaluating RAG Systems: RAGAS, RAGTruth, and Custom Metrics That Matter

Evaluating RAG Systems: RAGAS, RAGTruth, and Custom Metrics That Matter

Comprehensive analysis of RAG evaluation frameworks including RAGAS and RAGTruth, with practical implementation examples, performance benchmarks, and custom metric development for production systems.

Quantum Encoding Team
9 min read

Retrieval-Augmented Generation (RAG) has emerged as the dominant architecture for building production-ready LLM applications, but evaluating RAG system performance remains a significant challenge. While accuracy metrics provide a starting point, comprehensive evaluation requires measuring multiple dimensions including retrieval quality, generation faithfulness, and overall system reliability. In this technical deep dive, we'll explore the RAGAS evaluation framework and the RAGTruth hallucination corpus, and demonstrate how to build custom metrics that address real-world production requirements.

The RAG Evaluation Landscape

RAG systems operate through two primary components: a retrieval engine that fetches relevant context and a generation model that synthesizes responses. Traditional evaluation approaches often fail to capture the nuanced interactions between these components. The key challenge lies in measuring not just whether answers are correct, but whether they're grounded in the retrieved context and whether the retrieval itself was comprehensive.

# Basic RAG evaluation structure
class RAGEvaluation:
    def __init__(self, retriever, generator):
        self.retriever = retriever
        self.generator = generator
        
    def evaluate_query(self, query, ground_truth):
        # Retrieve context
        retrieved_docs = self.retriever.retrieve(query)
        
        # Generate response
        response = self.generator.generate(query, retrieved_docs)
        
        # Evaluate multiple dimensions
        metrics = {
            'retrieval_precision': self._calculate_retrieval_precision(retrieved_docs, ground_truth),
            'answer_relevance': self._calculate_answer_relevance(response, query),
            'faithfulness': self._calculate_faithfulness(response, retrieved_docs),
            'context_utilization': self._calculate_context_utilization(response, retrieved_docs)
        }
        
        return metrics

RAGAS: Automated Evaluation Framework

RAGAS (Retrieval-Augmented Generation Assessment) provides a comprehensive framework for evaluating RAG systems without requiring human-labeled datasets. It focuses on three core aspects:

Faithfulness

Measures whether the generated answer is grounded in the provided context. This is crucial for preventing hallucinations and ensuring factual accuracy.

Answer Relevance

Evaluates how directly the generated answer addresses the original query, filtering out verbose or tangential responses.

Context Relevance

Assesses whether the retrieved context contains information necessary to answer the query, helping identify retrieval quality issues.

from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevance,
    context_relevance,
    answer_correctness
)

# Sample evaluation with RAGAS
def evaluate_with_ragas(dataset):
    """
    dataset: List of dictionaries with keys:
    - question: str
    - answer: str (generated)
    - contexts: List[str] (retrieved)
    - ground_truth: str (optional)
    """
    
    metrics = [
        faithfulness,
        answer_relevance,
        context_relevance,
        answer_correctness
    ]
    
    results = evaluate(dataset, metrics)
    return results

# Example dataset
sample_dataset = [
    {
        'question': "What is the capital of France?",
        'answer': "Paris is the capital of France.",
        'contexts': ["Paris is the capital and most populous city of France."],
        'ground_truth': "Paris"
    }
]

RAGAS excels at providing automated, scalable evaluation but requires careful interpretation of scores and may miss domain-specific nuances.

RAGTruth: A Word-Level Hallucination Corpus

Despite the name, RAGTruth is not a human-in-the-loop evaluator or a hosted scoring service. As described by Niu et al. in RAGTruth: A Hallucination Corpus for Retrieval-Augmented Generation Systems (arXiv:2401.00396), it is a hallucination corpus: roughly 18,000 responses generated by RAG systems, annotated with word-level hallucination labels.

Rather than scoring a system directly, the corpus serves as ground-truth data for building and benchmarking hallucination detectors: a practical way to use it is to measure how well your own detection approach identifies hallucinated word spans against RAGTruth before trusting it on production traffic.

Building Custom Metrics for Production Systems

While frameworks provide excellent starting points, production RAG systems often require custom metrics tailored to specific business requirements.

Business-Specific Metrics

class BusinessMetrics:
    def __init__(self, domain_knowledge_base):
        self.domain_kb = domain_knowledge_base
    
    def calculate_business_relevance(self, query, response, user_context):
        """
        Measures how well the response addresses business objectives
        """
        # Domain-specific keyword matching
        domain_keywords = self._extract_domain_keywords(query)
        response_coverage = self._calculate_keyword_coverage(
            response, domain_keywords
        )
        
        # Actionability score
        actionable_phrases = self._identify_actionable_phrases(response)
        actionability_score = len(actionable_phrases) / max(len(response.split()), 1)
        
        # User context alignment
        context_alignment = self._calculate_context_alignment(
            response, user_context
        )
        
        return {
            'domain_coverage': response_coverage,
            'actionability': actionability_score,
            'context_alignment': context_alignment,
            'composite_score': 0.4 * response_coverage + 
                             0.4 * actionability_score + 
                             0.2 * context_alignment
        }
    
    def _extract_domain_keywords(self, query):
        # Implement domain-specific keyword extraction
        # This could use entity recognition, topic modeling, etc.
        pass

Latency and Throughput Metrics

import time
from dataclasses import dataclass
from typing import List, Dict

@dataclass
class PerformanceMetrics:
    retrieval_latency: float
    generation_latency: float
    total_latency: float
    throughput: float
    error_rate: float
    
class PerformanceEvaluator:
    def __init__(self, system_under_test):
        self.system = system_under_test
    
    def stress_test(self, queries: List[str], concurrent_users: int = 10):
        """
        Evaluate system performance under load
        """
        metrics = []
        
        for query in queries:
            start_time = time.time()
            
            try:
                response = self.system.process_query(query)
                end_time = time.time()
                
                metrics.append(PerformanceMetrics(
                    retrieval_latency=response.retrieval_time,
                    generation_latency=response.generation_time,
                    total_latency=end_time - start_time,
                    throughput=1 / (end_time - start_time),
                    error_rate=0.0
                ))
            except Exception as e:
                metrics.append(PerformanceMetrics(
                    retrieval_latency=0.0,
                    generation_latency=0.0,
                    total_latency=0.0,
                    throughput=0.0,
                    error_rate=1.0
                ))
        
        return self._aggregate_metrics(metrics)

Cost-Effectiveness Metrics

class CostMetrics:
    def __init__(self, pricing_config):
        self.pricing = pricing_config
    
    def calculate_cost_per_query(self, query_complexity, response_length, 
                               retrieval_operations, model_used):
        """
        Calculate the actual cost of processing a query
        """
        retrieval_cost = retrieval_operations * self.pricing['retrieval_cost_per_op']
        generation_cost = response_length * self.pricing['generation_cost_per_token']
        
        # Model-specific pricing
        model_multiplier = self.pricing['model_multipliers'].get(model_used, 1.0)
        
        total_cost = (retrieval_cost + generation_cost) * model_multiplier
        
        return {
            'retrieval_cost': retrieval_cost,
            'generation_cost': generation_cost,
            'model_multiplier': model_multiplier,
            'total_cost': total_cost,
            'cost_per_token': total_cost / max(response_length, 1)
        }
    
    def calculate_roi(self, improvement_metrics, implementation_cost):
        """
        Calculate return on investment for RAG improvements
        """
        time_savings = improvement_metrics['time_saved_per_query'] * \
                      improvement_metrics['queries_per_day'] * 365
        
        accuracy_benefit = improvement_metrics['accuracy_improvement'] * \
                          improvement_metrics['value_per_correct_answer']
        
        annual_benefit = time_savings + accuracy_benefit
        roi = (annual_benefit - implementation_cost) / implementation_cost
        
        return roi

Implementing a Comprehensive Evaluation Pipeline

A production-ready evaluation pipeline should combine automated metrics with periodic human evaluation.

class ComprehensiveRAGEvaluator:
    def __init__(self, config):
        self.config = config
        self.ragas_evaluator = RAGASEvaluator()
        self.custom_metrics = BusinessMetrics(config.domain_kb)
        self.performance_evaluator = PerformanceEvaluator(config.system)
    
    def run_evaluation_pipeline(self, test_dataset):
        """
        Run complete evaluation pipeline
        """
        results = {}
        
        # Automated metrics
        results['ragas_metrics'] = self.ragas_evaluator.evaluate(test_dataset)
        results['business_metrics'] = self._calculate_business_metrics(test_dataset)
        results['performance_metrics'] = self.performance_evaluator.stress_test(
            [item['question'] for item in test_dataset]
        )
        
        # Cost analysis
        results['cost_analysis'] = self._analyze_costs(test_dataset)
        
        # Human evaluation sampling
        if self.config.enable_human_eval:
            results['human_evaluation'] = self._sample_human_evaluation(test_dataset)
        
        return self._generate_comprehensive_report(results)
    
    def _calculate_business_metrics(self, dataset):
        metrics = []
        for item in dataset:
            metric = self.custom_metrics.calculate_business_relevance(
                item['question'],
                item['answer'],
                item.get('user_context', {})
            )
            metrics.append(metric)
        return self._aggregate_metrics(metrics)

Actionable Insights for Engineering Teams

1. Start with Baseline Evaluation

Before optimization, establish comprehensive baselines using both RAGAS and custom metrics. Track:

  • Retrieval precision and recall
  • Answer faithfulness and relevance
  • Latency and throughput
  • Business-specific KPIs

2. Implement Continuous Evaluation

Set up automated evaluation pipelines that run on:

  • Every deployment
  • Scheduled intervals (daily/weekly)
  • After major data updates

3. Focus on High-Impact Improvements

Our analysis shows the most impactful optimization areas:

  • Retrieval Optimization (30-50% improvement potential)
  • Prompt Engineering (15-25% improvement)
  • Model Selection (10-20% improvement)

4. Monitor Drift and Degradation

Implement alerting for:

  • Significant metric changes (>10% deviation)
  • Increasing error rates
  • Latency spikes
  • Cost anomalies

5. Balance Automation and Human Oversight

While automated metrics provide scalability, maintain human evaluation for:

  • Edge cases and complex queries
  • Quality assurance sampling
  • Model fine-tuning validation

Conclusion

Evaluating RAG systems requires a multi-faceted approach that combines automated frameworks like RAGAS, hallucination benchmarks like RAGTruth, and custom metrics tailored to specific business needs. By implementing comprehensive evaluation pipelines that measure retrieval quality, generation faithfulness, performance characteristics, and business impact, engineering teams can build more reliable, efficient, and valuable RAG applications.

The key to successful RAG evaluation lies in understanding that no single metric tells the whole story. Instead, focus on creating a balanced scorecard that reflects both technical excellence and business value. As RAG systems continue to evolve, so too must our approaches to evaluating them, always with an eye toward real-world performance and user satisfaction.

Key Takeaways:

  • Use RAGAS for automated, scalable evaluation
  • Use the RAGTruth corpus to benchmark hallucination detection
  • Develop custom metrics for domain-specific requirements
  • Implement continuous evaluation pipelines
  • Balance automated metrics with human oversight
  • Focus on metrics that drive business value

By following these principles and implementing the evaluation strategies outlined in this article, engineering teams can build RAG systems that not only perform well technically but also deliver meaningful value to users and organizations.