Cosa ti serve
- Python 3.11+ e familiarità di base con FastAPI
- Un LLM: Ollama in locale (va bene qualsiasi modello da 7B in su) o una API key (con prezzi in classe DeepSeek/Gemini il costo è quasi nullo)
- Nessuna GPU richiesta — a questa scala gli embedding girano bene su CPU
Passo 0 — Capisci la pipeline (5 min)
Il RAG (retrieval-augmented generation) è un motore di ricerca imbullonato a un LLM. Tutto il resto è dettaglio:
La qualità della risposta finale si decide quasi interamente nella parte sinistra del diagramma. In mesi di log di produzione, quasi ogni risposta sbagliata risaliva a un retrieval sbagliato — i chunk errati — non a un LLM debole. Investi il tuo sforzo in chunking e retrieval prima di cercare un modello più grande. (Per capire quando il RAG è lo strumento giusto, vedi RAG vs fine-tuning.)
Passo 1 — Installa e suddividi in chunk (15 min)
Una regola di chunking che ha retto alla prova della produzione: dividi prima sulla struttura, poi sulla dimensione. I confini di paragrafi e titoli portano significato; i tagli a lunghezza fissa spezzano le frasi a metà e avvelenano il retrieval. Punta a ~300–500 parole per chunk con un overlap del 10–15%:
Passo 2 — Embedding e indice in ChromaDB (15 min)
Per contenuti in italiano (o qualsiasi lingua non inglese) usa un modello di embedding multilingue —
il MiniLM inglese di default rovina silenziosamente il retrieval cross-lingua. paraphrase-multilingual-MiniLM-L12-v2
è piccolo, veloce su CPU, ed è quello che usa questo sito:
Due note di produzione. Primo, upsert con ID deterministici (doc_id:chunk_n) rende la
re-indicizzazione idempotente — puoi rilanciare l'ingest senza duplicati. Secondo, metti subito l'URL
sorgente nei metadati: le citazioni sono impossibili da aggiungere a posteriori se non hai salvato la provenienza.
Passo 3 — Retrieval e costruzione del prompt (15 min)
La soglia di similarità è il dettaglio che la maggior parte dei tutorial salta ed è quello che conta di più in produzione. Senza, una domanda fuori tema restituisce comunque i k chunk "meno dissimili", e l'LLM allucinerà volenterosamente una risposta da contesto irrilevante. Con la soglia, puoi rispondere onestamente "non ho questa informazione" — che è ciò che separa uno strumento interno affidabile da un rischio.
Passo 4 — L'endpoint FastAPI (15 min)
Lancialo con uvicorn main:app e hai un chatbot privato funzionante: domanda in ingresso, risposta
fondata più fonti cliccabili in uscita. Sostituire Ollama con un provider API è una modifica di cinque righe
alla chiamata HTTP — al layer RAG non importa chi genera.
Passo 5 — Le lezioni di produzione (la parte che nessuno ti dice)
Tutto quello che segue viene dall'esecuzione di questo stack dal vivo sull'internet pubblico:
- Indicizza in modo incrementale, non da zero. Ri-embeddare l'intero corpus a ogni aggiornamento è la prima cosa che smette di scalare. Embedda solo i documenti nuovi/modificati al momento dell'ingest (la nostra pipeline indicizza ogni nuovo articolo appena creato).
-
I bot troveranno il tuo endpoint e lo martelleranno. Ogni chiamata a /ask costa calcolo vero
(retrieval + LLM). Nel giro di poche settimane i crawler ci stavano prosciugando la macchina con domande
spazzatura. Difesa minima: rate limiting per IP,
Disallownel robots.txt, e mai esporre l'endpoint senza autenticazione se è interno. - Logga domanda + chunk recuperati + risposta. Questa tripla è la tua miniera d'oro per il debugging e il tuo set di valutazione. Quando una risposta è sbagliata, il log ti dice all'istante se ha fallito il retrieval o la generazione.
- Lingua della risposta ≠ lingua dei documenti. Con un embedder multilingue, le domande in italiano recuperano chunk in inglese e viceversa — ottimo per la copertura, ma di' esplicitamente all'LLM in che lingua rispondere, o rispecchierà le fonti.
- ChromaDB basta più a lungo di quanto pensi. Fino a centinaia di migliaia di chunk su una macchina, semplicemente funziona. Non passare a un vector database distribuito finché non hai misurato un limite reale.
Dove andare dopo
Due migliorie ripagano la complessità in più quando il RAG di base si impianta: il retrieval ibrido (aggiungi keyword/BM25 accanto ai vettori — nomi esatti e codici sono il punto debole degli embedding puri) e un reranker (un piccolo cross-encoder che riordina i tuoi top-20 in un top-5 più preciso). Aggiungili solo dopo che i log mostrano errori di retrieval; entrambi si innestano su questa architettura senza stravolgerla.