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:
@@ -3,7 +3,7 @@
|
||||
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.
|
||||
|
||||
**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
|
||||
|
||||
---
|
||||
@@ -12,18 +12,14 @@ Funziona su qualsiasi PDF digitale. Gira interamente in locale, senza GPU, senza
|
||||
|
||||
- [Panoramica](#panoramica)
|
||||
- [Struttura del progetto](#struttura-del-progetto)
|
||||
- [Gli step](#gli-step)
|
||||
- [Step 0 — Scegli il PDF](#step-0--scegli-il-pdf)
|
||||
- [Step 1 — Ispezione automatica](#step-1--ispezione-automatica)
|
||||
- [Step 2 — Conversione in Markdown grezzo](#step-2--conversione-in-markdown-grezzo)
|
||||
- [Step 3 — Rilevamento struttura](#step-3--rilevamento-struttura)
|
||||
- [Step 4 — Revisione manuale](#step-4--revisione-manuale)
|
||||
- [Step 5 — Chunking adattivo](#step-5--chunking-adattivo)
|
||||
- [Step 6 — Verifica chunk](#step-6--verifica-chunk)
|
||||
- [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)
|
||||
- [Pipeline](#pipeline)
|
||||
- [Conversione](#conversione)
|
||||
- [Revisione Markdown](#revisione-markdown)
|
||||
- [Chunking](#chunking)
|
||||
- [Verifica chunk](#verifica-chunk)
|
||||
- [Ambiente Ollama](#ambiente-ollama)
|
||||
- [Vettorizzazione](#vettorizzazione)
|
||||
- [Interrogazione](#interrogazione)
|
||||
- [Principi di progettazione](#principi-di-progettazione)
|
||||
|
||||
---
|
||||
@@ -31,36 +27,35 @@ Funziona su qualsiasi PDF digitale. Gira interamente in locale, senza GPU, senza
|
||||
## Panoramica
|
||||
|
||||
```
|
||||
PDF
|
||||
└─► STEP 1 Ispezione automatica
|
||||
└─► STEP 2 Conversione in Markdown grezzo
|
||||
└─► STEP 3 Rilevamento struttura
|
||||
└─► STEP 4 Revisione manuale ← step più importante
|
||||
└─► STEP 5 Chunking adattivo
|
||||
└─► STEP 6 Verifica chunk
|
||||
└─► STEP 8 Vettorizzazione
|
||||
└─► STEP 9 Pipeline RAG
|
||||
└─► STEP 10 Test automatici
|
||||
|
||||
STEP 0 Prerequisito iniziale (PDF adatto)
|
||||
STEP 7 Prerequisito tecnico (ambiente locale)
|
||||
PDF (sources/)
|
||||
│
|
||||
▼ conversione/pipeline.py
|
||||
clean.md ← revisiona con /prepare-md
|
||||
│
|
||||
▼ step-5/chunker.py
|
||||
chunks.json
|
||||
│
|
||||
▼ step-6/verify_chunks.py + fix_chunks.py
|
||||
chunks.json verificato
|
||||
│
|
||||
▼ step-8/ingest.py
|
||||
ChromaDB
|
||||
│
|
||||
▼ rag.py
|
||||
risposta
|
||||
```
|
||||
|
||||
### Dove si concentra il rischio
|
||||
|
||||
| Step | Rischio | Motivo |
|
||||
| Fase | Rischio | Motivo |
|
||||
|---|---|---|
|
||||
| Step 0 | 🔴 Alto | Un PDF inadatto invalida tutto il lavoro successivo |
|
||||
| Step 1 | 🟢 Basso | Automatico, solo osservazione |
|
||||
| Step 2 | 🟢 Basso | Automatico, tool maturo |
|
||||
| Step 3 | 🟢 Basso | Automatico, solo analisi |
|
||||
| Step 4 | 🔴 Alto | Manuale — la qualità del MD determina la qualità del RAG |
|
||||
| Step 5 | 🟡 Medio | Logica adattiva, dipende dalla qualità del MD |
|
||||
| Step 6 | 🟢 Basso | Automatico, solo verifica |
|
||||
| Step 7 | 🟢 Basso | Installazione standard |
|
||||
| Step 8 | 🟢 Basso | Meccanico, lento ma affidabile |
|
||||
| Step 9 | 🟡 Medio | Qualità del prompt |
|
||||
| Step 10 | 🟢 Basso | Test automatici |
|
||||
| Conversione | 🟡 Medio | Automatica, ma il PDF deve essere digitale e non protetto |
|
||||
| Revisione Markdown | 🔴 Alto | Manuale — la qualità del MD determina la qualità del RAG |
|
||||
| Chunking | 🟡 Medio | Logica adattiva, dipende dalla qualità del MD |
|
||||
| Verifica chunk | 🟢 Basso | Automatica, solo verifica |
|
||||
| Ambiente Ollama | 🟢 Basso | Installazione standard |
|
||||
| Vettorizzazione | 🟢 Basso | Meccanica, lenta ma affidabile |
|
||||
| Interrogazione | 🟡 Medio | Qualità del prompt e dei parametri in `config.py` |
|
||||
|
||||
---
|
||||
|
||||
@@ -72,54 +67,38 @@ rag-from-scratch/
|
||||
├── sources/ # PDF originali — non modificare mai
|
||||
│ └── documento.pdf
|
||||
│
|
||||
├── step-0/
|
||||
│ └── check_pdf.py # Verifica requisiti del PDF
|
||||
│
|
||||
├── step-1/
|
||||
│ └── inspect_pdf.py # Ispezione automatica del PDF
|
||||
│
|
||||
├── step-2/
|
||||
│ ├── convert_pdf.py # Conversione PDF → Markdown grezzo
|
||||
├── conversione/ # PDF → Markdown strutturato
|
||||
│ ├── pipeline.py # Conversione PDF → clean.md
|
||||
│ ├── validate.py # Validazione batch di tutti gli 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/
|
||||
│ ├── detect_structure.py # Rilevamento struttura MD
|
||||
├── step-5/ # Chunking adattivo
|
||||
│ ├── chunker.py
|
||||
│ └── <stem>/
|
||||
│ └── structure_profile.json # Profilo struttura
|
||||
│ └── chunks.json
|
||||
│
|
||||
├── step-4/
|
||||
│ ├── revise.py # Pre-processing automatico MD
|
||||
│ ├── revision_log.md # Log modifiche manuali
|
||||
│ └── <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
|
||||
├── step-6/ # Verifica e fix chunk
|
||||
│ ├── verify_chunks.py
|
||||
│ ├── fix_chunks.py
|
||||
│ └── <stem>/
|
||||
│ └── chunks.json # Chunk verificati
|
||||
│
|
||||
├── step-7/
|
||||
│ ├── check_env.py # Verifica ambiente locale
|
||||
│ └── 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
|
||||
├── step-8/ # Vettorizzazione → ChromaDB
|
||||
│ ├── ingest.py
|
||||
│ └── 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
|
||||
├── .gitignore
|
||||
└── README.md
|
||||
@@ -127,305 +106,67 @@ rag-from-scratch/
|
||||
|
||||
---
|
||||
|
||||
## Gli step
|
||||
## Pipeline
|
||||
|
||||
---
|
||||
|
||||
### Step 0 — Scegli il PDF
|
||||
|
||||
**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
|
||||
### Conversione
|
||||
|
||||
**Tipo:** automatico
|
||||
**Input:** tutti i PDF in `sources/`
|
||||
**Output:** `step-1/<stem>_step1_report.txt`
|
||||
**Script:** `step-1/inspect_pdf.py`
|
||||
**Input:** `sources/<stem>.pdf`
|
||||
**Output:** `conversione/<stem>/clean.md` + `report.json`
|
||||
**Script:** `conversione/pipeline.py`
|
||||
|
||||
```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.
|
||||
Serve per capire la qualità del documento e mappare i problemi
|
||||
prima di affrontare la revisione manuale.
|
||||
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.
|
||||
|
||||
**Cosa rileva:**
|
||||
Produce tre file in `conversione/<stem>/`:
|
||||
|
||||
- Testo non estraibile (pagine con sole immagini)
|
||||
- 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 |
|
||||
| File | Descrizione |
|
||||
|---|---|
|
||||
| ≥ 70 | Procedi allo step 2 |
|
||||
| 40–70 | Procedi con cautela, revisione estesa necessaria |
|
||||
| < 40 | Valuta una fonte PDF migliore |
|
||||
| `raw.md` | Markdown grezzo estratto dal PDF — **non modificare mai** |
|
||||
| `clean.md` | Markdown pulito e strutturato — input per il chunker |
|
||||
| `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
|
||||
|
||||
**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`
|
||||
**Validazione batch:**
|
||||
|
||||
```bash
|
||||
python step-2/convert_pdf.py # tutti i PDF in sources/
|
||||
python step-2/convert_pdf.py --pdf sources/doc.pdf # un solo PDF
|
||||
python conversione/validate.py
|
||||
```
|
||||
|
||||
Converte il PDF in Markdown usando `pymupdf4llm`. Il risultato non è perfetto — è la base
|
||||
su cui lavorerai nello step 4.
|
||||
Mostra una tabella di stato per tutti gli stem convertiti. Vedi [`conversione/README.md`](conversione/README.md) per dettagli completi.
|
||||
|
||||
Lo script crea due file:
|
||||
- `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
|
||||
**PDF supportati:** digitali con testo selezionabile. Non supportati: scansionati (solo immagini) e protetti da password.
|
||||
|
||||
---
|
||||
|
||||
### Step 3 — Rilevamento struttura
|
||||
### Revisione Markdown
|
||||
|
||||
**Tipo:** automatico
|
||||
**Input:** `step-2/<stem>/`
|
||||
**Output:** `step-3/<stem>/structure_profile.json`
|
||||
**Script:** `step-3/detect_structure.py`
|
||||
**Tipo:** semi-automatico
|
||||
**Input:** `conversione/<stem>/clean.md`
|
||||
**Output:** `conversione/<stem>/clean.md` corretto in-place
|
||||
|
||||
```bash
|
||||
python step-3/detect_structure.py # tutti i documenti in step-2/
|
||||
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
|
||||
> Questo è il passaggio più importante dell'intera pipeline.
|
||||
> La qualità del RAG dipende da questo passaggio più di qualsiasi
|
||||
> parametro tecnico o scelta di modello.
|
||||
|
||||
#### Pre-processing automatico
|
||||
|
||||
Prima di qualsiasi revisione manuale, esegui lo script di revisione automatica:
|
||||
|
||||
```bash
|
||||
python step-4/revise.py --stem documento
|
||||
```
|
||||
/prepare-md conversione/<stem>/clean.md
|
||||
```
|
||||
|
||||
Lo script applica le seguenti trasformazioni euristiche, valide per qualsiasi documento:
|
||||
|
||||
| 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 `#`.
|
||||
|
||||
---
|
||||
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.
|
||||
|
||||
**Struttura target dopo la revisione:**
|
||||
|
||||
@@ -441,36 +182,31 @@ Ogni paragrafo è semanticamente autonomo.
|
||||
Una riga vuota separa le sezioni.
|
||||
```
|
||||
|
||||
**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.
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
### Step 5 — Chunking adattivo
|
||||
### Chunking
|
||||
|
||||
**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`
|
||||
**Script:** `step-5/chunker.py`
|
||||
|
||||
```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
|
||||
per scegliere la strategia giusta. Non sa nulla del contenuto —
|
||||
si basa solo sulla struttura.
|
||||
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.
|
||||
|
||||
**Regole invarianti per qualsiasi documento:**
|
||||
|
||||
- Un chunk non attraversa mai il confine tra due sezioni diverse
|
||||
- Un chunk non spezza mai una frase a metà
|
||||
- Ogni chunk porta il suo contesto nel prefisso
|
||||
- L'overlap tra chunk avviene solo su frasi intere,
|
||||
mai tra sezioni diverse
|
||||
- L'overlap tra chunk avviene solo su frasi intere, mai tra sezioni diverse
|
||||
|
||||
**Parametri:**
|
||||
**Parametri (in `step-5/chunker.py`):**
|
||||
|
||||
| Parametro | Default | Significato |
|
||||
|---|---|---|
|
||||
@@ -491,95 +227,54 @@ si basa solo sulla struttura.
|
||||
}
|
||||
```
|
||||
|
||||
Il prefisso `[Sezione > Titolo]` è fondamentale: permette all'embedding
|
||||
di catturare il contesto topico del chunk anche quando il testo
|
||||
da solo sarebbe ambiguo.
|
||||
Il prefisso `[Sezione > Titolo]` è fondamentale: permette all'embedding 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
|
||||
**Input:** `step-5/<stem>/chunks.json`
|
||||
**Output:** `step-6/<stem>/chunks.json` verificato + `report.json`
|
||||
**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
|
||||
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:
|
||||
python step-6/fix_chunks.py --stem documento --dry-run # anteprima
|
||||
python step-6/fix_chunks.py --stem documento # applica
|
||||
python step-6/fix_chunks.py --stem <stem> --dry-run # anteprima
|
||||
python step-6/fix_chunks.py --stem <stem> # applica
|
||||
|
||||
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:
|
||||
python step-5/chunker.py --stem documento --force
|
||||
python step-6/verify_chunks.py --stem documento
|
||||
python step-5/chunker.py --stem <stem> --force
|
||||
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.
|
||||
|
||||
#### Senza Claude Code — come leggere l'output e decidere
|
||||
|
||||
**1. Leggi l'output di `verify_chunks.py`**
|
||||
|
||||
L'output termina con una delle tre condizioni:
|
||||
**Output di `verify_chunks.py` — tre condizioni finali:**
|
||||
|
||||
| Condizione | Significato | Cosa fare |
|
||||
|---|---|---|
|
||||
| `✅ N/N documenti senza problemi` | Nessun problema | Vai allo step 8 |
|
||||
| `🟡 Solo avvisi minori` | Chunk corti o lunghi, non bloccanti | Puoi andare allo step 8 oppure ottimizzare con `fix_chunks.py` |
|
||||
| `✅ N/N documenti senza problemi` | Nessun problema | Procedi |
|
||||
| `🟡 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 |
|
||||
|
||||
**2. Prima di applicare il fix: leggi il dry-run**
|
||||
**Cosa verifica:**
|
||||
|
||||
```bash
|
||||
python step-6/fix_chunks.py --stem <stem> --dry-run
|
||||
```
|
||||
|
||||
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` 🟡
|
||||
- Nessun chunk sotto `MIN_CHARS` 🟡
|
||||
- Nessun chunk sopra `MAX_CHARS × 1.5` 🟡
|
||||
- Ogni chunk finisce con punteggiatura di fine frase 🔴
|
||||
|
||||
**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` |
|
||||
| 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 spezzato creato dal fix stesso | Frase singola > `MAX_CHARS` non spezzabile | Spezza manualmente il paragrafo in step 4 |
|
||||
| Chunk che finisce a metà frase non risolvibile | Salto di pagina PDF non sanato nel MD | Correggi la riga spezzata in `clean.md` |
|
||||
|
||||
**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
|
||||
```
|
||||
| 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 | Numero di pagina nel mezzo del testo | Trova e rimuovi il numero di pagina, unisci le righe |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
### Step 7 — Installazione ambiente
|
||||
### Ambiente Ollama
|
||||
|
||||
**Tipo:** manuale (una volta sola)
|
||||
**Input:** nessuno
|
||||
**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
|
||||
python step-7/check_env.py
|
||||
python ollama/check_env.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 8 — Vettorizzazione
|
||||
### Vettorizzazione
|
||||
|
||||
**Tipo:** automatico (lento)
|
||||
**Input:** `step-6/<stem>/chunks.json`
|
||||
**Output:** `chroma_db/` popolato
|
||||
**Output:** `chroma_db/<stem>` popolato
|
||||
**Script:** `step-8/ingest.py`
|
||||
|
||||
```bash
|
||||
@@ -653,125 +335,126 @@ Per 900 chunk aspetta circa 15 minuti.
|
||||
| Argomento | Descrizione |
|
||||
|---|---|
|
||||
| `--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`:**
|
||||
Se hai modificato i chunk (es. hai rieseguito step-6 dopo correzioni), la collection in ChromaDB
|
||||
contiene ancora i vecchi vettori. `--force` la cancella e la ricrea da zero con i chunk aggiornati.
|
||||
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.
|
||||
|
||||
**Cosa succede per ogni chunk:**
|
||||
|
||||
```
|
||||
testo del chunk
|
||||
│
|
||||
▼ Ollama (nomic-embed-text)
|
||||
vettore di 768 numeri
|
||||
[0.23, -0.41, 0.87, 0.12, ...]
|
||||
▼ Ollama (EMBED_MODEL)
|
||||
vettore N-dim
|
||||
│
|
||||
▼ ChromaDB
|
||||
salva: testo + vettore + metadati (sezione, titolo, sub_index)
|
||||
```
|
||||
|
||||
**Perché 768 numeri:**
|
||||
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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
### Step 9 — Pipeline RAG
|
||||
### Interrogazione
|
||||
|
||||
**Tipo:** interattivo
|
||||
**Input:** `chroma_db/` + domanda dell'utente
|
||||
**Output:** risposta basata sul documento
|
||||
**Script:** `step-9/rag.py`
|
||||
**Input:** `chroma_db/<stem>` + domanda dell'utente
|
||||
**Output:** risposta basata sul documento
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
**Tipo:** automatico
|
||||
**Input:** sistema completo
|
||||
**Output:** tutti i test verdi
|
||||
**Script:** `step-10/test_pipeline.py` *(da implementare)*
|
||||
#### retrieve.py — Retrieval puro (senza LLM)
|
||||
|
||||
```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.
|
||||
I test non dipendono dal contenuto del documento — usano dati
|
||||
fittizi creati e distrutti in memoria.
|
||||
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.
|
||||
|
||||
**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
|
||||
✓ 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
|
||||
```
|
||||
Chat diretta con il modello, senza ChromaDB. Usa gli stessi parametri di `config.py`.
|
||||
|
||||
---
|
||||
|
||||
## Principi di progettazione
|
||||
|
||||
**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.
|
||||
Se un pezzo si rompe, sai esattamente dove.
|
||||
|
||||
**Verificabile**
|
||||
Ogni step ha un criterio di completamento oggettivo.
|
||||
Non si passa allo step successivo finché il precedente non è verificato.
|
||||
Ogni fase ha un criterio di completamento oggettivo.
|
||||
Non si passa alla fase successiva finché la precedente non è verificata.
|
||||
|
||||
**Reversibile**
|
||||
Puoi tornare indietro senza perdere il lavoro degli altri step.
|
||||
Cambi il MD? Riesegui solo step 5 e 8.
|
||||
Cambi i parametri del chunker? Riesegui solo step 5 e 8.
|
||||
Puoi tornare indietro senza perdere il lavoro delle altre fasi.
|
||||
Cambi il MD? Riesegui solo chunking e vettorizzazione.
|
||||
Cambi i parametri del chunker? Riesegui solo chunking e vettorizzazione.
|
||||
Non si riparte mai da zero.
|
||||
|
||||
**Senza assunzioni**
|
||||
|
||||
Reference in New Issue
Block a user