Minimal RAG stack: Claude Sonnet 4 + MongoDB Atlas Vector Search + hybrid retrieval.
Ingest your markdown/text docs, query with citations, in under 200 lines of Python. No LangChain, no LlamaIndex, no framework lock-in — just the pieces you actually need.
- Embedding: OpenAI
text-embedding-3-small(1536 dim, ~$0.02 per 1M tokens) - Storage: MongoDB Atlas Vector Search (free M0 tier handles ~100K chunks)
- Retrieval: hybrid — vector similarity + BM25 lexical, fused via RRF
- Generation: Claude Sonnet 4 with citation rules baked into the prompt
git clone https://github.com/arpit2005/rag-starter-claude.git
cd rag-starter-claude
pip install -r requirements.txt
cp .env.example .env
# Fill in: ANTHROPIC_API_KEY, OPENAI_API_KEY, MONGODB_URI
python ingest.py ./sample_docs/
python query.py "what does this repo do?"Output:
> what does this repo do?
This repository is a minimal RAG (Retrieval-Augmented Generation) starter
stack [1][2]. It combines OpenAI embeddings, MongoDB Atlas Vector Search,
and Claude Sonnet 4 for question answering with citations [1].
Sources:
[1] sample_docs/overview.md
[2] sample_docs/architecture.md
docs/ ──► ingest.py ──► Atlas (chunks + 1536-d embeddings)
│
▼
query.py
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
vector $vectorSearch BM25 $search RRF fusion
│
▼
Claude Sonnet 4
│
▼
answer + [N] citations
Pure vector search misses exact-match queries. Ask "where is the password_reset endpoint" — embeddings will return semantically similar code, not the exact symbol. Pure BM25 misses semantic queries — "how do users sign in" won't find a function named authenticate_session.
Reciprocal Rank Fusion (RRF) combines both ranked lists. Per-document score: sum(1 / (k + rank_i)) across both retrieval modes. Zero extra latency over running both in parallel.
In MongoDB Atlas UI: Search → Create Index → JSON Editor, paste:
{
"fields": [
{
"type": "vector",
"path": "embedding",
"numDimensions": 1536,
"similarity": "cosine"
},
{
"type": "filter",
"path": "source"
}
]
}Name it vector_index. Takes 1 minute to build.
Also create a standard text-search index named text_index on the text field for BM25.
| File | LOC | Purpose |
|---|---|---|
ingest.py |
~80 | Read folder → chunk → embed → upsert into Atlas |
query.py |
~110 | Hybrid retrieve → RRF fuse → Claude answer with citations |
requirements.txt |
6 | Dependencies (Anthropic, OpenAI, pymongo, dotenv) |
.env.example |
— | API keys + Atlas URI template |
sample_docs/ |
— | 3 markdown files to test against |
For a real deployment, layer on:
- Incremental ingest — currently full-replace. Add
source_hash+ skip-if-unchanged. - Tenant isolation — single namespace. Add
tenant_idfield + filter. - Reranker — Cohere or Voyage rerank-3 for the final top-k. ~20% recall lift.
- Eval harness — RAGAS metrics (faithfulness, answer relevance, context precision).
- Prompt caching — Anthropic's prompt caching cuts cost ~90% on stable system prompts.
- Observability — Langfuse or similar for trace + cost tracking.
I add these per-client when needed. Ask if you want a specific one walked through.
MIT. Use freely.