davide e88043baee docs: aggiunge notebook MinerU per Colab e configurazione rapida nel README
- Passo 1 ora presenta opzione Colab (mineru.ipynb) e installazione locale
- Notebook adattato per uso reale: variabile PDF_NAME, path dinamico, gestione pagine
- Nuova sezione "Configurazione rapida" con parametri chunking e RAG

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 11:45:13 +02:00

RAG su documenti accademici

Pipeline completa per trasformare PDF accademici in un sistema RAG interrogabile in linguaggio naturale.

Stack: Python · MinerU · ChromaDB · Ollama
Chunking: rule-based, deterministico, senza LLM
Embedding + Generazione: Ollama (locale)


Prerequisiti

1. MinerU — conversione PDF → Markdown strutturato

MinerU è il tool esterno che converte i PDF in Markdown + metadati strutturati.
Repository: 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 per Docker

Installazione

pip (raccomandato):

pip install mineru[all]
# oppure con uv (più veloce):
uv pip install -U "mineru[all]"

conda:

conda create -n mineru python=3.11
conda activate mineru
pip install mineru[all]

Docker (Linux / WSL2 only):

# CPU
docker run --rm -v /percorso/locale:/data opendatalab/mineru mineru -p /data/doc.pdf -o /data/output

# GPU (richiede nvidia-container-toolkit)
docker run --rm --gpus all -v /percorso/locale:/data opendatalab/mineru:cuda mineru -p /data/doc.pdf -o /data/output

Al primo avvio MinerU scarica automaticamente i modelli (~15 GB). Per forzare il download manuale:

mineru-models-download

Uso — CLI

mineru -p <input> -o <output_dir> [opzioni]
Flag Descrizione Default
-p Percorso PDF, cartella di PDF, o URL
-o Cartella di output
-b Backend di conversione (vedi sotto) pipeline
-m Metodo di estrazione: auto, txt, ocr auto
-l Lingua per OCR (es. it, en, zh) en
-s Pagina iniziale (0-based) 0
-e Pagina finale (0-based, inclusa) ultima
--formula Attiva/disattiva riconoscimento formule config
--table Attiva/disattiva riconoscimento tabelle config

Esempio tipico:

mineru -p articolo.pdf -o sources/articolo/
# Output in sources/articolo/auto/

Per estrarre solo alcune pagine:

mineru -p documento.pdf -o sources/documento/ -s 10 -e 50

Backend di conversione

Backend Descrizione Accuratezza GPU richiesta
pipeline CPU-only, modelli leggeri ~86% No
hybrid-auto-engine Combina pipeline + VLM selettivo ~95%+ Raccomandato
vlm-auto-engine VLM completo su tutte le pagine massima
pipeline-http / vlm-http Come sopra ma via API remota Remota
# Raccomandato se si ha una GPU con ≥ 4 GB VRAM
mineru -p doc.pdf -o output/ -b hybrid-auto-engine

# Solo CPU
mineru -p doc.pdf -o output/ -b pipeline

Configurazione avanzata

File di configurazione utente: ~/mineru.json

Variabili d'ambiente principali:

Variabile Valori Effetto
MINERU_FORMULA_ENABLE 0 / 1 Abilita/disabilita OCR formule matematiche
MINERU_TABLE_ENABLE 0 / 1 Abilita/disabilita riconoscimento tabelle
MINERU_DEVICE cpu, cuda, mps Forza il dispositivo di inferenza

Output di MinerU

MinerU produce per ogni PDF una cartella con questa struttura:

<stem>/
└── auto/
    ├── <stem>.md                       ← Markdown grezzo (non usato dalla pipeline)
    ├── <stem>_content_list_v2.json     ← struttura ricca: tipo, livello, bbox  [RICHIESTO]
    ├── <stem>_model.json               ← label di layout: doc_title, abstract… [RACCOMANDATO]
    ├── <stem>_middle.json              ← dati intermedi (non usato)
    ├── <stem>_content_list.json        ← formato v1 flat (non usato)
    ├── <stem>_layout.pdf               ← PDF annotato con i bounding box
    ├── <stem>_span.pdf                 ← PDF con span di testo evidenziati
    └── images/                         ← immagini estratte

<stem> è il nome del documento, usato come identificatore in tutta la pipeline.

I file usati dalla pipeline di questo repository sono:

  • _content_list_v2.json ← struttura ad albero: ogni blocco ha type (title/paragraph/table/list/image), level (per i titoli: 1=capitolo, 2=sezione), content con il testo, e bbox per la posizione sulla pagina.
  • _model.json ← label semantiche per bounding box: doc_title, paragraph_title, abstract, header, number, text, aside_text… Usato per arricchire la classificazione del testo (es. riconoscere SOMMARIO interni, prefissi di capitolo).

Limitazioni note di MinerU

  • Testo verticale (CJK ruotato, riviste asiatiche): qualità ridotta.
  • Testo scritto a mano: accuratezza molto bassa.
  • Fumetti e album d'arte: non supportati.
  • Blocchi di codice: non sempre preservati correttamente (indentazione può essere persa).
  • PDF scansionati senza OCR: richiede backend con OCR attivo (-m ocr).
  • Formule matematiche complesse: dipendono dalla qualità del rendering PDF originale.

2. Ollama — embedding e generazione

Scarica e avvia Ollama, poi installa i modelli:

ollama pull nomic-embed-text    # embedding (obbligatorio)
ollama pull qwen3.5:4b          # generazione LLM (o altro modello)

Setup

git clone <questo-repo>
cd rag

python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -r requirements.txt

Configurazione rapida

Prima di usare la pipeline verifica i parametri nei due file di configurazione:

chunks/config.py — parametri di chunking:

Parametro Default Quando cambiarlo
MAX_CHARS 1200 Aumenta per chunk più lunghi (es. testi narrativi)
MIN_CHARS 80 Abbassa se hai molti titoli brevi
FRONTMATTER_HEADINGS set italiano Svuota (set()) per documenti non italiani
OVERLAP_SENTENCES 1 Aumenta a 2 se il retrieval perde contesto tra chunk

config.py — parametri RAG:

Parametro Default Quando cambiarlo
OLLAMA_MODEL qwen3.5:4b Sostituisci con il modello Ollama che vuoi usare
EMBED_MODEL nomic-embed-text Deve corrispondere al modello usato in ingestion
TOP_K 6 Aumenta per recuperare più contesto per domanda
OLLAMA_URL localhost:11434 Cambia se Ollama gira su un altro host/porta

Se cambi EMBED_MODEL devi rieseguire l'ingestion con --force.


Flusso completo

PDF
 │
 ▼ (MinerU — esterno)
sources/<stem>/auto/
 │
 ▼  python chunks/chunker.py --stem <stem>
 │   Stage 1: _content_list_v2.json + _model.json → <stem>_clean.md
 │   Stage 2: <stem>_clean.md → chunks/<stem>/chunks.json
 │
 ▼  python chunks/verify_chunks.py --stem <stem>
 │   Verifica qualità chunk
 │
 ▼  python chunks/fix_chunks.py --stem <stem>     [se necessario]
 │   Correzioni automatiche
 │
 ▼  python ingestion/ingest.py --stem <stem>
 │   Embedding → ChromaDB
 │
 ▼  python rag.py --stem <stem>
    Interrogazione in linguaggio naturale

Passo 1 — Converti il PDF con MinerU

Per questo step è necessario MinerU (github.com/opendatalab/MinerU).

Opzione A — Google Colab (consigliata, nessuna installazione locale)

Usa il notebook incluso in questo repository:

mineru_demo.ipynb

Aprilo su Google Colab, carica il tuo PDF nella sessione e segui le celle in ordine. Al termine scarica la cartella output/<stem>/ generata.

Opzione B — Installazione locale

pip install "mineru[all]"
mineru -p documento.pdf -o sources/<stem>/

Al termine, copia la cartella completa prodotta da MinerU dentro sources/. La struttura attesa è:

sources/
└── <stem>/
    └── auto/
        ├── <stem>.md
        ├── <stem>_content_list_v2.json     ← richiesto
        ├── <stem>_model.json               ← raccomandato
        ├── <stem>_middle.json
        ├── <stem>_content_list.json
        ├── <stem>_layout.pdf
        ├── <stem>_span.pdf
        └── images/

Passo 2 — Chunking (Stage 1 + Stage 2)

Un singolo comando esegue entrambe le fasi:

# Singolo documento
.venv/bin/python chunks/chunker.py --stem <stem>

# Tutti i documenti in sources/
.venv/bin/python chunks/chunker.py

# Rigenera tutto da zero
.venv/bin/python chunks/chunker.py --stem <stem> --force

# Salta Stage 1 (usa il _clean.md già presente)
.venv/bin/python chunks/chunker.py --stem <stem> --skip-optimize

Stage 1 — Ottimizzazione Markdown (md_optimizer.py interno):

  • Legge _content_list_v2.json e _model.json
  • Riconosce la gerarchia heading (H1 = capitolo, H2 = sezione, H3 = sottosezione)
  • Fonde titoli di capitolo multipli: CAPITOLO 2 + Il titolo# CAPITOLO 2 — Il titolo
  • Rimuove TOC, frontespizio, copyright, indici e sommari interni
  • Produce sources/<stem>/auto/<stem>_clean.md

Stage 2 — Chunking semantico:

  • Un chunk per paragrafo — mai due paragrafi nello stesso chunk
  • Split a confine di frase se il paragrafo supera MAX_CHARS (default 1200 chars)
  • Overlap di 1 frase tra chunk consecutivi per continuità contestuale
  • Tabelle e liste sono blocchi atomici (non vengono mai spezzati)
  • Ogni chunk include un prefisso [Sezione > Titolo] per il retrieval

Output in chunks/<stem>/:

File Contenuto
chunks.json Lista di chunk con testo, sezione, titolo, n_chars
meta.json Parametri usati (max_chars, overlap, strategia)
report.json Statistiche e anomalie (generato da verify)

Passo 3 — Verifica i chunk

.venv/bin/python chunks/verify_chunks.py --stem <stem>
Verdict Significato Cosa fare
ok Nessun problema Procedi alla vettorizzazione
warnings_only Solo avvisi minori (frasi lunghe) Puoi procedere
blocked Chunk incompleti o senza prefisso Esegui il fix

Passo 4 — Correggi i problemi (se verdict = blocked)

# Anteprima senza applicare
.venv/bin/python chunks/fix_chunks.py --stem <stem> --dry-run

# Applica le correzioni
.venv/bin/python chunks/fix_chunks.py --stem <stem>

Il fix risolve automaticamente: chunk incompleti (frase spezzata), chunk troppo corti (accorpati al successivo), chunk troppo lunghi (spezzati su punteggiatura). Itera fino alla convergenza o per un massimo di FIX_MAX_ITERATIONS passate.

Passo 5 — Vettorizzazione

Verifica che Ollama sia attivo, poi:

# Singolo documento → collection omonima
.venv/bin/python ingestion/ingest.py --stem <stem>

# Più documenti → un'unica collection condivisa
.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 rilanciare l'ingestion con --force.

Passo 6 — Interrogazione

# RAG interattivo
.venv/bin/python rag.py --stem <stem>
.venv/bin/python rag.py --collection <nome>

# Retrieval puro (debug, senza generazione LLM)
.venv/bin/python retrieve.py --stem <stem>
.venv/bin/python retrieve.py --stem <stem> --top-k 10

Comandi nel loop interattivo di retrieve.py:

Input Effetto
<query> Chunk più simili con score (testo troncato)
<query> -f Testo completo dei chunk
exit Termina

Configurazione

Parametri di chunking — chunks/config.py

Parametro Default Descrizione
MAX_CHARS 1200 Lunghezza massima chunk in caratteri
MIN_CHARS 80 Soglia minima (warning sotto questa soglia)
OVERLAP_SENTENCES 1 Frasi di overlap tra chunk consecutivi
MIN_CONTENT_CHARS 2500 Soglia per distinguere frontespizio da contenuto reale
FRONTMATTER_HEADINGS set italiano Sezioni da rimuovere (Sommario, Indice, Autori…) — svuotare per documenti non italiani
SOMMARIO_PATTERNS 5 lingue Pattern regex per sommari interni da saltare
CHAPTER_PREFIX_PATTERNS 4 lingue Pattern per prefissi di capitolo come paragrafi separati
MODEL_SKIP_LABELS set Label _model.json da ignorare (header, number…)

Parametri RAG — config.py

Parametro Default Descrizione
OLLAMA_MODEL qwen3.5:4b Modello LLM per la generazione
EMBED_MODEL nomic-embed-text Modello embedding
TOP_K 6 Chunk recuperati per domanda
TEMPERATURE 0.2 Creatività del modello (0 = deterministico)
OLLAMA_URL localhost:11434 URL server Ollama

Struttura del repository

rag/
├── sources/                    ← output di MinerU (una cartella per documento)
│   └── <stem>/auto/
├── chunks/
│   ├── chunker.py              ← pipeline unificata (Stage 1 + Stage 2)
│   ├── md_optimizer.py         ← Stage 1: JSON MinerU → _clean.md
│   ├── config.py               ← tutti i parametri di chunking
│   ├── verify_chunks.py        ← verifica qualità
│   └── fix_chunks.py           ← correzioni automatiche
├── ingestion/
│   └── ingest.py               ← embedding → ChromaDB
├── ollama/
│   └── check_env.py            ← verifica ambiente Ollama
├── chroma_db/                  ← database vettoriale (ignorato da git)
├── config.py                   ← parametri RAG (modelli, TOP_K, prompt)
├── rag.py                      ← loop RAG interattivo
└── retrieve.py                 ← retrieval puro (debug)

Note su generalità e adattamento

La pipeline è progettata per funzionare con qualsiasi output MinerU, non solo per documenti in italiano. MinerU produce sempre la stessa struttura di file (_content_list_v2.json, _model.json) indipendentemente dal documento sorgente.

Per adattare a documenti in altre lingue o con struttura diversa, modificare in chunks/config.py:

  • FRONTMATTER_HEADINGS: impostare set() per disabilitare, o aggiungere i nomi delle sezioni da rimuovere nella lingua del documento
  • SOMMARIO_PATTERNS: aggiungere/rimuovere pattern regex per i sommari interni
  • CHAPTER_PREFIX_PATTERNS: aggiungere pattern per la lingua del documento
  • MIN_CONTENT_CHARS: alzare se il documento ha lunghe sezioni di copyright/prefazione da escludere
  • MIN_TOC_HEADINGS: abbassare se il documento ha pochi capitoli (es. 3-4)
S
Description
No description provided
Readme 358 KiB
Languages
Python 81.3%
Jupyter Notebook 11.7%
HTML 7%