Files
davide 8b14420d9e docs: README dettagliato MinerU + CLAUDE.md aggiornato per pipeline Stage 1+2
README.md: sezione MinerU espansa con requisiti di sistema, installazione
(pip/conda/Docker), CLI completo con tutti i flag, tabella comparativa
backend (pipeline/hybrid-auto-engine/vlm-auto-engine), configurazione
avanzata, struttura output e limitazioni note.

CLAUDE.md: aggiornato diagramma pipeline, input RICHIESTO/RACCOMANDATO,
comandi con --force e --skip-optimize, architettura chunker/md_optimizer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 16:08:00 +02:00

384 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](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):**
```bash
pip install mineru[all]
# oppure con uv (più veloce):
uv pip install -U "mineru[all]"
```
**conda:**
```bash
conda create -n mineru python=3.11
conda activate mineru
pip install mineru[all]
```
**Docker (Linux / WSL2 only):**
```bash
# 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:
```bash
mineru-models-download
```
#### Uso — CLI
```bash
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:**
```bash
mineru -p articolo.pdf -o sources/articolo/
# Output in sources/articolo/auto/
```
Per estrarre solo alcune pagine:
```bash
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 |
```bash
# 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](https://ollama.com), poi installa i modelli:
```bash
ollama pull nomic-embed-text # embedding (obbligatorio)
ollama pull qwen3.5:4b # generazione LLM (o altro modello)
```
---
## Setup
```bash
git clone <questo-repo>
cd rag
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
```
---
## 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
Usa MinerU per convertire il PDF e posiziona la cartella di output in `sources/`:
```
sources/
└── <stem>/
└── auto/
├── <stem>_content_list_v2.json
├── <stem>_model.json
└── ...
```
### Passo 2 — Chunking (Stage 1 + Stage 2)
Un singolo comando esegue entrambe le fasi:
```bash
# 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
```bash
.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`)
```bash
# 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:
```bash
# 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
```bash
# 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)