RAG Architect
Core Workflow
- Requirements Analysis — Identify retrieval needs, latency constraints, accuracy requirements, and scale
- Vector Store Design — Select database, schema design, indexing strategy, sharding approach
- Chunking Strategy — Document splitting, overlap, semantic boundaries, metadata enrichment
- Retrieval Pipeline — Embedding selection, query transformation, hybrid search, reranking
- Evaluation & Iteration — Metrics tracking, retrieval debugging, continuous optimization
For each step, validate before moving on (see checkpoints below).
Reference Guide
Load detailed guidance based on context:
| Topic |
Reference |
Load When |
| Vector Databases |
references/vector-databases.md |
Comparing Pinecone, Weaviate, Chroma, pgvector, Qdrant |
| Embedding Models |
references/embedding-models.md |
Selecting embeddings, fine-tuning, dimension trade-offs |
| Chunking Strategies |
references/chunking-strategies.md |
Document splitting, overlap, semantic chunking |
| Retrieval Optimization |
references/retrieval-optimization.md |
Hybrid search, reranking, query expansion, filtering |
| RAG Evaluation |
references/rag-evaluation.md |
Metrics, evaluation frameworks, debugging retrieval |
Implementation Examples
1. Chunking Documents
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Evaluate chunk_size on your domain data — never use 512 blindly
splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=100,
separators=["\n\n", "\n", ". ", " "],
)
chunks = splitter.create_documents(
texts=[doc.page_content for doc in raw_docs],
metadatas=[{"source": doc.metadata["source"], "timestamp": doc.metadata.get("timestamp")} for doc in raw_docs],
)
Checkpoint: assert all(c.metadata.get("source") for c in chunks), "Missing source metadata"
2. Generating Embeddings & Indexing
from openai import OpenAI
import qdrant_client
from qdrant_client.models import VectorParams, Distance, PointStruct
client = OpenAI()
qdrant = qdrant_client.QdrantClient("localhost", port=6333)
# Create collection
qdrant.recreate_collection(
collection_name="knowledge_base",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
)
def embed_chunks(chunks: list[str], model: str = "text-embedding-3-small") -> list[list[float]]:
response = client.embeddings.create(input=chunks, model=model)
return [r.embedding for r in response.data]
# Idempotent upsert with deduplication via deterministic IDs
import hashlib, uuid
points = []
for i, chunk in enumerate(chunks):
doc_id = str(uuid.UUID(hashlib.md5(chunk.page_content.encode()).hexdigest()))
embedding = embed_chunks([chunk.page_content])[0]
points.append(PointStruct(id=doc_id, vector=embedding, payload=chunk.metadata))
qdrant.upsert(collection_name="knowledge_base", points=points)
Checkpoint: assert qdrant.count("knowledge_base").count == len(set(p.id for p in points)), "Deduplication failed"
3. Hybrid Search (Vector + BM25)
from qdrant_client.models import Filter, FieldCondition, MatchValue, SparseVector
from rank_bm25 import BM25Okapi
def hybrid_search(query: str, tenant_id: str, top_k: int = 20) -> list:
# Dense retrieval
query_embedding = embed_chunks([query])[0]
tenant_filter = Filter(must=[FieldCondition(key="tenant_id", match=MatchValue(value=tenant_id))])
dense_results = qdrant.search(
collection_name="knowledge_base",
query_vector=query_embedding,
query_filter=tenant_filter,
limit=top_k,
)
# Sparse retrieval (BM25)
corpus = [r.payload.get("text", "") for r in dense_results]
bm25 = BM25Okapi([doc.split() for doc in corpus])
bm25_scores = bm25.get_scores(query.split())
# Reciprocal Rank Fusion
ranked = sorted(
zip(dense_results, bm25_scores),
key=lambda x: 0.6 * x[0].score + 0.4 * x[1],
reverse=True,
)
return [r for r, _ in ranked[:top_k]]
Checkpoint: assert len(hybrid_search("test query", tenant_id="demo")) > 0, "Hybrid search returned no results"
4. Reranking Top-K Results
Load provider API keys from environment variables or a secrets manager; never commit them to source code.
import os
import cohere
co = cohere.Client(os.environ["COHERE_API_KEY"])
def rerank(query: str, results: list, top_n: int = 5) -> list:
docs = [r.payload.get("text", "") for r in results]
reranked = co.rerank(query=query, documents=docs, top_n=top_n, model="rerank-english-v3.0")
return [results[r.index] for r in reranked.results]
5. Retrieval Evaluation
# Run precision@k and recall@k against a labeled evaluation set
# python evaluate.py --metrics precision@10 recall@10 mrr --collection knowledge_base
from ragas import evaluate
from ragas.metrics import context_precision, context_recall, faithfulness, answer_relevancy
from datasets import Dataset
eval_dataset = Dataset.from_dict({
"question": questions,
"contexts": retrieved_contexts,
"answer": generated_answers,
"ground_truth": ground_truth_answers,
})
results = evaluate(eval_dataset, metrics=[context_precision, context_recall, faithfulness, answer_relevancy])
print(results)
Checkpoint: Target context_precision >= 0.7 and context_recall >= 0.6 before moving to LLM integration.
Constraints
MUST DO
- Evaluate multiple embedding models on your domain data before committing
- Implement hybrid search (vector + keyword) for production systems
- Add metadata filters for multi-tenant or domain-specific retrieval
- Measure retrieval metrics (precision@k, recall@k, MRR, NDCG)
- Use reranking for top-k results before passing context to LLM
- Implement idempotent ingestion with deduplication (deterministic IDs)
- Monitor retrieval latency and quality over time
- Version embeddings and plan for model migration
MUST NOT DO
- Use default chunk size (512) without evaluation on your domain data
- Skip metadata enrichment (source, timestamp, section)
- Ignore retrieval quality metrics in favor of only LLM output quality
- Store raw documents without preprocessing/cleaning
- Use cosine similarity alone for complex multi-domain retrieval
- Deploy without testing on production-like data volumes
- Forget to handle edge cases (empty results, malformed docs)
- Couple the embedding model tightly to application code
Output Templates
When designing RAG architecture, deliver:
- System architecture diagram (ingestion + retrieval pipelines)
- Vector database selection with trade-off analysis
- Chunking strategy with examples and rationale
- Retrieval pipeline design (query → results flow)
- Evaluation plan with metrics, benchmarks, and pass/fail thresholds
Documentation
1---2name: rag-architect3description: Designs and implements production-grade RAG systems by chunking documents, generating embeddings, configuring vector stores, building hybrid search pipelines, applying reranking, and evaluating retrieval quality. Use when building RAG systems, vector databases, or knowledge-grounded AI applications requiring semantic search, document retrieval, context augmentation, similarity search, or embedding-based indexing.4license: MIT5---6
7# RAG Architect
8
9## Core Workflow
10
111. **Requirements Analysis** — Identify retrieval needs, latency constraints, accuracy requirements, and scale
122. **Vector Store Design** — Select database, schema design, indexing strategy, sharding approach
133. **Chunking Strategy** — Document splitting, overlap, semantic boundaries, metadata enrichment
144. **Retrieval Pipeline** — Embedding selection, query transformation, hybrid search, reranking
155. **Evaluation & Iteration** — Metrics tracking, retrieval debugging, continuous optimization
16
17For each step, validate before moving on (see checkpoints below).
18
19## Reference Guide
20
21Load detailed guidance based on context:
22
23| Topic | Reference | Load When |
24|-------|-----------|-----------|
25| Vector Databases | `references/vector-databases.md` | Comparing Pinecone, Weaviate, Chroma, pgvector, Qdrant |
26| Embedding Models | `references/embedding-models.md` | Selecting embeddings, fine-tuning, dimension trade-offs |
27| Chunking Strategies | `references/chunking-strategies.md` | Document splitting, overlap, semantic chunking |
28| Retrieval Optimization | `references/retrieval-optimization.md` | Hybrid search, reranking, query expansion, filtering |
29| RAG Evaluation | `references/rag-evaluation.md` | Metrics, evaluation frameworks, debugging retrieval |
30
31## Implementation Examples
32
33### 1. Chunking Documents
34
35```python
36from langchain.text_splitter import RecursiveCharacterTextSplitter
37
38# Evaluate chunk_size on your domain data — never use 512 blindly
39splitter = RecursiveCharacterTextSplitter(
40 chunk_size=800,
41 chunk_overlap=100,
42 separators=["\n\n", "\n", ". ", " "],
43)
44
45chunks = splitter.create_documents(
46 texts=[doc.page_content for doc in raw_docs],
47 metadatas=[{"source": doc.metadata["source"], "timestamp": doc.metadata.get("timestamp")} for doc in raw_docs],
48)
49```
50
51**Checkpoint:** `assert all(c.metadata.get("source") for c in chunks), "Missing source metadata"`
52
53### 2. Generating Embeddings & Indexing
54
55```python
56from openai import OpenAI
57import qdrant_client
58from qdrant_client.models import VectorParams, Distance, PointStruct
59
60client = OpenAI()
61qdrant = qdrant_client.QdrantClient("localhost", port=6333)
62
63# Create collection
64qdrant.recreate_collection(
65 collection_name="knowledge_base",
66 vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
67)
68
69def embed_chunks(chunks: list[str], model: str = "text-embedding-3-small") -> list[list[float]]:
70 response = client.embeddings.create(input=chunks, model=model)
71 return [r.embedding for r in response.data]
72
73# Idempotent upsert with deduplication via deterministic IDs
74import hashlib, uuid
75
76points = []
77for i, chunk in enumerate(chunks):
78 doc_id = str(uuid.UUID(hashlib.md5(chunk.page_content.encode()).hexdigest()))
79 embedding = embed_chunks([chunk.page_content])[0]
80 points.append(PointStruct(id=doc_id, vector=embedding, payload=chunk.metadata))
81
82qdrant.upsert(collection_name="knowledge_base", points=points)
83```
84
85**Checkpoint:** `assert qdrant.count("knowledge_base").count == len(set(p.id for p in points)), "Deduplication failed"`
86
87### 3. Hybrid Search (Vector + BM25)
88
89```python
90from qdrant_client.models import Filter, FieldCondition, MatchValue, SparseVector
91from rank_bm25 import BM25Okapi
92
93def hybrid_search(query: str, tenant_id: str, top_k: int = 20) -> list:
94 # Dense retrieval
95 query_embedding = embed_chunks([query])[0]
96 tenant_filter = Filter(must=[FieldCondition(key="tenant_id", match=MatchValue(value=tenant_id))])
97 dense_results = qdrant.search(
98 collection_name="knowledge_base",
99 query_vector=query_embedding,
100 query_filter=tenant_filter,
101 limit=top_k,
102 )
103
104 # Sparse retrieval (BM25)
105 corpus = [r.payload.get("text", "") for r in dense_results]
106 bm25 = BM25Okapi([doc.split() for doc in corpus])
107 bm25_scores = bm25.get_scores(query.split())
108
109 # Reciprocal Rank Fusion
110 ranked = sorted(
111 zip(dense_results, bm25_scores),
112 key=lambda x: 0.6 * x[0].score + 0.4 * x[1],
113 reverse=True,
114 )
115 return [r for r, _ in ranked[:top_k]]
116```
117
118**Checkpoint:** `assert len(hybrid_search("test query", tenant_id="demo")) > 0, "Hybrid search returned no results"`
119
120### 4. Reranking Top-K Results
121
122Load provider API keys from environment variables or a secrets manager; never commit them to source code.
123
124```python
125import os
126
127import cohere
128
129co = cohere.Client(os.environ["COHERE_API_KEY"])
130
131def rerank(query: str, results: list, top_n: int = 5) -> list:
132 docs = [r.payload.get("text", "") for r in results]
133 reranked = co.rerank(query=query, documents=docs, top_n=top_n, model="rerank-english-v3.0")
134 return [results[r.index] for r in reranked.results]
135```
136
137### 5. Retrieval Evaluation
138
139```python
140# Run precision@k and recall@k against a labeled evaluation set
141# python evaluate.py --metrics precision@10 recall@10 mrr --collection knowledge_base
142
143from ragas import evaluate
144from ragas.metrics import context_precision, context_recall, faithfulness, answer_relevancy
145from datasets import Dataset
146
147eval_dataset = Dataset.from_dict({
148 "question": questions,
149 "contexts": retrieved_contexts,
150 "answer": generated_answers,
151 "ground_truth": ground_truth_answers,
152})
153
154results = evaluate(eval_dataset, metrics=[context_precision, context_recall, faithfulness, answer_relevancy])
155print(results)
156```
157
158**Checkpoint:** Target `context_precision >= 0.7` and `context_recall >= 0.6` before moving to LLM integration.
159
160## Constraints
161
162### MUST DO
163- Evaluate multiple embedding models on your domain data before committing
164- Implement hybrid search (vector + keyword) for production systems
165- Add metadata filters for multi-tenant or domain-specific retrieval
166- Measure retrieval metrics (precision@k, recall@k, MRR, NDCG)
167- Use reranking for top-k results before passing context to LLM
168- Implement idempotent ingestion with deduplication (deterministic IDs)
169- Monitor retrieval latency and quality over time
170- Version embeddings and plan for model migration
171
172### MUST NOT DO
173- Use default chunk size (512) without evaluation on your domain data
174- Skip metadata enrichment (source, timestamp, section)
175- Ignore retrieval quality metrics in favor of only LLM output quality
176- Store raw documents without preprocessing/cleaning
177- Use cosine similarity alone for complex multi-domain retrieval
178- Deploy without testing on production-like data volumes
179- Forget to handle edge cases (empty results, malformed docs)
180- Couple the embedding model tightly to application code
181
182## Output Templates
183
184When designing RAG architecture, deliver:
1851. System architecture diagram (ingestion + retrieval pipelines)
1862. Vector database selection with trade-off analysis
1873. Chunking strategy with examples and rationale
1884. Retrieval pipeline design (query → results flow)
1895. Evaluation plan with metrics, benchmarks, and pass/fail thresholds
190
191[Documentation](https://jeffallan.github.io/claude-skills/skills/data-ml/rag-architect/)