The infrastructure layer between your RAG components. Configure providers in YAML, swap any stage, and get a production-grade RAG pipeline with zero framework lock-in.
pip install "nexrag[all]"
from pathlib import Path
from nexrag import NexRAG
pipeline = NexRAG.from_config("nexrag.yaml")
# Ingest your documents — pass file *content*, not a path
result = pipeline.ingest(Path("docs/manual.pdf").read_bytes())
print(f"Wrote {result.chunks_written} chunks")
print(f"Done in {result.latency_ms:.0f}ms")
# Ask anything
result = pipeline.query(
"What are the termination clauses?"
)
print(result.answer)
for src in result.sources:
print(f" [{src.rank}] {src.source}")
Two pipelines. One shared Embedder. One shared VectorDB. Ten swappable stages, wrapped in end-to-end guardrails. One YAML file to wire it all.
Every stage is a contract. All 10 pipeline stages are clean abstract interfaces. Swap anything by extending the base class.
YAML configures the pipeline. Secrets via ${ENV_VAR} substitution. No runtime configuration in application code.
Core has no dependency on LangChain, LlamaIndex, or any AI SDK. Bring your model. Swap providers without changing application code.
No hidden defaults. No magic. Every behavior is declared in YAML or documented. What you configure is exactly what runs.
SHA-256 deduplication, idempotent ingestion, exponential backoff with jitter, typed exceptions, and structured JSON observability at every stage.
Set mode: async in YAML for native async pipelines. Stream token-by-token with stream_query() or astream_query() — no extra setup.
Three lines of Python. One YAML file. The rest is NexRAG.
from pathlib import Path
from nexrag import NexRAG
from dotenv import load_dotenv
load_dotenv()
pipeline = NexRAG.from_config("nexrag.yaml")
# ── Ingest (loaders are converter-only: pass bytes/str, not a path) ──
pdf_bytes = Path("contracts/agreement.pdf").read_bytes()
result = pipeline.ingest(pdf_bytes, metadata={"source": "agreement.pdf"})
print(f"Ingested {result.documents_loaded} doc, {result.chunks_written} chunks")
# → Ingested 1 doc, 48 chunks in 1240.3 ms
# ── Query ────────────────────────────────────────────────────
result = pipeline.query("What are the termination clauses?")
print(result.answer)
for source in result.sources:
print(f" [{source.rank}] score={source.score:.3f} {source.source}")
# ── Runtime overrides ────────────────────────────────────────
result = pipeline.query(
"What changed in 2024?",
top_k=10,
score_threshold=0.75,
metadata_filter={"year": 2024},
)
from nexrag import NexRAG
pipeline = NexRAG.from_config("nexrag.yaml")
# ── Sync streaming — tokens printed as they arrive ───────────
for token in pipeline.stream_query("Summarize the contract"):
print(token, end="", flush=True)
# ── Async streaming — for FastAPI / async frameworks ─────────
import asyncio
async def stream_answer():
async for token in pipeline.astream_query(
"What are the termination clauses?",
top_k=8,
):
print(token, end="", flush=True)
asyncio.run(stream_answer())
# ── FastAPI SSE endpoint — wire it straight in ────────────────
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
@app.get("/ask")
async def ask(q: str):
async def gen():
async for token in pipeline.astream_query(q):
yield token
return StreamingResponse(gen(), media_type="text/plain")
# nexrag.yaml — minimal config
mode: sync # sync (default) | async — enables native async pipeline
ingestion:
loader:
type: pdf
chunker:
strategy: recursive
chunk_size: 512
chunk_overlap: 64
embedder:
provider: openai
model: text-embedding-3-small
api_key: ${OPENAI_API_KEY}
vector_db:
provider: chroma
default_collection: documents
collections:
documents:
mode: persistent
path: ./.nexrag/chroma
query:
embedder: inherit # reuse ingestion embedder
retriever:
provider: dense
top_k: 5
llm:
provider: openai
model: gpt-4o
api_key: ${OPENAI_API_KEY}
temperature: 0.2
# Swap to Ollama — no API keys, fully local:
# embedder.provider: ollama / model: nomic-embed-text
# llm.provider: ollama / model: llama3.2
# Drop in any custom component. Implement the interface,
# declare it in YAML. Works for all 9 pipeline stages.
# myapp/embedders.py
from nexrag.core.interfaces.embedder import BaseEmbedder
class SentenceTransformerEmbedder(BaseEmbedder):
def __init__(self, model: str = "all-MiniLM-L6-v2") -> None:
from sentence_transformers import SentenceTransformer
self._model = SentenceTransformer(model)
def embed(self, texts: list[str]) -> list[list[float]]:
return self._model.encode(texts).tolist()
def embed_query(self, text: str) -> list[float]:
return self._model.encode([text])[0].tolist()
---
# nexrag.yaml — wire it in:
ingestion:
embedder:
provider: custom
class: myapp.embedders.SentenceTransformerEmbedder
params:
model: all-MiniLM-L6-v2
OpenAI, Gemini, Ollama, Anthropic, HuggingFace, ChromaDB, Pinecone, BM25, Cohere, and CrossEncoder available now — plus a full chunking suite and a pluggable guardrails layer. More in v1.
| Category | Providers |
|---|---|
| Embedders | OpenAI, Gemini, Ollama, HuggingFace |
| LLMs | OpenAI, Gemini, Ollama, Anthropic |
| Vector DBs | ChromaDB (local, memory, server), Pinecone (serverless) |
| Loaders | PDF, plain text, auto-detect |
| Chunkers | Recursive, Fixed, Token, Sentence, Sentence-window, Markdown, Code, Semantic, Proposition |
| Retrievers | Dense (cosine similarity), BM25 (keyword), Hybrid (dense + BM25) |
| Rerankers | Cohere, CrossEncoder (sentence-transformers) |
| Guardrails | PII, Access control, Prompt-injection, Groundedness, Topic, Model |
| Category | Providers |
|---|---|
| Vector DBs | Weaviate, Qdrant |
| LLMs | Mistral |
| Loaders | Word (.docx), HTML, Excel |
Read the docs, explore the source, or jump straight into a quickstart.