RAG da zero in .NET: tipologie, database e tutorial con Ollama e MongoDB
Un modello linguistico è bravissimo a parlare, ma non conosce i tuoi documenti. La Retrieval-Augmented Generation colma questo divario: prima recupera i pezzi di testo rilevanti dai tuoi dati, poi li passa al modello che genera la risposta. In questo articolo partiamo dalle basi — cos'è la RAG e a cosa serve — attraversiamo le sue tipologie (hybrid, graph, re-ranking, agentic) e il panorama dei database, poi costruiamo passo passo una Minimal API in .NET che fa RAG in locale con Ollama e MongoDB Community. Niente cloud, nessun token speso.
🔍 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.