- 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>
15 KiB
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.10–3.13 | 3.11 |
| RAM | 16 GB | 32 GB+ |
| Disco | 20 GB (modelli inclusi) | 40 GB+ |
| GPU | opzionale | 4–8 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 | Sì |
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 hatype(title/paragraph/table/list/image),level(per i titoli: 1=capitolo, 2=sezione),contentcon il testo, ebboxper 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_MODELdevi 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.jsone_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_MODELinconfig.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: impostareset()per disabilitare, o aggiungere i nomi delle sezioni da rimuovere nella lingua del documentoSOMMARIO_PATTERNS: aggiungere/rimuovere pattern regex per i sommari interniCHAPTER_PREFIX_PATTERNS: aggiungere pattern per la lingua del documentoMIN_CONTENT_CHARS: alzare se il documento ha lunghe sezioni di copyright/prefazione da escludereMIN_TOC_HEADINGS: abbassare se il documento ha pochi capitoli (es. 3-4)