Meteora Web

RAG con LangChain: Retrieval Augmented Generation pratico per sviluppatori

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 mod...

Meteora Web Redazione 5 min di lettura Read in English
RAG con LangChain: Retrieval Augmented Generation pratico per sviluppatori

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:

Zenithby Meteora Web Il sistema operativo della tua attività. Social, clienti, prenotazioni e fatture in un'unica piattaforma. Palestre, barber, professionisti. Scopri Zenith → Demo gratis · senza carta

Leggi anche

Dal journal

Ultime guide