# GraphRAG: Perché la ricerca vettoriale si interrompe al livello del Corpus

<datetime class="hidden">2025-12-26T12:00</datetime>

<!-- category -- ASP.NET, Semantic Search, ONNX, Qdrant, Machine Learning, Vector Search, RAG -->
Il vostro sistema RAG è grande a "ago" domande: recuperare alcuni pezzi rilevanti e sintetizzare una risposta. Si lotta con due tipi di query comuni:

- **Sensemaking**: "Quali sono i temi principali di questo corpus?"
- **Connettivo**: "In che modo X si relaziona con Y attraverso diversi documenti?"

Non hanno risposta da nessuna parte. **copertura + cluster + collegamento**.

**Perché la ricerca vettoriale fallisce qui:**

- Rallegra i pezzi. **indipendente** per somiglianza alla query
- La similarità ottimizza la pertinenza, non **copertura globale**
- I montaggi catturano "ciò che suona simile," non "ciò che si collega a ciò"

Puoi forzarlo con il prompt e il post-processing, ma finisci per ricostruire una soluzione a forma di grafico.

**L'intuizione chiave:** GraphRAG cambia l'unità di recupero. Per le domande sul corpus non si desidera "top-K parti simili"; si desidera **comunità concettuali collegate** (e i loro riassunti), così il modello vede la struttura, non frammenti.

[GraphRAG](https://microsoft.github.io/graphrag/) proviene da Microsoft Research [carta](https://arxiv.org/pdf/2404.16130) ed è disponibile come [implementazione open source](https://github.com/microsoft/graphrag). Continua la ricerca vettoriale di domande specifiche, ma aggiunge un grafico della conoscenza e riassunti della comunità per il ragionamento a livello di corpus.

## Quando NON usare GraphRAG

Prima di immergerci, cerchiamo di essere chiari su quando questo è eccessivo:

- **Set di piccoli documenti** (in ~50 documenti): basta usare la ricerca vettoriale
- **Solo domande su "come faccio"**: GraphRAG non aiuterà
- **Tenore uniforme** (nessuna varietà di entità): nessuna struttura grafica da sfruttare
- **Costo limitato**: L'indicizzazione richiede molte chiamate LLM

Se i tuoi utenti fanno solo domande specifiche, resta con [ricerca semantica](/blog/semantic-search-with-onnx-and-qdrant). GraphRAG brilla quando gli utenti hanno bisogno di *quadro generale*E questo e' un pubblico piu' piccolo di quanto suggeriscano i venditori.

# Introduzione

**Navigazione della serie:** Questa è la parte 6 della serie RAG:

- [Parte 1: Origini e Fondamenti](/blog/rag-primer) - Storia, motivazione e concetti fondamentali
- [Parte 2: Architettura e Interni](/blog/rag-architecture) - Immersione tecnica profonda
- [Parte 3: RAG nella pratica](/blog/rag-practical-applications) - Costruzione di sistemi reali
- [Parte 4a: Implementazione di ONNX & Qdrant](/blog/semantic-search-with-onnx-and-qdrant) - CPU-friendly ricerca semantica
- [Parte 4b: Ricerca semantica in azione](/blog/semantic-search-in-action) - Typeae, ricerca ibrida e UI
- [Parte 5: Ricerca ibrida e integrazione automatica](/blog/rag-hybrid-search-and-indexing) - Integrazione della produzione
- **Parte 6: GraphRAG** (questo articolo) - Grafici della conoscenza per la comprensione a livello di corpus

Durante questa serie, abbiamo costruito sistemi RAG sempre più sofisticati. Abbiamo iniziato con la ricerca vettoriale di base, aggiunta parola chiave ibrida + recupero semantico, e auto-indicizzazione integrata. Ma tutti questi approcci condividono una limitazione fondamentale: trovano **pezzi simili**, non **concetti connessi**. per *livello corpus* domande (temi che coprono molti documenti) è necessario struttura.

**Il percorso consigliato:** Se hai già la ricerca locale basata su Qdrant (come facciamo noi), prototipo con il sidecar Python per convalidare il valore, mantenere i vettori per la ricerca locale e aggiungere un grafico leggero per le query globale/DRIFT. Fai solo "Full GraphRAG" una volta che hai dimostrato che gli utenti fanno queste domande.

[TOC]

# Il problema con RAG vettoriale puro

Lasciate che vi mostri cosa intendo con un esempio concreto.

## Quello che il vettore RAG fa bene

**Domanda:** "Come posso usare HTMX con Alpine.js?"

**Processo di RAG vettoriali:**

1. Incorpora la domanda: `[0.234, -0.891, 0.567, ...]`
2. Trova pezzi simili in Qdrant
3. Ritorna partite top-K su HTMX e Alpine.js
4. LLM sintetizza la risposta da quei pezzi

Questo funziona perché la domanda e il contenuto sono **Simile semanticamente**Gli incorniciamenti catturano quella somiglianza.

```csharp
// This is what our current SemanticSearchService does
var embedding = await _embeddingService.GetEmbeddingAsync(query);
var results = await _qdrantService.SearchAsync(
    collectionName: "blog_posts",
    queryVector: embedding,
    limit: 10
);
// Returns chunks about HTMX, Alpine.js, frontend patterns
```

## Dove il vettore RAG lotta

**Domanda:** "Quali sono le principali tecnologie di cui scrivo e come si relazionano tra loro?"

**Quale vettore RAG restituisce:**

```
Result 1: "HTMX makes it easy to add AJAX to your pages..."
Result 2: "Docker Compose orchestrates multiple containers..."
Result 3: "PostgreSQL's full-text search is surprisingly capable..."
Result 4: "Alpine.js provides reactive state management..."
```

Si parla di Docker, PostgreSQL, HTMX, ONNX... ma non li raggruppa o spiega come si connettono. Ottieni frammenti, non intuizioni.

Il problema: questa domanda richiede **l'aggregazione e la comprensione delle relazioni;** attraverso l'intero corpus. Devi:

- Identificare tutte le tecnologie menzionate
- Capire quali sono utilizzati insieme
- Raggruppateli in temi coerenti

La somiglianza vettoriale da sola non ti dà questo. Se provi a patchare questo con il suggerimento, finisci per reinventare un grafico.

# Inserisci graficoRAG

[GraphRAG](https://microsoft.github.io/graphrag/) è la soluzione di Microsoft Research a questo problema. [Carta GraphRAG](https://arxiv.org/pdf/2404.16130) ha individuato i due tipi di query che gli RAG di base gestiscono male (**sensemaking** e **Connecto**) e ha costruito un sistema specifico per affrontarli.

Invece di incorporare solo pezzi, GraphRAG costruisce un **grafico della conoscenza** che cattura le entità e le loro relazioni, poi le raggruppa in comunità con riassunti.

## Come funziona GraphRAG

**Conduttura a colpo d'occhio:**

- **Indicizzazione:** Chunks → entità/relazioni → grafico → comunità → sintesi
- **Interrogazione:** Locale = pezzi + quartiere grafico | Globale = sommari delle comunità | DRIFT = tracciati + sommari

GraphRAG aggiunge diversi componenti al gasdotto RAG, raggruppati in tre categorie:

1. **Estrazione** (entità + relazioni)
2. **Generazione grafico** (Memorizzazione del grafico della conoscenza)
3. **Sintesi** (individuazione comunitaria + gerarchia)

```mermaid
flowchart TB
    subgraph "Traditional RAG (What We Have)"
        A[Documents] --> B[Chunks]
        B --> C[Embeddings]
        C --> D[Vector Store]
    end

    subgraph "GraphRAG Additions"
        B --> E[Entity Extraction]
        E --> F[Relationship Extraction]
        F --> G[Knowledge Graph]
        G --> H[Community Detection]
        H --> I[Community Summaries]
    end

    subgraph "Query Time"
        J[User Query] --> K{Query Type?}
        K -->|Specific| L[Local Search]
        K -->|Global| M[Global Search]
        K -->|Hybrid| N[DRIFT Search]

        D --> L
        G --> L
        I --> M
        G --> N
        I --> N
    end

    style E stroke:#f9f,stroke-width:2px
    style H stroke:#bbf,stroke-width:2px
    style I stroke:#9f9,stroke-width:2px
```

### Fase 1: Estrazione di entità

Un LLM legge ogni pezzo ed estratti **entità** (le cose di cui si discute):

```
Chunk: "Docker Compose makes it easy to define multi-container applications.
        I use it with PostgreSQL for my blog's database layer."

Extracted Entities:
- Docker Compose (technology)
- PostgreSQL (database)
- blog (project)
- database layer (concept)
```

### Fase 2: Estrazione delle relazioni

Lo stesso LLM identifica come le entità si relazionano tra loro:

```
Relationships:
- Docker Compose --[used_with]--> PostgreSQL
- blog --[has_component]--> database layer
- PostgreSQL --[implements]--> database layer
```

### Fase 3: Costruzione di un grafico della conoscenza

Tutte le entità e le relazioni formano un grafico:

```mermaid
graph LR
    subgraph "Frontend Cluster"
        HTMX[HTMX]
        Alpine[Alpine.js]
        Tailwind[Tailwind CSS]
    end

    subgraph "Infrastructure Cluster"
        Docker[Docker]
        Compose[Docker Compose]
        Postgres[PostgreSQL]
        Qdrant[Qdrant]
    end

    subgraph "AI/ML Cluster"
        ONNX[ONNX Runtime]
        Embeddings[Embeddings]
        RAG[RAG]
    end

    HTMX -->|used_with| Alpine
    HTMX -->|styled_by| Tailwind
    Alpine -->|styled_by| Tailwind

    Docker -->|orchestrated_by| Compose
    Compose -->|runs| Postgres
    Compose -->|runs| Qdrant

    ONNX -->|generates| Embeddings
    Embeddings -->|stored_in| Qdrant
    RAG -->|uses| Embeddings
    RAG -->|uses| Qdrant

    style HTMX stroke:#f9f
    style Docker stroke:#bbf
    style RAG stroke:#9f9
```

### Fase 4: Rilevamento della comunità (Algoritmo di Leiden)

La [Algoritmo di Leida](https://arxiv.org/pdf/1810.08473.pdf) cluster densamente collegati nodi in comunità. Questo importa perché ti dà *stabile* cluster da riassumere e recuperare; le comunità diventano le vostre unità di recupero per le query globali.

- **Comunità 1**: "Frontend Stack" (HTMX, Alpine.js, Tailwind)
- **Comunità 2**: "Infrastructure container" (Docker, Compose, PostgreSQL, Qdrant)
- **Comunità 3**: "RAG Pipeline" (ONNX, Embeddings, Qdrant, RAG)

Notate come Qdrant appare in due comunità: collega l'infrastruttura e l'AI/ML.

### Fase 5: Sintesi della Comunità

Un LLM genera sommari per ciascuna comunità a ogni livello gerarchico:

```
Community 1 Summary (Frontend Stack):
"The frontend approach combines HTMX for server-driven interactivity
with Alpine.js for client-side state management, styled using Tailwind CSS.
This stack prioritizes HTML-first development with minimal JavaScript,
focusing on progressive enhancement over SPA complexity."

Community 2 Summary (Container Infrastructure):
"The blog runs on Docker Compose, orchestrating PostgreSQL for persistent
storage, Qdrant for vector search, and the ASP.NET Core application.
This containerized architecture enables consistent local development
and production deployment."
```

## Modalità interrogazione

GraphRAG fornisce tre modalità di query, ognuna ottimizzata per diversi tipi di domande:

### Ricerca globale

**Meglio per:** "Quali sono i temi principali?" "Summarizzare gli argomenti chiave."

Usa i riassunti delle comunità (non singoli pezzi) per rispondere alle domande di sensemaking:

```
Query: "What technologies does this blog cover most?"

Process:
1. Retrieve all community summaries
2. Map: Ask LLM to extract technology themes from each summary
3. Reduce: Combine partial answers into final response

Response:
"The writing centres on three technology clusters:
1. **Frontend Development** - HTMX, Alpine.js, Tailwind CSS for minimal-JS web UIs
2. **AI/ML Infrastructure** - RAG pipelines, ONNX embeddings, vector search with Qdrant
3. **DevOps/Containerization** - Docker, PostgreSQL, ASP.NET Core deployment"
```

### Ricerca locale

**Meglio per:** "Come posso configurare X?" "Che cos'è Y?"

Combina la traversata del grafico focalizzato sull'entità con la ricerca vettoriale tradizionale:

```
Query: "How do I use Qdrant with ONNX embeddings?"

Process:
1. Identify entities in query: Qdrant, ONNX, embeddings
2. Retrieve graph neighborhood around those entities
3. Also retrieve vector-similar chunks
4. Combine into rich context for LLM

Response includes:
- Direct relationships (ONNX generates embeddings stored in Qdrant)
- Related entities (all-MiniLM-L6-v2 model, cosine similarity)
- Specific code examples from vector-retrieved chunks
```

### Ricerca DRIFT

**Meglio per:** "Come fa X a relazionarsi con Y?" "Confrontare A e B."

ricerca DRIFT (Dynamic Reasoning and Inference with Flexible Traversal), [come descritto nei documenti GraphRAG](https://microsoft.github.io/graphrag/query/drift_search/), combina la ricerca locale con il contesto comunitario. Sta ancora usando il ragionamento LLM sopra il contesto strutturato recuperato (non l'inferenza del grafico magico), ma la struttura aiuta il LLM vedere le connessioni che perderebbe con pezzi piatti.

```
Query: "How do the frontend and backend technologies connect?"

Process:
1. Start with entities: HTMX, ASP.NET Core
2. Traverse graph to find connection paths
3. Include community summaries for context
4. Generate answer showing the full picture

Response:
"HTMX makes requests to ASP.NET Core endpoints, which query PostgreSQL
and Qdrant. The connection flows through the API layer, where endpoints
return HTML fragments that HTMX swaps into the DOM. Alpine.js handles
client-side state for interactive components like search typeahead."
```

# Confrontare GraphRAG con il nostro sistema attuale

Mappare i concetti di GraphRAG a quello che abbiamo già in `Mostlylucid.SemanticSearch`:

| Componente | Sistema di corrente | Equivalente RAG del grafico |
|-----------|---------------|---------------------|
| **Incorporazioni** | ONNX (all-miniLM-L6-v2) | Stesso (o OpenAI) |
| **Store vettoriale** | Qdrant | Qdrant / LanceDB |
| **Estrazione di entità** |Nessuno |LLM-powered extraction |
| **Grafico della conoscenza** | Nessuno | Banca dati grafico/memoria in memoria |
| **Rilevamento comunitario** | Nessuno | Algoritmo di Leiden |
| **Interrogazione: specifica** | `SemanticSearchService.SearchAsync()` | Ricerca locale |
| **Interrogazione: Globale** | Non supportato | Ricerca globale |

La nostra attuale gestione dell'implementazione **Ricerca locale** Bene. GraphRAG aggiungerebbe **Ricerca globale** e **Ricerca DRIFT** capacità.

```csharp
// What we have today (Local Search equivalent)
public async Task<List<SearchResult>> SearchAsync(string query, int limit = 10)
{
    var embedding = await _embeddingService.GetEmbeddingAsync(query);
    return await _qdrantService.SearchAsync("blog_posts", embedding, limit);
}

// What GraphRAG would add
public async Task<string> GlobalSearchAsync(string query)
{
    // 1. Retrieve community summaries (not chunks)
    var summaries = await _graphService.GetCommunitySummariesAsync();

    // 2. Map: Extract relevant themes from each summary
    var partialAnswers = await Task.WhenAll(
        summaries.Select(s => _llm.ExtractThemesAsync(query, s))
    );

    // 3. Reduce: Combine into final answer
    return await _llm.SynthesizeAsync(query, partialAnswers);
}
```

# Approcci di attuazione

Ci sono tre modi per aggiungere GraphRAG a un sistema esistente.

## Opzione 1: Python Sidecar (Consigliato per l'esplorazione)

Eseguire Microsoft GraphRAG come un servizio separato:

```yaml
# docker-compose.graphrag.yml
services:
  graphrag:
    build:
      context: ./graphrag
    volumes:
      - ./data/input:/app/input
      - ./data/output:/app/output
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}

  graphrag-api:
    build:
      context: ./graphrag-api
    ports:
      - "8001:8000"
    depends_on:
      - graphrag
```

```csharp
// GraphRagClient.cs - Call from ASP.NET Core
public class GraphRagClient
{
    private readonly HttpClient _http;

    public GraphRagClient(HttpClient http)
    {
        _http = http;
        _http.BaseAddress = new Uri("http://graphrag-api:8000");
    }

    public async Task<string> GlobalSearchAsync(string query)
    {
        var response = await _http.PostAsJsonAsync("/query/global", new { query });
        var result = await response.Content.ReadFromJsonAsync<GraphRagResponse>();
        return result.Answer;
    }

    public async Task<string> LocalSearchAsync(string query)
    {
        var response = await _http.PostAsJsonAsync("/query/local", new { query });
        var result = await response.Content.ReadFromJsonAsync<GraphRagResponse>();
        return result.Answer;
    }
}
```

**Pro:** Utilizzare l'implementazione testata dalla battaglia di Microsoft, veloce al prototipo
**Punti negativi:** Dipendenza da Python, costi LLM per l'indicizzazione, comunicazione tra processi

## Opzione 2: NET Native (Percorso di produzione)

Costruisci i componenti chiave in C#. L'estrazione basata su BERT e i modelli di Ollama da [DocSummarizer](/blog/docsummarizer-tool) lavorare allo stesso modo qui.

### Estrazione di entità

Chiedere a un LLM di identificare *cose* (enti) in ciascuna parte - entità strutturate piuttosto che soggetti a forma libera:

```csharp
public async Task<List<Entity>> ExtractEntitiesAsync(string chunk)
{
    var prompt = $"""
        Extract entities from this text. Return JSON array.
        Types: technology, concept, project, person, organization
        Text: {chunk}
        Format: [{{"name": "Docker", "type": "technology"}}]
        """;

    var response = await _ollama.GenerateAsync(prompt);
    return JsonSerializer.Deserialize<List<Entity>>(response);
}
```

**Fabbisogno di produzione:** Uscita LLM JSON *Will* break. Questo non è indurimento facoltativo. Hai bisogno di uno dei:

- **Generazione controllata dallo schema** (Ollama's `format: json`, funzione chiamata di OpenAI)
- **Retry-with-repair loop** (Trova JSON malformato, chiedi a LLM di aggiustarlo)
- **Estrazione ripiegamento** (modelli di regolamento per tipi di entità comuni)

I LLM sono probabilistici; la vostra pipeline di estrazione non deve esserlo.

### Estrazione delle relazioni

Una volta che si dispone di entità, chiedere al LLM come si collegano:

```csharp
public async Task<List<Relationship>> ExtractRelationshipsAsync(
    string chunk, List<Entity> entities)
{
    var names = string.Join(", ", entities.Select(e => e.Name));
    var prompt = $"""
        Given entities: {names}
        Extract relationships. Return JSON array.
        Text: {chunk}
        Format: [{{"source": "Docker", "target": "PostgreSQL", "rel": "runs"}}]
        """;

    return JsonSerializer.Deserialize<List<Relationship>>(
        await _ollama.GenerateAsync(prompt));
}
```

### Storage grafico con normalizzazione dell'entità

Il più grande dolore pratico è **aliasing dell'entità**: "ASP.NET Core," "ASP.NET" e "spnetcore" dovrebbero essere lo stesso nodo. La normalizzazione semplice aiuta:

```csharp
public class KnowledgeGraph
{
    private readonly Dictionary<string, Entity> _entities = new();
    private readonly List<Relationship> _relationships = new();

    public void AddEntity(Entity entity)
    {
        var key = Normalise(entity.Name);  // "ASP.NET Core" → "aspnetcore"
        _entities[key] = entity;
    }

    private string Normalise(string name) =>
        name.ToLowerInvariant().Replace(".", "").Replace("-", "").Trim();
}
```

Per un uso serio, considerare deduplicazione di entità basata sull'embedding: se due nomi di entità hanno embedding simili, probabilmente sono la stessa cosa.

**Punti di dolore alla produzione** (Il valore di GraphRAG dipende dalla qualità del grafico):

- **Tabelle sinonimo/alias**: mantenere nomi canonici e alias noti
- **Controllo schema di relazione**: vincolo consentito predicati per prevenire tipi di relazione allucinata
- **Punteggio di fiducia + potatura**: non tutte le relazioni estratte sono altrettanto affidabili
- **Riindicizzazione incrementale**: quando i documenti si aggiornano, è necessario patchare il grafico, non ricostruirlo

### Grafico trasversale

Trovare entità correlate è una ricerca wide-first:

```csharp
public List<Entity> GetNeighbors(string entityName, int depth = 1)
{
    var result = new HashSet<Entity>();
    var queue = new Queue<(string Name, int Depth)>();
    queue.Enqueue((Normalise(entityName), 0));

    while (queue.Count > 0)
    {
        var (name, d) = queue.Dequeue();
        if (d >= depth) continue;

        // Find all entities connected to this one
        var neighbours = _relationships
            .Where(r => Normalise(r.Source) == name || Normalise(r.Target) == name)
            .SelectMany(r => new[] { r.Source, r.Target });

        foreach (var neighbour in neighbours)
            if (_entities.TryGetValue(Normalise(neighbour), out var entity))
                if (result.Add(entity))
                    queue.Enqueue((Normalise(neighbour), d + 1));
    }
    return result.ToList();
}
```

### Rilevamento comunitario

Si tratta di una linea di base di componenti collegati, **non** Leida completa. Leida ottimizza la modularità (connessioni interne dense, quelle esterne sparse). Per una corretta implementazione, utilizza una libreria grafica o porta l'algoritmo.

```csharp
public List<Community> DetectCommunities(KnowledgeGraph graph)
{
    // Connected components: group everything reachable together
    var visited = new HashSet<string>();
    var communities = new List<Community>();

    foreach (var entity in graph.GetAllEntities())
    {
        if (visited.Contains(entity.Name)) continue;
        
        // BFS to find all connected entities
        var community = new Community();
        var queue = new Queue<string>();
        queue.Enqueue(entity.Name);

        while (queue.Count > 0)
        {
            var name = queue.Dequeue();
            if (!visited.Add(name)) continue;
            community.Entities.Add(graph.GetEntity(name));
            foreach (var neighbor in graph.GetNeighbors(name, depth: 1))
                queue.Enqueue(neighbor.Name);
        }
        communities.Add(community);
    }
    return communities;
}
```

### Sintesi della Comunità

Ogni comunità riceve un riassunto che ne descrive il tema. Questo è ciò che alimenta la Ricerca Globale:

```csharp
public async Task<string> SummarizeCommunityAsync(Community community)
{
    var entities = string.Join("\n", 
        community.Entities.Select(e => $"- {e.Name}: {e.Description}"));
    
    var prompt = $"""
        Summarize what unites these concepts (2-3 sentences):
        {entities}
        """;

    return await _ollama.GenerateAsync(prompt);
}
```

## Opzione 3: Hybrid (Prammatic Middle Ground)

Questo è l'approccio consigliato se hai già una ricerca vettoriale funzionante. Mantieni Qdrant per la ricerca locale, aggiungi un livello grafico leggero per le query Global/DRIFT.

### Classificazione delle interrogazioni

In primo luogo, individuare che tipo di domanda questo è:

```csharp
// WARNING: Toy heuristic for illustration only.
// In production, use a classifier prompt or few-shot rules and log misroutes.
private QueryMode ClassifyQuery(string query)
{
    var q = query.ToLowerInvariant();
    
    if (q.Contains("main theme") || q.Contains("summarize") || q.Contains("what topics"))
        return QueryMode.Global;
    
    if (q.Contains("relate") || q.Contains("connect") || q.Contains("compare"))
        return QueryMode.Drift;
    
    return QueryMode.Local;
}
```

### Ricerca locale (Enhanced)

Utilizzare la ricerca vettoriale esistente, eventualmente arricchita con il contesto grafico:

```csharp
private async Task<string> LocalSearchAsync(string query)
{
    // Existing semantic search (what we have today)
    var chunks = await _semanticSearch.SearchAsync(query, limit: 10);

    // NEW: Enrich with related entities from graph
    var entities = await _graphService.ExtractEntitiesFromQueryAsync(query);
    var related = await _graphService.GetEntityContextAsync(entities);

    return await _llm.GenerateAsync(query, FormatContext(chunks, related));
}
```

### Ricerca globale (Nuova capacità)

Riduci mappa sopra i riassunti della comunità (non è necessaria alcuna ricerca vettoriale):

```csharp
private async Task<string> GlobalSearchAsync(string query)
{
    var summaries = await _graphService.GetAllCommunitySummariesAsync();

    // Map: Extract relevant info from each community
    var partials = await Task.WhenAll(
        summaries.Select(s => _llm.ExtractRelevantInfoAsync(query, s)));

    // Reduce: Combine into final answer
    return await _llm.SynthesizeAsync(query, partials.Where(p => !string.IsNullOrEmpty(p)));
}
```

### Ricerca DRIFT (Queries Connective)

Combina i risultati locali con il contesto comunitario per le domande "come X si riferisce a Y":

```csharp
private async Task<string> DriftSearchAsync(string query)
{
    var localResults = await LocalSearchAsync(query);
    
    var entities = await _graphService.ExtractEntitiesFromQueryAsync(query);
    var communities = await _graphService.GetCommunitiesForEntitiesAsync(entities);
    var themes = string.Join("\n", communities.Select(c => c.Summary));

    return await _llm.GenerateAsync(
        $"Question: {query}\n\nDetails:\n{localResults}\n\nBroader themes:\n{themes}",
        systemPrompt: "Synthesize the details with the thematic context.");
}
```

# Considerazioni sui costi e sulle prestazioni

GraphRAG ha notevoli compromessi rispetto ai RAG vettori puri.

## Costi di indicizzazione

I costi di estrazione dell'entità/della relazione variano in modo significativo in base al modello, alla progettazione rapida e alle dimensioni del pezzo. **una o due chiamate LLM per pezzo** più un numero minore di richieste di riassunti della comunità.

| Funzionamento | RAG vettoriali | Grafico RAG |
|-----------|------------|----------|
| **Incorporazione** | 1 call/chunk | Stesso |
| **Estrazione di entità/relazione** |Nessuno | 1-2 chiamate LLM/chunk |
| **Sintesi della Comunità** | Nessuno | 1 LLM call/community |

Per un corpus di 1.000 post sul blog con 5 pezzi ciascuno (5.000 pezzi totali), l'indicizzazione vettoriale è essenzialmente solo un'integrazione dei costi. GraphRAG aggiunge migliaia di chiamate LLM per l'estrazione e la sommarizzazione. L'esatto costo dipende fortemente dalla scelta del modello e dall'efficienza immediata; l'utilizzo di modelli locali (Ollama con lama3.2 o simili) elimina completamente i costi API, che è l'approccio raccomandato per la sperimentazione.

## Costi della query

| Tipo di domanda | RAG vettoriali | Locale grafo RAG | Globale grafico RAG |
|------------|------------|----------------|-----------------|
| **Ricerca vettoriale** | 1 chiamata | 1 chiamata | 0 chiamate |
| **Traversale grafico** | 0 | 1-2 query | 0 |
| **Chiamate LLM** | 1 | 1-2 | N (mappa) + 1 (riduce) |

La ricerca globale è più costosa per ogni query, ma risponde a domande che la ricerca locale semplicemente non può. Puoi anche trovare risposte globali nella cache e aggiornarle solo quando il corpus cambia.

## Modalità guasti GraphRAG

Il GraphRAG non e' magico.

- **Errori di estrazione**: LLMs manca le entità o le relazioni allucinazioni
- **Pseudonimi dell'entità**: "ASP.NET Core" vs "ASP.NET" vs "aspnetcore" diventano nodi separati
- **Grafico deriva**: Quando i documenti vengono aggiornati, il grafico può diventare stantio
- **Riepiloghi comunitari in fase di stantia**: I sommari non aggiornano automaticamente quando le entità cambiano

La normalizzazione dell'entità è il più grande dolore pratico. Avrete bisogno di:

- Nomi canonici + alias
- Normalizzazione del caso-folding e della punteggiatura
- Deduplicazione facoltativa dell'entità basata sull'integrazione

# Integrare con la nostra ricerca sul blog

Ecco come GraphRAG potrebbe migliorare la ricerca semantica esistente del blog:

## Flusso di corrente

```
User types in search → SemanticSearchService → Qdrant → Results
```

## Flusso migliorato

Il classificatore indirizza le query a diverse strategie di ricerca. Ecco come **interrogazione globale** flussi - si noti che non tocca mai il vettore store:

```mermaid
sequenceDiagram
    participant U as User
    participant API as Search API
    participant C as Query Classifier
    participant G as Global Search
    participant KG as Knowledge Graph

    U->>API: "What topics does this blog cover?"
    API->>C: Classify query
    C-->>API: QueryMode.Global

    API->>G: GlobalSearch(query)
    G->>KG: GetCommunitySummaries()
    KG-->>G: [Frontend, Infrastructure, AI/ML]
    G->>G: MapReduce over summaries
    G-->>API: Synthesized answer

    API-->>U: "The blog covers three main areas..."
```

Confronta questo ad un **interrogazione locale**, che combina la ricerca vettoriale con il contesto grafico per risposte più ricche:

```mermaid
sequenceDiagram
    participant U as User
    participant API as Search API
    participant C as Query Classifier
    participant L as Local Search
    participant Q as Qdrant
    participant KG as Knowledge Graph

    U->>API: "How do I use HTMX?"
    API->>C: Classify query
    C-->>API: QueryMode.Local

    API->>L: LocalSearch(query)
    L->>Q: Vector search
    Q-->>L: Relevant chunks
    L->>KG: GetEntityContext("HTMX")
    KG-->>L: Related: Alpine.js, Tailwind, ASP.NET
    L-->>API: Answer with rich context

    API-->>U: "HTMX is used with Alpine.js for..."
```

La differenza chiave: le query globali sommano i riassunti delle comunità (temi a livello di corpus), mentre le query locali recuperano parti specifiche arricchite da relazioni tra entità.

> **Un'alternativa più semplice:** GraphRAG è ancora molto uno strumento di ricerca - l'estrazione di entità, la costruzione di grafici e la rilevazione della comunità aggiungono complessità significativa e costi LLM. Per la maggior parte dei casi di utilizzo, **Abbinamenti BERT + corrispondenza delle parole chiave BM25** funziona meglio. Questo è ciò che [Cody della Sourcegraph](https://sourcegraph.com/blog/how-cody-understands-your-codebase) utilizza per l'intelligenza di codice, e cosa [DocSummarizer](/blog/docsummarizer-part3) usi per la sommarizzazione dei documenti. Il modello: il recupero ibrido gestisce la pertinenza; le maniglie LLM *montaggio*, non *processo decisionale*. Si ottiene l'80% del beneficio con il 20% della complessità.

## Sketch di implementazione

L'API è semplice: classificare la query, il percorso al gestore appropriato:

```csharp
[HttpGet("api/search")]
public async Task<IActionResult> Search([FromQuery] string q, [FromQuery] string mode = "auto")
{
    if (mode == "auto")
        mode = ClassifyQuery(q);

    // global/local return synthesised answers; default returns raw search results
    return mode switch
    {
        "global" => Ok(await _graphRag.GlobalSearchAsync(q)),  // synthesised answer
        "local" => Ok(await SearchWithGraphContext(q)),        // answer with citations
        _ => Ok(await _semanticSearch.SearchAsync(q))          // raw ranked results
    };
}
```

Ricerca vettoriale e ricerca grafica completano l'un l'altro. Utilizzare vettori per "come faccio" domande, grafici per "quali sono i temi" domande.

# Conclusione

GraphRAG estende RAG da "trovare pezzi simili" a "capire la struttura della conoscenza." Non è una sostituzione per la ricerca vettoriale; è un miglioramento che consente nuovi tipi di query.

**Cosa aggiunge GraphRAG:**

- Estrazione di entità e rapporti
- Costruzione del grafico della conoscenza
- Rilevamento comunitario e sintesi gerarchiche
- Ricerca globale per questioni di sensemaking
- DRIGHT Ricerca ragionamenti connettivi

**Quando usarlo:**

- Hai una notevole collezione di documenti
- Gli utenti chiedono "quali sono i temi" tipo domande
- Il tuo contenuto ha entità e relazioni chiare
- Vuoi sfiorare automaticamente le connessioni

**Percorso di attuazione:**

1. In primo luogo, chiedere: hai davvero bisogno di questo? BERT + BM25 recupero ibrido gestisce la maggior parte dei casi di utilizzo
2. In caso affermativo, prototipo con sidecar Python per convalidare il valore
3. Costruire .NET nativo se i costi / materia latenza
4. Utilizzare LLM locali (Ollama) per controllare i costi di indicizzazione

## Risorse

**GraphRAG Funzionario:**

- [Documentazione GraphRAG](https://microsoft.github.io/graphrag/) - Documenti ufficiali di Microsoft
- [GraphRAG GitHub](https://github.com/microsoft/graphrag) - Codice sorgente ed esempi
- [Carta GraphRAG](https://arxiv.org/pdf/2404.16130) - Il documento di ricerca originale
- [Carta Algoritmo di Leiden](https://arxiv.org/pdf/1810.08473.pdf) - Algoritmo di rilevamento comunitario

**Serie RAG:**

- [Parte 1: Origini e fondamenti degli orientamenti](/blog/rag-primer)
- [Parte 2: Architettura e interni RAG](/blog/rag-architecture)
- [Parte 3: RAG nella pratica](/blog/rag-practical-applications)
- [Parte 4: Ricerca semantica con ONNX e Qdrant](/blog/semantic-search-with-onnx-and-qdrant)
- [Parte 5: Ricerca ibrida e integrazione automatica](/blog/rag-hybrid-search-and-indexing)

**Alternativa più semplice (BERT + BM25):**

- [DocSummarizer Parte 3](/blog/docsummarizer-part3) - Recupero ibrido senza grafico in alto
- [Sourcegraph Cody Architecture](https://sourcegraph.com/blog/how-cody-understands-your-codebase) - Ricerca ibrida di produzione