Skip to content

CookieDarb/libdocpilot

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

162 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

libdocpilot

libdocpilot is a Python library that provides an enhanced, multi-hop RAG (Retrieval-Augmented Generation) pipeline for chatting with documents. It goes beyond plain text retrieval by automatically extracting images from documents, understanding their surrounding context, and inserting relevant images inline into the generated answer.

Built on top of LlamaIndex and DSPy, it supports PDF, XLSX, and other document formats, with a PostgreSQL (pgvector) backend for persistent vector storage.


Features

  • πŸ“„ Rich document parsing β€” Extracts text, tables, and images from PDFs with bounding-box awareness. Also handles XLSX files.
  • πŸ–ΌοΈ Image-context retrieval β€” Maps each extracted image to its surrounding text using either sequential (single-column) or spatial (multi-column / floating) context extraction modes.
  • πŸ” Multi-hop retrieval β€” Rewrites the user query into precise search keywords before retrieval to improve relevance.
  • πŸ’¬ Conversational memory β€” Maintains per-session message history so follow-up questions are answered with context.
  • πŸ—„οΈ Persistent vector store β€” Uses PostgreSQL + pgvector (via LlamaIndex's PGVectorStore) with HNSW indexing. Falls back to an in-memory index if Postgres is unavailable.
  • βž• Incremental indexing β€” Only re-embeds documents that are not already present in the vector store.
  • πŸ—‘οΈ Document deletion β€” Removes a document's text embeddings, image embeddings, and on-disk image files, then rebuilds the in-memory indexes automatically.
  • πŸ€– LLM-agnostic β€” Works with any Ollama-served model or any LiteLLM-compatible provider through DSPy.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         libdocpilot                              β”‚
β”‚                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ PDFPreproc. β”‚    β”‚ CustomPDF/   β”‚    β”‚   load_docs()      β”‚  β”‚
β”‚  β”‚  get_elems  │───▢│ XLSXReader   │───▢│ (llama_utils)      β”‚  β”‚
β”‚  β”‚  get_image  β”‚    β”‚ (parsers.py) β”‚    β”‚ Skips indexed docs β”‚  β”‚
β”‚  β”‚  context    β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                 β”‚              β”‚
β”‚                                           β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”       β”‚
β”‚                                           β”‚ get_vector_ β”‚       β”‚
β”‚                                           β”‚ store_index β”‚       β”‚
β”‚                                           β”‚  (PGVector/ β”‚       β”‚
β”‚                                           β”‚  in-memory) β”‚       β”‚
β”‚                                           β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜       β”‚
β”‚                                                  β”‚              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                     MultiHopRAG                           β”‚  β”‚
β”‚  β”‚  LlamaIndexRMClient (text)   ImageRetriever (images)      β”‚  β”‚
β”‚  β”‚  QueryPrompt rewrite ──▢ retrieve ──▢ AnswerPrompt        β”‚  β”‚
β”‚  β”‚  ImageRanker ──▢ place_images_from_chunks ──▢ yield       β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

MultiHopRAG.forward() is a generator that yields intermediate status events so you can stream results to a UI or CLI as they arrive:

Event type Content
query Rewritten search keywords
files Source filenames retrieved
answer Plain-text LLM answer
answer_with_images Answer with inline base-64 images
streaming_answer Individual streamed text chunks (stream mode)
finalization Signal that the turn is complete

Requirements

  • Python β‰₯ 3.12
  • Ollama running locally (default: http://localhost:11434)
  • PostgreSQL with pgvector extension (recommended; in-memory fallback available)

Installation

From source

git clone https://github.com/Silent-Crafter/libdocpilot
cd libdocpilot
pip install .

With uv (recommended)

git clone https://github.com/Silent-Crafter/libdocpilot
cd libdocpilot
uv sync

Configuration

Copy config.py into your project root (or inherit from it) and fill in the values:

# config.py
import os
from dotenv import load_dotenv

load_dotenv()

class Config:
    PG_CONNECTION_URI  = os.getenv("PG_CONNECTION_URI")          # e.g. "postgresql://user:pass@localhost:5432/docpilot"
    embed_table        = "data_items"                             # PG table for text embeddings (must be prefixed with "data_")
    embed_model        = "ibm-granite/granite-embedding-278m-multilingual"  # HuggingFace embedding model
    ollama_model       = "dolphin3"                               # Ollama model tag
    ollama_url         = "http://localhost:11434"                 # Ollama base URL
    document_dir       = "data"                                   # Directory containing your documents

Set PG_CONNECTION_URI in a .env file or as an environment variable:

PG_CONNECTION_URI=postgresql://user:password@localhost:5432/docpilot

Quick Start

import dspy
from docpilot.dspyclasses import MultiHopRAG
from docpilot.utils.llama_utils import get_vector_store_index, load_docs
from config import Config as config

# 1. Load and embed documents (skips already-indexed ones automatically)
docs, image_docs, image_mappings = load_docs(
    config.document_dir,
    config.PG_CONNECTION_URI,
    config.embed_table,
)

# 2. Build vector indexes
index       = get_vector_store_index(docs,       config.embed_model, embeddings_table=config.embed_table,  uri=config.PG_CONNECTION_URI)
image_index = get_vector_store_index(image_docs, config.embed_model, embeddings_table="data_images",       uri=config.PG_CONNECTION_URI)

# 3. Configure DSPy LLM
dspy.settings.configure(lm=dspy.LM(
    model=f"ollama/{config.ollama_model}",
    base_url=config.ollama_url,
    cache=False,
))

# 4. Create the RAG module
rag = MultiHopRAG(index=index, image_index=image_index, num_passages=3)

# 5. Ask a question
for event in rag.forward("What is gradient descent?"):
    if event["type"] == "answer":
        print(event["content"])
    elif event["type"] == "answer_with_images":
        # Markdown string with base-64 images embedded
        with open("answer.html", "w") as f:
            f.write(event["content"])

Multi-Session Usage

Each MultiHopRAG instance maintains its own message_history, making it straightforward to run isolated concurrent sessions β€” simply instantiate one object per session:

session_a = MultiHopRAG(index=index, image_index=image_index)
session_b = MultiHopRAG(index=index, image_index=image_index)

The underlying retrieval components are shared (class-level) while the conversation state is per-instance.


Logging

libdocpilot uses Python's standard logging module throughout. To enable output:

from docpilot.utils.logger import setup_logging
setup_logging()  # Configures a stdout handler + optional metrics file

Set the LOG_LEVEL environment variable to control verbosity (DEBUG, INFO, WARNING, etc.).


Related Projects

  • docpilot-api β€” Flask REST API that wraps this library

License

This project is licensed under the GNU General Public License v3.0. See LICENSE for details.

About

Chat with your documents

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages

  • Python 100.0%