Series: Enterprise GenAI & RAG Architecture — Part 5 of 5
Why Testing a RAG System is Different
Testing a traditional API is straightforward: given input X, expect output Y. Testing a RAG system is categorically different. You are simultaneously validating:
- Data quality — are the right documents ingested correctly?
- Chunking quality — are chunks meaningful and complete?
- Embedding quality — do similar topics score highly together?
- Retrieval accuracy — are the most relevant chunks returned?
- LLM output quality — is the answer grounded, accurate, and complete?
- Security — is the system resistant to prompt injection and PII leakage?
Each layer can fail independently. A perfect LLM with bad retrieval still produces bad answers. This is why RAG testing requires a layered, systematic approach.
The most expensive RAG failure is silent degradation — retrieval quality slowly drifts as documents change, and nobody notices until users stop trusting the system.
The RAG Testing Pyramid — 4 Layers
Borrow the concept of the traditional test pyramid and apply it to RAG. Build from the foundation up:
| Layer | What is Tested | Tools | Runs In Pipeline |
| Layer 1 — Data Testing (Foundation) | Missing docs, duplicates, empty chunks, bad metadata | PyTest, Great Expectations | Every commit |
| Layer 2 — Embedding Testing | Correct dimensions, similarity scores, semantic coherence | Python, DeepEval | Every commit |
| Layer 3 — Retrieval Testing | Top-K precision, context recall, relevant chunks returned | RAGAS | Every commit |
| Layer 4 — LLM Response Testing | Hallucination, accuracy, completeness, answer relevance | RAGAS, DeepEval, PromptFoo | Every commit |
Layer 1: Data Quality Testing
This is the cheapest, fastest layer — and the most overlooked. Bad data causes silent failures downstream.
- Are all expected source documents present in the vector DB?
- Are there any duplicate chunks (same content stored twice)?
- Are any chunks empty, too short (< 50 tokens), or garbled text?
- Is metadata complete — source filename, page number, and ingestion date?
def test_no_empty_chunks(chunks):
for chunk in chunks:
assert len(chunk.text.strip()) > 50, f'Empty chunk: {chunk.id}'
assert chunk.metadata['source'] is not None
def test_no_duplicate_chunks(chunks):
texts = [c.text for c in chunks]
assert len(texts) == len(set(texts)), 'Duplicate chunks detected'
def test_document_count(vector_db, expected_count):
actual = vector_db.get_document_count()
assert actual >= expected_count, f'Expected {expected_count}, got {actual}'
Layer 2: Embedding Quality Testing
- Embedding generated for every chunk — no nulls or errors
- Correct vector dimensions: 1536 (Azure), 1024 (AWS/OCI)
- Semantically similar chunks score above 0.80 cosine similarity
- Unrelated chunks score below 0.30 — no false positives
def test_embedding_dimensions(embedding):
assert len(embedding) == 1536 # For Azure text-embedding-3-small
def test_semantic_similarity():
score = cosine_similarity(
embed('annual leave entitlement'),
embed('how many days holiday do I get?')
)
assert score > 0.80, f'Similarity too low: {score}'
def test_dissimilar_chunks_score_low():
score = cosine_similarity(
embed('leave policy'), embed('network firewall rules')
)
assert score < 0.30, f'False similarity detected: {score}'
Layer 3: Retrieval Testing with RAGAS
RAGAS (Retrieval Augmented Generation Assessment) is the industry-standard framework for evaluating RAG systems. It provides metric-driven, reproducible evaluation across your entire pipeline.
Context Precision — ‘Is what we retrieved actually relevant?’
Measures the proportion of retrieved chunks that are genuinely relevant to the question.
# Example calculation: # Retrieved 5 chunks for: 'What is the expense claim limit?' # Relevant: chunks 1, 3, 4 (about expenses) # Irrelevant: chunks 2, 5 (about leave policy, office hours) # Context Precision = 3/5 = 0.60 --> FAIL (threshold: 0.80)
Context Recall — ‘Did we retrieve everything needed?’
Measures whether all facts required to answer correctly were present in the retrieved context.
# Example calculation:
# Ground truth answer requires 4 key facts
# Retrieved context contains 3 of those facts
# Context Recall = 3/4 = 0.75 --> PASS (threshold: 0.75)
from ragas import evaluate
from ragas.metrics import context_precision, context_recall
results = evaluate(
dataset=test_dataset, # questions + ground truths + retrieved contexts
metrics=[context_precision, context_recall]
)
assert results['context_precision'] >= 0.80, 'Precision below threshold'
assert results['context_recall'] >= 0.75, 'Recall below threshold'
print(f"Precision: {results['context_precision']:.2f}")
print(f"Recall: {results['context_recall']:.2f}")
Layer 4: LLM Response Testing — Hallucination Detection
The Four Response Quality Metrics
| Metric | Definition | Minimum Threshold |
| Faithfulness | Is the answer grounded ONLY in the retrieved context? This is your hallucination score. | ≥ 0.85 |
| Answer Relevance | Does the generated answer actually address what the user asked? | ≥ 0.80 |
| Accuracy | Does the answer match known ground truth answers in your golden dataset? | Domain-specific |
| Completeness | Does the answer cover all the key facts needed to fully address the question? | ≥ 0.75 |
Hallucination in Detail
Faithfulness measures whether every claim in the LLM’s answer can be traced back to the retrieved context. A claim that cannot be traced is a hallucination.
# Context says: 'Employees are entitled to 20 days annual leave per year' # # BAD answer: 'Employees get 25 days plus 5 bonus days for performance' # --> '25 days' and 'bonus days' are NOT in context # --> Faithfulness = 0.0 --> HALLUCINATION --> FAIL # # GOOD answer: 'According to company policy, employees receive 20 days annual leave' # --> Every claim traces back to the context # --> Faithfulness = 1.0 --> PASS
Additional hallucination detection tools:
- DeepEval HallucinationMetric — standalone hallucination scorer with explanation
- PromptFoo — test known Q&A pairs, flag when answers deviate from expected output
- LangSmith — full chain tracing to see exactly what context was passed and what was generated
The Complete Enterprise CI/CD Pipeline
Every RAG system change — whether code, documents, or configuration — must go through this automated quality gate before reaching production.
| Pipeline Stage | Actions | Blocking on Failure? |
| Planning | Jira / ADO ticket created, developer assigned | N/A |
| Development | Code pushed to Git, PR created | N/A |
| Stage 1: Unit Tests | PyTest — chunking, extractors, prompt templates | Yes — blocks immediately |
| Stage 2: Integration Tests | API health, vector DB connection, embedding API | Yes |
| Stage 3: Document Ingestion | Full ingestion run on test document set | Yes |
| Stage 4: Vector DB Validation | Chunk count, metadata integrity, sample queries | Yes |
| Stage 5: RAGAS Evaluation | All 4 RAGAS metrics against thresholds | Yes — quality gate |
| Stage 6: Prompt Regression | 50+ golden Q&A pairs via PromptFoo/DeepEval | Yes |
| Stage 7: Security Tests | OWASP LLM Top 10 — injection, PII, access control | Yes — critical findings block |
| Deployment | Blue/Green or Canary to staging then production | Smoke tests block |
| Production Monitoring | Continuous RAGAS scoring, latency, user feedback | Alerts trigger re-evaluation |
The most important insight: RAGAS evaluation is NOT just a one-time quality check. Run it continuously in production. Retrieval quality drifts as your document base evolves.
QA Automation Framework — The Complete Stack
As a QA Lead or SDET on a RAG project, this is your complete toolbox:
| Check Type | Tool | What is Validated |
| ✅ API Testing | PyTest + Requests | REST endpoints, response codes, latency SLA |
| ✅ Vector DB Validation | PyTest | Chunk count, metadata integrity, index health |
| ✅ Retrieval Accuracy | RAGAS | Context Precision ≥ 0.80, Context Recall ≥ 0.75 |
| ✅ Hallucination Detection | DeepEval / RAGAS Faithfulness | Faithfulness ≥ 0.85 |
| ✅ Prompt Regression | PromptFoo | Golden Q&A dataset — no degradation vs baseline |
| ✅ LLM Tracing | LangSmith | Full chain traces, latency profiling, error diagnosis |
| ✅ Performance Testing | Locust / k6 | Embedding API P95, vector search P95, LLM P95 ≤ 3s |
| ✅ Security Testing | Custom + OWASP | Prompt injection, PII leakage, OWASP LLM Top 10 |
| ✅ UI / E2E Testing | Playwright | Chat interface, user workflows, accessibility (WCAG) |
Recommended Test Folder Structure
tests/ ├── data/ │ ├── test_ingestion.py # All source docs ingested │ ├── test_chunking.py # No empty or duplicate chunks │ └── test_metadata.py # Source, page, date correct ├── embeddings/ │ ├── test_embedding_dim.py # Vector shape correct │ └── test_similarity.py # Semantic similarity scores ├── retrieval/ │ ├── test_ragas_precision.py # Context Precision >= 0.80 │ └── test_ragas_recall.py # Context Recall >= 0.75 ├── llm/ │ ├── test_faithfulness.py # Hallucination detection │ ├── test_relevance.py # Answer relevance │ └── test_promptfoo.py # Golden Q&A regression └── security/ ├── test_prompt_injection.py # Injection blocked └── test_pii_leakage.py # No PII in responses
Series Summary — 9 Principles to Remember
- RAG is the enterprise standard. Private data + LLM = grounded, accurate answers. No retraining needed.
- Every pipeline stage needs testing. Ingest → Chunk → Embed → Store → Retrieve → Generate — each can fail independently.
- Chunking quality determines retrieval quality. 512 tokens + 10% overlap is your safe default. Invest time tuning this.
- Never switch embedding models without re-indexing. It will silently break your entire retrieval layer.
- RAGAS is non-negotiable. Context Precision + Recall measure retrieval. Faithfulness + Relevance measure LLM output.
- CI/CD for RAG is mandatory. Every document change can silently break retrieval. Automate your quality gate.
- Choose cloud by ecosystem. Microsoft shop → Azure. Multi-model → AWS. Oracle DB customers → OCI.
- Security testing for LLMs is different. OWASP LLM Top 10 is your checklist. Prompt injection is your highest risk.
- Monitoring never stops. Set up continuous RAGAS evaluation in production. Drift is silent and inevitable.
References
- RAGAS — https://docs.ragas.io
- DeepEval — https://docs.confident-ai.com
- PromptFoo — https://promptfoo.dev
- LangSmith — https://smith.langchain.com
- Azure AI Search — https://learn.microsoft.com/en-us/azure/search/
- AWS Bedrock — https://docs.aws.amazon.com/bedrock
- Oracle AI Vector Search — https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/
- OWASP LLM Top 10 — https://owasp.org/www-project-top-10-for-large-language-model-applications/