# RAG from scratch Esercizio pratico per costruire una pipeline RAG (Retrieval-Augmented Generation) completa a partire da documenti PDF, interamente in locale senza dipendenze cloud. L'obiettivo è avere un assistente che risponde a domande sui tuoi documenti usando solo il contenuto di quei documenti, senza allucinazioni. **Stack:** Python · MinerU · ChromaDB · Ollama **Chunking:** AST-based, deterministico, senza LLM **Embedding + Generazione:** Ollama (locale, nessun dato inviato a server esterni) ``` PDF └─ MinerU ──► sources/.md └─ chunker.py ──► chunks//chunks.json └─ ingest.py ──► chroma_db/ └─ rag.py / GUI ``` --- ## Requisiti minimi | Risorsa | Minimo | Note | |---------|--------|------| | Python | 3.10 – 3.13 | testato su 3.11 | | RAM | 8 GB | 16 GB+ per modelli LLM grandi | | Disco | 5 GB liberi | + spazio modelli Ollama | | GPU | non necessaria | accelera MinerU e modelli LLM | | OS | Linux, macOS, Windows (WSL2) | | **Dipendenze Python** — installate via `requirements.txt`: - `chromadb` — database vettoriale locale - `markdown-it-py` + `mdit-py-plugins` — parser Markdown AST-based per il chunking - `PySide6` — interfaccia grafica desktop (opzionale, solo per la GUI) **Dipendenze esterne:** - [Ollama](https://ollama.com) — server locale per embedding e generazione LLM - [MinerU](https://github.com/opendatalab/MinerU) — conversione PDF → Markdown (opzionale se hai già i file `.md`) --- ## Setup ambiente ```bash # Crea e attiva il virtual environment python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # Installa le dipendenze pip install -r requirements.txt ``` --- ## Passo 1 — Converti i PDF con MinerU MinerU estrae il testo dai PDF producendo un Markdown strutturato con tabelle, formule ed immagini. Ci sono due modalità: ### Opzione A — Google Colab (consigliata, usa la GPU gratuita di Google) Il notebook `mineru.ipynb` esegue MinerU su Colab sfruttando la GPU messa a disposizione gratuitamente. Utile se non hai una GPU locale o se i PDF sono pesanti. 1. Apri `mineru.ipynb` su Google Colab 2. Carica i PDF nella cartella `input/` indicata nelle celle 3. Esegui tutte le celle — al termine viene scaricato uno ZIP per ogni documento 4. Decomprimi gli ZIP nella cartella `sources/` del progetto: ``` sources/ └── _output/ └── auto/ ├── .md ← questo è l'input della pipeline └── images/ ``` ### Opzione B — Installazione locale ```bash pip install "mineru[all]" # installa MinerU (scarica ~15 GB di modelli al primo avvio) mineru -p documento.pdf -o sources/_output/ -b pipeline # Con GPU: mineru -p documento.pdf -o sources/_output/ -b hybrid-auto-engine ``` --- ## Passo 2 — Chunking Il chunker analizza il Markdown con un parser AST, divide il testo in blocchi semantici e li impacchetta in chunk ottimizzati per il retrieval. ```bash # Singolo documento python chunks/chunker.py --stem # Tutti i documenti presenti in sources/ in una volta python chunks/chunker.py # Rigenera anche se chunks.json esiste già python chunks/chunker.py --stem --force ``` Output in `chunks//`: `chunks.json`, `meta.json`, `report.json`. ### Configurazione — `chunks/config.py` Modifica i parametri nella classe `ChunkerConfig` se necessario: | Parametro | Default | Descrizione | |-----------|---------|-------------| | `max_chars` | `1200` | Dimensione massima di un chunk (caratteri) | | `min_chars` | `80` | Soglia minima; chunk piu' piccoli vengono accorpati | | `target_chars` | `800` | Dimensione target per il packing greedy | | `context_depth` | `3` | Livelli di heading inclusi come prefisso nel testo per l'embedding | | `skip_headings` | vedi file | Sezioni saltate completamente (indice, sommario, ecc.) | | `skip_pre_heading` | `True` | Salta il contenuto prima del primo heading (frontespizi, ecc.) | | `atomic_types` | `table, code, list, math, html` | Tipi di blocco che non vengono mai spezzati | --- ## Passo 3 — Vettorizzazione (ingest) Genera gli embedding dei chunk e li indicizza in ChromaDB. Richiede Ollama attivo. ```bash # Avvia Ollama (se non e' gia' in esecuzione) ollama serve # Installa il modello di embedding (una volta sola) ollama pull bge-m3 # Indicizza un singolo documento python ingestion/ingest.py --stem # Indicizza piu' documenti in una collection condivisa (RAG cross-documento) python ingestion/ingest.py --collection --stems doc1 doc2 doc3 # Rigenera dopo aver aggiornato i chunk o cambiato modello embedding python ingestion/ingest.py --stem --force ``` ### Configurazione — `ingestion/config.py` | Parametro | Default | Descrizione | |-----------|---------|-------------| | `EMBED_MODEL` | `bge-m3` | Modello Ollama per gli embedding. Se lo cambi, riesegui l'ingest con `--force` | | `EMBED_MAX_CHARS` | `6000` | Caratteri massimi inviati al modello per ogni chunk | | `OLLAMA_URL` | `http://localhost:11434` | URL del server Ollama | --- ## Passo 4 — Interrogazione ### Da terminale ```bash # Installa il modello LLM (una volta sola) ollama pull qwen3.5:4b # RAG interattivo — retrieval + risposta generata dal modello python rag/rag.py --collection python rag/rag.py --stem # scorciatoia per collection a documento singolo # Retrieval puro senza LLM (utile per debug) python rag/retrieve.py --collection python rag/retrieve.py --stem --top-k 10 ``` Nel loop interattivo: digita la domanda e premi Invio. Aggiungi `-v` alla fine per vedere i chunk recuperati con i relativi score di similarita'. ### Con la GUI desktop ```bash python gui/main.py python gui/main.py --collection # apre direttamente la collection ``` La GUI mostra le risposte con Markdown e formule matematiche renderizzate. Il tema chiaro/scuro e' selezionabile dal pulsante in alto a destra. ### Configurazione — `rag/config.py` | Parametro | Default | Descrizione | |-----------|---------|-------------| | `OLLAMA_MODEL` | `qwen3.5:4b` | Modello LLM per la generazione | | `TOP_K` | `6` | Numero di chunk recuperati per ogni domanda | | `TEMPERATURE` | `0.2` | 0 = deterministico, valori alti = piu' creativo | | `NO_THINK` | `True` | Disabilita il reasoning interno (piu' veloce; solo modelli Qwen3) | | `OLLAMA_URL` | `http://localhost:11434` | URL del server Ollama | | `SYSTEM_PROMPT` | vedi file | Istruzioni di sistema inviate al modello prima del contesto | --- ## Struttura del repository ``` rag/ ├── sources/ ← output MinerU (una cartella per documento) ├── chunks/ │ ├── chunker.py ← orchestrazione pipeline AST │ ├── config.py ← parametri di chunking │ ├── parser.py ← markdown-it-py + dollarmath │ ├── segmenter.py ← token stream -> Block[] │ ├── packer.py ← Block[] -> Chunk[] │ └── validator.py ← invarianti e metriche ├── ingestion/ │ ├── ingest.py ← embedding -> ChromaDB │ └── config.py ← parametri embedding ├── rag/ │ ├── rag.py ← loop RAG interattivo │ ├── retrieve.py ← retrieval puro (debug) │ └── config.py ← parametri RAG e system prompt ├── gui/ │ ├── main.py ← entry point GUI │ ├── chat_window.py ← finestra principale PySide6 │ ├── worker.py ← thread per il pipeline RAG │ └── chat.html ← template HTML (Markdown + KaTeX) ├── chroma_db/ ← database vettoriale (non tracciato da git) ├── mineru.ipynb ← notebook Colab per conversione PDF └── requirements.txt ```