In questo articolo
- 🔍 Cos'è la RAG (Retrieval-Augmented Generation)
- 🎯 A cosa serve: grounding, meno allucinazioni, dati privati
- 🧩 Le tipologie di RAG in una mappa
- 1️⃣ Naive / Standard RAG
- 2️⃣ Hybrid RAG: vettori + keyword (BM25 + RRF)
- 3️⃣ Graph RAG
- 4️⃣ e 5️⃣ Re-ranking e Agentic RAG (con una nota su HyDE e multimodale)
- 🗄️ Database per la RAG: vettoriale, ibrido, Docker o cloud
- 🍃 MongoDB in evidenza: da Atlas-only a Community 8.2
- 🏗️ Il tutorial: l'architettura della soluzione
- ⬇️ Passo 1 · Installare Ollama in locale
- ⌨️ Installare Ollama: i comandi (copia e incolla)
- 📦 Passo 2 · Scaricare i modelli e provare l'endpoint
- 🐳 Passo 3 · MongoDB Community 8.2 in Docker
- 🧱 Passo 4 · Creare il progetto e i pacchetti NuGet
- ⚙️ Passo 5 · appsettings.json: dove va l'URL di Ollama
- 🧩 Passo 6 · Registrare Ollama e MongoDB con una extension method
- 🚀 Passo 7 · Program.cs: una riga di wiring, indici allo startup
- 📄 Passo 8 · L'entità KnowledgeDocument e il vettore
- 🔑 Passo 9 · Creare l'indice vettoriale (e quello testuale)
- 📥 Passo 10 · Endpoint /documents: caricare e indicizzare
- 🔎 Passo 11 · Il servizio RAG: ricerca vettoriale
- 🔀 Passo 12 · Ricerca ibrida con $rankFusion
- 💬 Passo 13 · Endpoint /ask: domanda con RAG
- 🧪 Passo 14 · Provarlo da terminale con curl
- ✅ Checklist finale e prossimi passi
🔍 Cos'è la RAG (Retrieval-Augmented Generation)
La Retrieval-Augmented Generation (RAG) è una tecnica che collega un modello linguistico ai tuoi dati: invece di rispondere solo con ciò che ha imparato in addestramento, il modello prima recupera i documenti pertinenti e poi genera la risposta usandoli come contesto.
Funziona in due fasi. Una di indicizzazione, una tantum: i documenti vengono spezzati in chunk, trasformati in vettori — gli embedding — e salvati in un database. E una di interrogazione, a ogni domanda: si cercano i chunk più pertinenti e li si passa al modello insieme alla domanda.
# Indicizzazione (una tantum)
chunks = split(document)
vectors = embed(chunks) # modello di embedding
store(vectors, chunks) # vector database
# Interrogazione (a ogni domanda)
q = embed(question)
context = vector_search(q, topK) # i chunk piu' simili
answer = llm(question + context) # il modello rispondeTutorial RAG passo passo · LangChain ↗
🎯 A cosa serve: grounding, meno allucinazioni, dati privati
Il vantaggio numero uno è il grounding: le risposte sono ancorate a fonti reali e verificabili, spesso con citazioni. Questo riduce drasticamente le allucinazioni, perché il modello attinge a testo concreto invece che alla sola memoria del training.
Il secondo è l'accesso a dati privati e aggiornati senza fine-tuning: documentazione interna, manuali, ticket, contratti. Aggiorni il database e il modello ne tiene conto subito, senza riaddestrare nulla.
- Grounding: risposte basate su fonti, non su ricordi del training.
- Dati freschi: aggiorni l'indice, non il modello.
- Privacy: i documenti restano nel tuo database (e con Ollama anche il modello resta in casa).
Ingestione una tantum sopra, interrogazione a ogni domanda sotto.
🧩 Le tipologie di RAG in una mappa
Non esiste una RAG: esiste una famiglia di approcci che si distinguono per come recuperano e quanto ragionano prima di rispondere. Qui sotto le cinque più usate; più avanti le vediamo una a una.
La regola pratica: parti dalla più semplice e sali di complessità solo quando i numeri lo chiedono. Ogni livello aggiunge qualità, ma anche latenza e costo.
Dalla similarità pura all'agente che decide cosa recuperare.
1️⃣ Naive / Standard RAG
È la forma base: si trasforma la domanda in un vettore e si recuperano i k chunk più simili per distanza coseno. Semplice, veloce, economica — ed è il punto di partenza giusto per la maggior parte dei casi.
Il limite: cerca solo per significato. Se l'utente scrive un codice prodotto o un termine raro che l'embedder non ha mai visto, la similarità semantica può mancarlo. È qui che entrano le varianti successive.
2️⃣ Hybrid RAG: vettori + keyword (BM25 + RRF)
La ricerca ibrida somma due mondi: la ricerca vettoriale (significato) e quella full-text BM25 (parole esatte). Poi fonde le due classifiche con il Reciprocal Rank Fusion, che combina le posizioni invece dei punteggi — così evita di dover normalizzare scale diverse.
È la scelta giusta quando il corpus è pieno di identificatori, sigle, codici o gergo tecnico: BM25 prende le parole esatte, i vettori prendono il senso. Quasi sempre batte la sola ricerca vettoriale.
Due classifiche, una fusione: il meglio di semantico e keyword.
3️⃣ Graph RAG
Il Graph RAG non recupera chunk isolati: prima costruisce un knowledge graph di entità e relazioni dal corpus, poi naviga il grafo e riassume le sue community. Risolve i due punti deboli della RAG vettoriale: le domande multi-hop e quelle globali sull'intero corpus.
Il prezzo da pagare è un'indicizzazione costosa (molte chiamate all'LLM per estrarre il grafo). L'implementazione di riferimento è Microsoft GraphRAG.
# Recupero vettoriale classico
# bene per: "cosa dice il documento X su Y?"
# debole su: domande che collegano piu' fonti
# Graph RAG
# estrae entita' + relazioni -> knowledge graph
# bene per: multi-hop e domande globali ("riassumi i temi")
# costo: indicizzazione piu' pesante (molte chiamate LLM)4️⃣ e 5️⃣ Re-ranking e Agentic RAG (con una nota su HyDE e multimodale)
Il re-ranking lavora in due tempi: recupera molti candidati (per esempio 100) con la ricerca veloce, poi li riordina con un cross-encoder che legge domanda e documento insieme. Migliora la precisione finale al costo di un po' di latenza sulla short-list.
L'Agentic RAG mette un agente a decidere se, cosa e quante volte recuperare: può interrogare più fonti, usare strumenti e fare più passi. È il più potente e il più costoso in chiamate al modello.
Due menzioni utili: HyDE genera una risposta ipotetica e cerca i documenti vicini a quella (ottimo in zero-shot); la RAG multimodale estende il tutto a immagini e tabelle con embedding in uno spazio condiviso.
Re-ranking
- Recupera molti, poi riordina
- Cross-encoder per la precisione
- Una passata extra di calcolo
- Ottimo quando l'ordine conta
Agentic
- L'agente decide se recuperare
- Più fonti e più passi
- Può usare strumenti esterni
- Massima qualità, massimo costo
Sali di livello solo quando la qualità lo richiede davvero.
🗄️ Database per la RAG: vettoriale, ibrido, Docker o cloud
Per recuperare servono due capacità: la ricerca vettoriale e, idealmente, la ricerca ibrida (vettori + full-text). Quasi tutti i database seri oggi offrono entrambe, con SDK ufficiali per .NET, Node e Python.
La differenza pratica è il deploy: la maggior parte gira in Docker in locale; solo pochi sono solo cloud. Ecco il quadro d'insieme.
- In Docker: MongoDB, Qdrant, Weaviate, Milvus, PostgreSQL + pgvector, Redis 8, Elasticsearch, OpenSearch, Chroma.
- Solo cloud: Pinecone (in locale solo un emulatore per i test, non per la produzione).
- SDK .NET ufficiali per quasi tutti; in Chroma il client .NET è community, in Milvus è contribuito da Microsoft.
- Un solo comando per partire, per esempio docker run -p 6333:6333 qdrant/qdrant.
Vettoriale, ibrido, Docker o cloud, e SDK per .NET / Node / Python.
🍃 MongoDB in evidenza: da Atlas-only a Community 8.2
Per anni la ricerca vettoriale di MongoDB è stata esclusiva di Atlas, il servizio cloud gestito. Dalla versione 8.2 (anteprima annunciata a settembre 2025) arriva anche nel Community self-managed: puoi fare $vectorSearch sul tuo server, senza Atlas.
Sotto al cofano c'è un binario dedicato, mongot, affiancato a mongod. Supporta anche la ricerca ibrida con $rankFusion (RRF). Due avvertenze oneste: in anteprima è solo Linux e non ancora GA.
Lo stesso $vectorSearch di Atlas, ora anche sul tuo server (mongot, Linux, in preview).
🏗️ Il tutorial: l'architettura della soluzione
Costruiamo una Minimal API in .NET che fa RAG end-to-end con due dipendenze esterne, entrambe in casa: Ollama come motore LLM e di embedding, e MongoDB Community come archivio dei vettori con ricerca ibrida.
Il collante è Microsoft.Extensions.AI: le astrazioni IChatClient e IEmbeddingGenerator rendono il codice indipendente dal provider. Esponiamo due endpoint: uno per caricare un documento, uno per fare una domanda sfruttando la RAG.
- LLM + embedding: Ollama (llama3.1 per la chat, nomic-embed-text per i vettori).
- Vector store + hybrid: MongoDB Community 8.2 in Docker ($vectorSearch + $rankFusion).
- Pacchetti NuGet: Microsoft.Extensions.AI, Microsoft.Extensions.AI.Ollama, MongoDB.Driver.
Una extension method registra Ollama e MongoDB; due endpoint per ingest e domanda.
⬇️ Passo 1 · Installare Ollama in locale
Ollama fa girare i modelli sul tuo computer. Su macOS e Windows scarichi l'app dal sito; su Linux (o per un server) basta una riga di comando. Espone le API su http://localhost:11434.
L'inferenza gira sul tuo hardware: zero token da pagare e dati che non escono dalla macchina. Con 16 GB di RAM giri comodo i modelli da 7-8 miliardi di parametri.
macOS e Windows dall'app, Linux con lo script; endpoint su localhost:11434.
⌨️ Installare Ollama: i comandi (copia e incolla)
Se preferisci la riga di comando, ecco i comandi pronti da copiare. Su macOS c'è Homebrew; su Linux (o server headless) lo script ufficiale; su Windows l'installer dal sito. Poi un solo comando avvia il server.
# macOS (Homebrew)
$ brew install ollama
# Linux (o server headless)
$ curl -fsSL https://ollama.com/install.sh | sh
# Windows: scarica ed esegui OllamaSetup.exe da ollama.com/download
# avvia il server (di solito parte da solo)
$ ollama serve # http://localhost:11434📦 Passo 2 · Scaricare i modelli e provare l'endpoint
Servono due modelli: uno di chat (llama3.1) e uno di embedding (nomic-embed-text, 768 dimensioni). Li scarichi con ollama pull e li provi al volo.
Annota il numero di dimensioni dell'embedding: dovrà combaciare con l'indice vettoriale di MongoDB. Se cambi modello di embedding, ricostruisci l'indice con le nuove dimensioni.
# 1) scarica i due modelli
$ ollama pull llama3.1 # LLM per le risposte
$ ollama pull nomic-embed-text # embedding (768 dim)
# 2) avvia il server (di solito parte da solo)
$ ollama serve # http://localhost:11434
# 3) prova a generare un embedding
$ curl http://localhost:11434/api/embeddings \
-d '{"model":"nomic-embed-text","prompt":"ciao mondo"}'🐳 Passo 3 · MongoDB Community 8.2 in Docker
Tiriamo su MongoDB con un container. Serve la 8.2 (o successiva) per la ricerca vettoriale self-managed; ricorda che in anteprima il search è supportato su Linux.
Per la hybrid search useremo $rankFusion, disponibile dalla 8.1 in su — quindi la 8.2 copre tutto. Il binario mongot si occupa degli indici di ricerca.
# MongoDB Community 8.2 — vector + hybrid search (mongot, Linux)
$ docker run -d --name mongodb \
-p 27017:27017 \
mongodb/mongodb-community-server:8.2-ubi9
# verifica che sia su
$ docker logs -f mongodb | grep -i "Waiting for connections"MongoDB Vector Search · Docs ↗
🧱 Passo 4 · Creare il progetto e i pacchetti NuGet
Creiamo una Minimal API e aggiungiamo tre pacchetti: le astrazioni Microsoft.Extensions.AI, il provider Ollama e il driver MongoDB.
Le astrazioni di Microsoft.Extensions.AI sono stabili; il provider Ollama è in preview ma è quello usato negli esempi ufficiali. In alternativa puoi usare OllamaSharp: implementa le stesse interfacce.
# nuova Minimal API
$ dotnet new web -n RagDemo && cd RagDemo
# astrazioni AI + provider Ollama + driver Mongo
$ dotnet add package Microsoft.Extensions.AI
$ dotnet add package Microsoft.Extensions.AI.Ollama --prerelease
$ dotnet add package MongoDB.DriverMicrosoft.Extensions.AI · Microsoft Learn ↗
⚙️ Passo 5 · appsettings.json: dove va l'URL di Ollama
Tutta la configurazione vive in appsettings.json: l'URL di Ollama, i nomi dei due modelli, le dimensioni dell'embedding e la stringa di connessione a MongoDB. L'endpoint di Ollama è http://localhost:11434.
Tenere qui questi valori significa cambiare modello o puntare a un altro host senza toccare il codice.
{
"Ollama": {
"Endpoint": "http://localhost:11434",
"ChatModel": "llama3.1",
"EmbeddingModel": "nomic-embed-text",
"EmbeddingDimensions": 768
},
"Mongo": {
"ConnectionString": "mongodb://localhost:27017",
"Database": "ragdemo"
}
}🧩 Passo 6 · Registrare Ollama e MongoDB con una extension method
Seguendo il pattern di un progetto reale, raccogliamo tutto il wiring in una extension method AddRag. Registra il client di chat e il generatore di embedding di Ollama come IChatClient/IEmbeddingGenerator, il database MongoDB e i nostri servizi.
Il resto dell'app dipende solo dalle interfacce: Ollama non compare mai oltre questo file. Domani passi a OpenAI o ad Azure cambiando una riga.

Un solo punto registra Ollama (chat + embedding) e MongoDB nel container DI.
🚀 Passo 7 · Program.cs: una riga di wiring, indici allo startup
Il Program.cs di una Minimal API diventa minuscolo: una chiamata ad AddRag, la creazione degli indici allo startup (idempotente) e la mappatura dei due endpoint.
Creare gli indici all'avvio è comodo: la prima esecuzione li costruisce, le successive li trovano già pronti e li saltano.

AddRag, EnsureIndexesAsync, due endpoint. Tutto qui.
📄 Passo 8 · L'entità KnowledgeDocument e il vettore
Ogni chunk diventa un documento MongoDB con il suo embedding come float[] — esattamente ciò che produce il generatore e ciò che l'indice vettoriale usa nativamente (f32). Usare double[] raddoppierebbe lo spazio senza alcun guadagno.
Il nome della collezione e quello dell'indice li teniamo vicini all'entità, così un rename non lascia mai il codice a puntare a una collezione morta.
![Codice C# dell'entità KnowledgeDocument: campi SourceFile, ChunkIndex, Content, l'embedding come float[] destinato all'indice knowledge_vector_index, e CreatedAt.](/blog/code-knowledge-document.png)
Un chunk per documento; l'embedding è un float[] a 768 dimensioni.
🔑 Passo 9 · Creare l'indice vettoriale (e quello testuale)
All'avvio creiamo due indici: uno vettoriale per $vectorSearch e uno testuale per il lato keyword della ricerca ibrida. L'operazione è idempotente: se l'indice esiste, non fa nulla.
Le numDimensions dell'indice devono combaciare con il modello di embedding (768 per nomic-embed-text), con similarità coseno. La stessa API funziona su Atlas e su Community 8.2.

Stessa API su Atlas e su Community 8.2 (mongot). numDimensions = 768, cosine.
📥 Passo 10 · Endpoint /documents: caricare e indicizzare
Il primo endpoint accetta un file, lo spezza in chunk, genera gli embedding in un'unica chiamata batch e li inserisce in MongoDB. È la fase di ingestione della RAG.
Tutto asincrono: il file si legge una volta, i chunk si vettorizzano insieme e l'inserimento è bulk con InsertManyAsync.

Upload, chunking, embedding batch e InsertMany: l'ingestione in un colpo solo.
🔎 Passo 11 · Il servizio RAG: ricerca vettoriale
Il RagService è puro recupero: trasforma la domanda in vettore e chiede a MongoDB i chunk più vicini con $vectorSearch. Il numCandidates più alto del limit allarga la rete prima di restringere.
Lo stage $project riporta il testo e lo score di similarità (vectorSearchScore), pronto da iniettare nel prompt.

Embedda la domanda, recupera i top-K, proietta testo e score.
🔀 Passo 12 · Ricerca ibrida con $rankFusion
Per la hybrid search affianchiamo alla ricerca vettoriale quella full-text e fondiamo le due con $rankFusion (RRF). Pesiamo 0.7 il semantico e 0.3 il keyword — tarabile a piacere.
È la marcia in più sui corpus pieni di sigle e codici, e gira sul Community 8.2 self-managed (serve la 8.1 o superiore per $rankFusion).

Vettoriale + BM25 fusi con RRF: serve MongoDB 8.1+ (Community 8.2).
💬 Passo 13 · Endpoint /ask: domanda con RAG
Il secondo endpoint orchestra tutto: recupera i chunk (vettoriale o ibrido), li inietta nel prompt come contesto e chiede la risposta a Ollama via IChatClient. Il system prompt impone di rispondere solo dal contesto.
Il flag hybrid lascia scegliere al chiamante la strategia di recupero, domanda per domanda.

Recupera, costruisce il contesto, chiede a Ollama. La RAG in un endpoint.
🧪 Passo 14 · Provarlo da terminale con curl
Due curl e la RAG è viva: prima carichi un documento, poi chiedi. Con hybrid: true provi la ricerca ibrida sulla stessa domanda.
La prima domanda dopo l'avvio può essere più lenta: l'indice vettoriale viene costruito in background dal binario mongot.
# 1) carica un documento (multipart)
$ curl -F "file=@manuale.txt" http://localhost:5000/documents
# 2) fai una domanda (ricerca vettoriale)
$ curl http://localhost:5000/ask \
-H "Content-Type: application/json" \
-d '{"question":"come si configura il backup?"}'
# 3) stessa domanda, ricerca ibrida
$ curl http://localhost:5000/ask \
-H "Content-Type: application/json" \
-d '{"question":"codice errore E45?","hybrid":true}'✅ Checklist finale e prossimi passi
Ricapitoliamo il percorso. Se hai seguito gli step, ora hai una Minimal API che fa RAG in locale con Ollama e MongoDB, senza un token speso nel cloud.
Da qui puoi salire di livello: aggiungere il re-ranking, passare a un approccio agentic, o spostare i vettori su un altro database mantenendo la stessa architettura — perché dipendi dalle interfacce, non dai provider.
- 01Installa Ollamaapp o curl; endpoint 11434
- 02Scarica i modellillama3.1 + nomic-embed-text
- 03MongoDB in Dockercommunity-server 8.2
- 04Progetto + NuGetExtensions.AI + Ollama + Driver
- 05appsettings + AddRagURL Ollama e Mongo, una extension
- 06Entità + indicivector + text, allo startup
- 07Endpoint /documentschunk, embed, store
- 08Endpoint /askretrieve, context, Ollama
Da qui: re-ranking, agentic, o un altro vector database con la stessa struttura.
Domande frequenti su Retrieval-Augmented Generation
Che cos'è la RAG in parole semplici?
La RAG (Retrieval-Augmented Generation) fa recuperare a un modello i documenti rilevanti e poi generare la risposta usandoli come contesto. Così risponde sui tuoi dati, con fonti, riducendo le allucinazioni — senza riaddestrare il modello.
Qual è la differenza tra ricerca vettoriale e ibrida?
La ricerca vettoriale trova per significato (embedding); quella ibrida aggiunge il full-text BM25 e fonde le due classifiche con il Reciprocal Rank Fusion. L'ibrida vince quando contano parole esatte come codici, sigle e gergo tecnico.
Serve per forza un vector database dedicato?
No. Database generalisti come MongoDB, PostgreSQL (pgvector) o Redis fanno ricerca vettoriale e ibrida. Un database dedicato (Qdrant, Weaviate, Milvus) conviene su scala molto grande o per funzioni avanzate.
MongoDB Community basta per la RAG?
Sì, dalla 8.2: $vectorSearch e l'ibrido $rankFusion sono disponibili anche self-managed, tramite il binario mongot. In anteprima è solo Linux e non ancora GA; per la produzione gestita resta l'opzione Atlas.
Posso usare Ollama in produzione?
Ollama è ottimo per sviluppo, privacy e costo zero per richiesta. In produzione valuta hardware (GPU/RAM), concorrenza e SLA; l'architettura non cambia, perché dipendi dalle interfacce IChatClient e IEmbeddingGenerator, non da Ollama.
Quale tipo di RAG dovrei scegliere?
Parti dalla vettoriale semplice. Passa all'ibrida se hai codici e sigle; al re-ranking se l'ordine dei risultati è scarso; al graph per domande multi-hop o globali; all'agentic per compiti complessi multi-fonte. Sali solo quando i numeri lo chiedono.
Parliamone
Se questo tema ti riguarda, scrivimi: confrontarsi su codice e AI è sempre tempo speso bene.



