BestRAG - Hybrid RAG Storage & Retrieval Cheatsheet
BestRAG (Best Retrieval Augmented Generation) is a Python library for building hybrid RAG pipelines. It combines dense, sparse, and late-interaction embeddings for storing and searching documents efficiently, using Qdrant as the vector store and FastEmbed for the embeddings. The idea: store each chunk once with multiple representations, then retrieve with fused relevance so you get both the semantic recall of dense vectors and the exact-term precision of sparse (BM25-style) search.
Installation
| Method | Command |
|---|
| pip | pip install bestrag |
| Requirements | A Qdrant instance (local Docker or Qdrant Cloud) |
| Embeddings | Uses FastEmbed under the hood |
| Verify | python -c "import bestrag; print('ok')" |
Why Hybrid
| Retrieval type | Strong at | Weak at |
|---|
| Dense (vector) | Meaning, paraphrase | Exact IDs, numbers, rare terms |
| Sparse (BM25) | Exact terms, identifiers | Synonyms, paraphrase |
| Late interaction | Fine-grained token matching | Cost |
| Fused (hybrid) | Best of dense + sparse | — |
BestRAG stores all representations so a single query benefits from all of them.
Basic Usage
from bestrag import BestRAG
rag = BestRAG(
url="http://localhost:6333", # Qdrant
api_key="...", # if using Qdrant Cloud
collection_name="docs",
)
# Ingest documents (embeds dense + sparse + late-interaction, upserts to Qdrant)
rag.store_pdf("handbook.pdf")
rag.store_text("Nick prefers concise answers.", metadata={"source": "prefs"})
# Retrieve with fused hybrid relevance
results = rag.search("what does the refund policy say?", limit=5)
for r in results:
print(r.score, r.text)
| Method | Description |
|---|
BestRAG(url, collection_name, ...) | Connect to Qdrant, pick a collection |
store_pdf(path) | Parse + chunk + embed + upsert a PDF |
store_text(text, metadata=...) | Ingest raw text |
search(query, limit=k) | Hybrid (fused) retrieval |
delete(...) | Remove documents |
What Happens on Ingest
| Step | Detail |
|---|
| Parse | Extract text (e.g. from PDF) |
| Chunk | Split into passages |
| Embed | Dense + sparse + late-interaction vectors per chunk |
| Upsert | Store all vectors + payload in Qdrant |
What Happens on Search
| Step | Detail |
|---|
| Embed query | Dense + sparse (+ late interaction) |
| Query Qdrant | Retrieve candidates per representation |
| Fuse | Combine rankings (RRF-style) into one list |
| Return | Top-k passages with scores + metadata |
Feeding Results to an LLM
# Retrieve, then build a grounded prompt
hits = rag.search(user_question, limit=5)
context = "\n\n".join(h.text for h in hits)
prompt = f"Answer using only this context:\n{context}\n\nQ: {user_question}"
# → send prompt to your LLM
Tuning
| Lever | Effect |
|---|
| Chunk size/overlap | Retrieval granularity |
| Dense model | Semantic quality (via FastEmbed) |
| Sparse model | Exact-term matching (BM25) |
limit (k) | Recall vs prompt size |
| Metadata filters | Restrict search scope / access control |
BestRAG vs Rolling Your Own
| Aspect | BestRAG | Manual (LangChain/LlamaIndex) |
|---|
| Hybrid setup | Built-in (dense+sparse+LI) | Manual wiring |
| Store | Qdrant, opinionated | Any, flexible |
| Control | Simpler, fewer knobs | Full control |
| Best for | Fast hybrid RAG start | Custom pipelines |
Built on FastEmbed and Qdrant; reach for LangChain or LlamaIndex when you need full pipeline control.
Resources