RAGLite

repository·main·Indexed 22 days ago

https://github.com/superlinear-ai/raglite

A Python toolkit for building Retrieval-Augmented Generation (RAG) systems. It supports DuckDB and PostgreSQL backends for keyword and vector search, and integrates with any LLM provider via LiteLLM, including local models via llama-cpp-python. Key features include hybrid search, advanced chunking (late and contextual), a built-in Model Context Protocol (MCP) server, and optional integrations for Mistral OCR, Pandoc, and Ragas evaluation.

Tokens
13.7K
Snippets
47
Records
59
Agent score
78%

What's inside RAGLite

  1. Overview of RAGLite

    main

    RAGLite is a Python toolkit designed for Retrieval-Augmented Generation (RAG). It allows developers to build RAG systems using either DuckDB or PostgreSQL as the backend for keyword and vector search.

    Key capabilities include:

    • Configurable LLMs: Supports any provider via LiteLLM, including local models via llama-cpp-python.
    • Hybrid Search: Uses native database features (DuckDB FTS+VSS or PostgreSQL tsvector+pgvector).
    • Advanced Chunking: Implements late chunking, contextual chunk headings, and optimal semantic/sentence splitting.
    • Extensibility: Includes a built-in Model Context Protocol (MCP) server and optional integrations for Mistral OCR, Pandoc, and Ragas evaluation.
  2. Install accelerated llama-cpp-python for local models

    main

    If you plan to use local models, it is highly recommended to install an accelerated llama-cpp-python precompiled binary rather than the default installation. You must configure the version, Python version, accelerator (e.g., metal, cu121), and platform before installing the specific wheel.

    # Configure which llama-cpp-python precompiled binary to install (⚠️ not every combination is available):
    LLAMA_CPP_PYTHON_VERSION=0.3.9
    PYTHON_VERSION=310|311|312
    ACCELERATOR=metal|cu121|cu122|cu123|cu124
    PLATFORM=macosx_11_0_arm64|linux_x86_64|win_amd64 
    
    # Install llama-cpp-python:
    pip install "https://github.com/abetlen/llama-cpp-python/releases/download/v$LLAMA_CPP_PYTHON_VERSION-$ACCELERATOR/llama_cpp_python-$LLAMA_CPP_PYTHON_VERSION-cp$PYTHON_VERSION-cp$PYTHON_VERSION-$PLATFORM.whl"
  3. Implement a Full Manual RAG Pipeline

    main

    A high-quality manual pipeline involves: searching for a large set of chunks (e.g., 20), reranking them to select the top 5, grouping them into chunk spans, and then generating the response. This provides better precision than simple retrieval.

    from raglite import ( 
        hybrid_search, keyword_search, vector_search, 
        retrieve_chunks, rerank_chunks, retrieve_chunk_spans, 
        add_context, rag 
    )
    
    user_prompt = "How is intelligence measured?"
    
    # 1. Search (Hybrid)
    chunk_ids_hybrid, _ = hybrid_search(user_prompt, num_results=20, metadata_filter={"topic": "physics"}, config=my_config)
    
    # 2. Retrieve
    chunks_hybrid = retrieve_chunks(chunk_ids_hybrid, config=my_config)
    
    # 3. Rerank
    chunks_reranked = rerank_chunks(user_prompt, chunks_hybrid, config=my_config)[:5]
    
    # 4. Group into spans
    chunk_spans = retrieve_chunk_spans(chunks_reranked, config=my_config)
    
    # 5. Generate
    messages = [add_context(user_prompt=user_prompt, context=chunk_spans, config=my_config)]
    stream = rag(messages, config=my_config)
    for update in stream:
        print(update, end="")
  4. Install RAGLite with optional extras

    main

    RAGLite supports several optional extras for extended functionality:

    • Chainlit frontend: For a ChatGPT-like web, Slack, or Teams interface.
    • Pandoc support: To handle filetypes other than PDF.
    • Ragas support: For evaluating retrieval and generation performance.
    • Mistral OCR: For high-quality document processing (requires mistralai).
    # For ChatGPT-like frontend
    pip install raglite[chainlit]
    
    # For support for filetypes other than PDF
    pip install raglite[pandoc]
    
    # For evaluation support
    pip install raglite[ragas]
    
    # For Mistral OCR support
    pip install mistralai
  5. Insert Documents into RAGLite

    main

    Documents can be inserted from file paths or raw text. RAGLite handles Markdown conversion, semantic chunking, and multi-vector embedding with late chunking automatically. You can include metadata (e.g., author, topic) which can be used for later retrieval filtering.

    from pathlib import Path
    from raglite import Document, insert_documents
    
    # From file paths
    documents = [
        Document.from_path(Path("On the Measure of Intelligence.pdf")),
        Document.from_path(Path("Special Relativity.pdf")),
    ]
    insert_documents(documents, config=my_config)
    
    # From text content with metadata
    content = "# ON THE ELECTRODYNAMICS OF MOVING BODIES\n## By A. EINSTEIN"
    documents = [
        Document.from_text(content, author="Einstein", topic="physics", year=1905)
    ]
    insert_documents(documents, config=my_config)
  6. How chunklet splitting works conceptually

    main

    RAGLite uses a cost-minimization approach to split text into chunks (chunklets) that are optimized for retrieval and LLM processing.

    The Cost Model

    The splitting logic solves a dynamic programming problem where the goal is to minimize the total cost of all chunklets. The cost of a single chunklet is the sum of two components:

    1. Boundary Cost: Penalizes chunklets that start or end poorly relative to Markdown structure. It uses markdown_chunklet_boundaries to assign probabilities to sentences based on whether they represent Markdown elements like heading_open, blockquote_open, or paragraph_open.
    2. Statement Cost: Penalizes chunklets that deviate from an ideal information density. A 'statement' is a normalized measure of a sentence's word count. The default cost function (s - 3)² / sqrt(s) / 2 heavily penalizes chunklets that do not contain approximately 3 statements.

    Optimization Constraints

    • Max Size: A hard constraint on the character length of any single chunklet.
    • Markdown Awareness: The algorithm prefers splitting at natural structural boundaries (like headings) to ensure semantic coherence.
  7. Implement a custom IREvaluator for benchmarking

    main

    To benchmark RAG performance using TREC runs, you can implement a custom evaluator by subclassing IREvaluator. The IREvaluator base class provides the infrastructure for managing dataset-specific IDs, file paths for TREC runs, and a score() method that automates the query-and-save process.

    To create a functional evaluator, you must implement the following abstract methods:

    1. insert_documents(max_workers: int | None = None): Logic to ingest the dataset's documents into your specific search index or vector store.
    2. search(query_id: str, query: str, *, num_results: int = 10) -> list[ScoredDoc]: Logic to perform a search for a given query and return a list of ScoredDoc objects (containing query_id, doc_id, and score).

    The score() method will automatically handle the creation of a .trec file in the user's data directory, iterating through the dataset queries and writing results in the standard TREC format.

    from ir_datasets.datasets.base import Dataset
    from ir_measures import ScoredDoc
    # You must implement this class
    class MyCustomEvaluator(IREvaluator):
        def insert_documents(self, max_workers: int | None = None) -> None:
            # Your implementation
            pass
    
        def search(self, query_id: str, query: str, *, num_results: int = 10) -> list[ScoredDoc]:
            # Your implementation
            return []
  8. Enable Self-Query in RAGLiteConfig

    main

    To allow the LLM to automatically generate and apply metadata filters to refine search results based on user input, set self_query=True in your RAGLiteConfig.

    my_config = RAGLiteConfig(
        db_url="duckdb:///raglite.db",
        llm="gpt-4o-mini",
        embedder="text-embedding-3-large",
        self_query=True,
    )
  9. Configure Mistral OCR via MistralOCRConfig

    main

    When using the Mistral OCR processor, you can control how images are handled through the configuration.

    Key capabilities include:

    • include_image_descriptions: If True, the processor uses Mistral's bbox_annotation_format to generate text descriptions for images and diagrams, replacing image placeholders in the markdown with these descriptions.
    • image_types: A set of strings defining the types of images the vision model should annotate (e.g., diagram, table, photo).
    • exclude_image_types: A set of image types that, if matched by an annotation, will cause the image placeholder to be removed from the output markdown entirely.
  10. How agentic RAG retrieval works

    main

    RAGLite implements an agentic retrieval pattern. When rag() or async_rag() is called:

    1. Initial Attempt: The LLM receives the user prompt. If the prompt doesn't contain explicit RAG context, the system provides a search_knowledge_base tool.
    2. Tool Call: If the LLM determines it needs more information, it calls search_knowledge_base with a precise, single-faceted query.
    3. Iterative Search: The system executes the search and provides the results back to the LLM. The LLM can then decide to call the tool again with a refined query if necessary (up to config.agentic_iterations).
    4. Novelty Check: To prevent context bloat, RAGLite only keeps ChunkSpan objects that introduce at least one new chunk ID not seen in previous iterations.
    5. Final Answer: Once the LLM has sufficient information, it responds. If tool calls were made, a follow-up generation is triggered with a NO_TOOLS_FOLLOW_UP_PROMPT to ensure the LLM focuses on answering rather than searching again.
  11. Improve RAG with an Optimal Query Adapter

    main

    RAGLite can compute an optimal closed-form query adapter to improve prompt embedding quality. To use this, first generate evaluations using insert_evals, then compute and store the adapter with update_query_adapter. Once updated, every vector search will automatically use the adapter.

    from raglite import insert_evals, update_query_adapter
    
    insert_evals(num_evals=100, config=my_config)
    update_query_adapter(config=my_config)