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.

Watch the Tutorial - Video walkthrough of the toolkit in action.

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.6.tar.gz (122.5 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.6-py3-none-any.whl (143.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: visual_rag_toolkit-0.1.6.tar.gz
  • Upload date:
  • Size: 122.5 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.6.tar.gz
Algorithm Hash digest
SHA256 7b7568f8c2ee70aa7371186ccdaf33fd37ae9ea6185fe8d3b58d46ff11acc777
MD5 18b58407b09d73510aebf1a651b324d4
BLAKE2b-256 88fa99e8f7cc4b000e544fa18b937b260bd80947428fe4793d20a7743e434d29

See more details on using hashes here.

Provenance

The following attestation bundles were made for visual_rag_toolkit-0.1.6.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.6-py3-none-any.whl.

File metadata

File hashes

Hashes for visual_rag_toolkit-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 9b82a6436fb6511e0078cbc74387f5fba824f2a1e78643a988c3194d53602ae1
MD5 422090431c95aaabee531d694b30c35d
BLAKE2b-256 b5124421ed7596a0f5db15481b0095935df33ec187abb866c1025766173b9caf

See more details on using hashes here.

Provenance

The following attestation bundles were made for visual_rag_toolkit-0.1.6-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