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 --> JIndicizzazione 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
endPattern 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 --> VDeployment 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.
| Livello | Metriche consigliate | Perché sono utili | Fonti |
|---|---|---|---|
| Retrieval | Recall@k, Precision@k, nDCG@k, MRR, Context Precision, Context Recall | Misurano copertura delle evidenze e qualità del ranking prima che il generatore possa introdurre rumore. | |
| Generation | Exact Match/F1 dove applicabile, Answer Relevance, completezza, citation coverage | Misurano se la risposta risolve davvero il task. | |
| Grounding | Faithfulness, Groundedness, unsupported-claim rate | Distinguono risposte corrette da risposte solo fluenti. | |
| Operativo | p50/p95/p99 latency, throughput, timeout rate, cache hit-rate, costo/query | Senza questi numeri non si progetta il capacity planning. | |
| Sicurezza | injection success rate, poisoning detection rate, false negative human review rate | Il 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.
| Strumento | Licenza prevalente | Linguaggi SDK | Tipi vettoriali / retrieval | Scalabilità | Modello di costo | Facilità d’integrazione | LM supportati | Maturità | Fonti |
|---|---|---|---|---|---|---|---|---|---|
| Haystack | OSS | Python | Delega a document store esterni; supporta dense/sparse/rankers modulari | Alta lato orchestrazione; la scala del retrieval dipende dal backend | OSS self-host; supporto enterprise separato | Media-Alta | OpenAI, Anthropic, Hugging Face, local, Ollama, altri | Alta | |
| LangChain | OSS | Python, JS/TS | Ampio ecosistema di retriever e vector store via integrazioni | Alta, ma dipende molto dall’architettura applicativa | OSS; osservabilità/servizi commerciali separati | Alta | Ecosistema molto ampio di provider e tool | Alta | |
| LlamaIndex | OSS | Python, TS | Molte integrazioni con vector store; approccio forte al context engineering | Alta per workflow modulari | OSS; servizi cloud/document processing separati | Alta | OpenAI, Anthropic, Hugging Face, Ollama, altri | Alta |
| Strumento | Licenza prevalente | Linguaggi SDK | Tipi vettoriali / retrieval | Scalabilità | Modello di costo | Facilità d’integrazione | LM supportati | Maturità | Fonti |
|---|---|---|---|---|---|---|---|---|---|
| Elastic | Source-available/commerciale | REST, vari SDK | Dense, sparse, hybrid, reranking, playground RAG | Alta; autoscaling Cloud/Serverless | Self-managed o cloud commerciale | Media | Elastic Managed LLMs e provider esterni, inclusi OpenAI, Anthropic, Azure, Bedrock, Google | Alta | |
| Pinecone | Proprietaria SaaS | Python, JS, REST, altri SDK | Dense, sparse, hybrid, rerank | Molto alta; serverless e scaling dedicato | Managed SaaS; dettaglio di pricing non sviluppato nelle fonti consultate | Alta | LM esterni; embedding e reranking hosted/opzionali | Alta | |
| Milvus | Apache 2.0 | Python, Java, Go, REST, altri | Dense, sparse, binary, hybrid, rerank | Molto alta; architettura distribuita con compute/storage separati | OSS gratuito; cloud managed separato | Media | LM esterni e integrazioni varie | Alta | |
| Weaviate | OSS + cloud managed | Python, JS/TS, GraphQL/REST | HNSW, hybrid, multi-vector, reranking | Alta; clustering e supporto cloud dedicato | Self-host OSS; Cloud con piani pay-as-you-go/contratto | Alta | Integra provider come OpenAI, Anthropic, Cohere, Hugging Face, Ollama e altri | Alta | |
| OpenAI Retrieval | Proprietaria managed API | Python, JS, .NET, REST | Semantic + keyword search; dettagli interni dell’indice non pienamente pubblici; reranking gestito | Alta, gestita dal servizio | Token + storage vector store + tool call | Molto alta | Modelli OpenAI | Alta | |
| Anthropic | Proprietaria API | Python, TS/JS, REST | Search results, citations, tool use; retrieval store tipicamente esterno o non specificato pubblicamente nelle fonti consultate | Alta lato API; retrieval infra da comporre | Token-based API; prompt caching disponibile | Media | Modelli Claude | Alta | |
| Azure AI Search di Microsoft | Proprietaria managed service | REST, Python, .NET, JS, Java | Dense, hybrid, semantic ranker, agentic retrieval, binary/vector compression | Alta; replicas e partitions | Consumo del servizio + costo LM/embedding se usati | Alta | Qualsiasi LM esterno; integrazione first-class con Azure OpenAI | Alta |
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.