2026-06-09 10:04:38 +02:00
# RAG from scratch
2026-04-12 23:53:13 +02:00
2026-06-09 10:04:38 +02:00
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.
2026-04-12 23:53:13 +02:00
2026-05-20 16:08:00 +02:00
**Stack: ** Python · MinerU · ChromaDB · Ollama
2026-06-09 10:04:38 +02:00
**Chunking: ** AST-based, deterministico, senza LLM
**Embedding + Generazione: ** Ollama (locale, nessun dato inviato a server esterni)
2026-04-12 23:53:13 +02:00
```
2026-06-09 10:04:38 +02:00
PDF
└─ MinerU ──► sources/<stem>.md
└─ chunker.py ──► chunks/<stem>/chunks.json
└─ ingest.py ──► chroma_db/
└─ rag.py / GUI
2026-04-15 13:33:56 +02:00
```
2026-04-12 23:53:13 +02:00
2026-06-09 10:04:38 +02:00
---
2026-05-11 15:46:52 +02:00
2026-06-09 10:04:38 +02:00
## Requisiti minimi
2026-05-20 16:08:00 +02:00
2026-06-09 10:04:38 +02:00
| 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) | |
2026-05-11 15:46:52 +02:00
2026-06-09 10:04:38 +02:00
**Dipendenze Python ** — installate via `requirements.txt` :
2026-05-20 16:08:00 +02:00
2026-06-09 10:04:38 +02:00
- `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)
2026-05-20 16:08:00 +02:00
2026-06-09 10:04:38 +02:00
**Dipendenze esterne: **
2026-05-11 16:05:23 +02:00
2026-06-09 10:04:38 +02:00
- [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` )
2026-05-11 16:05:23 +02:00
2026-05-20 16:08:00 +02:00
---
2026-05-12 11:21:24 +02:00
2026-06-09 10:04:38 +02:00
## Setup ambiente
2026-05-20 16:08:00 +02:00
``` bash
2026-06-09 10:04:38 +02:00
# Crea e attiva il virtual environment
2026-05-20 16:08:00 +02:00
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
2026-06-09 10:04:38 +02:00
# Installa le dipendenze
pip install -r requirements.txt
2026-05-20 16:08:00 +02:00
```
2026-05-11 16:05:23 +02:00
2026-05-11 14:44:16 +02:00
---
2026-04-12 23:53:13 +02:00
2026-06-09 10:04:38 +02:00
## Passo 1 — Converti i PDF con MinerU
2026-04-12 23:53:13 +02:00
2026-06-09 10:04:38 +02:00
MinerU estrae il testo dai PDF producendo un Markdown strutturato con tabelle, formule ed immagini. Ci sono due modalità:
2026-05-28 15:48:08 +02:00
2026-06-09 10:04:38 +02:00
### Opzione A — Google Colab (consigliata, usa la GPU gratuita di Google)
2026-05-28 15:48:08 +02:00
2026-06-09 10:04:38 +02:00
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.
2026-05-28 15:48:08 +02:00
2026-06-09 10:04:38 +02:00
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:
2026-04-17 18:51:09 +02:00
2026-05-20 16:08:00 +02:00
```
sources/
2026-06-04 14:29:06 +02:00
└── <stem>_output/
2026-05-20 16:08:00 +02:00
└── auto/
2026-06-09 10:04:38 +02:00
├── <stem>.md ← questo è l'input della pipeline
2026-05-28 15:48:08 +02:00
└── images/
2026-05-11 15:46:52 +02:00
```
2026-06-09 10:04:38 +02:00
### Opzione B — Installazione locale
2026-06-04 14:29:06 +02:00
``` bash
2026-06-09 10:04:38 +02:00
pip install "mineru[all]" # installa MinerU (scarica ~15 GB di modelli al primo avvio)
2026-06-04 14:29:06 +02:00
mineru -p documento.pdf -o sources/<stem>_output/ -b pipeline
2026-06-09 10:04:38 +02:00
# Con GPU:
mineru -p documento.pdf -o sources/<stem>_output/ -b hybrid-auto-engine
2026-06-04 14:29:06 +02:00
```
---
2026-04-12 23:53:13 +02:00
2026-06-04 14:29:06 +02:00
## Passo 2 — Chunking
2026-05-20 16:08:00 +02:00
2026-06-09 10:04:38 +02:00
Il chunker analizza il Markdown con un parser AST, divide il testo in blocchi semantici e li impacchetta in chunk ottimizzati per il retrieval.
2026-05-20 16:08:00 +02:00
``` bash
# Singolo documento
2026-06-09 10:04:38 +02:00
python chunks/chunker.py --stem <stem>
2026-04-12 23:53:13 +02:00
2026-06-09 10:04:38 +02:00
# Tutti i documenti presenti in sources/ in una volta
python chunks/chunker.py
2026-05-12 10:39:27 +02:00
2026-06-04 14:29:06 +02:00
# Rigenera anche se chunks.json esiste già
2026-06-09 10:04:38 +02:00
python chunks/chunker.py --stem <stem> --force
2026-05-12 10:39:27 +02:00
```
2026-06-09 10:04:38 +02:00
Output in `chunks/<stem>/` : `chunks.json` , `meta.json` , `report.json` .
2026-06-04 14:29:06 +02:00
2026-06-09 10:04:38 +02:00
### Configurazione — `chunks/config.py`
2026-05-20 16:08:00 +02:00
2026-06-09 10:04:38 +02:00
Modifica i parametri nella classe `ChunkerConfig` se necessario:
2026-05-12 10:39:27 +02:00
2026-06-09 10:04:38 +02:00
| 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 |
2026-05-12 10:39:27 +02:00
2026-06-04 14:29:06 +02:00
---
2026-05-20 16:08:00 +02:00
2026-06-09 10:04:38 +02:00
## Passo 3 — Vettorizzazione (ingest)
2026-05-12 10:39:27 +02:00
2026-06-09 10:04:38 +02:00
Genera gli embedding dei chunk e li indicizza in ChromaDB. Richiede Ollama attivo.
2026-05-20 16:08:00 +02:00
2026-06-09 10:04:38 +02:00
``` bash
# Avvia Ollama (se non e' gia' in esecuzione)
ollama serve
2026-05-20 16:08:00 +02:00
2026-06-09 10:04:38 +02:00
# Installa il modello di embedding (una volta sola)
ollama pull bge-m3
2026-05-12 10:39:27 +02:00
2026-06-09 10:04:38 +02:00
# Indicizza un singolo documento
python ingestion/ingest.py --stem <stem>
2026-05-20 16:08:00 +02:00
2026-06-09 10:04:38 +02:00
# Indicizza piu' documenti in una collection condivisa (RAG cross-documento)
python ingestion/ingest.py --collection <nome> --stems doc1 doc2 doc3
2026-05-12 10:39:27 +02:00
2026-05-20 16:08:00 +02:00
# Rigenera dopo aver aggiornato i chunk o cambiato modello embedding
2026-06-09 10:04:38 +02:00
python ingestion/ingest.py --stem <stem> --force
2026-05-20 16:08:00 +02:00
```
2026-06-09 10:04:38 +02:00
### 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 |
2026-05-20 16:08:00 +02:00
2026-06-04 14:29:06 +02:00
---
2026-06-09 10:04:38 +02:00
## Passo 4 — Interrogazione
### Da terminale
2026-05-20 16:08:00 +02:00
``` bash
2026-06-09 10:04:38 +02:00
# Installa il modello LLM (una volta sola)
ollama pull qwen3.5:4b
2026-05-12 11:21:24 +02:00
2026-06-09 10:04:38 +02:00
# RAG interattivo — retrieval + risposta generata dal modello
python rag/rag.py --collection <nome>
python rag/rag.py --stem <stem> # scorciatoia per collection a documento singolo
# Retrieval puro senza LLM (utile per debug)
python rag/retrieve.py --collection <nome>
python rag/retrieve.py --stem <stem> --top-k 10
2026-05-12 10:39:27 +02:00
```
2026-06-09 10:04:38 +02:00
Nel loop interattivo: digita la domanda e premi Invio. Aggiungi `-v` alla fine per vedere i chunk recuperati con i relativi score di similarita'.
2026-05-12 10:39:27 +02:00
2026-06-09 10:04:38 +02:00
### Con la GUI desktop
2026-05-12 10:39:27 +02:00
2026-06-09 10:04:38 +02:00
``` bash
python gui/main.py
python gui/main.py --collection <nome> # apre direttamente la collection
```
2026-05-12 10:39:27 +02:00
2026-06-09 10:04:38 +02:00
La GUI mostra le risposte con Markdown e formule matematiche renderizzate. Il tema chiaro/scuro e' selezionabile dal pulsante in alto a destra.
2026-05-12 11:21:24 +02:00
2026-06-09 10:04:38 +02:00
### Configurazione — `rag/config.py`
2026-05-12 10:39:27 +02:00
2026-05-20 16:08:00 +02:00
| Parametro | Default | Descrizione |
|-----------|---------|-------------|
| `OLLAMA_MODEL` | `qwen3.5:4b` | Modello LLM per la generazione |
2026-06-09 10:04:38 +02:00
| `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 |
2026-05-12 10:39:27 +02:00
---
2026-05-20 16:08:00 +02:00
## Struttura del repository
2026-04-12 23:53:13 +02:00
2026-05-20 16:08:00 +02:00
```
rag/
2026-06-09 10:04:38 +02:00
├── sources/ ← output MinerU (una cartella per documento)
2026-05-20 16:08:00 +02:00
├── chunks/
2026-06-09 10:04:38 +02:00
│ ├── 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
2026-05-20 16:08:00 +02:00
├── ingestion/
2026-06-09 10:04:38 +02:00
│ ├── 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
2026-04-12 23:53:13 +02:00
```