docs/fix: aggiorna README e fix ingest per embedding robusto
README riscritto per rispecchiare la pipeline attuale: - rimossi riferimenti a Stage 1, md_optimizer, fix_chunks - pipeline semplificata: MinerU .md → chunker → ingest → rag - tabelle parametri aggiornate (SKIP_HEADINGS, SKIP_PRE_HEADING, MERGE_SHORT_PARAGRAPHS, EMBED_MAX_CHARS al posto dei vecchi) - struttura repo corretta ingest.py: strip tag HTML ed entità prima dell'embedding per evitare HTTP 500 da Ollama su chunk con tabelle HTML grezze; aggiunto EMBED_MAX_CHARS (default 6000) in config.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,9 +10,9 @@ Pipeline completa per trasformare PDF accademici in un sistema RAG interrogabile
|
|||||||
|
|
||||||
## Prerequisiti
|
## Prerequisiti
|
||||||
|
|
||||||
### 1. MinerU — conversione PDF → Markdown strutturato
|
### 1. MinerU — conversione PDF → Markdown
|
||||||
|
|
||||||
MinerU è il tool esterno che converte i PDF in Markdown + metadati strutturati.
|
MinerU è il tool esterno che converte i PDF in Markdown strutturato.
|
||||||
Repository: [https://github.com/opendatalab/MinerU](https://github.com/opendatalab/MinerU)
|
Repository: [https://github.com/opendatalab/MinerU](https://github.com/opendatalab/MinerU)
|
||||||
|
|
||||||
#### Requisiti di sistema
|
#### Requisiti di sistema
|
||||||
@@ -23,134 +23,41 @@ Repository: [https://github.com/opendatalab/MinerU](https://github.com/opendatal
|
|||||||
| RAM | 16 GB | 32 GB+ |
|
| RAM | 16 GB | 32 GB+ |
|
||||||
| Disco | 20 GB (modelli inclusi) | 40 GB+ |
|
| Disco | 20 GB (modelli inclusi) | 40 GB+ |
|
||||||
| GPU | opzionale | 4–8 GB VRAM (CUDA) |
|
| GPU | opzionale | 4–8 GB VRAM (CUDA) |
|
||||||
| OS | Linux, macOS, Windows | Linux / WSL2 per Docker |
|
| OS | Linux, macOS, Windows | Linux / WSL2 |
|
||||||
|
|
||||||
#### Installazione
|
#### Installazione
|
||||||
|
|
||||||
**pip (raccomandato):**
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install mineru[all]
|
pip install "mineru[all]"
|
||||||
# oppure con uv (più veloce):
|
|
||||||
uv pip install -U "mineru[all]"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**conda:**
|
Al primo avvio scarica automaticamente i modelli (~15 GB).
|
||||||
|
|
||||||
```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
|
#### Uso — CLI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mineru -p <input> -o <output_dir> [opzioni]
|
mineru -p <documento.pdf> -o <output_dir> -b pipeline
|
||||||
```
|
```
|
||||||
|
|
||||||
| Flag | Descrizione | Default |
|
| Flag | Descrizione |
|
||||||
|------|-------------|---------|
|
|------|-------------|
|
||||||
| `-p` | Percorso PDF, cartella di PDF, o URL | — |
|
| `-p` | Percorso PDF o cartella |
|
||||||
| `-o` | Cartella di output | — |
|
| `-o` | Cartella di output |
|
||||||
| `-b` | Backend di conversione (vedi sotto) | `pipeline` |
|
| `-b` | Backend: `pipeline` (CPU), `hybrid-auto-engine` (GPU raccomandato) |
|
||||||
| `-m` | Metodo di estrazione: `auto`, `txt`, `ocr` | `auto` |
|
| `-m` | Metodo: `auto`, `txt`, `ocr` |
|
||||||
| `-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
|
#### Output di MinerU
|
||||||
|
|
||||||
MinerU produce per ogni PDF una cartella con questa struttura:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
<stem>/
|
<stem>_output/
|
||||||
└── auto/
|
└── auto/
|
||||||
├── <stem>.md ← Markdown grezzo (non usato dalla pipeline)
|
├── <stem>.md ← Markdown (input della pipeline)
|
||||||
├── <stem>_content_list_v2.json ← struttura ricca: tipo, livello, bbox [RICHIESTO]
|
├── <stem>_content_list_v2.json
|
||||||
├── <stem>_model.json ← label di layout: doc_title, abstract… [RACCOMANDATO]
|
├── <stem>_model.json
|
||||||
├── <stem>_middle.json ← dati intermedi (non usato)
|
└── images/
|
||||||
├── <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.
|
Il file usato dalla pipeline è **`<stem>.md`** nella cartella `auto/`.
|
||||||
|
|
||||||
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
|
### 2. Ollama — embedding e generazione
|
||||||
|
|
||||||
@@ -176,99 +83,60 @@ 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
|
## Flusso completo
|
||||||
|
|
||||||
```
|
```
|
||||||
PDF
|
PDF
|
||||||
│
|
│
|
||||||
▼ (MinerU — esterno)
|
▼ MinerU (esterno — vedi sotto)
|
||||||
sources/<stem>/auto/
|
sources/<stem>_output/auto/<stem>.md
|
||||||
│
|
│
|
||||||
▼ python chunks/chunker.py --stem <stem>
|
▼ python chunks/chunker.py --stem <stem>
|
||||||
│ Stage 1: _content_list_v2.json + _model.json → <stem>_clean.md
|
chunks/<stem>/chunks.json
|
||||||
│ Stage 2: <stem>_clean.md → chunks/<stem>/chunks.json
|
|
||||||
│
|
│
|
||||||
▼ python chunks/verify_chunks.py --stem <stem>
|
▼ python chunks/verify_chunks.py --stem <stem>
|
||||||
│ Verifica qualità chunk
|
(verifica qualità — opzionale ma consigliato)
|
||||||
│
|
|
||||||
▼ python chunks/fix_chunks.py --stem <stem> [se necessario]
|
|
||||||
│ Correzioni automatiche
|
|
||||||
│
|
│
|
||||||
▼ python ingestion/ingest.py --stem <stem>
|
▼ python ingestion/ingest.py --stem <stem>
|
||||||
│ Embedding → ChromaDB
|
chroma_db/<stem>/
|
||||||
│
|
│
|
||||||
▼ python rag.py --stem <stem>
|
▼ python rag.py --stem <stem>
|
||||||
Interrogazione in linguaggio naturale
|
Interrogazione in linguaggio naturale
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Passo 1 — Converti il PDF con MinerU
|
## Passo 1 — Converti il PDF con MinerU
|
||||||
|
|
||||||
Per questo step è necessario **MinerU** ([github.com/opendatalab/MinerU](https://github.com/opendatalab/MinerU)).
|
**Opzione A — Google Colab (nessuna installazione locale)**
|
||||||
|
|
||||||
**Opzione A — Google Colab (consigliata, nessuna installazione locale)**
|
Usa il notebook incluso:
|
||||||
|
|
||||||
Usa il notebook incluso in questo repository:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
mineru_demo.ipynb
|
mineru.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.
|
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/
|
||||||
|
└── <stem>_output/
|
||||||
|
└── auto/
|
||||||
|
├── <stem>.md ← usato dalla pipeline
|
||||||
|
├── <stem>_content_list_v2.json
|
||||||
|
├── <stem>_model.json
|
||||||
|
└── images/
|
||||||
|
```
|
||||||
|
|
||||||
**Opzione B — Installazione locale**
|
**Opzione B — Installazione locale**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install "mineru[all]"
|
mineru -p documento.pdf -o sources/<stem>_output/ -b pipeline
|
||||||
mineru -p documento.pdf -o sources/<stem>/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Al termine, **copia la cartella completa** prodotta da MinerU dentro `sources/`. La struttura attesa è:
|
---
|
||||||
|
|
||||||
```
|
## Passo 2 — Chunking
|
||||||
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:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Singolo documento
|
# Singolo documento
|
||||||
@@ -277,119 +145,98 @@ Un singolo comando esegue entrambe le fasi:
|
|||||||
# Tutti i documenti in sources/
|
# Tutti i documenti in sources/
|
||||||
.venv/bin/python chunks/chunker.py
|
.venv/bin/python chunks/chunker.py
|
||||||
|
|
||||||
# Rigenera tutto da zero
|
# Rigenera anche se chunks.json esiste già
|
||||||
.venv/bin/python chunks/chunker.py --stem <stem> --force
|
.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):
|
Il chunker legge `sources/<stem>_output/auto/<stem>.md` e produce `chunks/<stem>/chunks.json`.
|
||||||
- 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**:
|
**Regole applicate:**
|
||||||
- Un chunk per paragrafo — mai due paragrafi nello stesso chunk
|
|
||||||
- Split a confine di frase se il paragrafo supera `MAX_CHARS` (default 1200 chars)
|
- 1 paragrafo = 1 chunk; paragrafi di sezioni diverse non si mescolano
|
||||||
- Overlap di 1 frase tra chunk consecutivi per continuità contestuale
|
- Split a confine di frase se il paragrafo supera `MAX_CHARS` (mai a metà frase)
|
||||||
- Tabelle e liste sono blocchi atomici (non vengono mai spezzati)
|
- Frasi spezzate tra paragrafi consecutivi vengono ri-fuse automaticamente
|
||||||
- Ogni chunk include un prefisso `[Sezione > Titolo]` per il retrieval
|
- 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>/`:
|
Output in `chunks/<stem>/`:
|
||||||
|
|
||||||
| File | Contenuto |
|
| File | Contenuto |
|
||||||
|------|-----------|
|
|------|-----------|
|
||||||
| `chunks.json` | Lista di chunk con testo, sezione, titolo, n_chars |
|
| `chunks.json` | Chunk con testo, sezione, titolo, n_chars |
|
||||||
| `meta.json` | Parametri usati (max_chars, overlap, strategia) |
|
| `meta.json` | Parametri usati |
|
||||||
| `report.json` | Statistiche e anomalie (generato da verify) |
|
| `report.json` | Statistiche e anomalie (generato da verify) |
|
||||||
|
|
||||||
### Passo 3 — Verifica i chunk
|
---
|
||||||
|
|
||||||
|
## Passo 3 — Verifica i chunk (opzionale)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.venv/bin/python chunks/verify_chunks.py --stem <stem>
|
.venv/bin/python chunks/verify_chunks.py --stem <stem>
|
||||||
```
|
```
|
||||||
|
|
||||||
| Verdict | Significato | Cosa fare |
|
| Verdict | Significato |
|
||||||
|---------|-------------|-----------|
|
|---------|-------------|
|
||||||
| `ok` | Nessun problema | Procedi alla vettorizzazione |
|
| `ok` | Nessun problema — procedi |
|
||||||
| `warnings_only` | Solo avvisi minori (frasi lunghe) | Puoi procedere |
|
| `warnings_only` | Solo avvisi minori — puoi procedere |
|
||||||
| `blocked` | Chunk incompleti o senza prefisso | Esegui il fix |
|
| `blocked` | Problemi strutturali — rivedi il sorgente `.md` |
|
||||||
|
|
||||||
### Passo 4 — Correggi i problemi (se verdict = `blocked`)
|
---
|
||||||
|
|
||||||
```bash
|
## Passo 4 — Vettorizzazione
|
||||||
# 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
|
```bash
|
||||||
# Singolo documento → collection omonima
|
# Singolo documento → collection omonima
|
||||||
.venv/bin/python ingestion/ingest.py --stem <stem>
|
.venv/bin/python ingestion/ingest.py --stem <stem>
|
||||||
|
|
||||||
# Più documenti → un'unica collection condivisa
|
# Più documenti → collection condivisa (RAG cross-documento)
|
||||||
.venv/bin/python ingestion/ingest.py --collection <nome> --stems doc1 doc2 doc3
|
.venv/bin/python ingestion/ingest.py --collection <nome> --stems doc1 doc2 doc3
|
||||||
|
|
||||||
# Rigenera dopo aver aggiornato i chunk o cambiato modello embedding
|
# Rigenera dopo aver aggiornato i chunk o cambiato modello embedding
|
||||||
.venv/bin/python ingestion/ingest.py --stem <stem> --force
|
.venv/bin/python ingestion/ingest.py --stem <stem> --force
|
||||||
```
|
```
|
||||||
|
|
||||||
> Se cambi `EMBED_MODEL` in `config.py`, devi rilanciare l'ingestion con `--force`.
|
> Se cambi `EMBED_MODEL` in `config.py` devi rieseguire l'ingestion con `--force`.
|
||||||
|
|
||||||
### Passo 6 — Interrogazione
|
---
|
||||||
|
|
||||||
|
## Passo 5 — Interrogazione
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# RAG interattivo
|
# RAG interattivo (retrieval + generazione LLM)
|
||||||
.venv/bin/python rag.py --stem <stem>
|
.venv/bin/python rag.py --stem <stem>
|
||||||
.venv/bin/python rag.py --collection <nome>
|
.venv/bin/python rag.py --collection <nome>
|
||||||
|
|
||||||
# Retrieval puro (debug, senza generazione LLM)
|
# Retrieval puro (debug, senza LLM)
|
||||||
.venv/bin/python retrieve.py --stem <stem>
|
.venv/bin/python retrieve.py --stem <stem>
|
||||||
.venv/bin/python retrieve.py --stem <stem> --top-k 10
|
.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
|
## Configurazione
|
||||||
|
|
||||||
### Parametri di chunking — `chunks/config.py`
|
### Chunking — `chunks/config.py`
|
||||||
|
|
||||||
| Parametro | Default | Descrizione |
|
| Parametro | Default | Descrizione |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
| `MAX_CHARS` | 1200 | Lunghezza massima chunk in caratteri |
|
| `MAX_CHARS` | 1200 | Lunghezza massima chunk (caratteri) |
|
||||||
| `MIN_CHARS` | 80 | Soglia minima (warning sotto questa soglia) |
|
| `MIN_CHARS` | 80 | Soglia minima (warning sotto questa soglia) |
|
||||||
| `OVERLAP_SENTENCES` | 1 | Frasi di overlap tra chunk consecutivi |
|
| `CONTEXT_DEPTH` | 3 | Livelli heading inclusi nel prefisso chunk (1–3) |
|
||||||
| `MIN_CONTENT_CHARS` | 2500 | Soglia per distinguere frontespizio da contenuto reale |
|
| `SKIP_HEADINGS` | set italiano | Sezioni saltate completamente (indice, sommario…) |
|
||||||
| `FRONTMATTER_HEADINGS` | set italiano | Sezioni da rimuovere (Sommario, Indice, Autori…) — svuotare per documenti non italiani |
|
| `SKIP_PRE_HEADING` | `True` | Salta contenuto prima del primo heading (frontespizio) |
|
||||||
| `SOMMARIO_PATTERNS` | 5 lingue | Pattern regex per sommari interni da saltare |
|
| `MERGE_SHORT_PARAGRAPHS` | `True` | Accorpa paragrafi brevi fino a `MIN_CHARS` |
|
||||||
| `CHAPTER_PREFIX_PATTERNS` | 4 lingue | Pattern per prefissi di capitolo come paragrafi separati |
|
| `SENTENCE_SPLIT_RE` | regex | Confine di fine frase per lo split |
|
||||||
| `MODEL_SKIP_LABELS` | set | Label `_model.json` da ignorare (header, number…) |
|
| `ATOMIC_TYPES` | `table, code, list` | Blocchi mai spezzati |
|
||||||
|
|
||||||
### Parametri RAG — `config.py`
|
### 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` | `nomic-embed-text` | Modello embedding |
|
| `EMBED_MODEL` | `nomic-embed-text` | Modello embedding |
|
||||||
|
| `EMBED_MAX_CHARS` | 6000 | Caratteri massimi inviati al modello embedding |
|
||||||
| `TOP_K` | 6 | Chunk recuperati per domanda |
|
| `TOP_K` | 6 | Chunk recuperati per domanda |
|
||||||
| `TEMPERATURE` | 0.2 | Creatività del modello (0 = deterministico) |
|
| `TEMPERATURE` | 0.2 | Creatività del modello (0 = deterministico) |
|
||||||
| `OLLAMA_URL` | `localhost:11434` | URL server Ollama |
|
| `OLLAMA_URL` | `localhost:11434` | URL server Ollama |
|
||||||
@@ -400,34 +247,17 @@ Comandi nel loop interattivo di `retrieve.py`:
|
|||||||
|
|
||||||
```
|
```
|
||||||
rag/
|
rag/
|
||||||
├── sources/ ← output di MinerU (una cartella per documento)
|
├── sources/ ← output MinerU (una cartella per documento)
|
||||||
│ └── <stem>/auto/
|
│ └── <stem>_output/auto/
|
||||||
├── chunks/
|
├── chunks/
|
||||||
│ ├── chunker.py ← pipeline unificata (Stage 1 + Stage 2)
|
│ ├── chunker.py ← chunking da MD pulito
|
||||||
│ ├── md_optimizer.py ← Stage 1: JSON MinerU → _clean.md
|
│ ├── config.py ← parametri di chunking
|
||||||
│ ├── config.py ← tutti i parametri di chunking
|
│ └── verify_chunks.py ← verifica qualità chunk
|
||||||
│ ├── verify_chunks.py ← verifica qualità
|
|
||||||
│ └── fix_chunks.py ← correzioni automatiche
|
|
||||||
├── ingestion/
|
├── ingestion/
|
||||||
│ └── ingest.py ← embedding → ChromaDB
|
│ └── ingest.py ← embedding → ChromaDB
|
||||||
├── ollama/
|
|
||||||
│ └── check_env.py ← verifica ambiente Ollama
|
|
||||||
├── chroma_db/ ← database vettoriale (ignorato da git)
|
├── chroma_db/ ← database vettoriale (ignorato da git)
|
||||||
├── config.py ← parametri RAG (modelli, TOP_K, prompt)
|
├── config.py ← parametri RAG (modelli, TOP_K, prompt)
|
||||||
├── 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
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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)
|
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ NO_THINK = True
|
|||||||
# Se cambi questo, devi rieseguire ingest.py con --force.
|
# Se cambi questo, devi rieseguire ingest.py con --force.
|
||||||
EMBED_MODEL = "nomic-embed-text"
|
EMBED_MODEL = "nomic-embed-text"
|
||||||
|
|
||||||
|
# Caratteri massimi inviati al modello di embedding.
|
||||||
|
# Il testo viene troncato SOLO per il vettore; il documento completo
|
||||||
|
# rimane in ChromaDB. nomic-embed-text: 8192 token ≈ 6000 char sicuri.
|
||||||
|
EMBED_MAX_CHARS: int = 6000
|
||||||
|
|
||||||
# ── Ollama ────────────────────────────────────────────────────────────────────
|
# ── Ollama ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# URL del server Ollama (default: locale sulla porta 11434).
|
# URL del server Ollama (default: locale sulla porta 11434).
|
||||||
|
|||||||
+15
-2
@@ -21,6 +21,7 @@ Uso:
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib.error
|
import urllib.error
|
||||||
@@ -37,16 +38,28 @@ CHUNKS_DIR = project_root / "chunks"
|
|||||||
CHROMA_DIR = project_root / "chroma_db"
|
CHROMA_DIR = project_root / "chroma_db"
|
||||||
|
|
||||||
sys.path.insert(0, str(project_root))
|
sys.path.insert(0, str(project_root))
|
||||||
from config import EMBED_MODEL, OLLAMA_URL # noqa: E402
|
from config import EMBED_MODEL, EMBED_MAX_CHARS, OLLAMA_URL # noqa: E402
|
||||||
|
|
||||||
EMBED_ENDPOINT = f"{OLLAMA_URL}/api/embeddings"
|
EMBED_ENDPOINT = f"{OLLAMA_URL}/api/embeddings"
|
||||||
|
|
||||||
|
|
||||||
# ─── Ollama ────────────────────────────────────────────────────────────────────
|
# ─── Ollama ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_HTML_TAG = re.compile(r"<[^>]+>")
|
||||||
|
_HTML_ENT = re.compile(r"&[a-zA-Z0-9#]+;")
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_for_embed(text: str) -> str:
|
||||||
|
"""Rimuove tag HTML ed entità prima dell'embedding; tronca al limite del modello."""
|
||||||
|
text = _HTML_TAG.sub(" ", text)
|
||||||
|
text = _HTML_ENT.sub(" ", text)
|
||||||
|
text = re.sub(r" {2,}", " ", text)
|
||||||
|
return text[:EMBED_MAX_CHARS]
|
||||||
|
|
||||||
|
|
||||||
def embed(text: str, model: str) -> list[float]:
|
def embed(text: str, model: str) -> list[float]:
|
||||||
"""Chiama Ollama /api/embeddings e ritorna il vettore."""
|
"""Chiama Ollama /api/embeddings e ritorna il vettore."""
|
||||||
payload = json.dumps({"model": model, "prompt": text}).encode()
|
payload = json.dumps({"model": model, "prompt": _clean_for_embed(text)}).encode()
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
EMBED_ENDPOINT,
|
EMBED_ENDPOINT,
|
||||||
data=payload,
|
data=payload,
|
||||||
|
|||||||
Reference in New Issue
Block a user