# GraphRAG Part 2: Minimum Viable Graph RAG

<datetime class="hidden">2025-12-27T14:00</datetime>

<!-- category -- ASP.NET, GraphRAG, DuckDB, Vector Search, Machine Learning, Knowledge Graphs -->
In [Parte 1](/blog/graphrag-knowledge-graphs-for-rag), abbiamo esplorato il motivo per cui GraphRAG è importante . Ora lasciate' costruire un **GraphRAG minimamente viabile** con tre modalità di estrazione:

| Mode | Calls LLM | Perfetto per M|
|------|-----------|----------|
| **Heuristica** (defaultM SK1 | | | 0 per blocco SSK4 Indicettazione veloce, Markdown strutturato |
| **Ibrido** | 1 per ogni documento | Balance tra velocità e qualità S|
| **LLM** |

Tutti i modi usano:

- **DuckDB** per l'archiviazione unificata (vectori + grafico in un singolo archivio
- **BM25 + Ricerca ibrida BERT** attraverso la fusione RRF.
- **Ollama** per la sintesi e la classificazione optionale di lotti (coste API zeroM SK1

**Navigation delle serie:**

- [Parte 1: GraphRAG Fundamentals](/blog/graphrag-knowledge-graphs-for-rag)
- **Parte 2: GraphRAG Minimum Viabile** (Questo articolo)

> **Code:** [Mostlylucid.GraphRag](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid.GraphRag) su GitHub

[TOC]

## Architecture Overview

```mermaid
flowchart LR
    subgraph Indexing
        MD[Markdown Files] --> CH[Chunker]
        CH --> EMB[BERT Embeddings]
        CH --> EXT[Entity Extractor]
        EXT --> |heuristics + links| ENT[Entities]
        ENT --> REL[Relationships]
        REL --> COM[Communities]
    end
    
    subgraph Storage
        EMB --> DB[(DuckDB)]
        ENT --> DB
        REL --> DB
        COM --> DB
    end
    
    subgraph Query
        Q[Query] --> CLASS{Classify}
        CLASS --> |local| HS[Hybrid Search]
        CLASS --> |global| CS[Community Search]
        CLASS --> |drift| BOTH[Both + Synthesis]
        HS --> LLM[Ollama]
        CS --> LLM
        BOTH --> LLM
    end
    
    DB --> HS
    DB --> CS
    
    style MD stroke:#22c55e,stroke-width:2px
    style DB stroke:#3b82f6,stroke-width:2px
    style LLM stroke:#a855f7,stroke-width:2px
```

## Perché DuckDB?

Microsoft's GraphRAG usa un'archiviazione separata per i vettori M SK1LanceDB),entità (ParquetMSC4 e relazioni SSK5di più ParquetMST6 DuckDB semplifica questo

- Single `.duckdb` file per tutto.
- La ricerca dei vettori nativi HNSW attraverso l'estensione VSS
- SQL per la ricerca dei vettori e la traversazione dei grafici
- Semplicità zero di distribuzione

**DuckDB non è una base di dati grafica - e cheM SK1 è il punto.** Questo non è 't NeoM SK1j. I travertili sono superficiali , SQLMST4 basati MST5 e deliberatiM ST6 Questa restrizione mantiene il sistema debuggerabile e economicoMst7

## Schema di Conservazione

Il schema usa **Si uniscono i tavoli per la provenienza.** - possiamo fare una domanda " quali frammenti menzionano l'entità X?" direttamenteM SK3

```mermaid
erDiagram
    documents ||--o{ chunks : contains
    chunks ||--o{ entity_mentions : has
    chunks ||--o{ relationship_mentions : has
    entities ||--o{ entity_mentions : mentioned_in
    entities ||--o{ relationships : source
    entities ||--o{ relationships : target
    relationships ||--o{ relationship_mentions : mentioned_in
    communities ||--o{ community_members : contains
    entities ||--o{ community_members : belongs_to
    
    chunks {
        varchar id PK
        varchar document_id FK
        text text
        float[] embedding
    }
    
    entities {
        varchar id PK
        varchar name
        varchar type
        int mention_count
    }
    
    entity_mentions {
        varchar entity_id FK
        varchar chunk_id FK
    }
```

Decisione di progettazione chiave: **No. `VARCHAR[]` per la provenienza**. Coinvolgere i tavoli (`entity_mentions`, `relationship_mentions`) permette di fare ricerche efficienti come " ottenere tutti i frammenti che menzionano Docker".

### La ricerca dei vettori: L'HNSW Ci ha preso

L'indice HNSW di DuckDB' si attiva solo con `array_cosine_distance` + `ORDER BY` + `LIMIT`:

```csharp
// GraphRagDb.cs - SearchChunksAsync
cmd.CommandText = $"""
    SELECT id, document_id, text, chunk_index, 
           array_cosine_distance(embedding, $1::FLOAT[{_dim}]) as distance
    FROM chunks 
    WHERE embedding IS NOT NULL
    ORDER BY distance
    LIMIT $2
    """;
// Convert distance to similarity: 1.0f - distance
```

**Utilizzando `array_cosine_similarity` non userà l'indice.** - ha vintoM SK1 non ha attivato l'indice HNSW. Su corpora non--trivialeMSC4 questo trasforma una domanda indexata di ~5ms in una scansione completa della tavola.

## Extrazione delle entità

Questo è il punto in cui ci diffondiamo dall'approccio di Microsoft' **LLM-per-passi d'estrazione di scarto** usata in Microsoft's riferimento Pipeline GraphRAG, che usiamo **Extrazione statistica basata su IDF-**. L'obiettivo è non essere un'entità perfetta. *stabile, corpusM SK1 segnali relativi* che non richiedono un LLM per produrre. Questo corrisponde a un certo richiamo al determinismo,Auditabilità, e costi prevedibili- una scelta deliberata per le corporazioni tecniche:

```mermaid
flowchart TB
    subgraph "Phase 1: Signal Collection"
        TEXT[All Chunks] --> IDF[Compute IDF Scores]
        TEXT --> STRUCT[Structural Signals]
        STRUCT --> HEAD[Headings]
        STRUCT --> CODE[Inline Code]
        STRUCT --> LINKS[Links]
        IDF --> RARE[High-IDF = Rare Terms]
        RARE --> CAND[Candidates]
        HEAD --> CAND
        CODE --> CAND
        LINKS --> |explicit rels| LINKREL[Link Relationships]
    end
    
    subgraph "Phase 2: Dedup"
        CAND --> EMBED[BERT Embeddings]
        EMBED --> SIM[Similarity > 0.85]
        SIM --> MERGE[Merge Duplicates]
    end
    
    subgraph "Phase 3: Classify"
        MERGE --> LLM{LLM Available?}
        LLM --> |yes| BATCH[Single Batch Call]
        LLM --> |no| HEUR[Heuristic Types]
    end
    
    style IDF stroke:#f59e0b,stroke-width:2px
    style BATCH stroke:#a855f7,stroke-width:2px
```

### Perché l'IDF, non contiene liste codificate

L'approccio naïvo è un metodo hardcode. `HashSet<string> KnownTech = { "Docker", "Kubernetes", ... }`. Questa interruzione per:

- Nouvelle tecnologie (tu'dovreste aggiornare la listaM SK2
- Domain- termini specifici M SK1corpo diverso =entità diverse)
- I difetti e le variazioni

**IDF (Frequenza inversa del documentoM SK1** Risolve questo statisticamente. Un termineM SK1s IDF è:

$$\text{IDFM SK2tMSC3 = \\logMST6fracMSV7NMSS8dfM SV9t\MSV10

Dove:

- $NM SK1 = totali pezzi
- $dfM SK1t)$ = documenti che contengono il termine SSK4t\$

High IDF = **termine raro** = probabile che un'entitàM SK1 "Docker" apparsa in SSK4 dei frammenti 100 abbia un IDF più alto di SSK6ilSNK7 apparso in \SSK8 dei S100.

Per saperne di più su TF-IDF e BM25, consultate il mio post su [ricerca ibrida con BM25](/blog/rag-hybrid-search-and-indexing).

### Signali strutturali

La struttura di marcazione ci dice cosa è importante.

- **Insieme** (`## Docker Setup`Entità )
- **Il codice inline** (`` `docker-compose` ``Entità )
- **Link** (`[Docker](https://docker.com)`) → entità + relazione

```csharp
// EntityExtractor.cs - structural signal extraction
private void ExtractStructuralEntities(string chunk, string chunkId)
{
    // Headings: ## Docker Compose Setup → "Docker Compose Setup"
    foreach (Match m in Regex.Matches(chunk, @"^#{1,3}\s+(.+)$", RegexOptions.Multiline))
    {
        var heading = m.Groups[1].Value.Trim();
        AddCandidate(heading, chunkId, weight: 2.0); // Higher weight
    }
    
    // Inline code: `docker-compose` → "docker-compose"
    foreach (Match m in Regex.Matches(chunk, @"`([^`]+)`"))
    {
        AddCandidate(m.Groups[1].Value, chunkId, weight: 1.5);
    }
}
```

### Extrazione di collegamenti (High-Relazioni di qualitàM SK2

I link Markdown forniscono **Esplicito** relationships that don't require LLM inference:

```csharp
// EntityExtractor.cs - ExtractLinks  
foreach (Match m in Regex.Matches(chunk, @"\[([^\]]+)\]\((/blog/[^)]+)\)"))
{
    var linkText = m.Groups[1].Value;  // "semantic search"
    var slug = m.Groups[2].Value;       // "/blog/semantic-search-with-qdrant"
    yield return new Relationship(linkText, $"blog:{slug}", "references", chunkId);
}
```

### Deduplicazione tramite i collegamenti BERT

I nomi di entità come "Docker Compose", "dockerM SK3composeMSC4 e "DokerComposeSSK6 dovrebbero essere unitiMST7 Noi usiamo **Inserzioni BERT** per rilevare la somiglianza semantica:

```csharp
// EntityExtractor.cs - DeduplicateAsync
var embeddings = await _embedder.EmbedBatchAsync(candidates.Select(c => c.Name), ct);

for (int i = 0; i < candidates.Count; i++)
{
    for (int j = i + 1; j < candidates.Count; j++)
    {
        var similarity = CosineSimilarity(embeddings[i], embeddings[j]);
        if (similarity > 0.85)
        {
            // Merge into canonical entity (keep higher mention count)
            canonical.MentionCount += duplicate.MentionCount;
            canonical.ChunkIds.UnionWith(duplicate.ChunkIds);
        }
    }
}
```

Questo passo è O(nM SK1 all'interno di un gruppo di candidati limitato, ma i conteggi dei candidati sono limitati da filtri IDF e segnali strutturali - non dimensione del corpusMSC4 Per i dettagli sulle inserzioni BERTMNK5 consultate [La ricerca semantica con ONNX e BERT](/blog/semantic-search-with-onnx-and-qdrant).

## Mode di Extrazione

Il CLI supporta tre modi di estrazione attraverso `--extraction-mode`:

### Mode euristico (Default)

```bash
dotnet run --project Mostlylucid.GraphRag -- index ./Markdown --extraction-mode heuristic
```

Usa i segnali strutturali IDF + per la rilevazione delle entità, con classificazione opzionale di lotti LLMM SK2 **Zero per le chiamate di LLM su -.** - solo ~1 chiamate per 50 entità per classificare il tipo.

### Mode ibrido (Recommended)

```bash
dotnet run --project Mostlylucid.GraphRag -- index ./Markdown --extraction-mode hybrid
```

La migliore di entrambi i mondi:

1. **Detezione euristica**: IDF + i segnali strutturali trovano candidati all'entità.
2. **Riparazione LLM**: Una chiamata per ogni *Documento* Valida entità e extrae relazioni semantiche.

```mermaid
flowchart LR
    subgraph "Per Document"
        CHUNKS[Document Chunks] --> HEUR[Heuristic Extraction]
        HEUR --> CAND[30 Candidates]
        CAND --> LLM[Single LLM Call]
        LLM --> ENT[Validated Entities]
        LLM --> REL[Semantic Relationships]
    end
    
    style HEUR stroke:#22c55e,stroke-width:2px
    style LLM stroke:#a855f7,stroke-width:2px
```

Per i documenti 5 con i frammenti 62, il modo ibrido fa **Le chiamate 5 LLM** (vs 124 per il modo completo LLMM SK2 ottenete:

- Copertura delle entità deterministiche dalla heuristica
- LLM-estrazione delle relazioni di qualità (semantici, non solo coM SK3occurrenzaMSC4
- Descrizioni e tipi validi

### Mode LLM (Microsoft-StyleM SK2

```bash
dotnet run --project Mostlylucid.GraphRag -- index ./Markdown --extraction-mode llm
```

L'approccio completo di Microsoft GraphRAG: **2 LLM chiama per blocco** (estrazione di entità +estramento di relazione). più costosoM SK3 ma di migliore qualità per testi non strutturatiMSC4

### Quando usare ogni?

| Mode | Calls LLM | Perfetto per M|
|------|-----------|----------|
| **Heuristica** |
| **Ibrido** | 1 per documento | Balance della copertura e della qualità S|
| **LLM** |

Per la documentation tecnica, cominciate con **ibrido.** mode. E vi dà relazioni semantiche senza il costo per l'uno. **Heuristica** per velocità pura, o **Llm** per il testo narrativo.

## La ricerca ibrida: BM25 M SK2 BERT

La ricerca ibrida combina due approcci complementari:

**Denso (BERTM SK1** Capisce il significato. "Container di dockerM SK2 corrisponde "containerizzazioneMSC4
**Sparse (BMM SK1** Corre i termini precisi. "HNSWM SK2 solo corrisponde "HN SWMSC4

```mermaid
flowchart LR
    Q[Query] --> BERT[BERT Embedding]
    Q --> BM25[BM25 Tokenize]
    
    BERT --> DENSE[Dense Search<br/>HNSW Index]
    BM25 --> SPARSE[Sparse Search<br/>TF-IDF Scoring]
    
    DENSE --> RRF[RRF Fusion]
    SPARSE --> RRF
    
    RRF --> TOP[Top K Results]
    TOP --> ENR[Enrich with<br/>Entities + Rels]
    
    style RRF stroke:#f59e0b,stroke-width:2px
```

### Cos'è BM25?

BM25 M SK1Best Match 25) valuta i documenti sulla base della frequenza del termine di domanda. La formulaMSC4

$$\ testoM SK1 punteggio_{iM SK1n} \textMSC4IDFMST5qMSSK6i+) ==\cdot MSSK9fracMSV10fM SV11q=MSV12i\MSV13 DMSS14 ≥MSV15cdo SSV16kM Sv17 ≤MSV18 ±MSV19f+MSV20q~MSV21i^MSV22 D\MSS23 \+ kMsv25 +M SV26c dot RMSV27 ++MSV28 b MMSV29 b

Principali intuizioni:

- **Termino IDF**: Parole rare contano di più ("HNSW" > \"theM SK5
- **La saturazione TF**: Una parola che appare 10x non èM SK2t 10x più rilevante di SSK4x
- **Normalizzazione della lunghezza**: Documenti lunghi non ricevono un vantaggio ingiusto.

Per la completa implementazione del BM25, see ,. [La ricerca e l'indexazione ibride](/blog/rag-hybrid-search-and-indexing).

### Fusione di posizione reciproca (RRFM SK1

RRF fonde classificazioni da diversi sistemi di rilevamento. Ogni posizione di classifica ottiene un punteggio:

$$\testM SK1RRF}(dMSC3 = | | 5 | somma_{r \in RM SK2 \frac{1}{k M+ rMSC6dMST7

Dove $kM SK1 ( tipicamente 60) impedisce l'eccesso di sovrapeso del risultato più alto. Documenti presenti in **Entrambi.** I ranking sono migliorati:

```csharp
// SearchService.cs - RRF fusion
const int k = 60;

foreach (var (chunk, rank) in denseResults.Select((c, i) => (c, i)))
    scores[chunk.Id] = 1.0 / (k + rank + 1);

foreach (var (chunk, rank) in sparseResults.Select((c, i) => (c, i)))
{
    var rrfScore = 1.0 / (k + rank + 1);
    if (scores.TryGetValue(chunk.Id, out var existing))
        scores[chunk.Id] = existing + rrfScore;  // Boost for appearing in both!
    else
        scores[chunk.Id] = rrfScore;
}
```

Esempio: Un documento classificato M SK1 in densità e #3 in scarsa

- Densità: $1/(60+1) | | = |
- Sparse: M SK1 | | = |
- **Combinato: M SK1** ( più alto di uno solo )

## Mode di ricerca

```mermaid
flowchart TB
    Q[Query] --> CLASS[Classify Query]
    
    CLASS --> |"How do I use X?"| LOCAL[Local Search]
    CLASS --> |"What are the themes?"| GLOBAL[Global Search]  
    CLASS --> |"How does X relate to Y?"| DRIFT[DRIFT Search]
    
    LOCAL --> HS[Hybrid Search] --> CTX1[Chunk + Entity Context]
    GLOBAL --> CS[Community Summaries] --> MAP[Map-Reduce]
    DRIFT --> BOTH[Local + Communities] --> SYN[Synthesize]
    
    CTX1 --> LLM[LLM Answer]
    MAP --> LLM
    SYN --> LLM
    
    style LOCAL stroke:#22c55e,stroke-width:2px
    style GLOBAL stroke:#3b82f6,stroke-width:2px
    style DRIFT stroke:#a855f7,stroke-width:2px
```

### Classificazione della domanda

```csharp
// QueryEngine.cs
private static QueryMode ClassifyQuery(string query)
{
    var q = query.ToLowerInvariant();
    if (q.Contains("main theme") || q.Contains("summarize") || q.Contains("overview"))
        return QueryMode.Global;
    if (q.Contains("relate") || q.Contains("connect") || q.Contains("compare"))
        return QueryMode.Drift;
    return QueryMode.Local;
}
```

Questo classificatore è deliberatamente semplice - ed è facile da sostituire con un piccolo modello di intenzione più tardi. Se nessuna entità corrisponde a , il sistema si degrada pulito per ottenere una ripresa pura ibridaM SK3

## Usazione del CLI

### Indice

```bash
# Heuristic mode (default) - fast, no per-chunk LLM
dotnet run --project Mostlylucid.GraphRag -- index ./test-markdown

# LLM mode - Microsoft-style classification
dotnet run --project Mostlylucid.GraphRag -- index ./test-markdown --extraction-mode llm
```

```text
GraphRAG Indexer
  Source: test-markdown
  Database: graphrag.duckdb
  Model: llama3.2:3b
  Extraction: Heuristic (IDF + signals)

Initializing...
Indexing docker-development-deep-dive.md: 0%
Indexing docker-swarm-cluster-guide.md: 40%
Indexing dockercomposedevdeps.md: 80%
Indexing complete: 100%
Classifying entities...: 0%
Extracted 168 entities, 315 rels (4 LLM calls): 100%
Found 10 communities: 100%
Summarizing c_0_2 (12 entities): 20%
Summarizing c_0_8 (4 entities): 80%

────────────────── Indexing Complete ───────────────────
┌───────────────┬───────┐
│ Metric        │ Count │
├───────────────┼───────┤
│ Documents     │ 5     │
│ Chunks        │ 62    │
│ Entities      │ 168   │
│ Relationships │ 312   │
│ Communities   │ 10    │
└───────────────┴───────┘
```

### La domanda

```bash
dotnet run --project Mostlylucid.GraphRag -- query "How do I use Docker Compose?"
```

```text
──────────────────── Local Search ────────────────────

Query: How do I use Docker Compose?

╭─Answer────────────────────────────────────────────────╮
│ To run the services defined in the                    │
│ devdeps-docker-compose.yml file, you need to run the  │
│ following command in the same directory as the file:  │
│                                                       │
│ docker compose -f .\devdeps-docker-compose.yml up -d  │
│                                                       │
│ This command will start the containers in detached    │
│ mode.                                                 │
╰───────────────────────────────────────────────────────╯

Related Entities: Docker, container, services, image

Sources: 5 chunks (top score: 0.016)
```

### Statistiche

```bash
dotnet run --project Mostlylucid.GraphRag -- stats
```

```text
─────────────── GraphRAG Database Stats ────────────────
┌───────────────┬───────┐
│ Metric        │ Count │
├───────────────┼───────┤
│ Documents     │     5 │
│ Chunks        │    62 │
│ Entities      │   168 │
│ Relationships │   312 │
│ Communities   │    10 │
└───────────────┴───────┘

Database size: 7.76 MB
```

## La comparazione dei costi

Per i post di blog 100 (~500 frammenti, | Documenti

| Operazione | MSFT GraphRAG | Heuristice MSSK3 Hybride \| LLM \ |
|-----------|---------------|-----------|--------|-----|
| Extrazione delle entità | 1,000 richiami | \0 | | | ♫ |0 ♫
| Riparazione dei documenti | - \| | | 4 | 5 | 6 | chiamate | 7 | 8 | 9
| Classificazione | Incluso | S~4 lotto M| \- || | | ~4 | lotto
| Esempi comuni | ~20 | | 3 | 4 | 5 | 6 | 7 | 8 | 9
| **Totali chiamate LLM** | ~1,020 | ~24 | ~120 | ~1,024 |
| **Calità delle relazioni** | Semantici | Co-Occurrenza | Semantiche
| **Costo (gpt -4oM SK2mini)** | ~$5-10 | ~$0.15 | ~$0.75 | ~$5-10 |
| **Costo (OllamaM SK1** | NM SK1A

**Il modo ibrido è il punto preferito.** per la maggior parte del contenuto tecnico: si ottengono relazioni semantiche M SK1non solo co-occurrenceMSC3 a ~10% di MSFTMST5costa di .

> Ordinazione ristretta-di-estimazione di grandezzaM SK2costa esatta dipende dalla dimensione del pezzo e dalla forma improvvisa

## Tradeoffs

| Aspetto | Heuristico MSFT GraphRAG SSK5
|--------|-----------|--------|-----|---------------|
| Detezione delle entità | | | IDF |+ | Structure | | | LLM per un pezzo |
| Relazioni | Co-occurrenza | LLMM SK4inferita M| Co\-occurenza ♫| LLSMSC8inferrata R|
| Calls LLM (100 docs) | \~24 | | | ♫ |~120 ♪ |
| Calità delle relazioni | Bassa | alta | bassa SMK4 alta
| Funziona offline | Sì | | | Sì
| Perfetto per | Velocità-Critificabile | **Recommended** | Compatto di sequenza | testo non strutturato |

Conceptualmente, questo è lo stesso tubo di costruzione. [DocSummarizer](/blog/docsummarizer-tool): costruire prima la struttura, poi lasciare che lo descriva un LLMM SK2

> **Dove questo si rompe:** Un testo narrativa o di fantascienza senza marcatura strutturale. Relazioni implicite senza segnale lexico. Nomi di entità molto ambigui che richiedono conoscenza del mondo per essere disinambiguatiM SK2 Per questi casi , usare il modo LLM o MicrosoftMST4 l'approccio completoMst5

## Il codice

L'implementazione è minimale - ~2,000 linee attraverso questi file:

```
Mostlylucid.GraphRag/
├── Storage/GraphRagDb.cs              # DuckDB with HNSW + provenance
├── Services/EmbeddingService.cs       # ONNX BERT wrapper
├── Services/OllamaClient.cs           # LLM client
├── Extraction/
│   ├── IEntityExtractor.cs            # Extractor interface
│   ├── EntityExtractor.cs             # Heuristic mode
│   ├── HybridEntityExtractor.cs       # Hybrid mode (recommended)
│   └── LlmEntityExtractor.cs          # Full LLM mode
├── Search/SearchService.cs            # BM25 + BERT hybrid
├── Graph/CommunityDetector.cs         # Leiden + summarization
├── Query/QueryEngine.cs               # Local/Global/DRIFT
├── Indexing/MarkdownIndexer.cs        # Chunking
├── GraphRagPipeline.cs                # Orchestration
├── Models.cs                          # Shared types + ExtractionMode enum
└── Program.cs                         # CLI
```

**Source:** [`Mostlylucid.GraphRag/`](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid.GraphRag)

## Post connessi

- [Parte 1: GraphRAG Fundamentals](/blog/graphrag-knowledge-graphs-for-rag) - Perché i grafici della conoscenza migliorano la RAG
- [La ricerca ibrida con BM25](/blog/rag-hybrid-search-and-indexing) - Immersi profondamente nel BMM SK1 valutazione e classificazione
- [La ricerca semantica con ONNX e BERT](/blog/semantic-search-with-onnx-and-qdrant) - Inserzioni BERT in .NET
- [DocSummarizer Tool](/blog/docsummarizer-tool) - L'outil di riassunto del mio documento che condivide il codice con questo

## Ressource esterne

- [L'estensione del VSS DuckDB](https://duckdb.org/docs/extensions/vss.html) - Cercare il vettore HNSW
- [La carta dell'algoritmo di Leiden](https://arxiv.org/pdf/1810.08473.pdf) - Detezione comunitaria
- [Papiero RRF](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) - Fusione di posizione reciproca