2026-04-12 23:53:13 +02:00
|
|
|
|
# RAG from Scratch — Singolo PDF Generico
|
|
|
|
|
|
|
|
|
|
|
|
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 8B · ChromaDB
|
|
|
|
|
|
**Compatibile con:** Linux · macOS · Windows (WSL2) · CPU Only · ~8 GB RAM libera
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Indice
|
|
|
|
|
|
|
|
|
|
|
|
- [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)
|
|
|
|
|
|
- [Principi di progettazione](#principi-di-progettazione)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Dove si concentra il rischio
|
|
|
|
|
|
|
|
|
|
|
|
| Step | 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 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Struttura del progetto
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
rag-from-scratch/
|
|
|
|
|
|
│
|
|
|
|
|
|
├── sources/ # PDF originali — non modificare mai
|
|
|
|
|
|
│ └── documento.pdf
|
|
|
|
|
|
│
|
|
|
|
|
|
├── processed/ # Output di ogni step per ogni documento
|
|
|
|
|
|
│ └── documento/
|
|
|
|
|
|
│ ├── raw.md # MD grezzo da marker (non toccare)
|
|
|
|
|
|
│ ├── clean.md # MD revisionato a mano
|
|
|
|
|
|
│ ├── structure_profile.json # Profilo struttura da step 3
|
|
|
|
|
|
│ └── chunks.json # Chunk pronti per la vettorizzazione
|
|
|
|
|
|
│
|
|
|
|
|
|
├── scripts/
|
|
|
|
|
|
│ ├── inspect.py # Step 1 — ispezione PDF
|
|
|
|
|
|
│ ├── detect_structure.py # Step 3 — analisi struttura MD
|
|
|
|
|
|
│ ├── chunker.py # Step 5 — chunking adattivo
|
|
|
|
|
|
│ ├── verify_chunks.py # Step 6 — verifica chunk
|
|
|
|
|
|
│ ├── ingest.py # Step 8 — vettorizzazione
|
|
|
|
|
|
│ ├── rag.py # Step 9 — pipeline RAG
|
|
|
|
|
|
│ └── test_pipeline.py # Step 10 — test automatici
|
|
|
|
|
|
│
|
|
|
|
|
|
├── chroma_db/ # Vector store — generato da ingest.py
|
|
|
|
|
|
├── notes/
|
|
|
|
|
|
│ └── revision_log.md # Log delle modifiche manuali al MD
|
|
|
|
|
|
├── requirements.txt
|
|
|
|
|
|
├── .gitignore
|
|
|
|
|
|
└── README.md
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Gli step
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 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
|
|
|
|
|
|
|
|
|
|
|
|
**Tipo:** automatico
|
|
|
|
|
|
**Input:** `sources/documento.pdf`
|
|
|
|
|
|
**Output:** report testuale con score e lista problemi
|
|
|
|
|
|
**Script:** `scripts/inspect.py`
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python scripts/inspect.py sources/documento.pdf --save
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Lo script analizza il PDF pagina per pagina e produce un report.
|
|
|
|
|
|
Serve per capire la qualità del documento e mappare i problemi
|
|
|
|
|
|
prima di affrontare la revisione manuale.
|
|
|
|
|
|
|
|
|
|
|
|
**Cosa rileva:**
|
|
|
|
|
|
|
|
|
|
|
|
- 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 |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| ≥ 70 | Procedi allo step 2 |
|
|
|
|
|
|
| 40–70 | Procedi con cautela, revisione estesa necessaria |
|
|
|
|
|
|
| < 40 | Valuta una fonte PDF migliore |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### Step 2 — Conversione in Markdown grezzo
|
|
|
|
|
|
|
|
|
|
|
|
**Tipo:** automatico
|
|
|
|
|
|
**Input:** `sources/documento.pdf`
|
|
|
|
|
|
**Output:** `processed/documento/raw.md`
|
|
|
|
|
|
**Tool:** marker-pdf
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
marker_single sources/documento.pdf processed/documento/raw.md
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Converte il PDF in Markdown. Il risultato non è perfetto — è la base
|
|
|
|
|
|
su cui lavorerai nello step 4.
|
|
|
|
|
|
|
|
|
|
|
|
**Regola fondamentale:** `raw.md` non va mai modificato.
|
|
|
|
|
|
È il punto di partenza di riferimento. Se qualcosa va storto
|
|
|
|
|
|
nella revisione, puoi sempre ripartire da qui.
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# Crea subito la copia su cui lavorare
|
|
|
|
|
|
cp processed/documento/raw.md processed/documento/clean.md
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Cosa produce marker:**
|
|
|
|
|
|
|
|
|
|
|
|
- Titoli riconosciuti e convertiti in `#` `##` `###`
|
|
|
|
|
|
- Paragrafi separati da righe vuote
|
|
|
|
|
|
- Sillabazione parzialmente risolta
|
|
|
|
|
|
|
|
|
|
|
|
**Cosa non produce marker:**
|
|
|
|
|
|
|
|
|
|
|
|
- Rimozione intestazioni e piè di pagina
|
|
|
|
|
|
- Correzione completa del layout a colonne
|
|
|
|
|
|
- Descrizione del contenuto delle immagini
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### Step 3 — Rilevamento struttura
|
|
|
|
|
|
|
|
|
|
|
|
**Tipo:** automatico
|
|
|
|
|
|
**Input:** `processed/documento/raw.md`
|
|
|
|
|
|
**Output:** `processed/documento/structure_profile.json`
|
|
|
|
|
|
**Script:** `scripts/detect_structure.py`
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python scripts/detect_structure.py processed/documento/raw.md
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Analizza la struttura del Markdown grezzo senza modificarlo.
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
**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`
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
|
|
|
|
|
> 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.
|
|
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
#### Pre-processing automatico
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
Prima di qualsiasi revisione manuale, esegui lo script di revisione automatica:
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
```bash
|
|
|
|
|
|
python step-4/revise.py --stem documento
|
|
|
|
|
|
```
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
Lo script applica le seguenti trasformazioni euristiche, valide per qualsiasi documento:
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
| 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 |
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
Il profilo strutturale aggiornato viene salvato in `step-4/<stem>/structure_profile.json`.
|
|
|
|
|
|
|
|
|
|
|
|
#### Revisione assistita da Claude Code
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
Dopo il pre-processing, usa la skill integrata per una revisione qualitativa:
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
```
|
|
|
|
|
|
/step4-review documento
|
2026-04-12 23:53:13 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
La skill analizza `step-4/<stem>/clean.md` e produce un report strutturato:
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
```
|
|
|
|
|
|
🔴 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.
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
#### Revisione manuale residua
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
Apri `step-4/<stem>/clean.md` nel tuo editor e verifica quanto segnalato
|
|
|
|
|
|
dalla skill. Il criterio di qualità rimane lo stesso:
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
**Struttura target:**
|
|
|
|
|
|
|
|
|
|
|
|
```markdown
|
|
|
|
|
|
# Titolo del documento
|
|
|
|
|
|
|
|
|
|
|
|
## Sezione principale
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
### Sottosezione o unità atomica
|
|
|
|
|
|
|
|
|
|
|
|
Testo fluente, frasi complete, nessun artefatto.
|
|
|
|
|
|
Ogni paragrafo è semanticamente autonomo.
|
|
|
|
|
|
Una riga vuota separa le sezioni.
|
|
|
|
|
|
```
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
|
|
|
|
|
**Il criterio di qualità:**
|
|
|
|
|
|
Leggi ogni sezione ad alta voce. Se suona naturale e fluente è corretta.
|
|
|
|
|
|
Se si interrompe c'è una riga spezzata. Se suona ripetitiva c'è un artefatto.
|
|
|
|
|
|
|
|
|
|
|
|
**Traccia il lavoro:**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-13 12:21:26 +02:00
|
|
|
|
# step-4/revision_log.md viene aggiornato automaticamente da revise.py
|
|
|
|
|
|
# Commita dopo la revisione manuale
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 12:21:26 +02:00
|
|
|
|
git add step-4/revision_log.md
|
|
|
|
|
|
git commit -m "step-4: revisione nietzsche completata"
|
2026-04-12 23:53:13 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### Step 5 — Chunking adattivo
|
|
|
|
|
|
|
|
|
|
|
|
**Tipo:** automatico
|
2026-04-13 13:36:53 +02:00
|
|
|
|
**Input:** `step-4/<stem>/clean.md` + `step-4/<stem>/structure_profile.json`
|
|
|
|
|
|
**Output:** `step-5/<stem>/chunks.json`
|
|
|
|
|
|
**Script:** `step-5/chunker.py`
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-13 13:36:53 +02:00
|
|
|
|
python step-5/chunker.py --stem documento
|
2026-04-12 23:53:13 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
|
|
|
|
**Parametri:**
|
|
|
|
|
|
|
|
|
|
|
|
| Parametro | Default | Significato |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `MIN_CHARS` | 200 | Sotto questa soglia, accorpa al chunk successivo |
|
|
|
|
|
|
| `MAX_CHARS` | 800 | Sopra questa soglia, spezza su frasi |
|
|
|
|
|
|
| `OVERLAP_S` | 2 | Frasi di overlap tra sotto-chunk dello stesso boundary |
|
|
|
|
|
|
|
|
|
|
|
|
**Struttura di ogni chunk:**
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"chunk_id": "sezione_principale__sottosezione_3__s0",
|
|
|
|
|
|
"text": "[Sezione principale > Sottosezione 3]\nTesto del chunk...",
|
|
|
|
|
|
"sezione": "Sezione principale",
|
|
|
|
|
|
"titolo": "Sottosezione 3",
|
|
|
|
|
|
"sub_index": 0,
|
|
|
|
|
|
"n_chars": 412
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
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 chunk
|
|
|
|
|
|
|
|
|
|
|
|
**Tipo:** automatico
|
2026-04-13 13:36:53 +02:00
|
|
|
|
**Input:** `step-5/<stem>/chunks.json`
|
2026-04-12 23:53:13 +02:00
|
|
|
|
**Output:** report problemi + statistiche
|
2026-04-13 14:03:41 +02:00
|
|
|
|
**Script:** `step-6/verify_chunks.py`
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-13 14:03:41 +02:00
|
|
|
|
python step-6/verify_chunks.py --stem documento
|
2026-04-12 23:53:13 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Analizza ogni chunk e segnala i problemi. Non corregge nulla.
|
|
|
|
|
|
Se ci sono problemi torni allo step 4 o aggiusti i parametri
|
|
|
|
|
|
dello step 5. Non si va allo step 8 finché questo step è pulito.
|
|
|
|
|
|
|
|
|
|
|
|
**Cosa verifica:**
|
|
|
|
|
|
|
|
|
|
|
|
- Ogni chunk ha il prefisso di contesto
|
|
|
|
|
|
- Nessun chunk è vuoto
|
|
|
|
|
|
- Nessun chunk è sotto `MIN_CHARS`
|
|
|
|
|
|
- Nessun chunk è sopra `MAX_CHARS` * 1.5
|
|
|
|
|
|
- Ogni chunk finisce con punteggiatura (frase completa)
|
|
|
|
|
|
|
|
|
|
|
|
**Tabella diagnosi:**
|
|
|
|
|
|
|
|
|
|
|
|
| Problema | Causa probabile | Soluzione |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| Molti chunk troppo corti | `MIN_CHARS` troppo alto | Abbassa `MIN_CHARS` |
|
|
|
|
|
|
| Molti chunk troppo lunghi | `MAX_CHARS` troppo basso | Alza `MAX_CHARS` |
|
|
|
|
|
|
| Chunk senza prefisso | Bug nel parsing | Controlla `###` nel MD |
|
|
|
|
|
|
| Chunk che finiscono a metà frase | Riga spezzata nel MD | Correggi nello step 4 |
|
|
|
|
|
|
|
|
|
|
|
|
**Output se tutto ok:**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Totale chunk: 301
|
|
|
|
|
|
✅ OK: 301
|
|
|
|
|
|
|
|
|
|
|
|
Distribuzione lunghezze:
|
|
|
|
|
|
Min: 187 char
|
|
|
|
|
|
Max: 923 char
|
|
|
|
|
|
Media: 401 char
|
|
|
|
|
|
|
|
|
|
|
|
✅ Nessun problema — procedi con la vettorizzazione.
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### Step 7 — Installazione ambiente
|
|
|
|
|
|
|
|
|
|
|
|
**Tipo:** manuale (una volta sola)
|
|
|
|
|
|
**Input:** nessuno
|
2026-04-13 23:57:20 +02:00
|
|
|
|
**Output:** ambiente locale funzionante
|
|
|
|
|
|
**Script:** `step-7/check_env.py`
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# Installa Ollama
|
|
|
|
|
|
curl -fsSL https://ollama.com/install.sh | sh
|
|
|
|
|
|
|
2026-04-13 23:57:20 +02:00
|
|
|
|
# Scarica un modello di embedding e un LLM
|
|
|
|
|
|
ollama pull nomic-embed-text # Embedding — ~274 MB (consigliato)
|
|
|
|
|
|
ollama pull qwen3.5:4b # LLM — ~3.4 GB (consigliato per 8 GB RAM)
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 23:57:20 +02:00
|
|
|
|
# Installa dipendenze Python nel venv
|
|
|
|
|
|
source .venv/bin/activate
|
2026-04-12 23:53:13 +02:00
|
|
|
|
pip install -r requirements.txt
|
|
|
|
|
|
|
2026-04-13 23:57:20 +02:00
|
|
|
|
# Verifica tutto
|
|
|
|
|
|
python step-7/check_env.py
|
2026-04-12 23:53:13 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-04-13 23:57:20 +02:00
|
|
|
|
**Modelli consigliati per 8 GB RAM:**
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-13 23:57:20 +02:00
|
|
|
|
| Modello | Ruolo | Dimensione |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `nomic-embed-text` | Embedding | ~274 MB |
|
|
|
|
|
|
| `qwen3.5:4b` | LLM | ~3.4 GB |
|
|
|
|
|
|
|
|
|
|
|
|
Per guide dettagliate su modelli alternativi, installazione e disinstallazione
|
|
|
|
|
|
di Ollama e ChromaDB, vedi [`step-7/README.md`](step-7/README.md).
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
|
|
|
|
|
Questo step si esegue una volta sola. Da questo momento
|
|
|
|
|
|
Ollama è sempre disponibile sul sistema.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### Step 8 — Vettorizzazione
|
|
|
|
|
|
|
|
|
|
|
|
**Tipo:** automatico (lento)
|
2026-04-14 10:59:40 +02:00
|
|
|
|
**Input:** `step-6/<stem>/chunks.json`
|
2026-04-12 23:53:13 +02:00
|
|
|
|
**Output:** `chroma_db/` popolato
|
2026-04-14 10:59:40 +02:00
|
|
|
|
**Script:** `step-8/ingest.py`
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-04-14 10:59:40 +02:00
|
|
|
|
source .venv/bin/activate
|
|
|
|
|
|
python step-8/ingest.py --stem <nome>
|
2026-04-12 23:53:13 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Trasforma ogni chunk in un vettore numerico e lo salva in ChromaDB.
|
2026-04-14 10:59:40 +02:00
|
|
|
|
È il processo più lento — su CPU circa 1 secondo per chunk.
|
|
|
|
|
|
Per 900 chunk aspetta circa 15 minuti.
|
|
|
|
|
|
|
|
|
|
|
|
**Argomenti:**
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
|
|
|
|
|
|
|
|
|
|
|
|
**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.
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
|
|
|
|
|
**Cosa succede per ogni chunk:**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
testo del chunk
|
|
|
|
|
|
│
|
|
|
|
|
|
▼ Ollama (nomic-embed-text)
|
|
|
|
|
|
vettore di 768 numeri
|
|
|
|
|
|
[0.23, -0.41, 0.87, 0.12, ...]
|
|
|
|
|
|
│
|
|
|
|
|
|
▼ ChromaDB
|
2026-04-14 10:59:40 +02:00
|
|
|
|
salva: testo + vettore + metadati (sezione, titolo, sub_index)
|
2026-04-12 23:53:13 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
2026-04-14 10:59:40 +02:00
|
|
|
|
📦 872 chunk da ingestire
|
|
|
|
|
|
|
|
|
|
|
|
[ 1/872] ✓ sezione_1__sotto_1__s0 ETA: 870s
|
|
|
|
|
|
[ 2/872] ✓ sezione_1__sotto_2__s0 ETA: 867s
|
2026-04-12 23:53:13 +02:00
|
|
|
|
...
|
2026-04-14 10:59:40 +02:00
|
|
|
|
[872/872] ✓ sezione_9__sotto_42__s0 ETA: 0s
|
2026-04-12 23:53:13 +02:00
|
|
|
|
|
2026-04-14 10:59:40 +02:00
|
|
|
|
✅ Ingestione completata in 718s — 872/872 chunk salvati
|
|
|
|
|
|
Collection 'nietzsche' in chroma_db/
|
2026-04-12 23:53:13 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`chroma_db/` contiene ora tutti i vettori su disco.
|
|
|
|
|
|
Non è necessario ripetere questo step a meno che il documento cambi.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### Step 9 — Pipeline RAG
|
|
|
|
|
|
|
|
|
|
|
|
**Tipo:** interattivo
|
|
|
|
|
|
**Input:** `chroma_db/` + domanda dell'utente
|
|
|
|
|
|
**Output:** risposta basata sul documento
|
|
|
|
|
|
**Script:** `scripts/rag.py`
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python scripts/rag.py
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Mette insieme retrieval e generation in un loop interattivo.
|
|
|
|
|
|
|
|
|
|
|
|
**Flusso per ogni domanda:**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
La tua domanda in testo
|
|
|
|
|
|
│
|
|
|
|
|
|
▼ embedding della domanda (nomic-embed-text)
|
|
|
|
|
|
vettore 768 dimensioni
|
|
|
|
|
|
│
|
|
|
|
|
|
▼ ricerca per similarità coseno in ChromaDB
|
|
|
|
|
|
top 3 chunk semanticamente più vicini
|
|
|
|
|
|
│
|
|
|
|
|
|
▼ costruzione del prompt
|
|
|
|
|
|
"Rispondi SOLO dal contesto:
|
|
|
|
|
|
[chunk 1]
|
|
|
|
|
|
[chunk 2]
|
|
|
|
|
|
[chunk 3]
|
|
|
|
|
|
Domanda: ..."
|
|
|
|
|
|
│
|
|
|
|
|
|
▼ Ollama (Qwen3 8B)
|
|
|
|
|
|
risposta generata
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Come si usa:**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Domanda: La tua domanda qui
|
|
|
|
|
|
Domanda: La tua domanda qui -v ← aggiunge i chunk recuperati
|
|
|
|
|
|
Domanda: exit
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**La similarità coseno:**
|
|
|
|
|
|
Misura l'angolo tra due vettori — non la distanza ma l'orientamento.
|
|
|
|
|
|
Due testi semanticamente simili puntano nella stessa direzione
|
|
|
|
|
|
nello spazio a 768 dimensioni, indipendentemente dalla loro lunghezza.
|
|
|
|
|
|
|
|
|
|
|
|
**Regola del prompt:**
|
|
|
|
|
|
Il LLM risponde SOLO dal contesto fornito.
|
|
|
|
|
|
Se la risposta non è nel documento lo dice esplicitamente.
|
|
|
|
|
|
Non inventa, non integra con conoscenza esterna.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### Step 10 — Test automatici
|
|
|
|
|
|
|
|
|
|
|
|
**Tipo:** automatico
|
|
|
|
|
|
**Input:** sistema completo
|
|
|
|
|
|
**Output:** tutti i test verdi
|
|
|
|
|
|
**Script:** `scripts/test_pipeline.py`
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python scripts/test_pipeline.py
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
**Struttura dei test:**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Principi di progettazione
|
|
|
|
|
|
|
|
|
|
|
|
**Atomico**
|
|
|
|
|
|
Ogni step 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.
|
|
|
|
|
|
|
|
|
|
|
|
**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.
|
|
|
|
|
|
Non si riparte mai da zero.
|
|
|
|
|
|
|
|
|
|
|
|
**Senza assunzioni**
|
|
|
|
|
|
Il sistema non assume nulla sulla struttura del documento.
|
|
|
|
|
|
Rileva il livello strutturale e si adatta.
|
|
|
|
|
|
Funziona su libri, manuali, articoli, contratti, dispense.
|
|
|
|
|
|
|
|
|
|
|
|
**Tutto locale**
|
|
|
|
|
|
Nessuna chiamata a API esterne.
|
|
|
|
|
|
Nessun dato trasmesso fuori dalla macchina.
|
|
|
|
|
|
Nessun costo di utilizzo.
|