Abstract, requisiti minimi, setup venv, MinerU (Colab + locale), chunking, ingest, interrogazione (terminale + GUI) con tabelle parametri per ogni config.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RAG from scratch
Esercizio pratico per costruire una pipeline RAG (Retrieval-Augmented Generation) completa a partire da documenti PDF, interamente in locale senza dipendenze cloud. L'obiettivo è avere un assistente che risponde a domande sui tuoi documenti usando solo il contenuto di quei documenti, senza allucinazioni.
Stack: Python · MinerU · ChromaDB · Ollama
Chunking: AST-based, deterministico, senza LLM
Embedding + Generazione: Ollama (locale, nessun dato inviato a server esterni)
PDF
└─ MinerU ──► sources/<stem>.md
└─ chunker.py ──► chunks/<stem>/chunks.json
└─ ingest.py ──► chroma_db/
└─ rag.py / GUI
Requisiti minimi
| Risorsa | Minimo | Note |
|---|---|---|
| Python | 3.10 – 3.13 | testato su 3.11 |
| RAM | 8 GB | 16 GB+ per modelli LLM grandi |
| Disco | 5 GB liberi | + spazio modelli Ollama |
| GPU | non necessaria | accelera MinerU e modelli LLM |
| OS | Linux, macOS, Windows (WSL2) |
Dipendenze Python — installate via requirements.txt:
chromadb— database vettoriale localemarkdown-it-py+mdit-py-plugins— parser Markdown AST-based per il chunkingPySide6— interfaccia grafica desktop (opzionale, solo per la GUI)
Dipendenze esterne:
- Ollama — server locale per embedding e generazione LLM
- MinerU — conversione PDF → Markdown (opzionale se hai già i file
.md)
Setup ambiente
# Crea e attiva il virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Installa le dipendenze
pip install -r requirements.txt
Passo 1 — Converti i PDF con MinerU
MinerU estrae il testo dai PDF producendo un Markdown strutturato con tabelle, formule ed immagini. Ci sono due modalità:
Opzione A — Google Colab (consigliata, usa la GPU gratuita di Google)
Il notebook mineru.ipynb esegue MinerU su Colab sfruttando la GPU messa a disposizione gratuitamente. Utile se non hai una GPU locale o se i PDF sono pesanti.
- Apri
mineru.ipynbsu Google Colab - Carica i PDF nella cartella
input/indicata nelle celle - Esegui tutte le celle — al termine viene scaricato uno ZIP per ogni documento
- Decomprimi gli ZIP nella cartella
sources/del progetto:
sources/
└── <stem>_output/
└── auto/
├── <stem>.md ← questo è l'input della pipeline
└── images/
Opzione B — Installazione locale
pip install "mineru[all]" # installa MinerU (scarica ~15 GB di modelli al primo avvio)
mineru -p documento.pdf -o sources/<stem>_output/ -b pipeline
# Con GPU:
mineru -p documento.pdf -o sources/<stem>_output/ -b hybrid-auto-engine
Passo 2 — Chunking
Il chunker analizza il Markdown con un parser AST, divide il testo in blocchi semantici e li impacchetta in chunk ottimizzati per il retrieval.
# Singolo documento
python chunks/chunker.py --stem <stem>
# Tutti i documenti presenti in sources/ in una volta
python chunks/chunker.py
# Rigenera anche se chunks.json esiste già
python chunks/chunker.py --stem <stem> --force
Output in chunks/<stem>/: chunks.json, meta.json, report.json.
Configurazione — chunks/config.py
Modifica i parametri nella classe ChunkerConfig se necessario:
| Parametro | Default | Descrizione |
|---|---|---|
max_chars |
1200 |
Dimensione massima di un chunk (caratteri) |
min_chars |
80 |
Soglia minima; chunk piu' piccoli vengono accorpati |
target_chars |
800 |
Dimensione target per il packing greedy |
context_depth |
3 |
Livelli di heading inclusi come prefisso nel testo per l'embedding |
skip_headings |
vedi file | Sezioni saltate completamente (indice, sommario, ecc.) |
skip_pre_heading |
True |
Salta il contenuto prima del primo heading (frontespizi, ecc.) |
atomic_types |
table, code, list, math, html |
Tipi di blocco che non vengono mai spezzati |
Passo 3 — Vettorizzazione (ingest)
Genera gli embedding dei chunk e li indicizza in ChromaDB. Richiede Ollama attivo.
# Avvia Ollama (se non e' gia' in esecuzione)
ollama serve
# Installa il modello di embedding (una volta sola)
ollama pull bge-m3
# Indicizza un singolo documento
python ingestion/ingest.py --stem <stem>
# Indicizza piu' documenti in una collection condivisa (RAG cross-documento)
python ingestion/ingest.py --collection <nome> --stems doc1 doc2 doc3
# Rigenera dopo aver aggiornato i chunk o cambiato modello embedding
python ingestion/ingest.py --stem <stem> --force
Configurazione — ingestion/config.py
| Parametro | Default | Descrizione |
|---|---|---|
EMBED_MODEL |
bge-m3 |
Modello Ollama per gli embedding. Se lo cambi, riesegui l'ingest con --force |
EMBED_MAX_CHARS |
6000 |
Caratteri massimi inviati al modello per ogni chunk |
OLLAMA_URL |
http://localhost:11434 |
URL del server Ollama |
Passo 4 — Interrogazione
Da terminale
# Installa il modello LLM (una volta sola)
ollama pull qwen3.5:4b
# RAG interattivo — retrieval + risposta generata dal modello
python rag/rag.py --collection <nome>
python rag/rag.py --stem <stem> # scorciatoia per collection a documento singolo
# Retrieval puro senza LLM (utile per debug)
python rag/retrieve.py --collection <nome>
python rag/retrieve.py --stem <stem> --top-k 10
Nel loop interattivo: digita la domanda e premi Invio. Aggiungi -v alla fine per vedere i chunk recuperati con i relativi score di similarita'.
Con la GUI desktop
python gui/main.py
python gui/main.py --collection <nome> # apre direttamente la collection
La GUI mostra le risposte con Markdown e formule matematiche renderizzate. Il tema chiaro/scuro e' selezionabile dal pulsante in alto a destra.
Configurazione — rag/config.py
| Parametro | Default | Descrizione |
|---|---|---|
OLLAMA_MODEL |
qwen3.5:4b |
Modello LLM per la generazione |
TOP_K |
6 |
Numero di chunk recuperati per ogni domanda |
TEMPERATURE |
0.2 |
0 = deterministico, valori alti = piu' creativo |
NO_THINK |
True |
Disabilita il reasoning interno (piu' veloce; solo modelli Qwen3) |
OLLAMA_URL |
http://localhost:11434 |
URL del server Ollama |
SYSTEM_PROMPT |
vedi file | Istruzioni di sistema inviate al modello prima del contesto |
Struttura del repository
rag/
├── sources/ ← output MinerU (una cartella per documento)
├── chunks/
│ ├── chunker.py ← orchestrazione pipeline AST
│ ├── config.py ← parametri di chunking
│ ├── parser.py ← markdown-it-py + dollarmath
│ ├── segmenter.py ← token stream -> Block[]
│ ├── packer.py ← Block[] -> Chunk[]
│ └── validator.py ← invarianti e metriche
├── ingestion/
│ ├── ingest.py ← embedding -> ChromaDB
│ └── config.py ← parametri embedding
├── rag/
│ ├── rag.py ← loop RAG interattivo
│ ├── retrieve.py ← retrieval puro (debug)
│ └── config.py ← parametri RAG e system prompt
├── gui/
│ ├── main.py ← entry point GUI
│ ├── chat_window.py ← finestra principale PySide6
│ ├── worker.py ← thread per il pipeline RAG
│ └── chat.html ← template HTML (Markdown + KaTeX)
├── chroma_db/ ← database vettoriale (non tracciato da git)
├── mineru.ipynb ← notebook Colab per conversione PDF
└── requirements.txt