- _score() ritorna (int, list[str]) con dettaglio penalità applicate - Rimossi criteri non pertinenti al chunking: sezioni_corte, sezioni_lunghe, mediana, p25 — il chunker le normalizza già in fase di suddivisione - Aggiunte penalità per residui che impattano i vettori: br_inline, simboli_encoding, formule_inline - Flag --detail / -d per mostrare breakdown penalità per documento - Colonne tabella aggiornate: btk, br, enc, url, med
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.5 · ChromaDB
Compatibile con: Linux · macOS · Windows (WSL2) · CPU Only · ~8 GB RAM libera
Indice
- Panoramica
- Struttura del progetto
- Gli step
- Step 0 — Scegli il PDF
- Step 1 — Ispezione automatica
- Step 2 — Conversione in Markdown grezzo
- Step 3 — Rilevamento struttura
- Step 4 — Revisione manuale
- Step 5 — Chunking adattivo
- Step 6 — Verifica chunk
- Step 7 — Installazione ambiente
- Step 8 — Vettorizzazione
- Step 9 — Pipeline RAG
- Step 10 — Test automatici
- 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
│
├── 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
│ └── <stem>/
│ └── raw.md # MD grezzo (non toccare)
│
├── step-3/
│ ├── detect_structure.py # Rilevamento struttura MD
│ └── <stem>/
│ └── structure_profile.json # Profilo struttura
│
├── 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
│ └── <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
│ └── README.md
│
├── chroma_db/ # Vector store — generato da step-8
├── 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: tutti i PDF in sources/
Output: step-1/<stem>_step1_report.txt
Script: step-1/inspect_pdf.py
python step-1/inspect_pdf.py
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.
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: 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
python step-2/convert_pdf.py # tutti i PDF in sources/
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
su cui lavorerai nello step 4.
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
Step 3 — Rilevamento struttura
Tipo: automatico
Input: step-2/<stem>/
Output: step-3/<stem>/structure_profile.json
Script: step-3/detect_structure.py
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:
{
"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.
Pre-processing automatico
Prima di qualsiasi revisione manuale, esegui lo script di revisione automatica:
python step-4/revise.py --stem documento
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):
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:
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:
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
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
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
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:
# Titolo del documento
## Sezione principale
### Sottosezione o unità atomica
Testo fluente, frasi complete, nessun artefatto.
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.
Step 5 — Chunking adattivo
Tipo: automatico
Input: step-4/<stem>/clean.md + step-4/<stem>/structure_profile.json
Output: step-5/<stem>/chunks.json
Script: step-5/chunker.py
python step-5/chunker.py --stem documento
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:
{
"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 e fix 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 🔴.
Workflow completo:
1. Verifica
python step-6/verify_chunks.py --stem documento
2a. Se ✅ OK o solo 🟡 → vai allo step 8
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
3. Ri-verifica dopo il fix:
python step-6/verify_chunks.py --stem documento
4. Se rimangono 🔴 → torna allo step 4 e correggi clean.md,
poi riesegui dall'inizio:
python step-5/chunker.py --stem documento --force
python step-6/verify_chunks.py --stem documento
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:
| 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 |
⚠️ 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
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:
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 🔴
Cosa corregge fix_chunks.py:
| Operazione | Quando |
|---|---|
| Rimuovi chunk vuoti | Chunk privi di testo |
| Fondi chunk incompleto col successivo | Chunk che finisce senza punteggiatura |
| 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:
| Sintomo | Causa probabile | Soluzione |
|---|---|---|
| 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
Step 7 — Installazione ambiente
Tipo: manuale (una volta sola)
Input: nessuno
Output: ambiente locale funzionante
Script: step-7/check_env.py
Installa Ollama, scarica i modelli e verifica l'ambiente. Si esegue una volta sola.
Vedi step-7/README.md per istruzioni dettagliate e scelta dei modelli.
python step-7/check_env.py
Step 8 — Vettorizzazione
Tipo: automatico (lento)
Input: step-6/<stem>/chunks.json
Output: chroma_db/ popolato
Script: step-8/ingest.py
source .venv/bin/activate
python step-8/ingest.py --stem <nome>
Trasforma ogni chunk in un vettore numerico e lo salva in ChromaDB. È 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.
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
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.
Step 9 — Pipeline RAG
Tipo: interattivo
Input: chroma_db/ + domanda dell'utente
Output: risposta basata sul documento
Script: step-9/rag.py
source .venv/bin/activate
python step-9/rag.py --stem <nome>
Loop interattivo che risponde a domande sul documento. Configura i parametri in step-9/config.py prima di avviare.
Vedi step-9/README.md per la configurazione completa.
Step 10 — Test automatici
Tipo: automatico
Input: sistema completo
Output: tutti i test verdi
Script: step-10/test_pipeline.py (da implementare)
python step-10/test_pipeline.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.
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.