Module 8: RAG Evaluation + The Capstone Project

Final Project: An Evaluated Advanced RAG System

Project description

This is the capstone project of the entire guide. It closes the full loop: it integrates the technical components you built in modules 2-7 (intelligent chunking, query optimization, hybrid search, re-ranking, metadata filtering, Pinecone production) with the operational evaluation discipline you just learned in M08.

It isn't "gluing components together". It's proving that the complete pipeline works end-to-end, scales under real load, holds a quality measured in numbers, and operates with the rigor that separates a professional system from a demo. It's the portfolio piece a senior interviewer will look at and say "this candidate really knows how to operate RAG in production".

By the end you'll have a public, production-ready repository that tells a quantitative story: "the baseline system had faithfulness 0.74 and p95 latency 800ms; after applying the guide's techniques, faithfulness is 0.92 and p95 is 350ms, validated against a golden dataset of 100 queries with CI/CD that blocks regressions automatically".

That story, told with data, is what separates "I studied RAG" from "I can build and operate RAG professionally".


The project's objective

Build and integrate a complete RAG system with six operational dimensions:

  1. A working end-to-end RAG pipeline: query → expansion → hybrid retrieval → re-ranking → metadata filtering → generation
  2. Production infrastructure on Pinecone with multi-tenancy and observability
  3. A versioned golden dataset with 50-100 representative queries and validated ground truth
  4. An automated evaluation pipeline with RAGAS, segmented metrics and JSON+MD reports
  5. Quality gates with calibrated thresholds, regression testing and a versioned baseline
  6. Active CI/CD in GitHub Actions with smoke on the PR, full nightly, and diff comments on pull requests

The complete system's architecture

┌──────────────────────────────────────────────────────────────────┐
│                      User Query (with TenantContext)              │
└──────────────────────────┬───────────────────────────────────────┘
                           │
                           ▼
                ┌──────────────────────┐
                │ Query Optimization   │  M03
                │ (expand, rewrite)    │
                └──────────┬───────────┘
                           │
                           ▼
            ┌──────────────────────────────┐
            │ Hybrid Retrieval             │  M05
            │ (BM25 + semantic, RRF fuse)  │
            └──────────┬───────────────────┘
                       │
                       ▼
        ┌─────────────────────────────────────┐
        │ Metadata Filtering                  │  M06
        │ (FilterSpec → namespace + filter)   │
        └──────────┬──────────────────────────┘
                   │
                   ▼
              ┌─────────────────────┐
              │ Re-ranking          │  M04
              │ (cross-encoder)     │
              └──────────┬──────────┘
                         │
                         ▼
                  ┌─────────────────┐
                  │ Generation      │
                  │ (gpt-4o-mini)   │
                  └────────┬────────┘
                           │
                           ▼
                ┌──────────────────────┐
                │ Evaluation Logging   │  M08
                │ (trace + metrics)    │
                └──────────────────────┘

Every box is a component you saw built in the previous modules. Your job is to integrate them while keeping the interfaces clean.


The mandatory stack

LayerTechnologySource capsule
Vector DBPinecone serverlessM07
EmbeddingsOpenAI text-embedding-3-smallM02, M07
GenerationOpenAI gpt-4o-mini
BM25rank-bm25M05
Re-rankersentence-transformers cross-encoderM04
Eval frameworkRAGASM08
APIFastAPIM07
Observabilitystructlog + PrometheusM07
CI/CDGitHub ActionsM08
Testspytest

The repository's structure

advanced-rag-system/
├── app/
│   ├── pipeline.py              # The AdvancedRAGSystem orchestrator
│   ├── retrieval/
│   │   ├── hybrid.py            # M05 hybrid search
│   │   ├── reranker.py          # M04 cross-encoder
│   │   └── pinecone_backend.py  # M07
│   ├── query/
│   │   └── optimizer.py         # M03 expansion
│   ├── tenant.py                # TenantContext from M07
│   ├── metadata.py              # FilterSpec from M06
│   ├── generation.py            # LLM generation with a grounding prompt
│   ├── observability.py         # structlog + traces
│   └── api.py                   # The FastAPI endpoints
├── eval/
│   ├── loader.py                # The golden dataset loader
│   ├── runner.py                # The pipeline executor
│   ├── metrics.py               # The RAGAS wrapper
│   ├── thresholds.py            # The quality gates
│   ├── reporter.py              # JSON + MD reports
│   └── baseline.json            # The versioned baseline
├── golden_dataset/
│   ├── v1.0.0.json              # 50+ queries
│   ├── META.json
│   ├── PROCESS.md
│   └── CHANGELOG.md
├── scripts/
│   ├── evaluate.py              # The main CLI
│   ├── migrate_from_chroma.py
│   ├── benchmark.py
│   ├── compare_baseline.py
│   └── update_baseline.py
├── tests/
│   ├── test_isolation.py        # Multi-tenant safety
│   ├── test_pipeline.py
│   └── test_quality_gates.py
├── .github/workflows/
│   ├── rag-eval-smoke.yml
│   ├── rag-eval-nightly.yml
│   └── rag-eval-compare.yml
├── docker-compose.yml
├── pyproject.toml
├── BENCHMARK.md
├── DECISION.md
└── README.md

Mandatory features

1) The integrated RAG pipeline

# app/pipeline.py
from typing import Awaitable

class AdvancedRAGSystem:
    def __init__(
        self,
        query_optimizer,
        hybrid_retriever,
        reranker,
        generator,
        tracer,
    ):
        self.query_optimizer = query_optimizer
        self.hybrid_retriever = hybrid_retriever
        self.reranker = reranker
        self.generator = generator
        self.tracer = tracer

    async def answer_with_context(
        self,
        query: str,
        tenant: TenantContext,
        filter_spec: FilterSpec | None = None,
    ) -> RAGResponse:
        trace = self.tracer.start_trace(query=query, tenant_id=tenant.tenant_id)

        expanded = await self.query_optimizer.expand(query)
        trace.record("query_expansion", {"variants": expanded})

        candidates = await self.hybrid_retriever.retrieve(
            queries=expanded,
            tenant=tenant,
            filter_spec=filter_spec or FilterSpec(),
            top_k=50,
        )
        trace.record("retrieval", {"n_candidates": len(candidates)})

        reranked = await self.reranker.rerank(query=query, documents=candidates, top_k=5)
        trace.record("rerank", {"final_top_k": 5})

        answer = await self.generator.generate(query=query, context=reranked)
        trace.record("generation", {"answer_length": len(answer)})

        return RAGResponse(
            answer=answer,
            sources=reranked,
            trace_id=trace.trace_id,
        )

Why this design:

  • Dependency injection: every component is testable in isolation
  • Tracing built in: every stage records metrics, latency and decisions
  • Async native: parallelizable under load without a rewrite
  • A mandatory TenantContext: isolation guaranteed by types, not by convention

2) The golden dataset and the evaluation

At minimum 50 queries with the distribution from M08/04. Your pipeline must run python scripts/evaluate.py --mode smoke in under 2 minutes.

3) Quality gates in CI

A smoke evaluation on every PR, blocking the merge if:

  • Faithfulness < 0.85
  • Answer Relevancy < 0.80
  • Context Recall < 0.80
  • Or a regression > 2% vs the baseline on any core metric

4) Observability

Every API query generates:

{
  "trace_id": "...",
  "tenant_id": "...",
  "query": "...",
  "stages": {
    "expansion": {"latency_ms": 180, "variants": 3},
    "retrieval": {"latency_ms": 120, "candidates": 50},
    "rerank": {"latency_ms": 250, "final": 5},
    "generation": {"latency_ms": 850, "tokens": 142}
  },
  "total_latency_ms": 1400,
  "cost_usd": 0.0023
}

Mandatory validations

The list of invariants your system must guarantee:

  • Every query requires a valid TenantContext
  • There's no direct call to index.query outside of secure_query
  • The cache key includes the tenant_id (a passing test: two tenants don't share a cache entry)
  • CI blocks the merge if the quality gates fail
  • Structured JSON logs with trace_id, tenant_id, latency_ms per stage
  • The test suite includes test_isolation.py, test_pipeline.py, test_quality_gates.py
  • The README includes an M1 vs M8 comparison table with real metrics

Success criteria

  • ✅ Precision@5 and Recall@20 improve ≥30% vs the simple RAG baseline (M01-style)
  • ✅ The RAGAS metrics clear the thresholds in the evaluation against the golden dataset
  • ✅ p95 latency ≤500ms at concurrency=50
  • ✅ CI/CD runs with no manual intervention; at least 5 PRs went through the pipeline
  • ✅ The final README explains the architecture with a diagram, the technical decisions, and the quantitative results
  • ✅ The repo is demonstrable as a portfolio project

The final M1 → M8 comparison table (mandatory)

This table closes the narrative of the whole guide. It's the first thing a reviewer looks at:

ModuleTechnique addedPrecision@5Recall@20Faithfulnessp95 (ms)
M1 (baseline)Simple RAG0.650.580.74800
M2Chunking optimization0.710.620.79760
M3Query expansion0.740.780.82870
M4Cross-encoder rerank0.850.780.88990
M5Hybrid + RRF0.880.840.89940
M6Metadata filtering0.890.840.90720
M7Pinecone production0.890.840.91350
M8Eval-driven tuning0.910.860.93350

Replace these numbers with your real ones. The structure matters more than the specific values: every row must show a module's quantitative contribution.


Grading rubric (100 points)

Technical integration (40 pts)

  • (15 pts) A working end-to-end pipeline with every stage
  • (10 pts) Multi-tenancy with TenantContext and tested isolation
  • (10 pts) Hybrid + reranking + filtering correctly composed
  • (5 pts) Observability with per-stage tracing

Evaluation (30 pts)

  • (10 pts) A versioned golden dataset with 50+ balanced queries
  • (10 pts) An automated evaluation pipeline, smoke + full modes
  • (10 pts) Regression testing with a baseline and calibrated thresholds

Operations (20 pts)

  • (10 pts) Working CI/CD: smoke on the PR, full nightly, diff comments
  • (10 pts) Error handling with retry, timeouts, fallbacks

Technical communication (10 pts)

  • (10 pts) A professional README with the comparison table, documented decisions, diagrams

Extra credit (+15)

  • (+5 pts) A simple dashboard of historical metrics (Grafana or Streamlit)
  • (+5 pts) Segmented evaluation (easy/medium/hard, by category)
  • (+5 pts) An automatic Slack report when the nightly eval fails

Common mistakes and how to avoid them

  1. Integrating modules without measuring the real impact → every technique must add a row to the comparison table with its delta. If you can't show the quantitative gain, the technique isn't contributing.
  2. A trivial dataset → 50% of the golden dataset must be sampled from real traffic, not invented. Perfect metrics on a trivial dataset are theater.
  3. Made-up thresholds → calibrate empirically with 5+ stable runs (M08/06). Thresholds copied from blogs don't apply to your system.
  4. CI/CD with no blocking → if the quality gate doesn't block the merge, it isn't a gate, it's decoration. --enforce-thresholds must be active.
  5. A README with no quantitative narrative → "the system improved" is useless. What counts is "faithfulness 0.74 → 0.93, validated against 100 queries". Pin the comparison table to the top of the README.
  6. No rollback plan → if a PR passes the quality gates but breaks in production, how do you go back? Document the procedure in DECISION.md.
  7. Ignoring segmentation → global metrics hide problems. Report by difficulty: if "hard" drops 15% but "easy" rises 5%, the average is lying to you.

Documents to deliver

README.md

  • The architecture diagram
  • The M1 → M8 comparison table
  • Setup in 5 minutes
  • How to run the evaluation
  • How to deploy

BENCHMARK.md (from M07)

  • ChromaDB vs Pinecone latency
  • Throughput under concurrency
  • A 12-month cost estimate

DECISION.md (from M07)

  • The trade-offs evaluated
  • The recommendation with quantitative reasons
  • The rollback plan

EVAL.md (new in M08)

  • How the golden dataset was built
  • The threshold calibration methodology
  • Historical quality reports

golden_dataset/PROCESS.md

  • Who annotated it, when, and by what criteria
  • How it gets maintained (the quarterly cycle)

Resources for the project

  1. RAGAS Documentation - The evaluation framework.
  2. Pinecone Production Guides - Operating the vector DB.
  3. GitHub Actions Documentation - CI/CD.
  4. FastAPI Production Patterns - Deploying APIs.
  5. structlog - Structured logging.
  6. LangChain Retrieval Concepts - Retrieval patterns.
  7. Anthropic on Building Eval-Driven Products - The operational philosophy.

Closing the guide

With this project you close a complete progression. You started by building a simple RAG. You learned why it fails in production. You applied advanced techniques: intelligent chunking, query optimization, hybrid search, re-ranking, metadata filtering. You migrated to production infrastructure. And you added the evaluation discipline that turns a system "that works" into a system "that keeps working".

What you have now is a concrete professional capability: designing, implementing, scaling and evaluating advanced RAG systems with objective quality criteria. That capability is scarce in the industry — most teams are stuck on simple RAG without knowing why it fails and without metrics to diagnose it.

The natural next step is to specialize in the frontier extensions:

  • Agentic RAG: systems where the LLM decides when and how to retrieve
  • Multimodal retrieval: images, video, audio in the same pipeline
  • Adaptive retrieval: the system learns from feedback and adjusts its strategies
  • Multi-step reasoning: queries that require multiple chained retrievals
  • Self-RAG and reflective retrieval: the system evaluates its own quality and retries

But before jumping to the frontier, prove you've mastered the fundamentals by publishing this project: a clear README, clean code, honest metrics, active CI/CD. It's the strongest proof of your ability.

Congratulations on making it here. The difference between the person who started the guide and the one who finishes it is real and measurable — the numbers in your final comparison table tell the story.


Created: March 13, 2026
Version: 2.0