docs: riscrive README con struttura passo-passo

Abstract, requisiti minimi, setup venv, MinerU (Colab + locale),
chunking, ingest, interrogazione (terminale + GUI) con tabelle
parametri per ogni config.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 10:04:38 +02:00
parent ba8035887c
commit b4e64ccedb
+154 -197
View File
@@ -1,245 +1,192 @@
# RAG su documenti accademici # RAG from scratch
Pipeline completa per trasformare PDF accademici in un sistema RAG interrogabile in linguaggio naturale. 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 **Stack:** Python · MinerU · ChromaDB · Ollama
**Chunking:** rule-based, deterministico, senza LLM **Chunking:** AST-based, deterministico, senza LLM
**Embedding + Generazione:** Ollama (locale) **Embedding + Generazione:** Ollama (locale, nessun dato inviato a server esterni)
---
## Prerequisiti
### 1. MinerU — conversione PDF → Markdown
MinerU è il tool esterno che converte i PDF in Markdown strutturato.
Repository: [https://github.com/opendatalab/MinerU](https://github.com/opendatalab/MinerU)
#### Requisiti di sistema
| Risorsa | Minimo | Raccomandato |
|---------|--------|--------------|
| Python | 3.103.13 | 3.11 |
| RAM | 16 GB | 32 GB+ |
| Disco | 20 GB (modelli inclusi) | 40 GB+ |
| GPU | opzionale | 48 GB VRAM (CUDA) |
| OS | Linux, macOS, Windows | Linux / WSL2 |
#### Installazione
```bash
pip install "mineru[all]"
```
Al primo avvio scarica automaticamente i modelli (~15 GB).
#### Uso — CLI
```bash
mineru -p <documento.pdf> -o <output_dir> -b pipeline
```
| Flag | Descrizione |
|------|-------------|
| `-p` | Percorso PDF o cartella |
| `-o` | Cartella di output |
| `-b` | Backend: `pipeline` (CPU), `hybrid-auto-engine` (GPU raccomandato) |
| `-m` | Metodo: `auto`, `txt`, `ocr` |
#### Output di MinerU
``` ```
<stem>_output/ PDF
└── auto/ └─ MinerU ──► sources/<stem>.md
├── <stem>.md ← Markdown (input della pipeline) └─ chunker.py ──► chunks/<stem>/chunks.json
├── <stem>_content_list_v2.json └─ ingest.py ──► chroma_db/
├── <stem>_model.json └─ rag.py / GUI
└── images/
```
Il file usato dalla pipeline è **`<stem>.md`** nella cartella `auto/`.
### 2. Ollama — embedding e generazione
Scarica e avvia [Ollama](https://ollama.com), poi installa i modelli:
```bash
ollama pull qwen3-embedding:0.6b # embedding (obbligatorio)
ollama pull qwen3.5:4b # generazione LLM (o altro modello)
``` ```
--- ---
## Setup ## 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 ```bash
git clone <questo-repo> # Crea e attiva il virtual environment
cd rag
python -m venv .venv python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate source .venv/bin/activate # Windows: .venv\Scripts\activate
# Installa le dipendenze
pip install -r requirements.txt pip install -r requirements.txt
``` ```
--- ---
## Flusso completo ## 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à:
PDF
▼ MinerU (esterno — vedi sotto)
sources/<stem>_output/auto/<stem>.md
▼ python chunks/chunker.py --stem <stem>
chunks/<stem>/chunks.json
▼ python chunks/verify_chunks.py --stem <stem>
(verifica qualità — opzionale ma consigliato)
▼ python ingestion/ingest.py --stem <stem>
chroma_db/<stem>/
▼ python rag.py --stem <stem>
Interrogazione in linguaggio naturale
```
--- ### Opzione A — Google Colab (consigliata, usa la GPU gratuita di Google)
## Passo 1 — Converti il PDF con MinerU 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.
**Opzione A — Google Colab (nessuna installazione locale)** 1. Apri `mineru.ipynb` su Google Colab
2. Carica i PDF nella cartella `input/` indicata nelle celle
Usa il notebook incluso: 3. Esegui tutte le celle — al termine viene scaricato uno ZIP per ogni documento
4. Decomprimi gli ZIP nella cartella `sources/` del progetto:
```
mineru.ipynb
```
Aprilo su Google Colab, carica il PDF e segui le celle. Al termine scarica automaticamente lo ZIP con l'output. Decomprimi la cartella in `sources/`:
``` ```
sources/ sources/
└── <stem>_output/ └── <stem>_output/
└── auto/ └── auto/
├── <stem>.md ← usato dalla pipeline ├── <stem>.md ← questo è l'input della pipeline
├── <stem>_content_list_v2.json
├── <stem>_model.json
└── images/ └── images/
``` ```
**Opzione B — Installazione locale** ### Opzione B — Installazione locale
```bash ```bash
pip install "mineru[all]" # installa MinerU (scarica ~15 GB di modelli al primo avvio)
mineru -p documento.pdf -o sources/<stem>_output/ -b pipeline mineru -p documento.pdf -o sources/<stem>_output/ -b pipeline
# Con GPU:
mineru -p documento.pdf -o sources/<stem>_output/ -b hybrid-auto-engine
``` ```
--- ---
## Passo 2 — Chunking ## 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 ```bash
# Singolo documento # Singolo documento
.venv/bin/python chunks/chunker.py --stem <stem> python chunks/chunker.py --stem <stem>
# Tutti i documenti in sources/ # Tutti i documenti presenti in sources/ in una volta
.venv/bin/python chunks/chunker.py python chunks/chunker.py
# Rigenera anche se chunks.json esiste già # Rigenera anche se chunks.json esiste già
.venv/bin/python chunks/chunker.py --stem <stem> --force python chunks/chunker.py --stem <stem> --force
``` ```
Il chunker legge `sources/<stem>_output/auto/<stem>.md` e produce `chunks/<stem>/chunks.json`. Output in `chunks/<stem>/`: `chunks.json`, `meta.json`, `report.json`.
**Regole applicate:** ### Configurazione — `chunks/config.py`
- 1 paragrafo = 1 chunk; paragrafi di sezioni diverse non si mescolano Modifica i parametri nella classe `ChunkerConfig` se necessario:
- Split a confine di frase se il paragrafo supera `MAX_CHARS` (mai a metà frase)
- Frasi spezzate tra paragrafi consecutivi vengono ri-fuse automaticamente
- Paragrafi brevi (< `MIN_CHARS`) vengono accorpati al successivo (stesso contesto)
- Sezioni configurabili vengono saltate (indice, sommario, frontespizio)
- Tabelle, liste e code block sono blocchi atomici (non si spezzano)
Output in `chunks/<stem>/`:
| File | Contenuto |
|------|-----------|
| `chunks.json` | Chunk con testo, sezione, titolo, n_chars |
| `meta.json` | Parametri usati |
| `report.json` | Statistiche e anomalie (generato da verify) |
---
## Passo 3 — Verifica i chunk (opzionale)
```bash
.venv/bin/python chunks/verify_chunks.py --stem <stem>
```
| Verdict | Significato |
|---------|-------------|
| `ok` | Nessun problema — procedi |
| `warnings_only` | Solo avvisi minori — puoi procedere |
| `blocked` | Problemi strutturali — rivedi il sorgente `.md` |
---
## Passo 4 — Vettorizzazione
```bash
# Singolo documento → collection omonima
.venv/bin/python ingestion/ingest.py --stem <stem>
# Più documenti → collection condivisa (RAG cross-documento)
.venv/bin/python ingestion/ingest.py --collection <nome> --stems doc1 doc2 doc3
# Rigenera dopo aver aggiornato i chunk o cambiato modello embedding
.venv/bin/python ingestion/ingest.py --stem <stem> --force
```
> Se cambi `EMBED_MODEL` in `config.py` devi rieseguire l'ingestion con `--force`.
---
## Passo 5 — Interrogazione
```bash
# RAG interattivo (retrieval + generazione LLM)
.venv/bin/python rag.py --stem <stem>
.venv/bin/python rag.py --collection <nome>
# Retrieval puro (debug, senza LLM)
.venv/bin/python retrieve.py --stem <stem>
.venv/bin/python retrieve.py --stem <stem> --top-k 10
```
---
## Configurazione
### Chunking — `chunks/config.py`
| Parametro | Default | Descrizione | | Parametro | Default | Descrizione |
|-----------|---------|-------------| |-----------|---------|-------------|
| `MAX_CHARS` | 1200 | Lunghezza massima chunk (caratteri) | | `max_chars` | `1200` | Dimensione massima di un chunk (caratteri) |
| `MIN_CHARS` | 80 | Soglia minima (warning sotto questa soglia) | | `min_chars` | `80` | Soglia minima; chunk piu' piccoli vengono accorpati |
| `CONTEXT_DEPTH` | 3 | Livelli heading inclusi nel prefisso chunk (13) | | `target_chars` | `800` | Dimensione target per il packing greedy |
| `SKIP_HEADINGS` | set italiano | Sezioni saltate completamente (indice, sommario…) | | `context_depth` | `3` | Livelli di heading inclusi come prefisso nel testo per l'embedding |
| `SKIP_PRE_HEADING` | `True` | Salta contenuto prima del primo heading (frontespizio) | | `skip_headings` | vedi file | Sezioni saltate completamente (indice, sommario, ecc.) |
| `MERGE_SHORT_PARAGRAPHS` | `True` | Accorpa paragrafi brevi fino a `MIN_CHARS` | | `skip_pre_heading` | `True` | Salta il contenuto prima del primo heading (frontespizi, ecc.) |
| `SENTENCE_SPLIT_RE` | regex | Confine di fine frase per lo split | | `atomic_types` | `table, code, list, math, html` | Tipi di blocco che non vengono mai spezzati |
| `ATOMIC_TYPES` | `table, code, list` | Blocchi mai spezzati |
### RAG — `config.py` ---
## 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 <stem>
# Indicizza piu' documenti in una collection condivisa (RAG cross-documento)
python ingestion/ingest.py --collection <nome> --stems doc1 doc2 doc3
# Rigenera dopo aver aggiornato i chunk o cambiato modello embedding
python ingestion/ingest.py --stem <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 <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
```
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 <nome> # 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 | | Parametro | Default | Descrizione |
|-----------|---------|-------------| |-----------|---------|-------------|
| `OLLAMA_MODEL` | `qwen3.5:4b` | Modello LLM per la generazione | | `OLLAMA_MODEL` | `qwen3.5:4b` | Modello LLM per la generazione |
| `EMBED_MODEL` | `qwen3-embedding:0.6b` | Modello embedding | | `TOP_K` | `6` | Numero di chunk recuperati per ogni domanda |
| `EMBED_MAX_CHARS` | 6000 | Caratteri massimi inviati al modello embedding | | `TEMPERATURE` | `0.2` | 0 = deterministico, valori alti = piu' creativo |
| `TOP_K` | 6 | Chunk recuperati per domanda | | `NO_THINK` | `True` | Disabilita il reasoning interno (piu' veloce; solo modelli Qwen3) |
| `TEMPERATURE` | 0.2 | Creatività del modello (0 = deterministico) | | `OLLAMA_URL` | `http://localhost:11434` | URL del server Ollama |
| `OLLAMA_URL` | `localhost:11434` | URL server Ollama | | `SYSTEM_PROMPT` | vedi file | Istruzioni di sistema inviate al modello prima del contesto |
--- ---
@@ -247,17 +194,27 @@ Output in `chunks/<stem>/`:
``` ```
rag/ rag/
├── sources/ ← output MinerU (una cartella per documento) ├── sources/ ← output MinerU (una cartella per documento)
│ └── <stem>_output/auto/
├── chunks/ ├── chunks/
│ ├── chunker.py ← chunking da MD pulito │ ├── chunker.py ← orchestrazione pipeline AST
│ ├── config.py ← parametri di chunking │ ├── config.py ← parametri di chunking
── verify_chunks.py ← verifica qualità chunk ── parser.py ← markdown-it-py + dollarmath
│ ├── segmenter.py ← token stream -> Block[]
│ ├── packer.py ← Block[] -> Chunk[]
│ └── validator.py ← invarianti e metriche
├── ingestion/ ├── ingestion/
── ingest.py ← embedding ChromaDB ── ingest.py ← embedding -> ChromaDB
├── chroma_db/ ← database vettoriale (ignorato da git) │ └── config.py ← parametri embedding
├── config.py ← parametri RAG (modelli, TOP_K, prompt) ├── rag/
├── rag.py ← loop RAG interattivo ├── rag.py ← loop RAG interattivo
├── retrieve.py ← retrieval puro (debug) ├── retrieve.py ← retrieval puro (debug)
└── mineru.ipynb ← notebook Colab per conversione PDF │ └── 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
``` ```