Pipeline RAG di Produzione nel 2026: Guida Pratica con LangChain, LlamaIndex e RAGAS

Guida pratica alla costruzione di pipeline RAG di produzione: architettura, chunking adattivo (87% vs 50% baseline), confronto Pinecone/Qdrant/Weaviate, ricerca ibrida con RRF, reranking e valutazione con RAGAS.

Se lavorate con i Large Language Model nel 2026, probabilmente avete già sentito parlare di Retrieval-Augmented Generation (RAG). E con buona ragione: si è affermata come il paradigma dominante per costruire applicazioni LLM che hanno bisogno di accesso a conoscenze specifiche, aggiornate e — cosa fondamentale — verificabili. A differenza del semplice prompting o del fine-tuning, RAG combina la potenza generativa dei modelli linguistici con un sistema di recupero informazioni che attinge da basi di conoscenza esterne. Il risultato? Meno allucinazioni e risposte fondate su dati reali.

In questa guida vi accompagnerò attraverso ogni aspetto della costruzione di una pipeline RAG pronta per la produzione: dall'architettura fondamentale alle strategie avanzate di chunking, dalla scelta del database vettoriale alle tecniche di reranking, fino alla valutazione della qualità con RAGAS. Che siate ingegneri ML esperti o sviluppatori che si avvicinano per la prima volta a questo mondo, troverete qui pattern architetturali concreti e codice funzionante per costruire sistemi robusti e scalabili.

Architettura di una Pipeline RAG di Produzione

Una pipeline RAG di produzione nel 2026 è composta da diversi componenti interconnessi che operano in due fasi principali: la fase di indicizzazione (offline) e la fase di interrogazione (online). Capire l'architettura complessiva è il primo passo — prima di perdersi nei dettagli di ogni singolo componente.

Componenti Principali e Flusso dei Dati

Il flusso dei dati segue un percorso ben definito. Nella fase di indicizzazione, i documenti vengono acquisiti da diverse sorgenti (PDF, pagine web, database, API), quindi suddivisi in chunk tramite strategie di chunking avanzate. Ogni chunk viene trasformato in un vettore numerico (embedding) e indicizzato in un database vettoriale.

Nella fase di interrogazione, la query dell'utente viene anch'essa trasformata in embedding, si esegue una ricerca di similarità nel database vettoriale, i risultati vengono riordinati tramite reranking, e infine il contesto recuperato viene passato all'LLM insieme alla query originale per generare la risposta finale.

I componenti chiave dell'architettura sono:

  • Document Ingestion Layer — Gestisce l'acquisizione e il preprocessing dei documenti da fonti eterogenee, inclusi parser per PDF, HTML, Markdown e formati proprietari.
  • Chunking Engine — Suddivide i documenti in segmenti semanticamente coerenti, ottimizzati per il recupero e la comprensione contestuale.
  • Embedding Service — Trasforma testo in rappresentazioni vettoriali dense utilizzando modelli come BGE-M3, OpenAI text-embedding-3-large o Cohere embed-v4.
  • Vector Store — Database vettoriale ad alte prestazioni (Pinecone, Qdrant, Weaviate) per l'indicizzazione e la ricerca per similarità.
  • Retrieval Engine — Combina ricerca vettoriale, BM25 e filtri metadata per il recupero multi-strategia.
  • Reranking Module — Riordina i risultati con modelli cross-encoder per massimizzare la rilevanza.
  • Generation Layer — L'LLM che sintetizza la risposta finale utilizzando il contesto recuperato.
  • Evaluation & Observability — Monitoraggio continuo della qualità con RAGAS, tracing distribuito e metriche operative.

Strategie Avanzate di Chunking

Il chunking è uno degli aspetti più critici — e onestamente più sottovalutati — di una pipeline RAG. La qualità del chunking influenza direttamente la qualità del recupero e, di conseguenza, la qualità delle risposte generate. Nel 2026, le strategie si sono evolute ben oltre il semplice splitting a dimensione fissa.

Chunking a Dimensione Fissa vs Semantico vs Adattivo

Il chunking a dimensione fissa suddivide il testo in segmenti di lunghezza predeterminata (ad esempio, 512 token) con un overlap configurabile. È semplice da implementare ma spesso spezza frasi e concetti a metà, degradando la qualità del recupero.

Il chunking semantico utilizza i confini naturali del testo — paragrafi, sezioni, frasi — per creare chunk che preservano l'integrità concettuale. Un approccio decisamente migliore, ma non ancora ottimale.

Poi c'è il chunking adattivo, l'approccio più avanzato, che regola dinamicamente la dimensione dei chunk in base alla densità informativa del contenuto. Test condotti su benchmark standard hanno dimostrato che il chunking adattivo raggiunge un'accuratezza dell'87% nel recupero rispetto al 50% del baseline con chunking fisso. Un miglioramento significativo che, secondo me, giustifica ampiamente la complessità aggiuntiva.

Strategia Parent-Child Chunk

La strategia parent-child è un pattern particolarmente elegante: i documenti vengono suddivisi in chunk piccoli (child) per il recupero preciso, ma ogni child mantiene un riferimento al chunk più grande (parent) che fornisce il contesto completo. Durante la generazione, viene passato all'LLM il chunk parent. In pratica, avete sia la precisione del recupero sia la ricchezza del contesto.

Proposition-Based Chunking

Una tecnica emergente nel 2026 è il proposition-based chunking, dove un LLM viene utilizzato per decomporre il testo in proposizioni atomiche auto-contenute. Ogni proposizione diventa un chunk indicizzabile, offrendo una granularità senza precedenti nel recupero. È un approccio affascinante, anche se va detto che aumenta i costi di pre-processing.

from llama_index.core.node_parser import (
    SentenceSplitter,
    SemanticSplitterNodeParser,
)
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core import Document

# Chunking a dimensione fissa con overlap
fixed_splitter = SentenceSplitter(
    chunk_size=512,
    chunk_overlap=50,
)

# Chunking semantico basato sulla similarità degli embedding
embed_model = OpenAIEmbedding(model="text-embedding-3-large")
semantic_splitter = SemanticSplitterNodeParser(
    buffer_size=1,
    breakpoint_percentile_threshold=95,
    embed_model=embed_model,
)

# Esempio di utilizzo con un documento
documento = Document(text="Il testo completo del documento da processare...")

# Genera chunk con entrambe le strategie
fixed_chunks = fixed_splitter.get_nodes_from_documents([documento])
semantic_chunks = semantic_splitter.get_nodes_from_documents([documento])

print(f"Chunk fissi: {len(fixed_chunks)}")
print(f"Chunk semantici: {len(semantic_chunks)}")

# Strategia Parent-Child con LangChain
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Parent splitter - chunk grandi per il contesto
parent_splitter = RecursiveCharacterTextSplitter(
    chunk_size=2000,
    chunk_overlap=200,
)

# Child splitter - chunk piccoli per il recupero preciso
child_splitter = RecursiveCharacterTextSplitter(
    chunk_size=400,
    chunk_overlap=50,
)

parent_chunks = parent_splitter.split_text(documento.text)
for i, parent in enumerate(parent_chunks):
    children = child_splitter.split_text(parent)
    for child in children:
        # Indicizza il child con metadata che punta al parent
        metadata = {"parent_id": i, "parent_text": parent}
        print(f"Child chunk con parent_id={i}: {child[:80]}...")

Database Vettoriali a Confronto: Pinecone, Qdrant, Weaviate

La scelta del database vettoriale è una decisione architetturale cruciale che influenza prestazioni, costi e complessità operativa della vostra pipeline RAG. Nel 2026, tre soluzioni dominano il mercato enterprise: Pinecone, Qdrant e Weaviate. Ognuna ha vantaggi e compromessi specifici — e la scelta giusta dipende molto dal vostro contesto.

Confronto Dettagliato

Ecco come si posizionano le tre soluzioni principali:

  • Pinecone — Servizio completamente gestito (serverless). Latenza p99 di 30ms a 1 milione di vettori, la più bassa del mercato. Supporta ricerca ibrida con sparse-dense vectors nativi. Modello di pricing serverless basato su letture/scritture e storage. Ideale per team che vogliono zero gestione infrastrutturale e massime prestazioni. Il compromesso? Personalizzazione limitata e niente deployment on-premise.
  • Qdrant — Open source con opzione cloud gestita. Latenza p99 di 50ms a 1 milione di vettori. Supporto eccellente per filtri complessi e payload ricchi. Ricerca ibrida tramite sparse vectors e named vectors. Deployment flessibile: self-hosted, cloud, o Kubernetes. Pricing trasparente per la versione cloud, gratuito per self-hosted. Onestamente, è la scelta migliore per chi necessita di flessibilità e controllo completo.
  • Weaviate — Open source con architettura modulare. Latenza p99 di circa 45ms a 1 milione di vettori. Integrazione nativa con moduli di vectorizzazione (trasformazione testo-vettore automatica). GraphQL API nativa per query complesse. Ricerca ibrida BM25 + vettoriale integrata. Ottimo per prototipazione rapida e scenari dove volete la vectorizzazione integrata senza troppi passaggi.

Quando Usare Quale Database

Scegliete Pinecone se la latenza è la vostra priorità assoluta e preferite un servizio completamente gestito senza preoccupazioni operative. Scegliete Qdrant se avete bisogno di flessibilità nel deployment, filtri avanzati sui metadata, o dovete operare on-premise per requisiti di compliance. Scegliete Weaviate se desiderate un ecosistema modulare con vectorizzazione integrata e preferite un'API GraphQL nativa.

Un consiglio pratico: se non siete sicuri, partite con Qdrant. È gratuito in self-hosted, ha un'ottima documentazione, e potete sempre migrare dopo.

Connessione Pratica a Qdrant

from qdrant_client import QdrantClient
from qdrant_client.models import (
    Distance, VectorParams, PointStruct,
    Filter, FieldCondition, MatchValue,
    SparseVectorParams, SparseIndexParams,
    NamedVector, NamedSparseVector,
    SparseVector,
)
import numpy as np

# Connessione al cluster Qdrant
client = QdrantClient(
    url="https://your-cluster.qdrant.io:6333",
    api_key="your-api-key",
    timeout=30,
)

# Creazione della collection con supporto ibrido dense + sparse
client.create_collection(
    collection_name="documenti_rag",
    vectors_config={
        "dense": VectorParams(
            size=1536,
            distance=Distance.COSINE,
        ),
    },
    sparse_vectors_config={
        "sparse": SparseVectorParams(
            index=SparseIndexParams(on_disk=False),
        ),
    },
)

# Inserimento di un punto con vettore denso e sparse
client.upsert(
    collection_name="documenti_rag",
    points=[
        PointStruct(
            id=1,
            vector={
                "dense": np.random.rand(1536).tolist(),
            },
            payload={
                "testo": "Contenuto del chunk documento",
                "fonte": "manuale_tecnico.pdf",
                "sezione": "capitolo_3",
                "data_indicizzazione": "2026-01-15",
            },
        ),
    ],
)

# Ricerca ibrida con filtro sui metadata
risultati = client.query_points(
    collection_name="documenti_rag",
    query=np.random.rand(1536).tolist(),
    using="dense",
    query_filter=Filter(
        must=[
            FieldCondition(
                key="fonte",
                match=MatchValue(value="manuale_tecnico.pdf"),
            )
        ]
    ),
    limit=10,
    with_payload=True,
)

for punto in risultati.points:
    print(f"ID: {punto.id}, Score: {punto.score:.4f}")
    print(f"Testo: {punto.payload['testo'][:100]}...")

Modelli di Embedding e Ricerca Ibrida

La qualità degli embedding determina direttamente l'efficacia del recupero nella vostra pipeline RAG. E nel 2026, il panorama dei modelli di embedding si è notevolmente arricchito. La cosa più interessante? La ricerca ibrida che combina approcci lessicali e semantici è diventata lo standard de facto per i sistemi di produzione.

Panoramica dei Modelli di Embedding

I principali modelli di embedding disponibili nel 2026 includono:

  • BGE-M3 (BAAI) — Modello open source multilingue che supporta nativamente embedding densi, sparsi e multi-vettore. Eccellente rapporto qualità/costo per deployment self-hosted. Dimensione vettore fino a 1024.
  • OpenAI text-embedding-3-large — Modello proprietario con dimensione vettore configurabile (256-3072). Prestazioni eccellenti su benchmark MTEB. Semplicità d'uso tramite API, ma attenzione ai costi ricorrenti e alla dipendenza dal vendor.
  • Cohere embed-v4 — Supporto nativo per la compressione degli embedding (binary e int8), che riduce drasticamente i costi di storage. Ottimizzato per la ricerca con tipo di input "search_document" e "search_query" differenziati.
  • Jina Embeddings v3 — Modello multilingue con supporto per task-specific embedding tramite adattatori LoRA. Supporta fino a 8192 token di contesto, il che lo rende ideale per chunk più lunghi.

Ricerca Ibrida con BM25 + Dense Vectors e RRF

La ricerca ibrida combina la ricerca lessicale (BM25) con quella semantica (vettori densi) per ottenere il meglio di entrambi i mondi. BM25 eccelle nel trovare corrispondenze esatte di termini e keyword, mentre i vettori densi catturano la similarità semantica anche in assenza di corrispondenza lessicale.

La Reciprocal Rank Fusion (RRF) è il metodo più utilizzato per fondere i risultati dei due approcci in un ranking unificato. La formula è semplice ma efficace: per ogni documento, il punteggio finale è la somma di 1 / (k + rank_i) per ciascun sistema di ranking, dove k è una costante (tipicamente 60) e rank_i è la posizione del documento nel ranking i-esimo.

from langchain_community.retrievers import BM25Retriever
from langchain_openai import OpenAIEmbeddings
from langchain_qdrant import QdrantVectorStore
from langchain.schema import Document
from typing import List, Dict
import numpy as np

def reciprocal_rank_fusion(
    rankings: List[List[Document]],
    k: int = 60,
) -> List[Document]:
    """
    Fonde risultati da più sistemi di ranking usando RRF.
    """
    rrf_scores: Dict[str, float] = {}
    doc_map: Dict[str, Document] = {}

    for ranking in rankings:
        for rank, doc in enumerate(ranking):
            doc_id = doc.page_content[:100]  # identificatore semplificato
            doc_map[doc_id] = doc
            if doc_id not in rrf_scores:
                rrf_scores[doc_id] = 0.0
            rrf_scores[doc_id] += 1.0 / (k + rank + 1)

    # Ordina per punteggio RRF decrescente
    sorted_docs = sorted(
        rrf_scores.items(),
        key=lambda x: x[1],
        reverse=True,
    )
    return [doc_map[doc_id] for doc_id, _ in sorted_docs]


# Configurazione del retriever ibrido
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")

# Retriever BM25 (lessicale)
documenti = [...]  # lista di Document di LangChain
bm25_retriever = BM25Retriever.from_documents(documenti, k=20)

# Retriever vettoriale (semantico)
vector_store = QdrantVectorStore.from_documents(
    documenti,
    embeddings,
    url="https://your-cluster.qdrant.io:6333",
    api_key="your-api-key",
    collection_name="documenti_rag",
)
vector_retriever = vector_store.as_retriever(search_kwargs={"k": 20})

# Ricerca ibrida con RRF
query = "Come configurare il sistema di autenticazione?"
risultati_bm25 = bm25_retriever.invoke(query)
risultati_vector = vector_retriever.invoke(query)

risultati_fusi = reciprocal_rank_fusion(
    [risultati_bm25, risultati_vector],
    k=60,
)

# Prendi i top-10 risultati fusi
top_risultati = risultati_fusi[:10]
for i, doc in enumerate(top_risultati):
    print(f"{i+1}. {doc.page_content[:120]}...")

Implementazione Pratica con LangChain e LlamaIndex

Nel 2026, LangChain e LlamaIndex sono i due framework dominanti per la costruzione di pipeline RAG. Entrambi sono maturati enormemente, e il pattern più efficace consiste nel combinarli sfruttando i punti di forza di ciascuno: LlamaIndex per l'ingestion e il parsing dei documenti, LangChain e LangGraph per l'orchestrazione e la logica applicativa.

Questo approccio — il cosiddetto "power move" del 2026 — vi permette di ottenere il meglio di entrambi gli ecosistemi. Ed è esattamente quello che vedremo in questa sezione.

Pipeline di Ingestion Completa

from llama_index.core import (
    VectorStoreIndex,
    SimpleDirectoryReader,
    StorageContext,
    Settings,
)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.extractors import (
    TitleExtractor,
    SummaryExtractor,
    QuestionsAnsweredExtractor,
)
from llama_index.core.ingestion import IngestionPipeline
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.embeddings.openai import OpenAIEmbedding
from qdrant_client import QdrantClient

# Configurazione del client Qdrant
qdrant_client = QdrantClient(
    url="https://your-cluster.qdrant.io:6333",
    api_key="your-api-key",
)

vector_store = QdrantVectorStore(
    client=qdrant_client,
    collection_name="knowledge_base",
)

# Pipeline di ingestion con arricchimento dei metadata
pipeline = IngestionPipeline(
    transformations=[
        SentenceSplitter(chunk_size=512, chunk_overlap=50),
        TitleExtractor(nodes=5),
        SummaryExtractor(summaries=["self"]),
        QuestionsAnsweredExtractor(questions=3),
        OpenAIEmbedding(model="text-embedding-3-large"),
    ],
    vector_store=vector_store,
)

# Caricamento e processamento dei documenti
documenti = SimpleDirectoryReader(
    input_dir="./knowledge_base",
    recursive=True,
    required_exts=[".pdf", ".md", ".txt", ".docx"],
).load_data()

# Esecuzione della pipeline
nodi = pipeline.run(
    documents=documenti,
    show_progress=True,
    num_workers=4,
)
print(f"Indicizzati {len(nodi)} chunk nel vector store")

Pipeline di Query con LangChain e LangGraph

from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_qdrant import QdrantVectorStore
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from qdrant_client import QdrantClient

# Configurazione dei componenti
llm = ChatOpenAI(model="gpt-4o", temperature=0)
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")

# Connessione al vector store (stessi dati indicizzati con LlamaIndex)
qdrant_client = QdrantClient(
    url="https://your-cluster.qdrant.io:6333",
    api_key="your-api-key",
)

vector_store = QdrantVectorStore(
    client=qdrant_client,
    collection_name="knowledge_base",
    embedding=embeddings,
)

retriever = vector_store.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 5},
)

# Template del prompt RAG
prompt = ChatPromptTemplate.from_template("""
Sei un assistente esperto che risponde alle domande basandosi
esclusivamente sul contesto fornito. Se il contesto non contiene
informazioni sufficienti per rispondere, dillo esplicitamente.

Contesto:
{context}

Domanda: {question}

Rispondi in modo preciso e dettagliato, citando le fonti dal contesto.
""")

def format_docs(docs):
    return "\n\n---\n\n".join(
        f"[Fonte: {doc.metadata.get('source', 'N/A')}]\n{doc.page_content}"
        for doc in docs
    )

# Chain RAG con LCEL (LangChain Expression Language)
rag_chain = (
    {
        "context": retriever | format_docs,
        "question": RunnablePassthrough(),
    }
    | prompt
    | llm
    | StrOutputParser()
)

# Esecuzione della query
risposta = rag_chain.invoke(
    "Quali sono le best practice per la gestione degli errori?"
)
print(risposta)

Il Power Move: LlamaIndex Ingestion + LangGraph Orchestration

Il vero vantaggio competitivo nel 2026 consiste nel combinare la potenza di ingestion di LlamaIndex con l'orchestrazione flessibile di LangGraph. LlamaIndex offre parser documentali superiori, estrattori di metadata avanzati e pipeline di trasformazione robuste. LangGraph, d'altra parte, eccelle nella creazione di flussi di lavoro complessi con stato, branching condizionale e cicli di feedback.

Questa combinazione permette di costruire agenti RAG sofisticati che possono decidere dinamicamente quale strategia di recupero utilizzare, quando effettuare query aggiuntive e come validare le risposte generate. Vediamo come funziona nella pratica.

from langgraph.graph import StateGraph, END
from typing import TypedDict, List, Annotated
from langchain_core.documents import Document
import operator

# Definizione dello stato del grafo
class RAGState(TypedDict):
    question: str
    documents: List[Document]
    generation: str
    relevance_check: str
    retry_count: int

# Nodo di recupero documenti
def retrieve(state: RAGState) -> RAGState:
    question = state["question"]
    documents = retriever.invoke(question)
    return {"documents": documents}

# Nodo di valutazione della rilevanza
def grade_documents(state: RAGState) -> RAGState:
    question = state["question"]
    documents = state["documents"]

    grading_prompt = ChatPromptTemplate.from_template("""
    Valuta se il seguente documento è rilevante per la domanda.
    Rispondi solo 'si' o 'no'.

    Documento: {document}
    Domanda: {question}
    """)

    relevant_docs = []
    for doc in documents:
        grade = (grading_prompt | llm | StrOutputParser()).invoke({
            "document": doc.page_content,
            "question": question,
        })
        if grade.strip().lower() == "si":
            relevant_docs.append(doc)

    relevance = "pertinente" if relevant_docs else "non_pertinente"
    return {
        "documents": relevant_docs,
        "relevance_check": relevance,
    }

# Nodo di generazione
def generate(state: RAGState) -> RAGState:
    question = state["question"]
    documents = state["documents"]
    context = format_docs(documents)

    generation = rag_chain.invoke(question)
    return {"generation": generation}

# Nodo di riformulazione della query
def rewrite_query(state: RAGState) -> RAGState:
    question = state["question"]
    rewrite_prompt = ChatPromptTemplate.from_template("""
    La domanda originale non ha prodotto risultati rilevanti.
    Riformulala per migliorare il recupero.

    Domanda originale: {question}
    Domanda riformulata:
    """)
    new_question = (rewrite_prompt | llm | StrOutputParser()).invoke({
        "question": question,
    })
    return {
        "question": new_question,
        "retry_count": state.get("retry_count", 0) + 1,
    }

# Funzione di routing condizionale
def route_after_grading(state: RAGState) -> str:
    if state["relevance_check"] == "pertinente":
        return "generate"
    elif state.get("retry_count", 0) >= 2:
        return "generate"  # genera comunque dopo 2 tentativi
    else:
        return "rewrite"

# Costruzione del grafo
workflow = StateGraph(RAGState)
workflow.add_node("retrieve", retrieve)
workflow.add_node("grade", grade_documents)
workflow.add_node("generate", generate)
workflow.add_node("rewrite", rewrite_query)

workflow.set_entry_point("retrieve")
workflow.add_edge("retrieve", "grade")
workflow.add_conditional_edges(
    "grade",
    route_after_grading,
    {
        "generate": "generate",
        "rewrite": "rewrite",
    },
)
workflow.add_edge("rewrite", "retrieve")
workflow.add_edge("generate", END)

# Compilazione e esecuzione
app = workflow.compile()
result = app.invoke({
    "question": "Come implementare il caching nella pipeline RAG?",
    "retry_count": 0,
})
print(result["generation"])

Reranking e Ottimizzazione del Recupero

Il reranking è una tecnica fondamentale per migliorare la qualità dei risultati recuperati. Mentre la ricerca vettoriale iniziale utilizza modelli bi-encoder per una ricerca efficiente su milioni di documenti, il reranking applica modelli cross-encoder più potenti (e computazionalmente più costosi) sui top-K risultati per riordinarne la rilevanza in modo più accurato.

Il Problema Lost-in-the-Middle

Ecco qualcosa che molti non sanno: la ricerca ha dimostrato che i modelli LLM tendono a prestare maggiore attenzione alle informazioni posizionate all'inizio e alla fine del contesto, trascurando quelle nel mezzo. Questo fenomeno, noto come "Lost-in-the-Middle", rende il reranking ancora più critico. Posizionare i documenti più rilevanti nelle prime posizioni migliora significativamente la qualità delle risposte generate.

Modelli di Reranking

I principali modelli di reranking disponibili nel 2026 sono:

  • Cohere Rerank v3.5 — Servizio API con eccellenti prestazioni multilingue. Supporta fino a 4096 token per documento. Facile da integrare, ma con costi per chiamata API.
  • BGE Reranker v2.5 — Modello open source di BAAI, disponibile in varianti base e large. Ottimo per deployment self-hosted con GPU. Prestazioni competitive con le soluzioni proprietarie.
  • Cross-encoder ms-marco-MiniLM — Modello leggero e veloce, ideale per scenari con vincoli di latenza stringenti. Le prestazioni sono inferiori ai modelli più grandi, ma i tempi di inferenza sono molto ridotti.
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
from langchain.retrievers.document_compressors import CrossEncoderReranker

# Opzione 1: Reranking con Cohere
cohere_reranker = CohereRerank(
    model="rerank-v3.5",
    top_n=5,
)

compression_retriever_cohere = ContextualCompressionRetriever(
    base_compressor=cohere_reranker,
    base_retriever=retriever,  # retriever base che recupera top-20
)

# Opzione 2: Reranking con cross-encoder open source
cross_encoder = HuggingFaceCrossEncoder(
    model_name="BAAI/bge-reranker-v2.5-gemma2-lightweight",
)
cross_encoder_reranker = CrossEncoderReranker(
    model=cross_encoder,
    top_n=5,
)

compression_retriever_local = ContextualCompressionRetriever(
    base_compressor=cross_encoder_reranker,
    base_retriever=retriever,
)

# Integrazione nella chain RAG con reranking
rag_chain_con_reranking = (
    {
        "context": compression_retriever_cohere | format_docs,
        "question": RunnablePassthrough(),
    }
    | prompt
    | llm
    | StrOutputParser()
)

# Esecuzione con reranking
risposta = rag_chain_con_reranking.invoke(
    "Quali sono le strategie di scaling per database vettoriali?"
)
print(risposta)

Valutazione con RAGAS e Metriche di Qualità

La valutazione sistematica è ciò che distingue una pipeline RAG sperimentale da una pronta per la produzione. E non sto esagerando. Senza metriche solide, state essenzialmente navigando alla cieca. Il framework RAGAS (Retrieval Augmented Generation Assessment) si è affermato come lo standard de facto per la valutazione delle pipeline RAG, offrendo un insieme completo di metriche che coprono ogni aspetto della qualità.

Le Metriche Fondamentali di RAGAS

RAGAS definisce quattro metriche principali che insieme forniscono una visione completa della qualità della pipeline:

  • Faithfulness (Fedeltà) — Misura quanto la risposta generata è fedelmente supportata dal contesto recuperato. Un punteggio alto indica che l'LLM non sta inventando informazioni. È la metrica chiave per rilevare le allucinazioni.
  • Answer Relevancy (Rilevanza della Risposta) — Valuta quanto la risposta generata è pertinente rispetto alla domanda posta. Una risposta può essere fedele al contesto ma non rispondere effettivamente alla domanda — e questo è un problema.
  • Context Precision (Precisione del Contesto) — Misura la proporzione di informazioni rilevanti nei documenti recuperati. Un'alta precisione indica che il sistema non sta recuperando troppi documenti irrilevanti che potrebbero confondere l'LLM.
  • Context Recall (Richiamo del Contesto) — Valuta se tutti i fatti necessari per rispondere alla domanda sono presenti nei documenti recuperati. Un basso recall indica che il sistema di recupero sta mancando documenti importanti.

Valutazione Reference-Free

Una delle caratteristiche più potenti di RAGAS è la capacità di eseguire valutazioni senza risposte di riferimento (reference-free). Utilizzando un LLM come giudice, RAGAS può valutare la fedeltà e la rilevanza delle risposte senza un dataset di gold standard. Questo rende possibile il monitoraggio continuo in produzione, ed è un vero game-changer.

from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
)
from ragas.llms import LangchainLLMWrapper
from ragas.embeddings import LangchainEmbeddingsWrapper
from datasets import Dataset

# Preparazione del dataset di valutazione
eval_data = {
    "question": [
        "Cos'è il chunking adattivo nel contesto RAG?",
        "Quali sono i vantaggi di Qdrant rispetto a Pinecone?",
        "Come funziona il reranking con cross-encoder?",
    ],
    "answer": [
        "Il chunking adattivo regola dinamicamente la dimensione dei chunk "
        "in base alla densità informativa del contenuto, raggiungendo "
        "un'accuratezza dell'87% rispetto al 50% del baseline.",
        "Qdrant offre maggiore flessibilità nel deployment con opzioni "
        "self-hosted e on-premise, filtri avanzati sui metadata e un "
        "modello di pricing trasparente, mentre Pinecone eccelle in "
        "latenza con 30ms p99.",
        "Il reranking con cross-encoder prende coppie query-documento "
        "e calcola un punteggio di rilevanza diretto, permettendo una "
        "valutazione più accurata rispetto ai bi-encoder usati nella "
        "ricerca iniziale.",
    ],
    "contexts": [
        [
            "Il chunking adattivo regola la dimensione dei segmenti in base "
            "alla densità informativa. Test hanno dimostrato un'accuratezza "
            "dell'87% vs 50% baseline.",
        ],
        [
            "Qdrant è open source con deployment flessibile. Latenza p99: "
            "50ms a 1M vettori. Pinecone: 30ms p99, completamente gestito.",
            "Qdrant supporta filtri complessi e payload ricchi con named "
            "vectors per la ricerca ibrida.",
        ],
        [
            "I cross-encoder elaborano coppie query-documento congiuntamente, "
            "producendo punteggi di rilevanza più accurati dei bi-encoder.",
        ],
    ],
    "ground_truth": [
        "Il chunking adattivo è una strategia che regola dinamicamente "
        "la dimensione dei chunk in base al contenuto, con 87% di accuratezza.",
        "Qdrant offre deployment flessibile e filtri avanzati, mentre "
        "Pinecone ha latenza inferiore.",
        "Il reranking con cross-encoder valuta coppie query-documento "
        "per riordinare i risultati per rilevanza.",
    ],
}

dataset = Dataset.from_dict(eval_data)

# Configurazione ed esecuzione della valutazione
evaluator_llm = LangchainLLMWrapper(ChatOpenAI(model="gpt-4o"))
evaluator_embeddings = LangchainEmbeddingsWrapper(
    OpenAIEmbeddings(model="text-embedding-3-large")
)

risultati = evaluate(
    dataset=dataset,
    metrics=[
        faithfulness,
        answer_relevancy,
        context_precision,
        context_recall,
    ],
    llm=evaluator_llm,
    embeddings=evaluator_embeddings,
)

# Visualizzazione dei risultati
print("=== Risultati Valutazione RAGAS ===")
print(f"Faithfulness:       {risultati['faithfulness']:.4f}")
print(f"Answer Relevancy:   {risultati['answer_relevancy']:.4f}")
print(f"Context Precision:  {risultati['context_precision']:.4f}")
print(f"Context Recall:     {risultati['context_recall']:.4f}")

# Converti in DataFrame per analisi dettagliata
df = risultati.to_pandas()
print("\nDettaglio per domanda:")
print(df[["question", "faithfulness", "answer_relevancy"]].to_string())

Pattern Architetturali Avanzati

L'ecosistema RAG nel 2026 ha prodotto diversi pattern architetturali avanzati che affrontano limitazioni specifiche dell'approccio naive. Questi pattern possono essere combinati e adattati alle esigenze del vostro caso d'uso — e vale la pena conoscerli anche se non li implementerete tutti subito.

Agentic RAG

L'Agentic RAG rappresenta l'evoluzione più significativa del paradigma. Invece di seguire un flusso rigido retrieve-then-generate, un agente autonomo decide dinamicamente quale strumento utilizzare, quando effettuare ricerche aggiuntive e come combinare informazioni da fonti diverse. L'agente può decidere di decomporre una query complessa in sotto-query, eseguire ricerche iterative e sintetizzare i risultati in modo intelligente.

LangGraph è il framework ideale per implementare questo pattern, grazie al suo supporto nativo per grafi con stato, branching condizionale e cicli.

Corrective RAG (CRAG)

Il Corrective RAG introduce un meccanismo di auto-correzione nella pipeline. Dopo il recupero iniziale, un modello valutatore analizza la rilevanza dei documenti recuperati. Se i documenti vengono giudicati irrilevanti, il sistema attiva strategie correttive: riformulazione della query, ricerca su fonti alternative (ad esempio web search), o decomposizione della domanda. Nella mia esperienza, questo pattern riduce significativamente i casi in cui l'LLM genera risposte basate su contesto non pertinente.

Self-RAG

Il Self-RAG porta l'auto-valutazione ancora oltre. Il modello genera token speciali di riflessione che indicano se il recupero è necessario, se i documenti recuperati sono rilevanti e se la risposta generata è supportata dal contesto. Questo consente al modello stesso di decidere quando e come utilizzare il recupero, ottimizzando il rapporto tra qualità e latenza.

Graph RAG con Neo4j

Il Graph RAG integra knowledge graph nella pipeline RAG per catturare relazioni strutturate tra entità. Utilizzando Neo4j come backend, il sistema può rispondere a domande che richiedono ragionamento multi-hop, connettendo informazioni distribuite in documenti diversi attraverso le relazioni del grafo.

Un esempio concreto: una domanda come "Quali clienti sono stati serviti dal team che ha sviluppato il prodotto X?" richiede la navigazione di relazioni che la semplice ricerca vettoriale non può catturare.

Multimodal RAG

Le pipeline RAG multimodali estendono il paradigma oltre il testo, indicizzando e recuperando immagini, tabelle, diagrammi e altri contenuti non testuali. Modelli di embedding multimodali come CLIP o i modelli vision-language di OpenAI e Anthropic permettono di creare embedding unificati che catturano sia il contenuto testuale sia quello visuale. Particolarmente rilevante per la documentazione tecnica, i report finanziari con grafici e le presentazioni aziendali.

Best Practice per la Produzione

Il passaggio da un prototipo RAG funzionante a un sistema di produzione robusto richiede attenzione a tanti aspetti operativi che vanno oltre la pura logica di recupero e generazione. Ecco quelli più importanti.

Observability e Tracing

In produzione, è fondamentale avere visibilità completa su ogni fase della pipeline. Implementate tracing distribuito per tracciare il percorso completo di ogni query: dal ricevimento della richiesta, attraverso il recupero e il reranking, fino alla generazione della risposta.

Strumenti come LangSmith, Langfuse e Phoenix (Arize AI) forniscono dashboard specializzate per pipeline RAG, con visualizzazione dei documenti recuperati, dei punteggi di similarità e delle metriche di qualità in tempo reale. Tracciate metriche chiave come la latenza end-to-end, il numero di documenti recuperati per query, i punteggi di reranking e i costi per query.

Strategie di Caching

Il caching è essenziale per ridurre latenza e costi. Implementate caching a più livelli:

  • Embedding cache — Memorizzate gli embedding calcolati per evitare ricalcoli costosi. Particolarmente utile durante la re-indicizzazione incrementale.
  • Semantic cache — Utilizzate un cache basato sulla similarità semantica delle query. Se una nuova query è sufficientemente simile a una precedente, restituite la risposta cached senza eseguire l'intera pipeline. GPTCache e Redis con supporto vettoriale sono soluzioni mature per questo pattern.
  • LLM response cache — Per query identiche, memorizzate la risposta dell'LLM. Riduce sia la latenza sia i costi delle chiamate API.

Gestione degli Errori

Una pipeline RAG di produzione deve gestire gracefully numerosi scenari di errore:

  • Fallback del vector store — Se il database vettoriale non è raggiungibile, prevedete un fallback (ad esempio, un indice locale o una risposta che informa l'utente del problema temporaneo).
  • Timeout e retry — Implementate timeout configurabili e retry con backoff esponenziale per tutte le chiamate a servizi esterni (embedding API, LLM, vector store).
  • Validazione dell'output — Verificate che la risposta generata sia coerente e completa prima di restituirla all'utente. Utilizzate guardrails per filtrare contenuti inappropriati o potenzialmente hallucinated.
  • Circuit breaker — Implementate il pattern circuit breaker per evitare di sovraccaricare servizi in difficoltà e per fornire risposte degradate ma rapide in caso di guasti parziali.

Considerazioni sulla Scalabilità

Per scalare una pipeline RAG, considerate questi aspetti:

  • Horizontal scaling del vector store — Qdrant e Weaviate supportano sharding e replication nativi per distribuire il carico su più nodi.
  • Async processing — Utilizzate processamento asincrono per l'ingestion dei documenti, così l'indicizzazione procede in background senza impattare le query.
  • Batching degli embedding — Calcolate gli embedding in batch per massimizzare il throughput e ridurre il numero di chiamate API.
  • Separazione dei carichi — Mantenete pipeline separate per ingestion (scrittura) e query (lettura), permettendo di scalare indipendentemente ciascuna.

Ottimizzazione dei Costi

I costi di una pipeline RAG in produzione possono crescere rapidamente. Ve lo dico per esperienza. Ecco strategie concrete per tenerli sotto controllo:

  • Embedding model selection — Considerate modelli open source come BGE-M3 per ridurre i costi ricorrenti delle API. Il costo di hosting di un modello su GPU può ammortizzarsi rapidamente ad alti volumi.
  • Dimensione degli embedding — Utilizzate la funzionalità di dimensione ridotta di OpenAI text-embedding-3 (ad esempio 256 invece di 3072) o la compressione binary di Cohere per ridurre i costi di storage nel vector store.
  • Caching aggressivo — Un semantic cache ben configurato può ridurre le chiamate all'LLM del 40-60% per carichi con query ricorrenti. Non è poco.
  • Modelli LLM appropriati — Non tutte le query richiedono il modello più potente. Implementate routing intelligente che utilizza modelli più piccoli ed economici per query semplici, riservando i modelli premium per quelle complesse.

Conclusioni

Costruire una pipeline RAG di produzione nel 2026 è un'impresa ingegneristica significativa che va ben oltre il semplice "collegare un LLM a un database vettoriale". In questa guida abbiamo coperto l'intero spettro: dall'architettura fondamentale alle strategie di chunking avanzate (con il chunking adattivo che raggiunge l'87% di accuratezza contro il 50% del baseline), dalla scelta tra Pinecone (30ms p99), Qdrant (50ms p99) e Weaviate, all'implementazione della ricerca ibrida con BM25 e dense vectors tramite Reciprocal Rank Fusion.

Abbiamo visto il "power move" del 2026 che combina LlamaIndex per l'ingestion con LangChain e LangGraph per l'orchestrazione. Il reranking con cross-encoder affronta il problema del Lost-in-the-Middle, e la valutazione con RAGAS e le sue quattro metriche chiave garantisce un monitoraggio continuo della qualità. I pattern architetturali avanzati — Agentic RAG, Corrective RAG, Self-RAG e Graph RAG — offrono soluzioni per scenari sempre più complessi.

Il campo RAG continua ad evolversi rapidamente. L'integrazione sempre più profonda con i knowledge graph, l'adozione di modelli multimodali e l'emergere di architetture completamente agentiche stanno ridefinendo i confini di ciò che è possibile. Ma i fondamenti che avete appreso qui rimarranno validi: chunking efficace, recupero di qualità, valutazione rigorosa e operatività robusta sono principi universali.

Il vostro prossimo passo? Prendete un caso d'uso concreto, applicate i pattern descritti e iterate sulla base dei risultati. La pipeline RAG perfetta non nasce al primo tentativo — ma con gli strumenti giusti, ogni iterazione vi avvicina a un sistema che genera valore reale.

Sull'Autore Editorial Team

Our team of expert writers and editors.