docs(README): riscrittura per struttura reale del progetto

Sostituisce la struttura step-0…step-10 con la pipeline
effettiva: conversione/, revisione /prepare-md, chunking,
verifica, ollama/, vettorizzazione, interrogazione
This commit is contained in:
2026-04-17 18:51:09 +02:00
parent 6d25834acd
commit 4c02102363
+200 -517
View File
@@ -3,7 +3,7 @@
Sistema RAG (Retrieval-Augmented Generation) costruito da zero, senza framework di alto livello. Sistema RAG (Retrieval-Augmented Generation) costruito da zero, senza framework di alto livello.
Funziona su qualsiasi PDF digitale. Gira interamente in locale, senza GPU, senza cloud. Funziona su qualsiasi PDF digitale. Gira interamente in locale, senza GPU, senza cloud.
**Stack:** Python · Ollama · nomic-embed-text · Qwen3.5 · ChromaDB **Stack:** Python · Ollama · ChromaDB · Qwen3-embedding · Qwen3.5
**Compatibile con:** Linux · macOS · Windows (WSL2) · CPU Only · ~8 GB RAM libera **Compatibile con:** Linux · macOS · Windows (WSL2) · CPU Only · ~8 GB RAM libera
--- ---
@@ -12,18 +12,14 @@ Funziona su qualsiasi PDF digitale. Gira interamente in locale, senza GPU, senza
- [Panoramica](#panoramica) - [Panoramica](#panoramica)
- [Struttura del progetto](#struttura-del-progetto) - [Struttura del progetto](#struttura-del-progetto)
- [Gli step](#gli-step) - [Pipeline](#pipeline)
- [Step 0 — Scegli il PDF](#step-0--scegli-il-pdf) - [Conversione](#conversione)
- [Step 1 — Ispezione automatica](#step-1--ispezione-automatica) - [Revisione Markdown](#revisione-markdown)
- [Step 2 — Conversione in Markdown grezzo](#step-2--conversione-in-markdown-grezzo) - [Chunking](#chunking)
- [Step 3 — Rilevamento struttura](#step-3--rilevamento-struttura) - [Verifica chunk](#verifica-chunk)
- [Step 4 — Revisione manuale](#step-4--revisione-manuale) - [Ambiente Ollama](#ambiente-ollama)
- [Step 5 — Chunking adattivo](#step-5--chunking-adattivo) - [Vettorizzazione](#vettorizzazione)
- [Step 6 — Verifica chunk](#step-6--verifica-chunk) - [Interrogazione](#interrogazione)
- [Step 7 — Installazione ambiente](#step-7--installazione-ambiente)
- [Step 8 — Vettorizzazione](#step-8--vettorizzazione)
- [Step 9 — Pipeline RAG](#step-9--pipeline-rag)
- [Step 10 — Test automatici](#step-10--test-automatici)
- [Principi di progettazione](#principi-di-progettazione) - [Principi di progettazione](#principi-di-progettazione)
--- ---
@@ -31,36 +27,35 @@ Funziona su qualsiasi PDF digitale. Gira interamente in locale, senza GPU, senza
## Panoramica ## Panoramica
``` ```
PDF PDF (sources/)
└─► STEP 1 Ispezione automatica
└─► STEP 2 Conversione in Markdown grezzo ▼ conversione/pipeline.py
└─► STEP 3 Rilevamento struttura clean.md ← revisiona con /prepare-md
└─► STEP 4 Revisione manuale ← step più importante
└─► STEP 5 Chunking adattivo ▼ step-5/chunker.py
└─► STEP 6 Verifica chunk chunks.json
└─► STEP 8 Vettorizzazione
└─► STEP 9 Pipeline RAG ▼ step-6/verify_chunks.py + fix_chunks.py
└─► STEP 10 Test automatici chunks.json verificato
STEP 0 Prerequisito iniziale (PDF adatto) ▼ step-8/ingest.py
STEP 7 Prerequisito tecnico (ambiente locale) ChromaDB
▼ rag.py
risposta
``` ```
### Dove si concentra il rischio ### Dove si concentra il rischio
| Step | Rischio | Motivo | | Fase | Rischio | Motivo |
|---|---|---| |---|---|---|
| Step 0 | 🔴 Alto | Un PDF inadatto invalida tutto il lavoro successivo | | Conversione | 🟡 Medio | Automatica, ma il PDF deve essere digitale e non protetto |
| Step 1 | 🟢 Basso | Automatico, solo osservazione | | Revisione Markdown | 🔴 Alto | Manuale — la qualità del MD determina la qualità del RAG |
| Step 2 | 🟢 Basso | Automatico, tool maturo | | Chunking | 🟡 Medio | Logica adattiva, dipende dalla qualità del MD |
| Step 3 | 🟢 Basso | Automatico, solo analisi | | Verifica chunk | 🟢 Basso | Automatica, solo verifica |
| Step 4 | 🔴 Alto | Manuale — la qualità del MD determina la qualità del RAG | | Ambiente Ollama | 🟢 Basso | Installazione standard |
| Step 5 | 🟡 Medio | Logica adattiva, dipende dalla qualità del MD | | Vettorizzazione | 🟢 Basso | Meccanica, lenta ma affidabile |
| Step 6 | 🟢 Basso | Automatico, solo verifica | | Interrogazione | 🟡 Medio | Qualità del prompt e dei parametri in `config.py` |
| Step 7 | 🟢 Basso | Installazione standard |
| Step 8 | 🟢 Basso | Meccanico, lento ma affidabile |
| Step 9 | 🟡 Medio | Qualità del prompt |
| Step 10 | 🟢 Basso | Test automatici |
--- ---
@@ -72,54 +67,38 @@ rag-from-scratch/
├── sources/ # PDF originali — non modificare mai ├── sources/ # PDF originali — non modificare mai
│ └── documento.pdf │ └── documento.pdf
├── step-0/ ├── conversione/ # PDF → Markdown strutturato
── check_pdf.py # Verifica requisiti del PDF ── pipeline.py # Conversione PDF → clean.md
├── validate.py # Validazione batch di tutti gli stem
├── step-1/
│ └── inspect_pdf.py # Ispezione automatica del PDF
├── step-2/
│ ├── convert_pdf.py # Conversione PDF → Markdown grezzo
│ └── <stem>/ │ └── <stem>/
── raw.md # MD grezzo (non toccare) ── raw.md # MD grezzo (non toccare)
│ ├── clean.md # MD pulito — copia di lavoro
│ └── report.json # Metriche qualità conversione
├── step-3/ ├── step-5/ # Chunking adattivo
│ ├── detect_structure.py # Rilevamento struttura MD │ ├── chunker.py
│ └── <stem>/ │ └── <stem>/
│ └── structure_profile.json # Profilo struttura │ └── chunks.json
├── step-4/ ├── step-6/ # Verifica e fix chunk
│ ├── revise.py # Pre-processing automatico MD │ ├── verify_chunks.py
│ ├── revision_log.md # Log modifiche manuali │ ├── fix_chunks.py
│ └── <stem>/
│ ├── clean.md # MD revisionato
│ └── structure_profile.json # Profilo aggiornato
├── step-5/
│ ├── chunker.py # Chunking adattivo
│ └── <stem>/
│ └── chunks.json # Chunk pronti per la vettorizzazione
├── step-6/
│ ├── verify_chunks.py # Verifica chunk
│ ├── fix_chunks.py # Fix chunk problematici
│ └── <stem>/ │ └── <stem>/
│ └── chunks.json # Chunk verificati │ └── chunks.json # Chunk verificati
├── step-7/ ├── step-8/ # Vettorizzazione → ChromaDB
│ ├── check_env.py # Verifica ambiente locale │ ├── ingest.py
│ └── README.md # Guida installazione Ollama e dipendenze
├── step-8/
│ └── ingest.py # Vettorizzazione → ChromaDB
├── step-9/
│ ├── config.py # Configurazione pipeline RAG ← modifica qui
│ ├── rag.py # Pipeline RAG interattiva
│ ├── test_ollama.py # Test chat Ollama senza RAG
│ └── README.md │ └── README.md
├── chroma_db/ # Vector store — generato da step-8 ├── ollama/ # Ambiente Ollama
│ ├── check_env.py # Verifica prerequisiti
│ ├── test_ollama.py # Test chat senza RAG
│ └── README.md
├── chroma_db/ # Vector store — generato da ingest.py
├── config.py # Configurazione pipeline RAG ← modifica qui
├── rag.py # Pipeline RAG interattiva
├── retrieve.py # Retrieval puro (senza LLM)
├── requirements.txt ├── requirements.txt
├── .gitignore ├── .gitignore
└── README.md └── README.md
@@ -127,305 +106,67 @@ rag-from-scratch/
--- ---
## Gli step ## Pipeline
--- ---
### Step 0 — Scegli il PDF ### Conversione
**Tipo:** prerequisito manuale
**Input:** nessuno
**Output:** un PDF adatto al sistema
Il PDF deve soddisfare requisiti minimi prima di qualsiasi elaborazione.
Un PDF inadatto rende tutto il lavoro successivo inutile.
**Criteri obbligatori:**
- Il testo è selezionabile nel PDF reader — se non riesci a copiare una parola,
pdfplumber non la leggerà
- Non è protetto da password
- È generato digitalmente, non scansionato — una foto di un libro non è un PDF di testo
- Il contenuto importante è nel testo, non nelle immagini
**Criteri desiderabili:**
- Ha una struttura logica riconoscibile: capitoli, sezioni, paragrafi
- Le sezioni hanno titoli espliciti
- Non ha layout a colonne multiple
- È in una lingua sola o prevalentemente una
**Come verificarlo:**
Apri il PDF nel tuo reader, seleziona del testo da pagine diverse e copialo.
Se il testo copiato è leggibile e nell'ordine giusto, il PDF è adatto.
Se ottieni caratteri strani o testo nell'ordine sbagliato, il PDF ha problemi.
---
### Step 1 — Ispezione automatica
**Tipo:** automatico **Tipo:** automatico
**Input:** tutti i PDF in `sources/` **Input:** `sources/<stem>.pdf`
**Output:** `step-1/<stem>_step1_report.txt` **Output:** `conversione/<stem>/clean.md` + `report.json`
**Script:** `step-1/inspect_pdf.py` **Script:** `conversione/pipeline.py`
```bash ```bash
python step-1/inspect_pdf.py # Singolo documento
python conversione/pipeline.py --stem <nome>
# Tutti i PDF in sources/
python conversione/pipeline.py
# Forza riesecuzione (sovrascrive output esistente)
python conversione/pipeline.py --stem <nome> --force
``` ```
Lo script scansiona automaticamente tutti i PDF in `sources/`, analizza ogni documento pagina per pagina e produce un report. Converte il PDF in Markdown strutturato in quattro fasi automatiche: validazione, estrazione testo (algoritmo XY-Cut++ per layout multi-colonna), pulizia strutturale e analisi della struttura del documento.
Serve per capire la qualità del documento e mappare i problemi
prima di affrontare la revisione manuale.
**Cosa rileva:** Produce tre file in `conversione/<stem>/`:
- Testo non estraibile (pagine con sole immagini) | File | Descrizione |
- Sillabazioni a fine riga
- Layout a colonne (righe molto corte e numerose)
- Intestazioni e piè di pagina ripetitivi
- Caratteri Unicode anomali
- Pagine vuote
**Output del report:**
```
Score: 87/100
Pagine totali: 243
Pagine con problemi: 12
Pagina 14: sillabazione rilevata (3 occorrenze)
Pagina 67: possibile layout a colonne
Pagina 201: caratteri Unicode anomali
PROSSIMI PASSI:
→ conversione con marker funzionerà bene
→ attenzione alle pagine 14 e 67 nella revisione manuale
```
**Decisione:**
| Score | Azione |
|---|---| |---|---|
| ≥ 70 | Procedi allo step 2 | | `raw.md` | Markdown grezzo estratto dal PDF — **non modificare mai** |
| 4070 | Procedi con cautela, revisione estesa necessaria | | `clean.md` | Markdown pulito e strutturato — input per il chunker |
| < 40 | Valuta una fonte PDF migliore | | `report.json` | Metriche qualità, anomalie, strategia di chunking suggerita |
--- **Requisiti aggiuntivi:** Java 11+ nel PATH (`opendataloader-pdf` lo richiede).
### Step 2 — Conversione in Markdown grezzo **Validazione batch:**
**Tipo:** automatico
**Input:** tutti i PDF in `sources/` (o uno solo con `--pdf`)
**Output:** `step-2/<stem>/raw.md` + `step-2/<stem>/clean.md`
**Script:** `step-2/convert_pdf.py`
```bash ```bash
python step-2/convert_pdf.py # tutti i PDF in sources/ python conversione/validate.py
python step-2/convert_pdf.py --pdf sources/doc.pdf # un solo PDF
``` ```
Converte il PDF in Markdown usando `pymupdf4llm`. Il risultato non è perfetto — è la base Mostra una tabella di stato per tutti gli stem convertiti. Vedi [`conversione/README.md`](conversione/README.md) per dettagli completi.
su cui lavorerai nello step 4.
Lo script crea due file: **PDF supportati:** digitali con testo selezionabile. Non supportati: scansionati (solo immagini) e protetti da password.
- `raw.md` — conversione grezza, **non modificare mai**. È il punto di partenza di riferimento.
- `clean.md` — copia di lavoro che verrà modificata negli step successivi.
**Cosa produce la conversione:**
- Titoli riconosciuti e convertiti in `#` `##` `###`
- Paragrafi separati da righe vuote
- Sillabazione parzialmente risolta
**Cosa non produce:**
- Rimozione intestazioni e piè di pagina
- Correzione completa del layout a colonne
- Descrizione del contenuto delle immagini
--- ---
### Step 3 — Rilevamento struttura ### Revisione Markdown
**Tipo:** automatico **Tipo:** semi-automatico
**Input:** `step-2/<stem>/` **Input:** `conversione/<stem>/clean.md`
**Output:** `step-3/<stem>/structure_profile.json` **Output:** `conversione/<stem>/clean.md` corretto in-place
**Script:** `step-3/detect_structure.py`
```bash > Questo è il passaggio più importante dell'intera pipeline.
python step-3/detect_structure.py # tutti i documenti in step-2/ > La qualità del RAG dipende da questo passaggio più di qualsiasi
python step-3/detect_structure.py --stem <nome> # un solo documento
python step-3/detect_structure.py --force # riesegui anche se già presente
```
Copia `raw.md` e `clean.md` da `step-2/<stem>/` e analizza la struttura del Markdown senza modificarla.
Il profilo prodotto guida sia la revisione manuale che il chunker.
**I quattro livelli strutturali:**
```
Livello 3 — struttura ricca
Il documento ha ### con regolarità.
Ogni ### è un'unità semantica chiara.
Esempi: opere filosofiche, manuali tecnici, leggi.
Strategia chunking: boundary su ###
Livello 2 — struttura parziale
Il documento ha ## ma pochi o nessun ###.
Le sezioni sono i capitoli, non le sottosezioni.
Esempi: articoli scientifici, report, saggi.
Strategia chunking: boundary su ##, split interno su paragrafi
Livello 1 — solo paragrafi
Il documento non ha titoli significativi.
La struttura è data dalle righe vuote.
Esempi: testi narrativi, lettere, trascrizioni.
Strategia chunking: boundary su paragrafo
Livello 0 — testo piatto
Un blocco continuo senza struttura riconoscibile.
Esempi: PDF mal convertiti, testi antichi.
Strategia chunking: sliding window su frasi
```
**Profilo prodotto:**
```json
{
"livello_struttura": 3,
"n_h1": 1,
"n_h2": 9,
"n_h3": 296,
"n_paragrafi": 312,
"boundary_primario": "h3",
"lingua_rilevata": "it",
"lunghezza_media_sezione": 420,
"strategia_chunking": "h3_aware",
"avvertenze": [
"14 sezioni sotto i 200 caratteri — verranno accorpate",
"8 sezioni sopra i 800 caratteri — verranno divise"
]
}
```
---
### Step 4 — Revisione manuale
**Tipo:** manuale (con pre-processing automatico)
**Input:** `step-3/<stem>/clean.md` + `step-3/<stem>/structure_profile.json`
**Output:** `step-4/<stem>/clean.md` — MD revisionato
**Script:** `step-4/revise.py`
> Questo è lo step più importante dell'intera pipeline.
> La qualità del RAG dipende da questo step più di qualsiasi
> parametro tecnico o scelta di modello. > parametro tecnico o scelta di modello.
#### Pre-processing automatico ```
/prepare-md conversione/<stem>/clean.md
Prima di qualsiasi revisione manuale, esegui lo script di revisione automatica:
```bash
python step-4/revise.py --stem documento
``` ```
Lo script applica le seguenti trasformazioni euristiche, valide per qualsiasi documento: La skill analizza il `clean.md` e corregge automaticamente i problemi che compromettono il chunking: sillabazione, artefatti, header malformati, paragrafi spezzati, gerarchia incoerente, sezioni vuote.
| Trasformazione | Descrizione |
|---|---|
| Rimozione TOC | Righe che iniziano con `INDICE`, `INDEX`, `CONTENTS`, ecc. |
| ALL-CAPS → `##` | Righe standalone in maiuscolo convertite in header section-case |
| `N. testo``### N.` | Sezioni numerate (con 1+ spazio dopo il punto) convertite in h3 |
| Unione paragrafi | Blocchi spezzati da salti pagina PDF uniti automaticamente |
| Whitespace | Spazi multipli normalizzati, righe vuote ridotte |
Il profilo strutturale aggiornato viene salvato in `step-4/<stem>/structure_profile.json`.
#### Revisione assistita da Claude Code
Dopo il pre-processing, usa la skill integrata per una revisione qualitativa:
```
/step4-review documento
```
La skill analizza `step-4/<stem>/clean.md` e produce un report strutturato:
```
🔴 BLOCCANTI — problemi che compromettono il chunking
🟡 MINORI — artefatti visibili ma non bloccanti
🟢 OK — categorie senza problemi
```
Poi propone le correzioni e le applica solo su tua approvazione.
#### Revisione manuale (senza Claude Code)
Se non usi Claude Code, esegui questi 6 check dal terminale.
In tutti i comandi sostituisci `<stem>` con il nome reale del documento.
**Check 1 — Sillabazione residua**
Parole spezzate a fine riga con trattino (artefatto da PDF non risolto):
```bash
grep -n "\-$" step-4/<stem>/clean.md | head -20
```
Se trovi risultati: unisci la riga con la successiva eliminando il trattino
e il ritorno a capo.
**Check 2 — Righe orfane**
Righe brevi (<60 char) isolate che sembrano numeri di pagina, autori, intestazioni:
```bash
grep -n "^[^#\-\*\|].\{1,59\}$" step-4/<stem>/clean.md | grep -v "^\s*$" | head -30
```
Per ogni riga: valuta se è testo legittimo (frase breve) o artefatto
(numero di pagina, nome autore ripetuto, intestazione PDF). Gli artefatti vanno eliminati.
**Check 3 — Frasi spezzate**
Paragrafi che terminano senza punteggiatura di fine frase:
```bash
grep -n "[^.!?»)\]\'\"]$" step-4/<stem>/clean.md \
| grep -v "^[0-9]*:#" \
| grep -v "^[0-9]*:\s*$" \
| grep -v "^\s*[-\*]" \
| head -20
```
Segnala le righe brevi che finiscono a metà concetto. Uniscile alla riga successiva.
**Check 4 — Header sospetti**
```bash
grep -n "^##\? " step-4/<stem>/clean.md | head -40
```
Verifica:
- Header con testo >80 caratteri → probabilmente è testo normale, non un header
- Header in MAIUSCOLO non convertito → cambia in formato sentence-case
- Header duplicati (stesso testo due volte) → valuta se unire o rinominare
- `###` senza un `##` padre → salto di gerarchia anomalo
**Check 5 — Sezioni quasi vuote**
```bash
python3 -c "
import re
text = open('step-4/<stem>/clean.md').read()
sections = re.split(r'^(#{1,3} .+)$', text, flags=re.MULTILINE)
for i in range(1, len(sections)-1, 2):
header = sections[i].strip()
body = sections[i+1].strip() if i+1 < len(sections) else ''
if not body:
print(f'VUOTA: {header!r}')
elif len(body) < 80:
print(f'CORTA ({len(body)} char): {header!r} → {body[:60]!r}')
"
```
Le sezioni vuote generano chunk inutili. Eliminale o accorpale alla sezione precedente.
**Check 6 — Gerarchia strutturale**
```bash
grep -n "^#\{1,3\} " step-4/<stem>/clean.md | head -50
```
Deve esserci un solo `# h1` all'inizio. Poi `## h2` e opzionalmente `### h3`.
Segnala `###` prima del primo `##`, o più di un `#`.
---
**Struttura target dopo la revisione:** **Struttura target dopo la revisione:**
@@ -441,36 +182,31 @@ Ogni paragrafo è semanticamente autonomo.
Una riga vuota separa le sezioni. Una riga vuota separa le sezioni.
``` ```
**Criterio di qualità:** **Criterio di qualità:** leggi ogni sezione ad alta voce. Se suona naturale è corretta. Se si interrompe c'è una riga spezzata. Se suona ripetitiva c'è un artefatto.
Leggi ogni sezione ad alta voce. Se suona naturale è corretta.
Se si interrompe c'è una riga spezzata. Se suona ripetitiva c'è un artefatto.
--- ---
### Step 5 — Chunking adattivo ### Chunking
**Tipo:** automatico **Tipo:** automatico
**Input:** `step-4/<stem>/clean.md` + `step-4/<stem>/structure_profile.json` **Input:** `conversione/<stem>/clean.md`
**Output:** `step-5/<stem>/chunks.json` **Output:** `step-5/<stem>/chunks.json`
**Script:** `step-5/chunker.py` **Script:** `step-5/chunker.py`
```bash ```bash
python step-5/chunker.py --stem documento python step-5/chunker.py --stem <stem>
``` ```
Divide il Markdown pulito in chunk. Usa il profilo strutturale Divide il Markdown pulito in chunk. Usa il profilo strutturale da `report.json` per scegliere la strategia giusta. Non sa nulla del contenuto — si basa solo sulla struttura.
per scegliere la strategia giusta. Non sa nulla del contenuto —
si basa solo sulla struttura.
**Regole invarianti per qualsiasi documento:** **Regole invarianti per qualsiasi documento:**
- Un chunk non attraversa mai il confine tra due sezioni diverse - Un chunk non attraversa mai il confine tra due sezioni diverse
- Un chunk non spezza mai una frase a metà - Un chunk non spezza mai una frase a metà
- Ogni chunk porta il suo contesto nel prefisso - Ogni chunk porta il suo contesto nel prefisso
- L'overlap tra chunk avviene solo su frasi intere, - L'overlap tra chunk avviene solo su frasi intere, mai tra sezioni diverse
mai tra sezioni diverse
**Parametri:** **Parametri (in `step-5/chunker.py`):**
| Parametro | Default | Significato | | Parametro | Default | Significato |
|---|---|---| |---|---|---|
@@ -491,95 +227,54 @@ si basa solo sulla struttura.
} }
``` ```
Il prefisso `[Sezione > Titolo]` è fondamentale: permette all'embedding Il prefisso `[Sezione > Titolo]` è fondamentale: permette all'embedding di catturare il contesto topico del chunk anche quando il testo da solo sarebbe ambiguo.
di catturare il contesto topico del chunk anche quando il testo
da solo sarebbe ambiguo.
--- ---
### Step 6 — Verifica e fix chunk ### Verifica chunk
**Tipo:** automatico **Tipo:** automatico
**Input:** `step-5/<stem>/chunks.json` **Input:** `step-5/<stem>/chunks.json`
**Output:** `step-6/<stem>/chunks.json` verificato + `report.json` **Output:** `step-6/<stem>/chunks.json` verificato + `report.json`
**Script:** `step-6/verify_chunks.py`, `step-6/fix_chunks.py` **Script:** `step-6/verify_chunks.py`, `step-6/fix_chunks.py`
Questo step si articola in un ciclo: verifica → fix automatico → ri-verifica. Non si va allo step 8 finché non ci sono 🔴. Questo passaggio si articola in un ciclo: verifica → fix automatico → ri-verifica. Non si procede alla vettorizzazione finché non ci sono 🔴.
**Workflow completo:** **Workflow:**
``` ```
1. Verifica 1. Verifica
python step-6/verify_chunks.py --stem documento python step-6/verify_chunks.py --stem <stem>
2a. Se ✅ OK o solo 🟡 → vai allo step 8 2a. Se ✅ OK o solo 🟡 → vai alla vettorizzazione
2b. Se ci sono 🔴 → prova il fix automatico: 2b. Se ci sono 🔴 → prova il fix automatico:
python step-6/fix_chunks.py --stem documento --dry-run # anteprima python step-6/fix_chunks.py --stem <stem> --dry-run # anteprima
python step-6/fix_chunks.py --stem documento # applica python step-6/fix_chunks.py --stem <stem> # applica
3. Ri-verifica dopo il fix: 3. Ri-verifica dopo il fix:
python step-6/verify_chunks.py --stem documento python step-6/verify_chunks.py --stem <stem>
4. Se rimangono 🔴 → torna allo step 4 e correggi clean.md, 4. Se rimangono 🔴 → torna alla revisione Markdown e correggi clean.md,
poi riesegui dall'inizio: poi riesegui dall'inizio:
python step-5/chunker.py --stem documento --force python step-5/chunker.py --stem <stem> --force
python step-6/verify_chunks.py --stem documento python step-6/verify_chunks.py --stem <stem>
``` ```
> **Shortcut con Claude:** usa `/step6-fix <stem>` — esegue dry-run, spiega le operazioni, chiede conferma e ri-verifica automaticamente. > **Shortcut con Claude:** usa `/step6-fix <stem>` — esegue dry-run, spiega le operazioni, chiede conferma e ri-verifica automaticamente.
#### Senza Claude Code — come leggere l'output e decidere **Output di `verify_chunks.py` — tre condizioni finali:**
**1. Leggi l'output di `verify_chunks.py`**
L'output termina con una delle tre condizioni:
| Condizione | Significato | Cosa fare | | Condizione | Significato | Cosa fare |
|---|---|---| |---|---|---|
| `✅ N/N documenti senza problemi` | Nessun problema | Vai allo step 8 | | `✅ N/N documenti senza problemi` | Nessun problema | Procedi |
| `🟡 Solo avvisi minori` | Chunk corti o lunghi, non bloccanti | Puoi andare allo step 8 oppure ottimizzare con `fix_chunks.py` | | `🟡 Solo avvisi minori` | Chunk corti o lunghi, non bloccanti | Puoi procedere o ottimizzare con `fix_chunks.py` |
| `⚠️ 0/N documenti senza problemi` + 🔴 | Frasi spezzate o chunk vuoti | Esegui `fix_chunks.py`, poi ri-verifica | | `⚠️ 0/N documenti senza problemi` + 🔴 | Frasi spezzate o chunk vuoti | Esegui `fix_chunks.py`, poi ri-verifica |
**2. Prima di applicare il fix: leggi il dry-run** **Cosa verifica:**
```bash - Nessun chunk sotto `MIN_CHARS` 🟡
python step-6/fix_chunks.py --stem <stem> --dry-run - Nessun chunk sopra `MAX_CHARS × 1.5` 🟡
```
L'output elenca le operazioni pianificate. Significato:
| Operazione | Cosa fa | Sicurezza |
|---|---|---|
| `fondi N chunk incompleti` | Unisce il chunk troncato col successivo | Sempre sicura |
| `fondi N chunk troppo corti` | Unisce chunk <200 char col successivo | Sicura se il risultato non supera MAX×1.5 |
| `spezza N chunk troppo lunghi` | Divide chunk >1200 char su frasi | Sicura solo se esistono frasi naturali dove spezzare |
| `rimuovi N chunk vuoti` | Elimina chunk senza testo | Sempre sicura |
**3. Se i 🔴 persistono dopo il fix**
`fix_chunks.py` non riesce ad autocorreggersi quando il problema
è nella struttura del testo sorgente. I casi tipici e la soluzione in `clean.md`:
| Sintomo nel report | Causa in `clean.md` | Correzione |
|---|---|---|
| Chunk finisce con `:` | Intro di un elenco separata dall'elenco da una riga vuota | Rimuovi la riga vuota tra l'intro e la lista |
| Chunk finisce a metà parola | Salto di pagina PDF con numero di pagina nel mezzo | Trova e rimuovi il numero di pagina, unisci le righe |
| Chunk con testo artefatto (URL, watermark) | Artefatto non rimosso allo step 4 | Elimina la sezione in `clean.md` |
| Chunk con frase enorme non spezzabile | Singolo paragrafo >MAX_CHARS senza frasi intermedie | Spezza manualmente il paragrafo su frasi logiche |
Dopo ogni correzione in `clean.md` riesegui dall'inizio dello step 5:
```bash
python step-5/chunker.py --stem <stem> --force
rm -f step-6/<stem>/chunks.json # forza la rilettura da step-5
python step-6/verify_chunks.py --stem <stem>
```
**Cosa verifica `verify_chunks.py`:**
- Nessun chunk è sotto `MIN_CHARS` 🟡
- Nessun chunk è sopra `MAX_CHARS × 1.5` 🟡
- Ogni chunk finisce con punteggiatura di fine frase 🔴 - Ogni chunk finisce con punteggiatura di fine frase 🔴
**Cosa corregge `fix_chunks.py`:** **Cosa corregge `fix_chunks.py`:**
@@ -591,52 +286,39 @@ python step-6/verify_chunks.py --stem <stem>
| Fondi chunk troppo corto col successivo | Chunk sotto `MIN_CHARS` | | Fondi chunk troppo corto col successivo | Chunk sotto `MIN_CHARS` |
| Spezza chunk troppo lungo | Chunk sopra `MAX_CHARS × 1.5` | | Spezza chunk troppo lungo | Chunk sopra `MAX_CHARS × 1.5` |
**Tabella diagnosi problemi non risolvibili con fix_chunks:** **Se i 🔴 persistono dopo il fix** — i casi tipici e la soluzione in `clean.md`:
| Sintomo | Causa probabile | Soluzione | | Sintomo nel report | Causa in `clean.md` | Correzione |
|---|---|---| |---|---|---|
| Molti chunk corti dopo il fix | `MIN_CHARS` troppo alto o testo frammentato nel MD | Abbassa `MIN_CHARS` o correggi step 4 | | Chunk finisce con `:` | Intro di un elenco separata dall'elenco da una riga vuota | Rimuovi la riga vuota tra l'intro e la lista |
| Chunk spezzato creato dal fix stesso | Frase singola > `MAX_CHARS` non spezzabile | Spezza manualmente il paragrafo in step 4 | | Chunk finisce a metà parola | Numero di pagina nel mezzo del testo | Trova e rimuovi il numero di pagina, unisci le righe |
| Chunk che finisce a metà frase non risolvibile | Salto di pagina PDF non sanato nel MD | Correggi la riga spezzata in `clean.md` | | Chunk con testo artefatto | Artefatto non rimosso nella revisione | Elimina la sezione in `clean.md` |
| Chunk con frase enorme non spezzabile | Paragrafo >MAX_CHARS senza frasi intermedie | Spezza manualmente il paragrafo |
**Output se tutto ok:**
```
Totale chunk: 301
✅ OK: 301
Distribuzione lunghezze:
Min: 187 char
Max: 923 char
Media: 401 char
✅ 1/1 documenti senza problemi
```
--- ---
### Step 7 — Installazione ambiente ### Ambiente Ollama
**Tipo:** manuale (una volta sola) **Tipo:** manuale (una volta sola)
**Input:** nessuno **Input:** nessuno
**Output:** ambiente locale funzionante **Output:** ambiente locale funzionante
**Script:** `step-7/check_env.py` **Script:** `ollama/check_env.py`
Installa Ollama, scarica i modelli e verifica l'ambiente. Si esegue una volta sola. Installa Ollama, scarica i modelli e verifica l'ambiente. Si esegue una volta sola prima della vettorizzazione.
Vedi [`step-7/README.md`](step-7/README.md) per istruzioni dettagliate e scelta dei modelli. Vedi [`ollama/README.md`](ollama/README.md) per istruzioni dettagliate e scelta dei modelli.
```bash ```bash
python step-7/check_env.py python ollama/check_env.py
``` ```
--- ---
### Step 8 — Vettorizzazione ### Vettorizzazione
**Tipo:** automatico (lento) **Tipo:** automatico (lento)
**Input:** `step-6/<stem>/chunks.json` **Input:** `step-6/<stem>/chunks.json`
**Output:** `chroma_db/` popolato **Output:** `chroma_db/<stem>` popolato
**Script:** `step-8/ingest.py` **Script:** `step-8/ingest.py`
```bash ```bash
@@ -653,125 +335,126 @@ Per 900 chunk aspetta circa 15 minuti.
| Argomento | Descrizione | | Argomento | Descrizione |
|---|---| |---|---|
| `--stem <nome>` | Processa un singolo documento. Senza questo argomento processa tutti gli stem trovati in `step-6/` | | `--stem <nome>` | Processa un singolo documento. Senza questo argomento processa tutti gli stem trovati in `step-6/` |
| `--force` | Cancella e ricrea la collection se esiste già. Senza `--force`, se la collection è presente lo step viene saltato | | `--force` | Cancella e ricrea la collection se esiste già |
**Quando usare `--force`:** **Quando usare `--force`:**
Se hai modificato i chunk (es. hai rieseguito step-6 dopo correzioni), la collection in ChromaDB Se hai modificato i chunk o cambiato `EMBED_MODEL` in `config.py`, la collection in ChromaDB contiene i vecchi vettori. `--force` la cancella e ricrea da zero.
contiene ancora i vecchi vettori. `--force` la cancella e la ricrea da zero con i chunk aggiornati.
**Cosa succede per ogni chunk:** **Cosa succede per ogni chunk:**
``` ```
testo del chunk testo del chunk
▼ Ollama (nomic-embed-text) ▼ Ollama (EMBED_MODEL)
vettore di 768 numeri vettore N-dim
[0.23, -0.41, 0.87, 0.12, ...]
▼ ChromaDB ▼ ChromaDB
salva: testo + vettore + metadati (sezione, titolo, sub_index) salva: testo + vettore + metadati (sezione, titolo, sub_index)
``` ```
**Perché 768 numeri:** Vedi [`step-8/README.md`](step-8/README.md) per la scelta del modello di embedding e le regole di coerenza con la fase di interrogazione.
Ogni numero rappresenta una dimensione semantica.
Testi con significato simile producono vettori simili —
i loro numeri sono vicini nello spazio a 768 dimensioni.
Questo è ciò che permette il retrieval semantico.
**Output durante l'esecuzione:**
```
✅ Ollama OK — nomic-embed-text disponibile
📦 872 chunk da ingestire
[ 1/872] ✓ sezione_1__sotto_1__s0 ETA: 870s
[ 2/872] ✓ sezione_1__sotto_2__s0 ETA: 867s
...
[872/872] ✓ sezione_9__sotto_42__s0 ETA: 0s
✅ Ingestione completata in 718s — 872/872 chunk salvati
Collection 'nietzsche' in chroma_db/
```
`chroma_db/` contiene ora tutti i vettori su disco.
Non è necessario ripetere questo step a meno che il documento cambi.
--- ---
### Step 9 — Pipeline RAG ### Interrogazione
**Tipo:** interattivo **Tipo:** interattivo
**Input:** `chroma_db/` + domanda dell'utente **Input:** `chroma_db/<stem>` + domanda dell'utente
**Output:** risposta basata sul documento **Output:** risposta basata sul documento
**Script:** `step-9/rag.py`
Due modalità:
| Script | Modalità | Quando usarlo |
|---|---|---|
| `rag.py` | Retrieval + generazione LLM | Risposta in linguaggio naturale |
| `retrieve.py` | Solo retrieval (no LLM) | Debug, verifica chunk, ricerca semantica |
#### rag.py — Risposta in linguaggio naturale
```bash ```bash
source .venv/bin/activate source .venv/bin/activate
python step-9/rag.py --stem <nome> python rag.py --stem <nome>
``` ```
Loop interattivo che risponde a domande sul documento. Configura i parametri in `step-9/config.py` prima di avviare. | Sintassi | Comportamento |
|---|---|
| `<testo>` | Risposta basata sul documento |
| `<testo> -v` | Risposta + chunk recuperati con score di similarità |
| `exit` | Esce dal programma |
Vedi [`step-9/README.md`](step-9/README.md) per la configurazione completa. Flusso interno:
--- ```
domanda
▼ embed (EMBED_MODEL, Ollama)
vettore N-dim
▼ query ChromaDB — similarità coseno, top-K
chunk rilevanti
▼ build_prompt (SYSTEM_PROMPT + contesti + domanda)
▼ generate (OLLAMA_MODEL, Ollama)
risposta
```
### Step 10 — Test automatici #### retrieve.py — Retrieval puro (senza LLM)
**Tipo:** automatico
**Input:** sistema completo
**Output:** tutti i test verdi
**Script:** `step-10/test_pipeline.py` *(da implementare)*
```bash ```bash
python step-10/test_pipeline.py --stem <nome> source .venv/bin/activate
python retrieve.py --stem <nome>
``` ```
Verifica ogni componente in isolamento e poi nel sistema completo. Vettorizza la query e restituisce i chunk più simili con score di similarità — senza chiamare Ollama per la generation. Utile per verificare la qualità del retrieval e diagnosticare risposte sbagliate.
I test non dipendono dal contenuto del documento — usano dati
fittizi creati e distrutti in memoria.
**Struttura dei test:** | Sintassi | Comportamento |
|---|---|
| `<testo>` | Chunk più simili con score (testo troncato a 200 car.) |
| `<testo> -f` | Chunk più simili con testo completo |
| `exit` | Esce dal programma |
Accetta `--top-k N` per sovrascrivere il valore di `config.py` per quella sessione.
#### Configurazione (`config.py`)
| Parametro | Default | Descrizione |
|---|---|---|
| `TOP_K` | `6` | Chunk recuperati per ogni domanda. Valori consigliati: `3``10` |
| `TEMPERATURE` | `0.0` | Deterministico a `0.0`, creativo verso `1.0`. Per RAG consigliato `0.0` |
| `NO_THINK` | `True` | Disabilita il chain-of-thought interno dei modelli Qwen3/Qwen3.5. `True` = risposta diretta, più veloce |
| `EMBED_MODEL` | `"nomic-embed-text"` | Deve corrispondere al modello usato in `ingest.py`. Se cambiato, rieseguire con `--force` |
| `OLLAMA_URL` | `"http://localhost:11434"` | Modifica solo se Ollama gira su porta o host diversi |
| `OLLAMA_MODEL` | `"qwen3.5:0.8b"` | Modello LLM. Vedi [`ollama/README.md`](ollama/README.md) per la scelta |
| `SYSTEM_PROMPT` | *(vedi file)* | Istruzioni di comportamento inviate al LLM. Modifica per cambiare tono, lingua o condizione di fallback |
#### Test senza RAG
Per verificare che Ollama risponda correttamente prima di interrogare il documento:
```bash
python ollama/test_ollama.py
``` ```
Test unitari — ogni componente isolato
✓ split_sentences non spezza le frasi
✓ parse_markdown rileva la struttura corretta
✓ chunk_sezione rispetta i boundary
✓ il prefisso è sempre presente in ogni chunk
Test integrazione — i componenti parlano tra loro Chat diretta con il modello, senza ChromaDB. Usa gli stessi parametri di `config.py`.
✓ Ollama è raggiungibile
✓ i modelli sono disponibili
✓ l'embedding produce 768 dimensioni
✓ testi diversi producono vettori diversi
✓ ChromaDB scrive e legge correttamente
Test qualità — il sistema si comporta bene
✓ il retrieval trova il chunk pertinente
✓ il retrieval non trova il chunk non pertinente
✓ il LLM usa il contesto fornito
✓ il LLM ammette quando la risposta non è nel contesto
```
--- ---
## Principi di progettazione ## Principi di progettazione
**Atomico** **Atomico**
Ogni step fa una cosa sola. Il chunker non sa niente di Ollama. Ogni fase fa una cosa sola. Il chunker non sa niente di Ollama.
L'ingestione non sa niente del MD originale. L'ingestione non sa niente del MD originale.
Se un pezzo si rompe, sai esattamente dove. Se un pezzo si rompe, sai esattamente dove.
**Verificabile** **Verificabile**
Ogni step ha un criterio di completamento oggettivo. Ogni fase ha un criterio di completamento oggettivo.
Non si passa allo step successivo finché il precedente non è verificato. Non si passa alla fase successiva finché la precedente non è verificata.
**Reversibile** **Reversibile**
Puoi tornare indietro senza perdere il lavoro degli altri step. Puoi tornare indietro senza perdere il lavoro delle altre fasi.
Cambi il MD? Riesegui solo step 5 e 8. Cambi il MD? Riesegui solo chunking e vettorizzazione.
Cambi i parametri del chunker? Riesegui solo step 5 e 8. Cambi i parametri del chunker? Riesegui solo chunking e vettorizzazione.
Non si riparte mai da zero. Non si riparte mai da zero.
**Senza assunzioni** **Senza assunzioni**