minimal_rag.py

script

← Back to skill

Content hash: 9a8249d47f053482766c4c30742d996209247fa1a660ecabd7f5ccaa2cff24c0
#!/usr/bin/env python3
"""Minimal end-to-end RAG pipeline: chunk -> embed -> index -> retrieve -> generate.

Uses sentence-transformers for local embeddings and sqlite-vec for vector storage.
Self-contained; no API keys needed.
"""
from __future__ import annotations

import sqlite3
import uuid


# --- 1. Embedding model (local, free) ---
try:
    from sentence_transformers import SentenceTransformer
except ImportError:
    print("Install: pip install sentence-transformers sqlite-vec")
    raise

model = SentenceTransformer("all-MiniLM-L6-v2")   # 384-dim


# --- 2. Vector store (sqlite-vec) ---
try:
    import sqlite_vec
except ImportError:
    print("Install: pip install sqlite-vec")
    raise

DB_PATH = ":memory:"


def _ensure_sqlite_vec() -> sqlite3.Connection:
    conn = sqlite3.connect(DB_PATH)
    conn.enable_load_extension(True)
    sqlite_vec.load(conn)
    conn.enable_load_extension(False)
    conn.execute("PRAGMA journal_mode=WAL")
    return conn


def create_index(conn: sqlite3.Connection) -> None:
    conn.execute(
        "CREATE VIRTUAL TABLE IF NOT EXISTS chunks USING vec0(embedding float[384])"
    )
    conn.execute(
        "CREATE TABLE IF NOT EXISTS chunk_texts (id TEXT PRIMARY KEY, text TEXT)"
    )
    conn.commit()


def insert_chunks(conn: sqlite3.Connection, texts: list[str]) -> None:
    embeddings = model.encode(texts, normalize_embeddings=True)
    for text, embedding in zip(texts, embeddings):
        chunk_id = str(uuid.uuid4())
        conn.execute(
            "INSERT INTO chunk_texts (id, text) VALUES (?, ?)", (chunk_id, text)
        )
        conn.execute(
            "INSERT INTO chunks (id, embedding) VALUES (?, ?)",
            (chunk_id, embedding.tobytes()),
        )
    conn.commit()


def search(conn: sqlite3.Connection, query: str, top_k: int = 3) -> list[tuple[str, float]]:
    q_embedding = model.encode([query], normalize_embeddings=True)[0]
    rows = conn.execute(
        """SELECT ct.text, vec_distance_cosine(c.embedding, ?) AS score
           FROM chunks c JOIN chunk_texts ct ON c.id = ct.id
           ORDER BY score ASC LIMIT ?""",
        (q_embedding.tobytes(), top_k),
    ).fetchall()
    return [(row[0], 1.0 - row[1]) for row in rows]  # similarity = 1 - distance


# --- 3. Demo ---

def main() -> None:
    conn = _ensure_sqlite_vec()
    create_index(conn)

    documents = [
        "Python is a high-level programming language known for readability.",
        "SQLite is a self-contained, serverless SQL database engine.",
        "RAG stands for Retrieval-Augmented Generation, combining search with LLMs.",
        "Vector embeddings map text to dense numerical vectors for similarity search.",
    ]
    insert_chunks(conn, documents)

    queries = [
        "What is RAG?",
        "Tell me about databases",
        "What language is Python?",
    ]
    for q in queries:
        results = search(conn, q, top_k=2)
        print(f"\nQuery: {q}")
        for text, score in results:
            print(f"  [{score:.4f}] {text[:80]}")

    conn.close()


if __name__ == "__main__":
    main()