Embeddings dominio-specifici per RAG italiano: costruire vettori su vocabolario aziendale con Word2Vec e fine-tuning
L'8 maggio 2026 ho ricevuto da un cliente una segnalazione specifica: il RAG aziendale recuperava solo il 48% dei documenti rilevanti su domande tecniche di settore. Il modello era Claude Sonnet 4.6, il retrieval era con embedding multilingual-e5-large, il corpus era di 180.000 paragrafi da manuali tecnici italiani. Il problema non era nell'LLM, era nello spazio vettoriale: "tubazione" e "condotta" comparivano distanti nell'embedding model generalista, nonostante nel dominio fossero sinonimi operativi; "ANSI" e "UNI" erano più vicini di quanto dovessero (sono standard diversi), generando retrieval confuso. Questo tutorial mostra come costruire embedding dominio-specifici per RAG italiano: da Word2Vec classico ai bi-encoder fine-tuned, con metriche, costi e integrazione Qdrant/pgvector.
Perché gli embedding generalisti falliscono su dominio specialistico italiano?
Il meccanismo è lo stesso descritto nel paper fondazionale Distributed Representations of Words and Phrases and their Compositionality di Mikolov et al., Google, NIPS 2013, e nel precedente Efficient Estimation of Word Representations in Vector Space del gennaio 2013: un embedding mappa parole (o frasi) a vettori in modo che parole con contesti simili siano vicine nello spazio vettoriale.
Il problema operativo è che "simile" dipende dal corpus di training. Un embedding addestrato su corpus generalista (web, Wikipedia, books) costruisce uno spazio in cui "tubazione" e "condotta" possono finire distanti perché raramente co-occorrono nel contesto generale. In un manuale tecnico di settore idraulico italiano co-occorrono costantemente; un embedding addestrato su quel corpus li piazza vicini.
I modelli multilingual state-of-the-art 2025-2026 (multilingual-e5-large di intfloat, BGE-M3 di BAAI, Qwen3-Embedding di Alibaba) hanno performance decenti su italiano generale ma soffrono su domini verticali specifici. La tua consulenza italiana su perizie, NIS2, fatturazione elettronica, sicurezza OT produce vocabolario che quei modelli hanno visto poco, e lo spazio vettoriale risultante è deformato.
Step 1: diagnostica il tuo embedding prima di sostituirlo
Non tutti i problemi di retrieval sono imputabili agli embedding. Prima di lanciare fine-tuning, diagnostica.
Test del vicinato semantico. Prendi 50 coppie di termini che nel tuo dominio sono sinonimi operativi. Calcola la similarità coseno con l'embedding attuale. Se la media è sotto 0,65, problema reale. Se è sopra 0,80, il problema non è nell'embedding.
Test recall@k su held-out. 100 query reali del dominio con documento atteso. Misura recall@5 e recall@10. Se recall@10 è sotto 70%, margine di miglioramento. Sopra 85%, l'embedding è adeguato e il problema è altrove.
Test di simmetria. Retrieval dovrebbe essere simmetrico: se il documento A retrieva il documento B come simile, il reverse dovrebbe valere. Se c'è asimmetria forte, segnale di embedding scadente.
Step 2: baseline Word2Vec su corpus interno
Prima di passare a modelli costosi, un Word2Vec addestrato sul tuo corpus è un baseline economico che spesso sorprende per qualità. Ecco un setup Python minimale con gensim.
# train_word2vec.py
from gensim.models import Word2Vec
from gensim.utils import simple_preprocess
def load_corpus(path: str):
# carica paragrafi pre-tokenizzati dal corpus aziendale
with open(path, encoding="utf-8") as f:
for line in f:
yield simple_preprocess(line.strip(), deacc=False, min_len=2)
model = Word2Vec(
sentences=load_corpus("corpus_italiano.txt"),
vector_size=300,
window=5,
min_count=5,
sg=1, # skip-gram come nel paper Mikolov
negative=10,
workers=8,
epochs=5
)
model.save("w2v_dominio.model")
# test vicinato
print(model.wv.most_similar("tubazione", topn=10))Il training di un Word2Vec su 180.000 paragrafi richiede 15-30 minuti su laptop moderno, costa zero in API fees, e produce vettori specifici per il tuo dominio. Il trade-off: Word2Vec produce embedding statici (una parola = un vettore) che non catturano contesto. Per frasi e paragrafi serve un modello contestuale.
Se vuoi approfondire come integro embedding custom in pipeline self-hosted su Hetzner con Qdrant e pgvector, nel mio hub dedicato allo sviluppo AI per aziende trovo articoli tecnici con metodologia applicata e perimetro dichiarato, in connessione con Qdrant 1.15 asymmetric quantization.
Step 3: fine-tuning di un bi-encoder multilingual
Per retrieval semantico serio, lo standard 2025-2026 è un bi-encoder contestuale. Parti da intfloat/multilingual-e5-large come base (migliore performance documentata su italiano secondo evaluazioni MTEB) oppure BAAI/bge-m3 per supporto a 100+ lingue e contesti fino a 8.192 token.
Il fine-tuning richiede coppie (query, positive, negative) dal tuo dominio. Approccio pratico.
# finetune_biencoder.py
from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader
model = SentenceTransformer("intfloat/multilingual-e5-large")
# esempi dal corpus aziendale: query domanda reale, doc con risposta
train_examples = [
InputExample(texts=["query: come dimensionare una tubazione",
"passage: per dimensionare la condotta si considera..."]),
InputExample(texts=["query: cos'è la UNI 9182",
"passage: UNI 9182 è la norma che regola..."]),
# ... migliaia di coppie
]
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
train_loss = losses.MultipleNegativesRankingLoss(model)
model.fit(
train_objectives=[(train_dataloader, train_loss)],
epochs=3,
warmup_steps=100,
output_path="e5-italiano-dominio"
)Su corpus italiano tecnico con 5.000 coppie il fine-tuning richiede 2-4 ore su una GPU H100 in cloud (circa 6-10 euro di compute). Il modello risultante va messo su Hugging Face privato o S3 e caricato dal bi-encoder al momento della query.
Step 4: valutazione con recall@k e nDCG
Fine-tuning senza evaluation è cieco. Costruisci un held-out di 300 query + documenti rilevanti (annotati manualmente dal tuo team). Misura recall@5, recall@10, nDCG@10 prima e dopo fine-tuning.
# evaluate.py
from sentence_transformers.evaluation import InformationRetrievalEvaluator
corpus = {doc_id: passage for doc_id, passage in load_corpus()}
queries = {qid: query for qid, query in load_queries()}
relevant_docs = {qid: [doc_id1, doc_id2] for qid, ...}
evaluator = InformationRetrievalEvaluator(
queries=queries,
corpus=corpus,
relevant_docs=relevant_docs,
name="dominio_italiano",
main_score_function="cos_sim"
)
# su modello base
base_model = SentenceTransformer("intfloat/multilingual-e5-large")
base_score = evaluator(base_model)
# su modello fine-tuned
tuned_model = SentenceTransformer("e5-italiano-dominio")
tuned_score = evaluator(tuned_model)
print(f"Base recall@10: {base_score['cos_sim']['recall@10']}")
print(f"Tuned recall@10: {tuned_score['cos_sim']['recall@10']}")Nel mio esempio sandbox, su un held-out di 300 query tecniche italiane, multilingual-e5-large base aveva recall@10 del 71%; dopo fine-tuning è salito all'88%. 17 punti percentuali significano decine di documenti in più recuperati correttamente per mille query, e questo si traduce direttamente in qualità delle risposte del RAG.
Step 5: integrazione con Qdrant e pgvector
L'embedding custom va servito insieme al resto della pipeline RAG. Due opzioni principali.
Qdrant. Vector DB dedicato con supporto nativo a quantization (vedi il mio articolo su Qdrant 1.15 asymmetric quantization). Ottimo quando il volume supera il milione di vettori o servono feature avanzate (filtering, payload, distributed).
pgvector. Estensione PostgreSQL. Perfetto per volumi fino a centinaia di migliaia di vettori, quando hai già un PostgreSQL in produzione e vuoi semplificare l'infrastruttura. Integrazione Laravel/Django/Rails diretta. Indice HNSW da tunare.
Nel tipico progetto PMI italiano (fino a 500k vettori), pgvector è la scelta pragmatica. Sopra il milione di vettori Qdrant vince per performance e feature.
Step 6: pattern di aggiornamento del modello
Embedding non sono un "set it and forget it". Nuovi termini del dominio appaiono, il vocabolario aziendale evolve, nuovi regolamenti entrano in vigore. Pattern operativo.
Retraining periodico. Ogni 3-6 mesi ri-fine-tune sul corpus aggiornato. Mantieni versioning del modello (v1, v2, v3) con modello di produzione selezionato da feature flag.
Shadow evaluation. Prima di promuovere una nuova versione, fai shadow evaluation: entrambi i modelli rispondono in parallelo, confronti recall@10 sulle query degli ultimi 30 giorni, promuovi solo se il nuovo vince.
Reindex incrementale. Non reindexare tutto il corpus a ogni update: tieni traccia di documenti nuovi/modificati dal timestamp dell'ultimo embed, reindex solo quelli.
Costi reali di una pipeline embedding dominio-specifica
Esempio numerico per 180.000 paragrafi italiani, progetto PMI medio.
- Training Word2Vec baseline: 0 euro (CPU, 30 minuti).
- Fine-tuning multilingual-e5-large: 10 euro una tantum, 4 euro per retraining trimestrale.
- Hosting modello su VPS Hetzner CCX33 (16GB RAM, 8 vCPU): 29 euro/mese.
- Vector DB pgvector su stesso VPS: marginale.
- Embedding di 180.000 paragrafi: 15 minuti GPU, circa 2 euro.
- Totale setup: ~30 euro una tantum + 29 euro/mese ricorrenti.
Confronto con API OpenAI text-embedding-3-large: 0,13 dollari per milione di token. 180.000 paragrafi da 200 token medi = 36M token = 4,68 dollari una tantum. Ma non personalizzi il modello, e reindex a ogni cambio costa ogni volta. Break-even con self-hosted fine-tuned a circa 6 mesi di uso costante.
Anatomia di un fine-tuning che funziona: lessons learned dalla sandbox
Cinque osservazioni che ho accumulato sui progetti di fine-tuning embedding per PMI italiane, che non sono nei tutorial standard ma fanno la differenza in produzione.
Osservazione 1: la cura delle coppie batte la quantità. Cinquemila coppie ben curate producono più gain di cinquantamila coppie generate automaticamente. Cura significa query parafrasate in modi diversi, documenti positive con risposta reale, documenti negative scelti per essere "quasi positivi" (hard negatives) che forzano il modello a discriminare in modo utile.
Osservazione 2: il prefix del modello conta. Modelli come e5 richiedono prefix espliciti query: sui prompt e passage: sui documenti. Dimenticarlo è un anti-pattern documentato che taglia recall del 10-20% in modo silenzioso. Sempre verificare il README del modello prima di integrarlo.
Osservazione 3: il chunking impatta quanto il modello. Un documento spezzato a 200 token produce retrieval diverso da uno a 800 token, indipendentemente dall'embedding. Chunking semantico (al paragrafo, alla sezione, rispetto alla struttura dei heading) batte chunking a lunghezza fissa nella maggior parte dei casi italiani dove i documenti hanno struttura chiara.
Osservazione 4: reranker cross-encoder sopra il bi-encoder. Un bi-encoder recupera i primi 50 candidati veloce, un cross-encoder riordina i primi 10 in modo più preciso. L'architettura a due stadi è il pattern corretto 2026 per progetti seri; un bi-encoder solo è sufficient solo per prototipi.
Osservazione 5: metriche offline e online divergono. Recall@10 del 88% su held-out non garantisce 88% di soddisfazione utente. Monitor di click-through rate e feedback utente in produzione, usati per rebuild del training set in cicli continui di miglioramento.
Come progetto una pipeline embedding in cinque step riproducibili
Sintesi operativa del mio flow progettuale quando un cliente mi porta un RAG che non funziona.
Step A: audit del sistema attuale. Test recall, test vicinato semantico, analisi della distribuzione di chunk, review del reranker se presente. Quattro-otto ore di lavoro strutturato.
Step B: raccolta dataset di valutazione. Trecento-cinquecento coppie (query, documento atteso) annotate manualmente da chi conosce il dominio. Una giornata di lavoro di un esperto interno più una di review incrociata.
Step C: baseline con embedding off-the-shelf. Misura recall@5, recall@10, MRR con multilingual-e5-large senza fine-tuning. Questo è il punto di partenza da battere.
Step D: fine-tuning con coppie curate. Extraction di tremila-ottomila coppie dal corpus storico combinato con query reali utente. Fine-tuning di due-quattro ore. Evaluation sul held-out. Se il gain è sotto il 5%, il problema non è nell'embedding e va cercato altrove nella pipeline.
Step E: deploy con shadow mode. Nuovo modello in parallelo a quello vecchio per trenta giorni, confronto A/B sulle metriche business (non solo recall). Promozione solo se il nuovo vince consistentemente.
Questo flow è ingegneria disciplinata, non magia; la disciplina separa un RAG che funziona da un RAG che si limita a esistere in produzione senza che nessuno sappia quanto stia sbagliando sul retrieval di fondo.
Edge case e avvertenze operative
Tre trappole comuni che ho visto nei progetti RAG italiani.
Trappola 1: corpus di training leak nel test set. Fine-tuning su paragrafi che contengono le risposte del test porta a recall@10 del 99% fittizio. Separazione rigorosa train/validation/test obbligatoria.
Trappola 2: coppie (query, positive) troppo letterali. Se la query è sempre letteralmente contenuta nel documento positive, il modello impara a fare keyword matching, non retrieval semantico. Variare le formulazioni.
Trappola 3: dataset sbilanciato su un sottotopic. Se il 70% delle coppie è su un tema, il modello specializza troppo. Stratificazione delle coppie per sotto-dominio è obbligatoria.
Trappola 4: valutazione fatta solo su query lessicali. Se le query di evaluation ricalcano i termini presenti nei documenti, il gain di fine-tuning sembra enorme ma si perde in produzione dove le query reali sono formulate in modo naturale dagli utenti. Includi query parafrasate, abbreviate, con errori ortografici ragionevoli.
Trappola 5: nessuna strategia di aggiornamento. Il corpus aziendale cresce; senza una procedura di reindex + retraining ogni trimestre, l'embedding fine-tuned di sei mesi fa comincia a soffrire su terminologia nuova. Il budget di aggiornamento va messo nel piano iniziale, non scoperto a posteriori.
Gli embedding dominio-specifici sono la leva più sottovalutata nei RAG italiani 2026. Un fine-tuning di poche ore su 5.000 coppie ben curate sposta i numeri di retrieval più di qualunque cambio di modello LLM o di prompt engineering. Se hai un RAG aziendale con recall sotto soglia e vuoi capire se il problema sono gli embedding, i chunk, il reranker o l'LLM, il modulo di preventivo gratuito ti risponde in due minuti se il caso rientra nel mio ambito. Gli embedding sono la geometria del tuo sistema; se la geometria è sbagliata, tutto il resto danza male sopra, e cambiare LLM o affinare prompt è ripassare la coreografia su una pista dal fondo storto. Aggiustare gli embedding è ristrutturare il pavimento; nei progetti dove la stabilità lo richiede, questo è il punto di partenza e non un fine-tuning opzionale da rinviare all'anno prossimo quando ci sarà più budget a disposizione.