Skip to content

Retrieval-Augmented Generation

Sintesi esecutiva

Il Retrieval-Augmented Generation, o RAG, è una classe di architetture in cui un modello generativo non risponde solo con la propria memoria parametrica, ma integra evidenze recuperate da basi documentali esterne al momento della query. In pratica, RAG serve quando la conoscenza è proprietaria, dinamica, soggetta a frequenti aggiornamenti, o quando servono tracciabilità e citazioni verificabili. Le survey recenti collocano l’evoluzione del campo lungo tre famiglie operative — naive, advanced e modular RAG — e mostrano che il valore non sta in “aggiungere un vector store”, ma nel progettare bene ingestione, retrieval, reranking, generazione, osservabilità ed eval.

Dal punto di vista ingegneristico, il baseline più robusto oggi non è quasi mai “dense retrieval puro + LLM”, ma una pipeline ibrida con metadati/ACL, ricerca lessicale e semantica, fusione di ranking, reranking di secondo stadio e prompt grounded con policy di astensione. La documentazione ufficiale di Azure AI Search e di OpenAI File Search converge proprio su questi pattern: hybrid search, query rewriting, decomposition multi-query, semantic reranking, citazioni e limiti sul numero di chunk usati come contesto.

Per la produzione enterprise, la distinzione decisiva non è “RAG sì o no”, ma “quale grado di modularità, osservabilità e governance”. Fare affidamento su ragionamenti impliciti del modello è meno controllabile di decomporre il processo in moduli espliciti: query rewriting, routing, subquery planning, candidate generation, fusion, reranking, citation packing e answer policy. Questi artefatti sono ispezionabili, testabili e versionabili; diventano quindi preferibili a trace verbali non governati.

La valutazione deve essere bifocale: retriever e generator vanno misurati separatamente e insieme. Metriche come context precision, context recall, answer relevance e faithfulness sono oggi standard de facto nei toolkit di valutazione; la sola accuracy finale non basta perché può mascherare errori di retrieval, over-contexting o risposte fluenti ma non supportate. In parallelo, vanno misurate latenze p50/p95/p99, throughput, hit-rate di cache, tasso di fallback, costo per query e tasso di risposte senza evidenza sufficiente.

Sul piano di sicurezza e compliance, il punto critico è che RAG amplia la superficie d’attacco: indirect prompt injection nei documenti, poisoning dell’indice, leakage cross-tenant, esfiltrazione tramite citazioni, inversione degli embedding e uso eccessivo di dati personali. Le linee guida di OWASP, NIST, della Commissione europea e del Garante per la protezione dei dati personali convergono su controlli molto concreti: data minimization, purpose limitation, filtri ACL deterministici, logging, incident response, revisione umana nei casi critici e gestione della retention/deletion di file, chunk, embedding e cache.

Definizioni, storia e tassonomia

RAG è il pattern architetturale in cui un LLM combina conoscenza parametrica con conoscenza non parametrica recuperata da una base esterna al tempo di inferenza. Le survey contemporanee descrivono il RAG come una risposta ai limiti strutturali degli LLM generalisti: conoscenza statica, rischio di hallucination, scarsa trasparenza sul supporto documentale e difficoltà di aggiornamento continuo senza riaddestramento. Per questo motivo, RAG è oggi il paradigma di default in assistenti documentali, copiloti enterprise, knowledge bots, semantic search e sistemi di supporto decisionale con basi informative proprietarie.

La tassonomia più utile in pratica distingue tre livelli. Il naive RAG esegue chunking, embedding, vector search e generazione in una pipeline lineare. L’advanced RAG aggiunge query rewriting, hybrid retrieval, reranking, filtri metadato, context compression e strategie di grounding più severe. Il modular RAG spezza la pipeline in componenti intercambiabili, fino ad arrivare a sistemi agentic o graph-based che orchestrano più retrieval step, più fonti e più trasformazioni del contesto. La survey sul GraphRAG mostra inoltre che l’indicizzazione strutturata su entità e relazioni è ormai una sottofamiglia importante, soprattutto per domande multi-hop e contesti fortemente relazionali.

Dal punto di vista del ciclo di vita, “RAG” coincide meno con una singola tecnica e più con una disciplina di information retrieval applicata agli LLM. In altre parole, il retriever non è un accessorio del generatore: ne determina direttamente qualità, costo, latenza e rischio di risposte infondate. Non a caso, documentazioni di prodotto e linee guida ufficiali parlano sempre più di retrieval engineering o context engineering, non solo di prompting.

Una distinzione importante, spesso sottovalutata, è fra “RAG per aggiornare la conoscenza” e “RAG per imporre provenienza”. Nel primo caso il focus è il refresh informativo. Nel secondo caso il focus è la validabilità: recuperare le evidenze corrette, selezionarle, ordinarle, filtrare rumore e imporre che l’output non oltrepassi il supporto disponibile. È questo secondo uso a rendere RAG particolarmente adatto a ambienti regolati o con obbligo di audit.

Architettura, componenti e flussi

Una pipeline RAG completa ha due macro-percorsi: ingestione/offline indexing e query-time retrieval/generation. Nel percorso offline si acquisiscono file o record, si esegue parsing, si applicano normalizzazione e chunking, si arricchiscono i metadati, si calcolano embedding e si costruiscono gli indici. Nel percorso online si riceve la query, la si riscrive o decompone se necessario, si recuperano candidati, li si reranka, si costruisce il prompt grounded e solo a quel punto si invoca il modello generativo. Sia Azure AI Search sia OpenAI File Search formalizzano questo dualismo nei propri workflow ufficiali.

mermaid
flowchart LR
    A[Sorgenti: PDF, HTML, DB, ticket, wiki] --> B[Parsing e normalizzazione]
    B --> C[Chunking]
    C --> D[Metadati: source, lang, ACL, tenant, version]
    D --> E[Embeddings dense]
    D --> F[Embeddings sparse o indice BM25]
    E --> G[Vector store / indice ANN]
    F --> H[Indice lessicale]
    D --> I[Catalogo documentale e lineage]
    G --> J[Corpus interrogabile]
    H --> J
    I --> J

Indicizzazione e chunking. L’ingestione non è un dettaglio accessorio: chunking, clean-up, deduplica e metadati influenzano direttamente recall e faithfulness. OpenAI File Search espone persino la chunking strategy come parametro per-file; Azure AI Search supporta pipeline integrate con text splitting e vectorization; i document store moderni consentono inoltre filtri su attributi, multi-tenancy e, in diversi casi, named vectors o multivector schemas.

Embeddings. Gli embedding trasformano testo, immagini o altri artefatti in vettori confrontabili geometricamente. La documentazione di OpenAI ricorda che la dimensionalità può variare per modello e persino essere ridotta via parametro dimensions; gli embedding model moderni possono essere multilingua e spesso cross-lingual. Il paper su BGE-M3 è rilevante perché unifica dense retrieval, sparse retrieval e multi-vector retrieval in un solo modello multilingue, con copertura di oltre 100 lingue, caratteristica utile anche per corpora it-IT misti o in code-switching.

Tipi di rappresentazione vettoriale. In pratica conviene distinguere almeno quattro famiglie: dense vectors per similarità semantica; sparse vectors per espansione lessicale o learned sparse retrieval; binary vectors per casi specializzati o ottimizzazioni di storage; multi-vector/late interaction per documenti lunghi o retrieval più fine, ad esempio in stile ColBERT. Pinecone distingue dense, sparse e hybrid index; Milvus supporta dense, sparse e binary vectors; Weaviate supporta anche multi-vector encodings; Azure AI Search documenta float32, float16, int8 e binary vector fields come opzioni di memorizzazione o compressione.

Vector stores e indici ANN. Il ruolo del vector store non è “salvare embedding”, ma ottimizzare compromessi tra recall, latenza, memoria, aggiornabilità e filtri. HNSW è oggi il default più comune per ANN low-latency; Milvus, Qdrant, Weaviate e Azure lo documentano esplicitamente. Quando il carico o i corpus crescono, entrano in gioco sharding, replicas, compressione, quantizzazione, oversampling e rescoring. Azure, Qdrant e Weaviate documentano bene questi pattern; Milvus li porta su architetture compute-storage disaggregate per scala elevata.

Retrieval. Dense-only retrieval è raramente il punto di arrivo. La documentazione di Azure AI Search e Pinecone converge su hybrid retrieval: query lessicale e vettoriale eseguite in parallelo e fuse via Reciprocal Rank Fusion. Questo pattern è forte perché combina precisione lessicale con ricchezza semantica. Anthropic, nel proprio lavoro su Contextual Retrieval, mostra inoltre che contextual embeddings e contextual BM25 migliorano sensibilmente il retrieval, con un calo dei failed retrievals dichiarato del 49%, che arriva al 67% in combinazione con reranking.

Reranking. Il reranking è il secondo stadio qualitativo più importante dopo il retrieval. I cross-encoder di Sentence Transformers non producono embedding documentali precomputabili: valutano direttamente la coppia query-document e per questo risultano più accurati ma più costosi. La documentazione di Pinecone, Milvus, Weaviate e Azure semantic ranker descrive esattamente questa logica: first-stage retrieval per recall, second-stage rerank per precisione. In produzione, il punto chiave è contenere il candidate set prima del reranker; rerankare migliaia di chunk quasi sempre degrada latenza e costo senza benefici corrispondenti.

Argomentazione implicita contro moduli espliciti. In un RAG industriale non conviene trattare il ragionamento come una “scatola nera verbale” demandata al modello. Conviene esternalizzare dove possibile il piano di lavoro: query rewriting, decomposition in subquery, retrieval budget, filtri, fusion, citation packing e answer policy. È esattamente ciò che fanno oggi agentic retrieval in Azure AI Search e i tool di file search gestiti, che riscrivono query, le scompongono e fondono più retrieval prima della generazione. Questo approccio non elimina il ragionamento del modello, ma lo incapsula in artefatti osservabili e quindi governabili.

Generazione e integrazione LM. Il generatore dovrebbe ricevere non tutta l’evidenza grezza, ma il sottoinsieme migliore, già ordinato e con metadati essenziali. Il prompt dovrebbe imporre almeno quattro policy: rispondi solo con evidenza presente; cita le fonti; dichiara insufficienza di supporto quando serve; non trasformare assenza di evidenza in negazione fattuale. OpenAI lo formalizza come grounding, citations e retrieval budgets; Anthropic parla ormai di context engineering come estensione naturale del prompt engineering.

mermaid
sequenceDiagram
    actor U as Utente
    participant API as API RAG
    participant C as Cache
    participant P as Query planner
    participant D as Dense retriever
    participant L as Lessicale/BM25
    participant R as Reranker
    participant G as Generatore
    participant A as Audit/Tracing

    U->>API: Query
    API->>C: lookup(query, tenant, version)
    alt cache hit
        C-->>API: contesto/risposta
        API-->>U: Risposta
    else cache miss
        API->>P: rewrite/decompose/filter
        P->>D: dense search
        P->>L: keyword/hybrid search
        D-->>P: candidati
        L-->>P: candidati
        P->>R: candidate set fuso
        R-->>API: top-k rerankati
        API->>G: prompt grounded + contesto
        G-->>API: risposta + citazioni
        API->>A: trace, chunks, score, latenza
        API-->>U: Risposta + citazioni
    end

Pattern implementativi e deployment

Il pattern più comune in produzione è “ingestione offline, retrieval online”. Il corpus viene parsato, chunkato e indicizzato fuori banda; la query utente attiva solo il percorso tempo-critico. Questo separa write path e read path, riduce latenza percepita e rende possibile reindicizzare per versioni, modelli di embedding o politiche ACL diverse. Quando invece il dato è molto volatile — ticketing live, telemetria, trading research, incident management — bisogna progettare anche ingestion near-real-time e freshness policies esplicite. Le architetture documentate da Azure AI Search, Qdrant, Weaviate e Milvus convergono proprio su questa separazione di responsabilità.

Caching. Ci sono almeno quattro cache utili. Query cache per domande ripetitive; embedding cache per testi già vettorizzati; retrieval cache per candidate set stabili; prompt/context cache per contesti lunghi e poco variabili. Anthropic documenta esplicitamente prompt caching e lo collega anche alle citazioni; OpenAI e i servizi hosted di retrieval riducono costi e latenza limitando il numero di chunk passati al modello. In presenza di corpora statici o semi-statici, la cache del contesto spesso rende più dei micro-tuning sul modello.

Scalabilità. I meccanismi dichiarati dai fornitori sono abbastanza convergenti: repliche per throughput, partizioni/shard per storage e parallelismo, compressione/quantizzazione per footprint, autoscaling o serverless per carichi elastici. Pinecone distingue chiaramente pods e replicas; Azure AI Search usa replicas e partitions; Weaviate e Qdrant scalano orizzontalmente via cluster/sharding; Milvus separa compute e storage per adattarsi a carichi read-heavy e write-heavy in modo indipendente.

Fault tolerance. Un RAG robusto deve degradare bene. Se il reranker fallisce, la pipeline deve poter rispondere con first-stage retrieval e una confidence policy più stretta. Se il dense retriever degrada, il lessicale deve restare disponibile. Se il generatore non è raggiungibile, l’app dovrebbe poter dare almeno estratti o answer snippets non generativi. I servizi moderni espongono già primitive per replica, rebalancing e rolling updates, ma il failover end-to-end rimane responsabilità applicativa.

I grafici seguenti sono concettuali, non benchmark universali: servono a visualizzare il trade-off tipico, non a sostituire misure reali sul proprio workload. La direzione del trade-off è però coerente con la documentazione dei retriever ANN, dei semantic ranker e dei cross-encoder.

mermaid
xychart-beta
    title "Latenza p95 indicativa"
    x-axis ["Dense","Hybrid","Hybrid+Rerank","Hybrid+Rerank+Cache","Agentic"]
    y-axis "Indice relativo" 0 --> 240
    bar [100,115,165,120,230]
mermaid
xychart-beta
    title "Throughput indicativo"
    x-axis ["Dense","Hybrid","Hybrid+Rerank","Hybrid+Rerank+Cache","Agentic"]
    y-axis "Indice relativo" 0 --> 160
    bar [100,92,65,135,45]

Per deployment, tre blueprint coprono la maggior parte dei casi.

Deployment piccolo. Adatto a team singoli, knowledge base limitate, workload moderati e pochi tenant.

mermaid
flowchart LR
    U[Utente] --> A[API RAG]
    A --> V[Vector DB / Search]
    A --> M[LLM endpoint]
    S[Storage documenti] --> W[Worker ingestione]
    W --> V

Deployment medio. Adatto a più team, ACL serie, pipeline CI/CD, osservabilità e refresh frequenti.

mermaid
flowchart LR
    U[Utente] --> GW[API Gateway]
    GW --> APP[Servizio RAG]
    APP --> RC[Redis / retrieval cache]
    APP --> SRCH[Search + vector store]
    APP --> RER[Reranker service]
    APP --> LLM[Model gateway]
    SRC[Object storage / DB / wiki] --> ING[Ingestion workers]
    ING --> SRCH
    ING --> CAT[Catalogo metadati / lineage]
    APP --> OBS[Tracing + metrics + logs]

Deployment grande. Adatto a multi-tenant, alta disponibilità, policy severe, grandi corpus e carichi imprevedibili.

mermaid
flowchart LR
    U[Clienti / canali] --> CDN[Edge / API gateway]
    CDN --> ORCH[Orchestratore RAG]
    ORCH --> PLAN[Planner / policy engine]
    ORCH --> C1[Cache query]
    PLAN --> S1[Cluster search shard A]
    PLAN --> S2[Cluster search shard B]
    PLAN --> S3[Cluster search shard C]
    PLAN --> RR[Pool reranker]
    ORCH --> MGW[Model gateway multi-provider]
    MGW --> LM1[LLM primary]
    MGW --> LM2[LLM fallback]
    BUS[Event bus] --> ING[Ingestion stream processors]
    DLS[Data lake / object store] --> ING
    ING --> IDX[Index build workers]
    IDX --> S1
    IDX --> S2
    IDX --> S3
    ORCH --> OBS[Tracing, SIEM, audit, cost telemetry]

Le migliori pratiche operative sono molto concrete: baseline ibrida prima di ottimizzare; candidate set piccolo prima del reranking; metadati e ACL obbligatori in indice; versionamento di chunk ed embedding; trace completo dei chunk usati in risposta; answer policy con astensione; misurazione separata di retrieval e generation. Gli errori ricorrenti sono l’opposto: dense-only retrieval, assenza di lineage, chunking non versionato, niente filtri tenant, niente eval regressivi e uso di chain-of-thought implicito come sostituto di componenti espliciti.

Valutazione, benchmark e test

Il modo più utile di valutare RAG è separare tre livelli: qualità del retrieval, qualità della risposta, qualità operativa del sistema. Ragas propone un set di metriche component-wise che è diventato quasi standard: faithfulness, answer relevancy, context recall, context precision e altre varianti. TruLens parla di RAG triad — context relevance, groundedness, answer relevance — che è concettualmente compatibile. DeepEval copre invece metriche di hallucination e altre verifiche LLM-as-a-judge; OpenAI Evals fornisce il telaio per regression test e flywheel di miglioramento continuo.

Una tassonomia minima di metriche è la seguente.

LivelloMetriche consigliatePerché sono utiliFonti
RetrievalRecall@k, Precision@k, nDCG@k, MRR, Context Precision, Context RecallMisurano copertura delle evidenze e qualità del ranking prima che il generatore possa introdurre rumore.
GenerationExact Match/F1 dove applicabile, Answer Relevance, completezza, citation coverageMisurano se la risposta risolve davvero il task.
GroundingFaithfulness, Groundedness, unsupported-claim rateDistinguono risposte corrette da risposte solo fluenti.
Operativop50/p95/p99 latency, throughput, timeout rate, cache hit-rate, costo/querySenza questi numeri non si progetta il capacity planning.
Sicurezzainjection success rate, poisoning detection rate, false negative human review rateIl rischio di sicurezza va misurato come i KPI di qualità.

Per i dataset, una suite minima sensata non deve essere “un solo benchmark”, ma un mix di casi single-hop, multi-hop e grounding rigoroso. Natural Questions resta utile per open-domain QA a risposta breve; HotpotQA, 2WikiMultihopQA e MuSiQue sono particolarmente rilevanti per progettare RAG che debbano comporre evidenze da più documenti; per il lato faithfulness/hallucination conviene sempre affiancare un gold set interno annotato, perché i benchmark pubblici non riflettono ACL, formati, tassonomie e linguaggio del dataset aziendale.

Gli esperimenti raccomandati, in ordine di ritorno tecnico, sono cinque. Primo: ablation delle strategie di retrieval, confrontando dense-only, sparse-only, hybrid, hybrid+rerank, con gold evidence fissato. Secondo: grid search su chunking, overlap, top-k e top-n del reranker. Terzo: test multi-hop su query che richiedono composizione documentale, non lookup diretto. Quarto: eval di robustezza con query ambigue, incomplete, typo-heavy, adversariali e fuori dominio. Quinto: load test con misurazione separata del costo del first-stage retrieval, del reranker e del generatore.

Sul piano del testing strategy, la combinazione migliore è: offline eval su dataset gold; regression suite ad ogni change di chunker, embedder, ranker o prompt; shadow traffic su percentuali controllate; annotazione umana per falsi positivi critici; e un set dedicato di “no answer cases”, cioè query per cui il sistema deve astenersi. Nei sistemi RAG maturi, la qualità non deriva da un prompt brillante ma da un eval flywheel stabile.

Sicurezza, privacy e governance

RAG aggiunge superfici di attacco specifiche. La più nota è l’indirect prompt injection: istruzioni malevole nascoste in file, pagine, email o tool output che il modello interpreta come comandi. OWASP lo colloca esplicitamente come LLM01, e sia OpenAI sia Anthropic trattano il prompt injection come rischio di frontiera, non come problema “risolto”. In un sistema RAG questo significa che il corpus va considerato input potenzialmente non affidabile, anche se proviene da fonti interne o curate.

Accanto al prompt injection c’è il poisoning del retrieval: documenti manipolati per dominare ranking, ingannare il reranker o forzare citazioni apparenti. Poi c’è il rischio di secure output handling: una risposta con HTML, SQL, shell fragment o link può diventare vettore verso sistemi downstream se non viene trattata come output non trusted. Per questo le checklist OWASP insistono su validazione, sanitizzazione, monitoring, incident response e kill switch applicativi.

Sul piano della riservatezza, non bisogna assumere che l’embedding sia “anonimo”. La letteratura recente su embedding inversion mostra che i dense embeddings possono conservare abbastanza segnale da ricostruire parte del testo originale o estrarre dati sensibili. Conseguenza pratica: per dati con PII, segreti industriali o contenuti regolati, gli embedding vanno trattati come dato sensibile o quasi-sensibile, con segmentazione, cifratura, retention minima e controlli di accesso coerenti con il dato sorgente.

In ambito privacy, la Commissione europea e il Garante per la protezione dei dati personali richiamano i principi generali del GDPR: liceità, trasparenza, purpose limitation e data minimization. Tradotto in RAG: indicizzare solo ciò che serve; evitare PII non necessaria; usare dati anonimi o pseudonimi quando possibile; conservare file, chunk, embedding e cache solo per il tempo strettamente necessario; e gestire i diritti dell’interessato in modo coerente su tutte le rappresentazioni derivate.

Questo implica una governance concreta del lineage. Ogni chunk dovrebbe conservare almeno source id, version, tenant, ACL, lingua, hash del contenuto, modello di embedding, timestamp di ingestione e stato di validazione. Gli strumenti hosted cominciano a supportare retention e cancellazione a livello di vector store e file object; ad esempio OpenAI documenta expiration policies e la rimozione del file sottostante da tutti i vector store collegati. Ma la coerenza fra oggetto sorgente, indice, cache e log resta responsabilità del sistema applicativo.

Sul piano regolatorio, la normativa europea non tratta “RAG” come categoria a sé: conta il ruolo del soggetto e soprattutto l’uso finale del sistema. La documentazione ufficiale sul Regolamento AI chiarisce che si tratta di un quadro risk-based per provider e deployer; le obbligazioni sui modelli GPAI sono entrate in applicazione il 2 agosto 2025, mentre la timeline complessiva del regolamento arriva al 2 agosto 2027. Per i deployer di sistemi high-risk valgono poi obblighi specifici, inclusi uso conforme alle istruzioni, human oversight, monitoraggio e, in certi casi, conservazione dei log per almeno sei mesi. Di conseguenza, un assistente RAG documentale non è automaticamente “high-risk”, ma può entrare in quell’area se incorporato in un caso d’uso di Allegato III o safety-critical.

Strumenti e tutorial

Nelle tabelle seguenti, “linguaggi SDK” indica i linguaggi di sviluppo del framework o servizio. La copertura linguistica del contenuto, incluso l’italiano, dipende soprattutto da embedding model, tokenizer, retriever e LM scelti, non solo dal prodotto orchestratore. Le colonne facilità d’integrazione e maturità sono valutazioni sintetiche inferenziali, non claim ufficiali.

StrumentoLicenza prevalenteLinguaggi SDKTipi vettoriali / retrievalScalabilitàModello di costoFacilità d’integrazioneLM supportatiMaturitàFonti
HaystackOSSPythonDelega a document store esterni; supporta dense/sparse/rankers modulariAlta lato orchestrazione; la scala del retrieval dipende dal backendOSS self-host; supporto enterprise separatoMedia-AltaOpenAI, Anthropic, Hugging Face, local, Ollama, altriAlta
LangChainOSSPython, JS/TSAmpio ecosistema di retriever e vector store via integrazioniAlta, ma dipende molto dall’architettura applicativaOSS; osservabilità/servizi commerciali separatiAltaEcosistema molto ampio di provider e toolAlta
LlamaIndexOSSPython, TSMolte integrazioni con vector store; approccio forte al context engineeringAlta per workflow modulariOSS; servizi cloud/document processing separatiAltaOpenAI, Anthropic, Hugging Face, Ollama, altriAlta
StrumentoLicenza prevalenteLinguaggi SDKTipi vettoriali / retrievalScalabilitàModello di costoFacilità d’integrazioneLM supportatiMaturitàFonti
ElasticSource-available/commercialeREST, vari SDKDense, sparse, hybrid, reranking, playground RAGAlta; autoscaling Cloud/ServerlessSelf-managed o cloud commercialeMediaElastic Managed LLMs e provider esterni, inclusi OpenAI, Anthropic, Azure, Bedrock, GoogleAlta
PineconeProprietaria SaaSPython, JS, REST, altri SDKDense, sparse, hybrid, rerankMolto alta; serverless e scaling dedicatoManaged SaaS; dettaglio di pricing non sviluppato nelle fonti consultateAltaLM esterni; embedding e reranking hosted/opzionaliAlta
MilvusApache 2.0Python, Java, Go, REST, altriDense, sparse, binary, hybrid, rerankMolto alta; architettura distribuita con compute/storage separatiOSS gratuito; cloud managed separatoMediaLM esterni e integrazioni varieAlta
WeaviateOSS + cloud managedPython, JS/TS, GraphQL/RESTHNSW, hybrid, multi-vector, rerankingAlta; clustering e supporto cloud dedicatoSelf-host OSS; Cloud con piani pay-as-you-go/contrattoAltaIntegra provider come OpenAI, Anthropic, Cohere, Hugging Face, Ollama e altriAlta
OpenAI RetrievalProprietaria managed APIPython, JS, .NET, RESTSemantic + keyword search; dettagli interni dell’indice non pienamente pubblici; reranking gestitoAlta, gestita dal servizioToken + storage vector store + tool callMolto altaModelli OpenAIAlta
AnthropicProprietaria APIPython, TS/JS, RESTSearch results, citations, tool use; retrieval store tipicamente esterno o non specificato pubblicamente nelle fonti consultateAlta lato API; retrieval infra da comporreToken-based API; prompt caching disponibileMediaModelli ClaudeAlta
Azure AI Search di MicrosoftProprietaria managed serviceREST, Python, .NET, JS, JavaDense, hybrid, semantic ranker, agentic retrieval, binary/vector compressionAlta; replicas e partitionsConsumo del servizio + costo LM/embedding se usatiAltaQualsiasi LM esterno; integrazione first-class con Azure OpenAIAlta

Stack open-source puro: Qdrant + Sentence Transformers + CrossEncoder + Ollama.
Questo stack è utile quando servono pieno controllo, esecuzione locale, assenza di lock-in e ispezionabilità massima. Qdrant supporta local mode persistente; Sentence Transformers copre semantic search e cross-encoders; Ollama fornisce un’API locale semplice per la generazione. Per un corpus in italiano o multilingua, BGE-M3 e il reranker BGE v2 m3 sono una scelta pragmatica, ma il pattern resta identico con altri modelli open.

python
# pip install qdrant-client sentence-transformers ollama

import os
import uuid
from pathlib import Path

from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
from sentence_transformers import SentenceTransformer, CrossEncoder
from ollama import Client as OllamaClient

EMBED_MODEL = "BAAI/bge-m3"
RERANK_MODEL = "BAAI/bge-reranker-v2-m3"
OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "gemma3")
COLLECTION = "kb_it"

embedder = SentenceTransformer(EMBED_MODEL)
reranker = CrossEncoder(RERANK_MODEL)
qdrant = QdrantClient(path="./qdrant_data")  # persistente
ollama = OllamaClient(host="http://localhost:11434")


def chunk_text(text: str, max_chars: int = 1200, overlap: int = 200) -> list[str]:
    chunks = []
    start = 0
    text = text.strip()
    while start < len(text):
        end = min(len(text), start + max_chars)
        chunk = text[start:end].strip()
        if chunk:
            chunks.append(chunk)
        if end == len(text):
            break
        start = max(0, end - overlap)
    return chunks


def load_documents(folder: str) -> list[dict]:
    docs = []
    for path in Path(folder).glob("*.*"):
        if path.suffix.lower() not in {".txt", ".md"}:
            continue
        docs.append(
            {
                "doc_id": path.stem,
                "source": path.name,
                "text": path.read_text(encoding="utf-8"),
            }
        )
    return docs


def ensure_collection():
    dim = embedder.get_sentence_embedding_dimension()
    existing = {c.name for c in qdrant.get_collections().collections}
    if COLLECTION not in existing:
        qdrant.create_collection(
            collection_name=COLLECTION,
            vectors_config=VectorParams(size=dim, distance=Distance.COSINE),
        )


def index_folder(folder: str):
    ensure_collection()
    points = []

    for doc in load_documents(folder):
        for chunk_id, chunk in enumerate(chunk_text(doc["text"])):
            vec = embedder.encode(chunk, normalize_embeddings=True).tolist()
            points.append(
                PointStruct(
                    id=str(uuid.uuid4()),
                    vector=vec,
                    payload={
                        "doc_id": doc["doc_id"],
                        "source": doc["source"],
                        "chunk_id": chunk_id,
                        "text": chunk,
                    },
                )
            )

    qdrant.upsert(collection_name=COLLECTION, points=points)
    print(f"Indicizzati {len(points)} chunk nella collection '{COLLECTION}'.")


def retrieve(query: str, top_k: int = 20, top_n: int = 5) -> list[dict]:
    qvec = embedder.encode(query, normalize_embeddings=True).tolist()

    hits = qdrant.query_points(
        collection_name=COLLECTION,
        query=qvec,
        limit=top_k,
    ).points

    candidates = [hit.payload for hit in hits]

    pairs = [(query, c["text"]) for c in candidates]
    scores = reranker.predict(pairs)

    ranked = sorted(
        [{"rerank_score": float(s), **c} for s, c in zip(scores, candidates)],
        key=lambda x: x["rerank_score"],
        reverse=True,
    )
    return ranked[:top_n]


def answer(query: str) -> tuple[str, list[dict]]:
    docs = retrieve(query)

    context = "\n\n".join(
        f"[{i+1}] source={d['source']} chunk={d['chunk_id']}\n{d['text']}"
        for i, d in enumerate(docs)
    )

    system_prompt = (
        "Sei un assistente RAG tecnico. "
        "Rispondi solo con fatti supportati dal CONTEXT. "
        "Se l'evidenza non basta, scrivi 'Non determinabile dal contesto'. "
        "Cita le fonti come [1], [2], ..."
    )

    user_prompt = f"""QUESTION:
{query}

CONTEXT:
{context}

Rispondi in italiano, in modo tecnico, senza inventare dettagli."""
    resp = ollama.chat(
        model=OLLAMA_MODEL,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt},
        ],
        options={"temperature": 0},
    )
    return resp["message"]["content"], docs


if __name__ == "__main__":
    index_folder("./docs")
    text, sources = answer("Qual è la procedura di failover documentata?")
    print(text)
    print("\n--- SOURCES ---")
    for s in sources:
        print(s["source"], s["chunk_id"], round(s["rerank_score"], 4))

Questo esempio copre l’intera catena: indexing, dense retrieval, reranking esplicito e generation. In produzione, le estensioni naturali sono cinque: filtri ACL/tenant nel payload, hybrid retrieval con canale lessicale aggiuntivo, batching degli embedding, tracing per chunk usati in risposta e fallback a answer extractive/no-answer quando il rerank score resta sotto soglia.

Stack cloud-managed: OpenAI Responses API + File Search.
Qui l’indice, la ricerca keyword+semantic, il query rewriting, la decomposition multi-search e il reranking sono gestiti dal servizio. È il percorso più rapido quando la priorità è spedire un assistente grounded senza costruire internamente l’infrastruttura di retrieval. Le fonti ufficiali dicono chiaramente che File Search, nelle versioni documentate, riscrive query, spezza query complesse, esegue semantic+keyword search e reranka i risultati prima della generazione.

python
# pip install openai

import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
MODEL = os.getenv("OPENAI_MODEL", "gpt-4.1")

# 1) Upload dei file
file_ids = []
for file_path in ["./docs/architettura.pdf", "./docs/runbook.md"]:
    with open(file_path, "rb") as f:
        file_obj = client.files.create(file=f, purpose="assistants")
        file_ids.append(file_obj.id)

# 2) Creazione del vector store
vs = client.vector_stores.create(name="kb-rag-it")

# 3) Batch ingest con metadati e strategia di chunking per-file
batch = client.vector_stores.file_batches.create_and_poll(
    vector_store_id=vs.id,
    files=[
        {
            "file_id": file_ids[0],
            "attributes": {"domain": "architecture", "lang": "it"},
        },
        {
            "file_id": file_ids[1],
            "attributes": {"domain": "operations", "lang": "it"},
            "chunking_strategy": {
                "type": "static",
                "max_chunk_size_tokens": 1000,
                "chunk_overlap_tokens": 200,
            },
        },
    ],
)

print("Batch status:", batch.status)

# 4) Retrieval + reranking managed + generation
response = client.responses.create(
    model=MODEL,
    input=(
        "Quali dipendenze tecniche ha il servizio X e quale procedura di rollback "
        "è prevista nel runbook?"
    ),
    tools=[
        {
            "type": "file_search",
            "vector_store_ids": [vs.id],
            "filters": {
                "type": "eq",
                "key": "lang",
                "value": "it",
            },
            "max_num_results": 6,
        }
    ],
    include=["file_search_call.results"],
)

print(response.output_text)

# 5) Ispezione dei risultati effettivamente recuperati
for item in response.output:
    if item.type == "file_search_call" and getattr(item, "results", None):
        print("\n--- SEARCH RESULTS ---")
        for r in item.results:
            # a seconda della versione SDK, alcuni campi possono essere opzionali
            filename = getattr(r, "filename", "<unknown>")
            score = getattr(r, "score", None)
            print(filename, score)

Per questo stack è utile conoscere i default documentati lato servizio: chunk size 800 token, overlap 400, embedding model text-embedding-3-large a 256 dimensioni, fino a 20 chunk portati in contesto, ranker auto. Sono buoni default, ma non vanno trattati come ottimali a priori: su corpus legali, ticketing o SOP interne conviene sempre sperimentare chunking, filtri metadata e max results.

Se serve un reranking più esplicito e controllabile, con segnali lessicali e semantici gestiti in modo dichiarativo, Azure AI Search semantic ranker o servizi come Pinecone Rerank e Weaviate Reranking sono più adatti di un fully hosted retriever opaco. Se invece la priorità è ridurre time-to-value e codice applicativo, il managed file search è spesso il compromesso migliore.

Limiti e questioni aperte

Questo report confronta oggetti eterogenei — framework di orchestrazione, vector database, motori search e API hosted di retrieval — quindi alcune celle della tabella sono inevitabilmente normalizzate e non perfettamente omogenee. In particolare, licenze e modelli di costo possono differire fra core OSS, estensioni enterprise e offerte cloud managed; dove la documentazione consultata non esplicita un dettaglio, il report lo mantiene generale o lo segnala come non specificato.

I grafici di latenza e throughput sono volutamente concettuali: servono a visualizzare la direzione dei trade-off, non a sostituire benchmark reali. Per numeri decisionali servono prove sul proprio dataset, con il proprio embedder, il proprio LM, le proprie ACL e il proprio pattern di query.

Restano tre questioni aperte di ricerca e architettura. La prima è la robustezza contro indirect prompt injection nei documenti recuperati: il problema è noto e serio, ma non ancora risolto in modo generale. La seconda è la privacy degli embedding, perché inversione e leakage rendono sempre meno sostenibile considerarli “anonimi”. La terza è la gestione efficiente di reasoning multi-hop e GraphRAG senza esplodere costi e latenze. Su tutti e tre i fronti, la direzione più promettente non sembra essere “più prompting”, ma più struttura: retrieval explicit, policy engine, logging, eval continui e controlli di sicurezza stratificati.

Workspace reference: /Users/jeanpaul/projects/cs-repository