Skip to main content

RAGWire logo

RAGWire

Build production-ready RAG pipelines with document ingestion, metadata-aware retrieval, hybrid search, and Qdrant storage in a few lines of Python.

PyPI Python Versions Downloads License Documentation YouTube

Documentation

RAGWire 10-second explainer


Table of Contents

Why RAGWire?

RAGWire gives you the building blocks for document-heavy RAG systems without forcing you into a full application framework. It keeps ingestion, metadata extraction, vector storage, and retrieval configurable, inspectable, and easy to wire into your own apps or agents.

  • Ingest PDFs, DOCX, XLSX, PPTX, Markdown, and text files
  • Extract structured metadata with your chosen LLM
  • Store dense and optional sparse vectors in Qdrant
  • Retrieve with metadata filters, MMR, hybrid search, or cross-encoder reranking
  • Answer questions with citations, and measure whether retrieval is any good
  • Swap LLM and embedding providers through YAML config
  • Re-run ingestion safely with SHA256 deduplication

Try It In 60 Seconds

pip install ragwire

Start Qdrant locally:

docker run -p 6333:6333 qdrant/qdrant
from ragwire import RAGWire

rag = RAGWire("config.yaml")

rag.ingest_directory("data/", recursive=True)

results = rag.retrieve("What was Apple's revenue in 2025?", top_k=5)
for doc in results:
    print(doc.page_content[:300])
    print(doc.metadata)

Example output:

Apple reported total net sales of ...
{'company_name': 'apple inc.', 'doc_type': '10-k', 'fiscal_year': [2025], 'file_name': 'Apple_10k_2025.pdf'}

Best For

  • Financial document QA and SEC filing analysis
  • Internal knowledge bases over PDFs and office documents
  • Research document search with metadata filters
  • Agentic RAG systems that need filter context
  • Hybrid retrieval applications using Qdrant

Supported Stack

Layer Supported
Document loading PDF, DOCX, XLSX, PPTX, TXT, MD via MarkItDown
Vector database Qdrant
Embeddings Ollama, OpenAI, OpenRouter, HuggingFace, Google, FastEmbed
LLM metadata extraction Ollama, OpenAI, OpenRouter, Gemini, Groq, Anthropic
Retrieval Similarity, MMR, hybrid dense+sparse search, cross-encoder reranking
Configuration YAML + environment variables

Project Status

RAGWire is in beta and designed for developers building production-style RAG systems. The core ingestion, metadata extraction, Qdrant storage, and retrieval workflows are usable today; APIs may evolve as the toolkit matures.

Features

  • Document Loading: PDF, DOCX, XLSX, PPTX and more via MarkItDown
  • LLM Metadata Extraction: extracts company, doc type, fiscal period using your LLM; fully customisable via YAML
  • Smart Text Splitting: markdown-aware and recursive chunking strategies
  • Multiple Embedding Providers: Ollama, OpenAI, OpenRouter, HuggingFace, Google, FastEmbed
  • Qdrant Vector Store: dense, sparse, and hybrid search
  • Advanced Retrieval: similarity, MMR, and hybrid search with metadata filtering
  • Grounded Answers: rag.query() returns cited answers and refuses when the documents come up short
  • MCP Server: ragwire mcp serve exposes a collection to Claude Desktop, Claude Code and Cursor
  • Source Sync: rag.sync() reconciles against local folders and S3, including deletions
  • Reranking: optional cross-encoder second stage, local and API-key-free by default
  • Evaluation: recall@k, MRR and config sweeps against your own golden set
  • SHA256 Deduplication: at both file and chunk level
  • Directory Ingestion: ingest an entire folder with one call, with optional recursive scan
  • Env Var Substitution: use ${VAR} in config.yaml for secrets

Architecture

RAGWire system architecture showing document ingestion and query retrieval workflows

RAGWire is coordinated by the RAGWire core and configured through YAML. Documents move through conversion, deduplication, chunking, metadata extraction, embeddings, and Qdrant storage. Queries can use explicit or LLM-extracted metadata filters before dense, sparse, or hybrid retrieval returns the most relevant chunks.

Document Ingestion Flow

RAGWire document ingestion flow from source files to searchable chunks in Qdrant

The ingestion path is safe to re-run: SHA256 file hashes skip previously stored documents. New files are converted to Markdown, split into overlapping chunks, enriched with structured LLM metadata, embedded, and stored in Qdrant with dense and optional sparse vectors.

Installation

pip install ragwire

# With Ollama support (local, no API key)
pip install "ragwire[ollama]"

# With OpenRouter support (LLM + embeddings; requires Python >= 3.10)
pip install "ragwire[openrouter]"

# With all providers
pip install "ragwire[all]"

Quick Start

from ragwire import RAGWire

rag = RAGWire("config.yaml")

# Ingest files. SHA256 deduplication makes this safe to re-run.
stats = rag.ingest_documents(["data/Apple_10k_2025.pdf", "data/Microsoft_10k_2025.pdf"])
print(f"Processed: {stats['processed']}, Skipped: {stats['skipped']}, Chunks: {stats['chunks_created']}")

# Or ingest an entire directory
stats = rag.ingest_directory("data/", recursive=True)

# Basic retrieval returns a list of LangChain Document objects
results = rag.retrieve("What is the total revenue?", top_k=5)
for doc in results:
    print(doc.page_content[:300])
    print(doc.metadata["company_name"])   # str, lowercased, e.g. "apple"
    print(doc.metadata["fiscal_year"])    # int, e.g. 2025
    print(doc.metadata["file_name"])      # str, e.g. "Apple_10k_2025.pdf"

# Retrieval with explicit metadata filters
results = rag.retrieve(
    "What is the net income?",
    filters={"company_name": "apple", "fiscal_year": 2025}  # pass year as int
)

# A list gives OR logic within a field, matching any of the listed values
results = rag.retrieve("Compare revenue trends", filters={"fiscal_year": [2023, 2024, 2025]})

# Agent-controlled filtering (recommended for AI agents)
filters = rag.extract_filters("Apple's revenue in 2025")
# → {"company_name": "apple", "fiscal_year": 2025} or None
results = rag.retrieve("Apple's revenue in 2025", filters=filters)

Grounded Answers

retrieve() returns chunks. query() returns an answer with the sources it came from, and refuses rather than falling back on the model's own knowledge.

answer = rag.query("What was Apple's net income in fiscal 2025?")

print(answer.text)          # "Apple reported net income of $93.7 billion [1]."
print(answer.sources)       # ["Apple_10k_2025.pdf"]
print(answer.confidence)    # fraction of sentences carrying a citation

# An Answer is falsy when the documents did not support one
if not answer:
    print("Not in the collection")

# Async, for anything serving concurrent users
answer = await rag.aquery("What was net income?")

Keeping the Collection Current

Ingestion only ever adds, so a file deleted at the source keeps answering queries forever. sync() reconciles instead.

sources:
  - type: local
    path: "./documents"
    recursive: true
stats = rag.sync(dry_run=True)   # see what would change
stats = rag.sync()               # ingest new, replace changed, delete gone

Command Line

ragwire ingest ./documents --recursive
ragwire sync --dry-run
ragwire eval golden.yaml --compare-rerank
ragwire mcp serve --config config.yaml    # expose the collection to Claude Desktop

Query & Retrieval Flow

RAGWire query and retrieval flow with metadata filters and hybrid search

At query time, callers can pass filters directly or enable LLM-assisted filter extraction grounded in values already stored in Qdrant. The query is then resolved through similarity, MMR, or dense-plus-sparse hybrid search and returned as top-ranked LangChain Document chunks for a RAG application or agent.

Configuration

Create a minimal config.yaml:

embeddings:
  provider: "ollama"
  model: "qwen3-embedding:0.6b"
  base_url: "http://localhost:11434"

llm:
  provider: "ollama"
  model: "qwen3.5:9b"
  num_ctx: 16384

vectorstore:
  url: "http://localhost:6333"
  collection_name: "rag_documents"
  use_sparse: true

retriever:
  search_type: "hybrid"
  top_k: 5

Copy config.example.yaml to config.yaml for the full template. Secrets can be injected via environment variables:

vectorstore:
  url: "https://your-cluster.qdrant.io"
  api_key: "${QDRANT_API_KEY}"

llm:
  provider: "openai"
  model: "gpt-5.4-nano"
  api_key: "${OPENAI_API_KEY}"

Full example:

embeddings:
  provider: "ollama"
  model: "qwen3-embedding:0.6b"
  base_url: "http://localhost:11434"

llm:
  provider: "ollama"
  model: "qwen3.5:9b"
  num_ctx: 16384

vectorstore:
  url: "http://localhost:6333"
  collection_name: "my_docs"
  use_sparse: true

retriever:
  search_type: "hybrid"
  top_k: 5
  auto_filter: false   # set true to enable LLM-based filter extraction from every query

Embedding Providers

# Ollama (local)
embeddings:
  provider: "ollama"
  model: "qwen3-embedding:0.6b"

# OpenAI
embeddings:
  provider: "openai"
  model: "text-embedding-3-small"

# OpenRouter (free-tier models available)
embeddings:
  provider: "openrouter"
  model: "nvidia/llama-nemotron-embed-vl-1b-v2:free"
  api_key: "${OPENROUTER_API_KEY}"

# HuggingFace (local)
embeddings:
  provider: "huggingface"
  model_name: "sentence-transformers/all-MiniLM-L6-v2"

# Google
embeddings:
  provider: "google"
  model: "models/embedding-001"

Examples

Start with the basic examples, then move into app and agent tutorials:

Example Path
Basic ingestion and retrieval examples/basic_usage.py
Custom metadata extraction examples/custom_metadata_usage.py
RAG agent helper examples/rag_agent.py
Tutorial series overview examples/tutorials/00_series_overview.md
FastAPI production app examples/tutorials/15_fastapi_production_app.md
LangGraph RAG pipeline examples/tutorials/05_langgraph_rag_pipeline.md
Chainlit RAG chatbot examples/tutorials/03_chainlit_rag_chatbot.md

Production Notes

  • Use Qdrant Cloud or a persistent local Qdrant volume for real projects.
  • Keep force_recreate: false after initial setup to avoid accidental collection resets.
  • Store API keys in environment variables and reference them as ${VAR} in YAML.
  • Tune chunk_size, chunk_overlap, top_k, and search_type for your document type.
  • Use metadata filters for high-precision retrieval over multi-company, multi-year, or multi-domain collections.
  • Enable use_sparse: true with fastembed when keyword matching matters alongside semantic search.

Security & Privacy

RAGWire can run with local Ollama models for teams that do not want document text sent to hosted LLM providers. If you configure hosted LLM or embedding providers, the relevant document text or query text is sent to those providers for metadata extraction, embeddings, or filter extraction. Keep secrets out of source control by using environment variables in config.yaml.

How RAGWire Fits

RAGWire is a composable Python toolkit, not a hosted RAG platform and not a full application framework. Use it when you want direct control over document ingestion, metadata design, Qdrant storage, and retrieval behavior while still avoiding boilerplate.

Component Usage

from ragwire import (
    MarkItDownLoader,
    get_splitter,
    get_markdown_splitter,
    get_embedding,
    QdrantStore,
    MetadataExtractor,
    hybrid_search,
    mmr_search,
)

# Load a document
loader = MarkItDownLoader()
result = loader.load("document.pdf")

# Split text
splitter = get_markdown_splitter(chunk_size=10000, chunk_overlap=2000)
chunks = splitter.split_text(result["text_content"])

# Embeddings
embedding = get_embedding({"provider": "ollama", "model": "qwen3-embedding:0.6b"})

# Vector store
store = QdrantStore(config={"url": "http://localhost:6333"}, embedding=embedding)
store.set_collection("my_collection")
vectorstore = store.get_store()

Package Structure

ragwire/
├── core/          # Config loader + RAGWire orchestrator
├── loaders/       # MarkItDown document converter
├── processing/    # Text splitters + SHA256 hashing
├── metadata/      # Pydantic schema + LLM extractor
├── embeddings/    # Multi-provider embedding factory
├── vectorstores/  # Qdrant wrapper with hybrid search
├── retriever/     # Similarity, MMR, hybrid retrieval and reranking
├── generation/    # Grounded answers with citations
├── eval/          # Golden sets, recall@k, MRR, config sweeps
├── sources/       # Local and S3 connectors for rag.sync()
├── mcp/           # MCP server and tools
├── cli.py         # The ragwire command
└── utils/         # Logging

Troubleshooting

Error Fix
Qdrant connection refused docker run -p 6333:6333 qdrant/qdrant
markitdown[pdf] missing pip install "markitdown[pdf]"
Ollama model not found ollama pull <model-name>
fastembed missing pip install fastembed (needed for hybrid search)
Embedding dimension mismatch Set force_recreate: true in config once, then back to false

Contributing

Contributions are welcome. Please open an issue for bugs, feature requests, provider integrations, or documentation improvements before larger changes.

License

MIT © 2026 KGP Talkie Private Limited

Links

Documentation   GitHub   YouTube

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ragwire-1.5.0.tar.gz (3.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ragwire-1.5.0-py3-none-any.whl (3.7 MB view details)

Uploaded Python 3

File details

Details for the file ragwire-1.5.0.tar.gz.

File metadata

  • Download URL: ragwire-1.5.0.tar.gz
  • Upload date:
  • Size: 3.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for ragwire-1.5.0.tar.gz
Algorithm Hash digest
SHA256 f8b9954838a9b045d4d7cc244e35b4c6bc4bf15d8d86591af48728c6270836e3
MD5 a7ad358994c8e21d2a448c288d5eaf58
BLAKE2b-256 2b7c3c5f804724bb830d716f37703cf41addee00ad3c09a52d3ca9af933e4af1

See more details on using hashes here.

Provenance

The following attestation bundles were made for ragwire-1.5.0.tar.gz:

Publisher: publish.yml on laxmimerit/RAGWire

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ragwire-1.5.0-py3-none-any.whl.

File metadata

  • Download URL: ragwire-1.5.0-py3-none-any.whl
  • Upload date:
  • Size: 3.7 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for ragwire-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0383fb75c52e6641e19da4f3d6f4d1cf4fcefa6d80e9807836f5d69c9809704d
MD5 28769d683579b845a1e7da31ddca3019
BLAKE2b-256 08ab3a97f8ce14c47195b11ed8b823aaefd8e59cb85b3e74365a68c30c9b8ce6

See more details on using hashes here.

Provenance

The following attestation bundles were made for ragwire-1.5.0-py3-none-any.whl:

Publisher: publish.yml on laxmimerit/RAGWire

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.6.1

2 files

1.6.0

2 files

This release

1.5.0 This release

2 files

1.4.1

2 files

1.4.0

2 files

1.3.1

2 files

1.2.9

2 files

1.2.8

2 files

1.2.7

2 files

1.2.1

2 files

1.2.0

2 files

1.1.9

2 files

1.1.8

2 files

1.1.7

2 files

1.1.5

2 files

1.1.4

2 files

1.1.3

2 files

1.1.1

2 files

1.0.1

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page