Skip to main content

End-to-end visual document retrieval with ColPali, featuring two-stage pooling for scalable search

Project description

Visual RAG Toolkit

PyPI Python License Demo

End-to-end visual document retrieval toolkit featuring fast multi-stage retrieval (prefetch with pooled vectors + exact MaxSim reranking).

Try the Live Demo - Upload PDFs, index to Qdrant, and query with visual retrieval.

This repo contains:

  • a Python package (visual_rag)
  • a Streamlit demo app (demo/)
  • benchmark & evaluation scripts for ViDoRe v2 (benchmarks/)

๐ŸŽฏ Key Features

  • Modular: PDF โ†’ images, embedding, Qdrant indexing, retrieval can be used independently.
  • Multi-stage retrieval: two-stage and three-stage retrieval modes built for Qdrant named vectors.
  • Model-aware embedding: ColSmol + ColPali support behind a single VisualEmbedder interface.
  • Token hygiene: query special-token filtering by default for more stable MaxSim behavior.
  • Practical pipelines: robust indexing, retries, optional Cloudinary image URLs, evaluation reporting.

๐Ÿ“ฆ Installation

# Core package (minimal dependencies)
pip install visual-rag-toolkit

# With specific features
pip install visual-rag-toolkit[ui]           # Streamlit demo dependencies
pip install visual-rag-toolkit[qdrant]       # Vector database
pip install visual-rag-toolkit[embedding]    # ColSmol/ColPali embedding support
pip install visual-rag-toolkit[cloudinary]   # Image CDN

# All dependencies
pip install visual-rag-toolkit[all]

System dependencies (PDF)

pdf2image requires Poppler.

  • macOS: brew install poppler
  • Ubuntu/Debian: sudo apt-get update && sudo apt-get install -y poppler-utils

๐Ÿš€ Quick Start

Minimal: embed a query and run two-stage search (server-side)

from qdrant_client import QdrantClient
from visual_rag import VisualEmbedder, TwoStageRetriever

client = QdrantClient(url="https://YOUR_QDRANT", api_key="YOUR_KEY")
collection_name = "your_collection"

# Embed query tokens
embedder = VisualEmbedder(model_name="vidore/colpali-v1.3")
q = embedder.embed_query("What is the budget allocation?")

# Fast path: all stages computed in Qdrant (prefetch + exact rerank)
retriever = TwoStageRetriever(client, collection_name)
results = retriever.search_server_side(
    query_embedding=q,
    top_k=10,
    prefetch_k=256,
    stage1_mode="tokens_vs_experimental",  # or: tokens_vs_tiles / pooled_query_vs_tiles / pooled_query_vs_global
)

for r in results[:3]:
    print(r["id"], r["score_final"])

End-to-end: ingest PDFs (with cropping) โ†’ index in Qdrant

This is the "SDK-style" pipeline: PDF โ†’ images โ†’ optional crop โ†’ embed โ†’ store vectors + payload in Qdrant.

import os
from pathlib import Path

import numpy as np
import torch

from visual_rag import VisualEmbedder
from visual_rag.indexing import ProcessingPipeline, QdrantIndexer

QDRANT_URL = os.environ["QDRANT_URL"]
QDRANT_KEY = os.getenv("QDRANT_API_KEY", "")

collection = "my_visual_docs"

embedder = VisualEmbedder(
    model_name="vidore/colSmol-500M",
    torch_dtype=torch.float16,
    output_dtype=np.float16,
    batch_size=8,
)

indexer = QdrantIndexer(
    url=QDRANT_URL,
    api_key=QDRANT_KEY,
    collection_name=collection,
    prefer_grpc=True,
    vector_datatype="float16",
)

# Creates collection + required payload indexes (e.g., "filename" for skip_existing)
indexer.create_collection(force_recreate=False)

pipeline = ProcessingPipeline(
    embedder=embedder,
    indexer=indexer,
    embedding_strategy="all",  # store full tokens + pooled vectors in one pass
    crop_empty=True,
    crop_empty_percentage_to_remove=0.99,  # kept for traceability
    crop_empty_remove_page_number=True,
    crop_empty_preserve_border_px=1,
    crop_empty_uniform_rowcol_std_threshold=3.0,
)

pdfs = [Path("docs/a.pdf"), Path("docs/b.pdf")]
for pdf_path in pdfs:
    result = pipeline.process_pdf(
        pdf_path,
        skip_existing=True,  # Skip pages already in Qdrant (uses filename index)
        upload_to_cloudinary=False,
        upload_to_qdrant=True,
    )
    # Logs automatically shown:
    # [10:23:45] ๐Ÿ“š Processing PDF: a.pdf
    # [10:23:45] ๐Ÿ–ผ๏ธ Converting PDF to images...
    # [10:23:46]    โœ… Converted 12 pages
    # [10:23:46] ๐Ÿ“ฆ Processing pages 1-8/12
    # [10:23:46] ๐Ÿค– Generating embeddings for 8 pages...
    # [10:23:48] ๐Ÿ“ค Uploading batch of 8 pages...
    # [10:23:48]    โœ… Uploaded 8 points to Qdrant
    # [10:23:48] ๐Ÿ“ฆ Processing pages 9-12/12
    # [10:23:48] ๐Ÿค– Generating embeddings for 4 pages...
    # [10:23:50] ๐Ÿ“ค Uploading batch of 4 pages...
    # [10:23:50]    โœ… Uploaded 4 points to Qdrant
    # [10:23:50] โœ… Completed a.pdf: 12 uploaded, 0 skipped, 0 failed

CLI equivalent:

export QDRANT_URL="https://YOUR_QDRANT"
export QDRANT_API_KEY="YOUR_KEY"

visual-rag process \
  --reports-dir ./docs \
  --collection my_visual_docs \
  --model vidore/colSmol-500M \
  --strategy all \
  --batch-size 8 \
  --qdrant-vector-dtype float16 \
  --prefer-grpc \
  --crop-empty \
  --crop-empty-remove-page-number

Process a PDF into images (no embedding, no vector DB)

from pathlib import Path
from visual_rag import PDFProcessor

processor = PDFProcessor(dpi=140)
images, texts = processor.process_pdf(Path("report.pdf"))
print(len(images), "pages")

๐Ÿ”ฌ Multi-stage Retrieval (Two-stage / Three-stage)

Traditional ColBERT-style MaxSim scoring compares all query tokens vs all document tokens, which becomes expensive at scale.

Our approach:

Stage 1: Fast prefetch with tile-level pooled vectors
         โ”œโ”€โ”€ Pool each tile (64 patches) โ†’ num_tiles vectors
         โ”œโ”€โ”€ Use HNSW index for O(log N) retrieval  
         โ””โ”€โ”€ Retrieve top-K candidates (e.g., 200)

Stage 2: Exact MaxSim reranking on candidates
         โ”œโ”€โ”€ Load full multi-vector embeddings
         โ”œโ”€โ”€ Compute exact ColBERT MaxSim scores
         โ””โ”€โ”€ Return top-k results (e.g., 10)

Three-stage extends this with an additional "cheap prefetch" stage before stage 2.

๐Ÿ“ Package Structure

visual-rag-toolkit/
โ”œโ”€โ”€ visual_rag/              # Import as: from visual_rag import ...
โ”‚   โ”œโ”€โ”€ embedding/           # VisualEmbedder, pooling functions
โ”‚   โ”œโ”€โ”€ indexing/            # PDFProcessor, QdrantIndexer, CloudinaryUploader
โ”‚   โ”œโ”€โ”€ retrieval/           # TwoStageRetriever
โ”‚   โ”œโ”€โ”€ visualization/       # Saliency maps
โ”‚   โ”œโ”€โ”€ cli/                 # Command-line: visual-rag process/search
โ”‚   โ””โ”€โ”€ config.py            # load_config, get, get_section
โ”‚
โ”œโ”€โ”€ benchmarks/              # ViDoRe evaluation scripts
โ””โ”€โ”€ examples/                # Usage examples

โš™๏ธ Configuration

Configure via environment variables or YAML:

# Qdrant credentials (preferred names used by the demo + scripts)
export QDRANT_URL="https://your-cluster.qdrant.io"
export QDRANT_API_KEY="your-api-key"

# Special token handling (default: filter them out)
export VISUALRAG_INCLUDE_SPECIAL_TOKENS=true  # Include special tokens

Or use a config file (visual_rag.yaml):

model:
  name: "vidore/colSmol-500M"
  batch_size: 4
  
qdrant:
  url: "https://your-cluster.qdrant.io"
  collection: "my_documents"
  
search:
  strategy: "two_stage"  # or "multi_vector", "pooled"
  prefetch_k: 200
  top_k: 10

๐Ÿ–ฅ๏ธ Demo (Streamlit)

pip install "visual-rag-toolkit[ui,qdrant,embedding,pdf]"

# Option A: from Python
python -c "import visual_rag; visual_rag.demo()"

# Option B: CLI launcher
visual-rag-demo

๐Ÿ“Š Benchmark Evaluation

Run ViDoRe benchmark evaluation:

# Example: evaluate a collection against ViDoRe BEIR datasets in Qdrant
python -m benchmarks.vidore_beir_qdrant.run_qdrant_beir \
  --datasets vidore/esg_reports_v2 vidore/biomedical_lectures_v2 \
  --collection YOUR_COLLECTION \
  --mode two_stage \
  --stage1-mode tokens_vs_experimental \
  --prefetch-k 256 \
  --top-k 100 \
  --evaluation-scope union

More commands (including multi-stage variants and cropping configs) live in:

  • examples/COMMANDS.md

๐Ÿ”ง Development

git clone https://github.com/Ara-Yeroyan/visual-rag-toolkit
cd visual-rag-toolkit
pip install -e ".[dev]"
pytest tests/ -v

๐Ÿ“„ Citation

If you use this toolkit in your research, please cite:

@software{visual_rag_toolkit,
  title = {Visual RAG Toolkit: Scalable Visual Document Retrieval with 1D Convolutional Pooling},
  author = {Ara Yeroyan},
  year = {2026},
  url = {https://github.com/Ara-Yeroyan/visual-rag-toolkit}
}

๐Ÿ“ License

MIT License - see LICENSE for details.

๐Ÿ™ Acknowledgments

  • Qdrant - Vector database with multi-vector support
  • ColPali - Visual document retrieval models
  • ViDoRe - Benchmark dataset

Project details


Download files

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

Source Distribution

visual_rag_toolkit-0.1.4.tar.gz (122.2 kB view details)

Uploaded Source

Built Distribution

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

visual_rag_toolkit-0.1.4-py3-none-any.whl (142.7 kB view details)

Uploaded Python 3

File details

Details for the file visual_rag_toolkit-0.1.4.tar.gz.

File metadata

  • Download URL: visual_rag_toolkit-0.1.4.tar.gz
  • Upload date:
  • Size: 122.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for visual_rag_toolkit-0.1.4.tar.gz
Algorithm Hash digest
SHA256 8bc7e9172cebdd0ca0ac81e6393b0183fed5f0c6ba7f631801843482ba22ba25
MD5 aec000ea0deeec164eaf64e4c90b5530
BLAKE2b-256 23d9738fe0f387ca76a13cc9b83047e4a70437cf876795c3ccc44d48e302dbf4

See more details on using hashes here.

Provenance

The following attestation bundles were made for visual_rag_toolkit-0.1.4.tar.gz:

Publisher: publish_pypi.yaml on Ara-Yeroyan/visual-rag-toolkit

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

File details

Details for the file visual_rag_toolkit-0.1.4-py3-none-any.whl.

File metadata

File hashes

Hashes for visual_rag_toolkit-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 0001f91b7b369cd52c082f66acd8d64611b1542b3e38c9a428ac75395f6394c9
MD5 ea9cd3e19334f8f729540dc5d4516df3
BLAKE2b-256 776f86cd0f91e7f94acc9b6b6c60f7006dc74d39ed9fbb5498c2a4d8144f6335

See more details on using hashes here.

Provenance

The following attestation bundles were made for visual_rag_toolkit-0.1.4-py3-none-any.whl:

Publisher: publish_pypi.yaml on Ara-Yeroyan/visual-rag-toolkit

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page