Capstone 08 — Production RAG Chatbot for a Regulated Vertical
Production RAG in 2026 has one shape: ingest with docling or Unstructured (ColPali for visuals), search hybrid, re-rank with bge-reranker-v2-gemma, synthesize with Claude Sonnet 4.7 on a 60-80% prompt-cache hit rate, guard with Llama Guard 4 and NeMo Guardrails, watch with Langfuse and Phoenix, grade with RAGAS on a 200-question golden set. Harvey, Glean, Mendable, and LlamaCloud all run it. Build one in a regulated domain (legal, clinical, insurance). The capstone is passing the golden set, the red team, and the drift dashboard.
Learning Objectives
- Design the two sides of a regulated-domain RAG pipeline: ingestion into access-labeled chunks, and conversation with hybrid retrieval, reranking, and role plus jurisdiction filters applied before search.
- Structure each request for prompt caching, with the stable prefix cached and the user question as the uncached suffix, targeting a 60-80% hit rate.
- Layer guardrails (Llama Guard 4, NeMo Guardrails, Presidio, citation enforcement) around synthesis so unsafe or unsupported output never reaches the user.
- Evaluate the system with a 200-question golden set, a red-team suite, online RAGAS scores, and a drift dashboard, and report cost per query by stage.
Problem
A regulated-domain RAG chatbot answers questions from legal contracts, clinical trial protocols, or insurance policies, and backs every claim with a citation to the source text. It is the most-shipped production shape of 2026 because the ROI is obvious and the stakes are concrete. Harvey (Allen & Overy) built it for legal, Mendable ships the developer-docs flavor, and Glean covers enterprise search. The pattern is the same everywhere: ingest at high fidelity, retrieve hybrid with a rerank, synthesize with citation enforcement and prompt caching, guard with several safety layers, and monitor drift continuously.
The model is the easy part. The hard parts are jurisdiction-aware compliance (HIPAA, GDPR, SOC2), citation-level auditability, and cost control, where prompt caching buys a 60-90% discount when the hit rate is high. Add hallucination detection through RAGAS faithfulness, and drift detection for the day the source documents change and the index doesn't catch up. This capstone asks you to ship all of it against a 200-question golden set, with a red-team suite alongside.
Concept
A production RAG pipeline has two sides. Ingestion turns raw documents into searchable, access-controlled chunks: docling or Unstructured parses structured documents, ColPali handles visually rich ones, and each chunk gets a summary, tags, and role-based access labels. Vectors go into pgvector + pgvectorscale (under 50M vectors) or Qdrant Cloud, with sparse BM25 running alongside. Conversation answers a query: LangGraph handles memory and multi-turn state, and each query runs hybrid retrieval, reranks with bge-reranker-v2-gemma-2b, and synthesizes with Claude Sonnet 4.7 (prompt-cached). The output then passes through Llama Guard 4 and NeMo Guardrails and comes back as a citation-anchored response.
The eval stack has four layers, each covering a different failure:
- Golden set: 200 labeled Q/A pairs with citations, for correctness.
- Red team: jailbreaks, PII extraction attempts, and off-domain questions, for safety.
- RAGAS: faithfulness, answer relevance, and context precision, scored automatically per turn.
- Drift dashboard: Arize Phoenix watching retrieval quality and hallucination score weekly.
Prompt caching is the cost lever. It lets the provider reuse a stored prefix of the prompt, and Claude 4.5+ and GPT-5+ both support caching system prompts plus retrieved context. At a 60-80% hit rate, per-query cost drops 3-5x. That only happens if you design for stable prefixes: system prompt and reranked context first, the user's question last.
Architecture
documents (contracts, protocols, policies)
|
v
docling / Unstructured parse + ColPali for visuals
|
v
chunks + summaries + role-labels + jurisdiction tags
|
v
pgvector + pgvectorscale + BM25 (Tantivy)
|
query + role + jurisdiction
|
v
LangGraph conversational agent
+--- retrieve (hybrid)
+--- filter by role + jurisdiction
+--- rerank (bge-reranker-v2-gemma-2b or Voyage rerank-2)
+--- synthesize (Claude Sonnet 4.7, prompt cached)
+--- guard (Llama Guard 4 + NeMo Guardrails + Presidio output PII scrub)
+--- cite + return
|
v
eval:
RAGAS faithfulness / answer_relevance / context_precision (online)
Langfuse annotation queue (sampled)
Arize Phoenix drift (weekly)
red team suite (pre-release)
Stack
- Ingestion: Unstructured.io or docling for structured documents; ColPali for visually-rich PDFs
- Vector DB: pgvector + pgvectorscale under 50M vectors; Qdrant Cloud otherwise
- Sparse: Tantivy BM25 with field weights
- Orchestration: LlamaIndex Workflows (ingestion) + LangGraph (conversation)
- Re-ranker: bge-reranker-v2-gemma-2b self-hosted or Voyage rerank-2 hosted
- LLM: Claude Sonnet 4.7 with prompt caching; fallback Llama 3.3 70B self-hosted
- Eval: RAGAS 0.2 online, DeepEval for hallucination and jailbreak suites
- Observability: Langfuse self-hosted with annotation queue; Arize Phoenix for drift
- Guardrails: Llama Guard 4 input/output classifier, NeMo Guardrails v0.12 policy, Presidio PII scrub
- Compliance: role-based access labels on chunks; jurisdiction tags for GDPR/HIPAA
Build It
-
Ingestion. Parse your corpus (1000-10000 documents for a serious build) with Unstructured or docling. Route scanned and visual-heavy pages through ColPali. Produce chunks with summaries, role-labels, and jurisdiction tags.
-
Index. Put dense embeddings (Voyage-3 or Nomic-embed-v2) into pgvector + pgvectorscale, and build a BM25 side-index with Tantivy. Store role and jurisdiction as payload filters.
-
Hybrid retrieve. Filter by role and jurisdiction first. Then run dense and BM25 in parallel, merge with reciprocal rank fusion, send the top-20 to the reranker, and pass the top-5 to synthesis.
-
Synthesize with prompt caching. The system prompt and static policies go in the cache header, reranked context extends the cache, and the user question is the uncached suffix. Target a 60-80% cache hit rate in steady state.
-
Guardrails. Llama Guard 4 screens input. NeMo Guardrails rails block off-domain questions and policy-forbidden topics. Presidio scrubs accidental PII from the output, and a citation-enforcement post-filter runs last.
-
Golden set. Have a domain expert label 200 Q/A pairs with (answer, citations). Score the agent on exact-citation match, answer correctness, and faithfulness (RAGAS).
-
Red team. Write 50 adversarial prompts: jailbreaks (PAIR, TAP), PII exfiltration attempts, off-domain questions, and cross-jurisdiction leaks. Score each pass/fail with a severity.
-
Drift dashboard. Arize Phoenix tracks retrieval quality (nDCG, citation faithfulness) weekly. Alert on a 5% drop.
-
Cost report. Langfuse reports prompt-caching hit rate, tokens per query, and $/query broken down by stage.
Use It
$ chat --role=analyst --jurisdiction=GDPR
> what is the data-retention obligation for EU user profiles under our contract?
[retrieve] hybrid top-20 filtered to GDPR + analyst-role
[rerank] top-5 kept
[synth] claude-sonnet-4.7, cache hit 74%, 0.8s
answer:
The contract (Section 12.4, Master Services Agreement dated 2024-03-11)
obligates EU user profile deletion within 30 days of termination per GDPR
Article 17. The DPA amendment (DPA-v2.1, Section 5) extends this to 14 days
for "restricted" category data.
citations: [MSA-2024-03-11 s12.4, DPA-v2.1 s5]
Ship It
outputs/skill-production-rag.md describes the deliverable: a regulated-domain chatbot deployed with compliance labels, scored against the rubric below, and watched by a live drift monitor.
| Weight | Criterion | How it is measured |
|---|---|---|
| 25 | RAGAS faithfulness + answer relevance | Online scores on the golden set (200 Q/A) |
| 20 | Citation correctness | Fraction of answers with verifiable source anchors |
| 20 | Guardrail coverage | Llama Guard 4 pass rate + jailbreak suite results |
| 20 | Cost / latency engineering | Prompt-cache hit rate, p95 latency, $/query |
| 15 | Drift monitoring dashboard | Phoenix live dashboard with weekly retrieval-quality trend |
| 100 |
I'd write the cross-jurisdiction probe from Exercise 1 before the first demo, not after. The role and jurisdiction filter in step 3 is what keeps a GDPR analyst away from HIPAA-scoped chunks, and a leak there is a compliance failure, not a quality dip. I'd put my testing time into the filters and the citation post-filter first, because the Problem section lists compliance and auditability among the hard parts.
I'd also ship the "unsure" mode from Exercise 5 in the first release instead of treating it as an extra. In a regulated domain, "I do not have confident citations" is a better answer than a fluent one with weak anchors, and the rubric gives 20 points to citation correctness.
Exercises
-
Build a second corpus slice under a different jurisdiction (HIPAA alongside GDPR, say). Show that role and jurisdiction filtering prevents cross-leaks on a 20-question cross-jurisdiction probe.
-
Measure prompt-cache hit rate over a week of production traffic. Find the queries that break the cache prefix and restructure them.
-
Add multi-turn memory with a 10k-token summary buffer. Measure whether faithfulness drops as the conversation grows.
-
Swap Claude Sonnet 4.7 for self-hosted Llama 3.3 70B. Measure the $/query and faithfulness delta.
-
Add an "unsure" mode: if the top reranked scores fall below a threshold, the agent says "I do not have confident citations" instead of answering. Measure the reduction in false confidence.
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Prompt caching | "Cached system + context" | Claude/OpenAI feature: cached prefix tokens are discounted 60-90% on a hit |
| RAGAS | "RAG evaluator" | Automated scoring of faithfulness, answer relevance, and context precision |
| Golden set | "Labeled eval" | 200+ expert-labeled Q/A pairs with citations; the ground truth |
| Jurisdiction tag | "Compliance label" | GDPR/HIPAA/SOC2 scope attached to chunks and enforced by a retrieval filter |
| Citation faithfulness | "Grounded answer rate" | Fraction of claims backed by retrievable source spans |
| Drift | "Retrieval quality decay" | Weekly change in nDCG or citation score; alert threshold 5% |
| Red team | "Adversarial eval" | Pre-release jailbreak, PII extraction, and off-domain probes |
Further Reading
- Harvey AI: reference legal production stack
- Glean enterprise search: reference RAG at enterprise scale
- Mendable documentation: developer-docs RAG reference
- LlamaCloud Parse + Index: managed ingestion
- Anthropic prompt caching: the cost-lever reference
- RAGAS 0.2 documentation: the canonical RAG eval framework
- Arize Phoenix: reference drift observability
- Llama Guard 4: 2026 safety classifier
- NeMo Guardrails v0.12: policy rail framework
Code
- main.py (python, 250 lines)
- ts/package.json (json, 23 lines)
- ts/README.md (markdown, 51 lines)
- ts/src/index.ts (typescript, 138 lines)
- ts/src/server.ts (typescript, 142 lines)
- ts/src/session.ts (typescript, 38 lines)
- ts/src/stream.ts (typescript, 93 lines)
- ts/src/types.ts (typescript, 33 lines)
- ts/tests/server.test.ts (typescript, 77 lines)
- ts/tests/session.test.ts (typescript, 51 lines)
- ts/tests/stream.test.ts (typescript, 69 lines)
- ts/tsconfig.json (json, 22 lines)