f in x
RAG con LangChain: Retrieval Augmented Generation pratico per sviluppatori
> cd .. / HUB_EDITORIALE > Visualizza in Inglese
Analisi dei dati e metriche

RAG con LangChain: Retrieval Augmented Generation pratico per sviluppatori

[2026-06-06] Author: Ing. Calogero Bono

Hai un LLM che allucina risposte sui tuoi documenti interni? Oppure un chatbot aziendale che risponde solo fino alla data di training del modello? Il problema è sempre lo stesso: la conoscenza del modello è generale, non specifica. Noi, di Meteora Web, affrontiamo questo limite con RAG — Retrieval Augmented Generation. Un approccio che non richiede fine-tuning costosi e tiene i dati sotto il tuo controllo. Ecco come lo implementiamo con LangChain, in produzione.

Perché RAG e non un fine-tuning

Immagina di avere un manuale di prodotto di 500 pagine. Il fine-tuning richiederebbe GPU, ore di training e rischierebbe l'overfitting su domande inviste. RAG invece lascia il modello base intatto e gli fornisce i pezzi giusti di contesto al momento della risposta. Vantaggi: aggiornabile in tempo reale (basta cambiare i documenti), zero training, trasparenza (sai esattamente quali fonti ha usato). E funziona subito.

Architettura di un RAG con LangChain

I componenti sono tre: Indicizzazione (caricare, suddividere, vettorizzare i documenti), Retrieval (cercare per similarità semantica) e Generazione (LLM + contesto recuperato). LangChain li unisce in una pipeline coerente.

1. Indicizzazione dei documenti

Partiamo da un PDF. Noi usiamo PyPDFLoader o UnstructuredPDFLoader per estrarre testo. Poi lo dividiamo in chunk: troppo piccoli perdono contesto, troppo grandi sforano il context window. Noi usiamo RecursiveCharacterTextSplitter con chunk size ~1000 caratteri e overlap 200.

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

loader = PyPDFLoader("manuale_prodotto.pdf")
documents = loader.load()

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"{len(chunks)} chunk creati")

Poi generiamo gli embeddings con un modello. Noi usiamo text-embedding-3-small di OpenAI per rapporto qualità/costo, ma funzionano anche modelli locali con HuggingFaceEmbeddings. Per vettori usiamo Chroma (leggero, nessun server) o Qdrant in produzione.

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"
)
vectorstore.persist()

2. Retrieval – trovare i chunk giusti

Il retrieval è il cuore di RAG. Con LangChain usiamo as_retriever e impostatiamo il numero di chunk da restituire (k=4 o 5 di solito). Possiamo aggiungere filtri di metadati (es. solo documenti di una certa categoria).

retriever = vectorstore.as_retriever(
    search_kwargs={"k": 4}
)

Un errore comune: affidarsi solo alla similarità coseno. Se i chunk sono di lunghezza diversa, meglio normalizzare. Noi a volte usiamo Maximal Marginal Relevance (MMR) per diversificare i risultati ed evitare ridondanze.

retriever = vectorstore.as_retriever(
    search_type="mmr",
    search_kwargs={"k": 4, "lambda_mult": 0.5}
)

3. Generazione con contesto

Qui arriva la magia: creiamo un prompt che istruisce il LLM a usare SOLO il contesto fornito. Niente conoscenze generali se non supportate. In LangChain usiamo ChatPromptTemplate e la chain RAG classica con create_stuff_documents_chain e create_retrieval_chain.

from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

prompt = ChatPromptTemplate.from_messages([
    ("system", "Sei un assistente esperto. Usa solo il contesto fornito per rispondere. Se non trovi la risposta, dì che non lo sai."),
    ("human", "Contesto: \n{context}\n\nDomanda: {input}")
])

combine_docs_chain = create_stuff_documents_chain(llm, prompt)
qa_chain = create_retrieval_chain(retriever, combine_docs_chain)

result = qa_chain.invoke({"input": "Quali istruzioni di sicurezza ci sono per l'uso del prodotto?"})
print(result["answer"])

Attenzione: nel prompt specifichiamo esplicitamente di non allucinare. I modelli più recenti (GPT-4o, Claude 3.5) rispettano meglio questa direttiva.

Ottimizzare un RAG per la produzione

Un RAG funzionante è solo l'inizio. Per portarlo in produzione servono accorgimenti.

Chunking intelligente

Documenti con tabelle, immagini, codice? Il chunking naive può rompere contesti critici. Noi usiamo semantic chunking con modelli come NLTKTextSplitter o SpacyTextSplitter per rispettare confini di frase. Per codice sorgente, chunk per funzione/classe. Mai spezzare in mezzo a un paragrafo che contiene un riferimento a un'altra parte del documento.

Hybrid search

La similarità semantica non basta sempre: termini esatti come “ID cliente 12345” vanno recuperati con BM25 o full-text. Uniamo i due in un hybrid retriever. LangChain lo supporta con `EnsembleRetriever`.

from langchain.retrievers import EnsembleRetriever
from langchain.retrievers import BM25Retriever

bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 3

semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, semantic_retriever],
    weights=[0.3, 0.7]
)

Controllo dei contesto – Reranking

Non tutti i chunk recuperati sono ugualmente utili. Un reranker (es. Cohere Rerank) riordina i risultati per pertinenza. LangChain supporta ContextualCompressionRetriever o il wrapper per Cohere Rerank. Noi lo usiamo quando il numero di chunk cresce (k>5).

Errori comuni e come evitarli

  • Over-stuffing: passare troppi chunk (più di 8-10) degrada la risposta. Il modello si distrae. K=4/5 è un buon punto di partenza.
  • Chunk senza metadati: se non salvi il nome del documento e la pagina, non saprai indicare la fonte. LangChain permette di includere metadati nei document loader.
  • Prompt debole: un prompt generico “Usa il contesto” non basta. Specifica cosa fare se il contesto è irrilevante o contraddittorio.
  • Embedding vecchi: se la conoscenza del dominio è particolare, usa un modello fine-tuned per il tuo dominio (es. embedding legali).

Strumenti e tecnologie reali che usiamo

Per clienti che richiedono privacy totale (documenti sensibili), usiamo Ollama con modelli locali (Llama 3, Mistral) e Chroma in locale. Per carichi alti, Qdrant su cloud. L'AI generativa va amplificata, non sostituita: ogni output viene verificato da chi conosce il dominio. Noi lo inseriamo in un sistema di feedback: l'utente può votare la risposta, e i log finiscono in un database per migliorare il retrieval.

Cosa fare adesso

1. Prepara un dataset di prova. Prendi 3-5 documenti aziendali (PDF, markdown, pagine web).

2. Installa le dipendenze: pip install langchain langchain-openai langchain-community chromadb pypdf e imposta la variabile d'ambiente OPENAI_API_KEY.

3. Copia e adatta il codice sopra per creare la tua pipeline di RAG. Sperimenta con chunk size e k.

4. Misura la qualità. Chiedi 10 domande di cui conosci la risposta. Controlla quante volte il sistema risponde correttamente e quante allucina. Un buon RAG dovrebbe avere almeno l'80% di correttezza sulle domande coperte dai documenti.

5. Integra in produzione — backend FastAPI, frontend React o Streamlit. Documenta la fonte di ogni risposta con i metadati.


Link utili:

Sponsored Protocol

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Co-founder di Meteora Web. Ingegnere informatico, sviluppo ecosistemi digitali ad alte prestazioni. AI, automazione, SEO tecnica e infrastrutture web. Scrivo di tecnologia per rendere complesso… semplice.

[ Read Full Dossier ]

Hai bisogno di applicare questa strategia?

Esegui il protocollo di contatto per iniziare un progetto con noi.

> INIZIA_PROGETTO

Sponsored

> MW_JOURNAL

> READ_ALL()