Raglan
A lightweight, highly configurable RAG retrieval engine. Framework-free, protocol-driven.
Why Raglan?
Standard RAG has one step: vector search → top-K results. Real-world retrieval needs more:
| Problem | Standard RAG | Raglan |
|---|---|---|
| Single query misses aspects | "return policy" finds policy text, misses refund flow | Auto-generates 3 query variants, searches in parallel |
| Dense-only retrieval | Poor short-keyword / jargon matching | BM25 sparse + vector dense, RRF hybrid fusion |
| Small chunks lack context | "refund takes 7 days" without surrounding doc | Child-chunk match → expanded to full parent context |
| Vector similarity != semantic match | "how to return" vs "I don't want this" look close | Cross-Encoder pair-wise reranking filters false positives |
| One failure kills the pipeline | Embedding service timeout → empty results | Each stage degrades independently, rest continues |
Six-Stage Pipeline
User query
│
▼
┌─────────────────────────────────┐
│ Stage 1: QueryExpander │ ← LLM generates entity extraction + 3 variants
└──────────────┬──────────────────┘
│ original + variants = parallel search
▼
┌─────────────────────────────────┐
│ Stage 2: Retrievers (parallel) │
│ · Dense: pgvector / Qdrant / ChromaDB / ...
│ · Sparse: BM25 full-text │
└──────────────┬──────────────────┘
│ multi-source results
▼
┌─────────────────────────────────┐
│ Stage 3: Fusion │ ← RRF / Weighted / RoundRobin
└──────────────┬──────────────────┘
│ fused candidates
▼
┌─────────────────────────────────┐
│ Stage 4: Reranker (optional) │ ← Cross-Encoder / Cohere Rerank
│ Filters scores below threshold │
└──────────────┬──────────────────┘
│ top-N candidates
▼
┌─────────────────────────────────┐
│ Stage 5: ContextBuilder │ ← Parent expansion / window / passthrough
│ Greedy fill up to max_tokens │
└──────────────┬──────────────────┘
│
▼
Final Top-N results
Installation
pip install raglan-retrieval
With optional providers:
pip install raglan-retrieval[openai] # OpenAI embedder + expander
pip install raglan-retrieval[pgvector] # Postgres + pgvector retriever
pip install raglan-retrieval[huggingface] # HF embedder + Cross-Encoder reranker
pip install raglan-retrieval[qdrant] # Qdrant retriever
pip install raglan-retrieval[chromadb] # ChromaDB retriever
pip install raglan-retrieval[cohere] # Cohere reranker
pip install raglan-retrieval[dashscope] # Alibaba DashScope embedder
pip install raglan-retrieval[litellm] # LiteLLM multi-provider expander
pip install raglan-retrieval[all] # Everything
5-Minute Quickstart
from raglan import Raglan
from raglan.retrievers import BM25Retriever
import asyncio
async def main():
# Step 1: Set up a retriever with your data
bm25 = BM25Retriever()
async def chunks():
yield [
("doc1", "Return policy: items can be returned within 30 days.", None),
("doc2", "Refund process: refunds are issued to the original payment method.", None),
("doc3", "Shipping: orders ship within 2 business days.", None),
]
await bm25.index(chunks())
# Step 2: Build the pipeline (all stages optional beyond retrievers)
rag = Raglan([bm25])
# Step 3: Search
results, trace = await rag.search("how to return my order")
for r in results:
print(f"[{r.score:.3f}] {r.content}")
print(f"Pipeline took {trace.total_ms:.1f}ms")
asyncio.run(main())
Incremental configuration
Prefer building up a pipeline piece by piece? Start with an empty
instance and add stages incrementally — no build() needed:
from raglan import Raglan
from raglan.retrievers import BM25Retriever
rag = Raglan() # empty, configurable
rag.add_retriever(bm25) # add a retriever
rag.set_embedder("openai:text-embedding-3-small") # string shorthand
rag.set_expander("openai:gpt-4o-mini") # vendor:model form
rag.set_fusion("rrf")
# every setter returns self — chainable:
# Raglan().add_retriever(bm25).set_fusion("rrf")
results, trace = await rag.search("my query")
Components accept three forms: a live object (BM25Retriever()), a
"vendor:model" string ("openai:text-embedding-3-small", "rrf"), or a
{"type": ..., "params": ...} dict — the same format used by from_dict().
Configuration templates
For team-shared or file-based configuration, grab a template and fill it in:
cfg = Raglan.config() # → {"retrievers": [], "fusion": "rrf", "expander": None, ...}
cfg["retrievers"].append(
{"type": "pgvector", "params": {"connection_string": "...", "table": "kb.chunks"}}
)
cfg["expander"] = "openai:gpt-4o-mini"
rag = Raglan.from_config(cfg)
The template is JSON/YAML-serialisable, so configs can live in a file:
# raglan.yaml
retrievers:
- type: pgvector
params: {connection_string: "postgresql://...", table: kb.chunks}
- type: bm25
fusion: rrf
expander: "openai:gpt-4o-mini"
Adding vector search and reranking
from raglan.context_builders import ParentExpander
from raglan.embedders import OpenAIEmbedder
from raglan.expanders import OpenAIExpander
from raglan.rerankers import CrossEncoderReranker
from raglan.retrievers import ConfigurablePgvectorRetriever
rag = (
Raglan.builder()
.with_expander(OpenAIExpander(model="gpt-4o-mini"))
.with_embedder(OpenAIEmbedder(model="text-embedding-3-small"))
.with_retrievers(
[
ConfigurablePgvectorRetriever(
connection_string="postgresql://...",
table="kb.chunks",
embedding_column="embedding",
parent_id_column="parent_id",
),
BM25Retriever(),
]
)
.with_reranker(
CrossEncoderReranker(
model_name="ms-marco-TinyBERT-L2-v2",
min_score=0.5,
)
)
.with_context_builder(
ParentExpander(
loader=my_parent_loader,
max_tokens=6000,
)
)
.with_fallback_mode("degrade")
.build()
)
results, trace = await rag.search("damaged order return policy")
# Pass request-scoped context (identity, tenant, permissions) down to retrievers
results, trace = await rag.search(
"damaged order return policy",
request={"user_id": "u123", "tenant": "acme"},
)
Design Philosophy
-
Framework-Free — No dependency on LangChain, LlamaIndex, or any specific vector database or LLM provider. Protocols define the interfaces; you provide the implementations.
-
Graceful Degradation — Every stage is independent. Query expansion fails? Use the original query. Cross-Encoder not installed? Skip reranking. BM25 unavailable? Pure vector search still works.
-
Fully Configurable — Every stage's parameters, weights, models, and thresholds are configurable via the Builder or
from_dict(). -
Production-Ready — Extracted from production systems processing thousands of support emails daily. Handles extreme text, multilingual, and high-concurrency scenarios.
Comparison with Existing RAG Frameworks
| Tool | Positioning | vs Raglan |
|---|---|---|
| LangChain RAG | Full-stack LLM framework's RAG module | LC binds to LangChain ecosystem; Raglan is zero-dependency, direct use |
| LlamaIndex | Data→LLM full pipeline | LI has many concepts (Node, Index, QueryEngine); Raglan has one: Search |
| RAGatouille | ColBERT-specific RAG | RAGatouille focuses on ColBERT; Raglan is general retrieval + reranking |
| Cohere Rerank | Commercial API reranking | Cohere charges, sends data to cloud; Raglan runs locally |
| rerankers (answer.ai) | Unified reranking API | rerankers only does reranking; Raglan covers retrieval→fusion→reranking |
Project Structure
raglan/
├── README.md
├── docs/
│ ├── architecture.md # Architecture design
│ ├── configuration.md # Full configuration reference
│ ├── pipeline.md # Six-stage deep dive
│ └── examples.md # Multi-scenario examples
├── raglan/
│ ├── __init__.py
│ ├── raglan.py # Raglan facade + Builder
│ ├── pipeline.py # Pipeline engine + stage dispatch
│ ├── protocols.py # User-implementable abstract interfaces
│ ├── types.py # ScoredChunk, SearchResult, Filter, etc.
│ ├── exceptions.py # Exception hierarchy
│ ├── observability.py # Metrics collector
│ ├── expanders/ # Query expansion (OpenAI, LiteLLM, Identity)
│ ├── embedders/ # Embedding (OpenAI, HuggingFace, DashScope)
│ ├── retrievers/ # Search backends (BM25, pgvector, Qdrant, ChromaDB)
│ ├── fusion/ # Result fusion (RRF, Weighted, RoundRobin)
│ ├── rerankers/ # Reranking (CrossEncoder, Cohere)
│ ├── context_builders/ # Context assembly (Parent, Window, Passthrough)
│ ├── middleware/ # Timeout, Retry, CircuitBreaker, Logging
│ └── resilience/ # RateLimiter, RetryBudget, HealthChecker
└── tests/
License
MIT
Metadata
Release files for raglan-retrieval 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| raglan_retrieval-0.2.2.tar.gz | 747.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| raglan_retrieval-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 825.1 kB
Release files / raglan_retrieval-0.2.2.tar.gz
| Download URL | raglan_retrieval-0.2.2.tar.gz |
|---|---|
| Size | 747.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5e058a472fd04ee1382842399640cc4b91b2f2fa6513b7e26bca62127931468f
|
|
BLAKE2b-256 checksum How to use checksums |
b4514d84743a658d365287758734a7c18434931588202aa3581a314060b1dc56
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / raglan_retrieval-0.2.2-py3-none-any.whl
| Download URL | raglan_retrieval-0.2.2-py3-none-any.whl |
|---|---|
| Size | 77.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f36d6e99f7448f78ab478bdaea53df8c1849e3191a76e6fc03ff810ce19f0b43
|
|
BLAKE2b-256 checksum How to use checksums |
522adad775b908a62f3bcb9a8bf30f422ed94f318da1c030d80b488094adf59c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|