# GraphRAG: Miksi Vektorihaku katkeaa Corpus-tasolla

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

<!-- category -- ASP.NET, Semantic Search, ONNX, Qdrant, Machine Learning, Vector Search, RAG -->
Regage-järjestelmäsi on hyvä "neula" -kysymyksissä: hae muutama relevantti palanen ja syntetisoi vastaus. Se kamppailee kahden yhteisen kyselytyypin kanssa:

- **Järkeilyä**: "Mitkä ovat tärkeimmät teemat tässä kokoonpanossa?"
- **Liitäntä**"Miten X liittyy Y:hen eri dokumenteissa?"

Yksikään osa ei vastaa niihin. **Kattavuus + ryhmittely + yhteys**.

**Miksi vektorihaku epäonnistuu tässä:**

- Se sijoittuu palasiksi **itsenäisesti** samankaltaisuudella kyselyn kanssa
- Samankaltaisuus optimoi relevanssin, ei **maailmanlaajuinen kattavuus**
- Upotus vangitsee "mikä kuulostaa samanlaiselta", ei "mikä yhdistää mihin"

Tätä voi raastaa kehottamalla ja jälkikäsittelyllä, mutta päädyt rakentamaan uudelleen käyrän muotoista ratkaisua.

**Avainnäkemys:** GraphRAG muuttaa hakuyksikön. Korporaatiokysymyksiin et halua "top-K vastaavia kappaleita", haluat **kytköksissä olevat konseptiyhteisöt** (ja niiden tiivistelmät), niin malli näkee rakenteen, ei sirpaleita.

[Graflagi](https://microsoft.github.io/graphrag/) tulee Microsoft Researchin [paperi](https://arxiv.org/pdf/2404.16130) ja on saatavilla [Avoimen lähdekoodin toteutus](https://github.com/microsoft/graphrag). Se jatkaa vektorihakua erityiskysymyksiin, mutta lisää tietokäyrän ja yhteisötiivistelmiä korporaatiotason päättelyyn.

## Kun et saa käyttää Grafragia

Ennen kuin hyppäämme sisään, tehdään selväksi, milloin tämä on liioittelua:

- **Pienet asiakirjasarjat** (alle ~50 asiakirjaa): käytä vain vektorihakua
- **Vain "miten voin" -kysymyksiä**: GraphRAG ei auta
- **Yhdenmukainen sisältö** (ei yhteisövalikoimaa): ei kaaviorakennetta hyödynnettäväksi
- **Kustannuksia rajoitetaan**: indeksointi vaatii useita LLM-puheluita

Jos käyttäjäsi kysyvät vain tiettyjä kysymyksiä, pysy mukana [semanttinen haku](/blog/semantic-search-with-onnx-and-qdrant). GraphRAG loistaa, kun käyttäjät tarvitsevat *iso kuva*Se on pienempi yleisö kuin myyjät antavat ymmärtää.

# Johdanto

**Sarjanavigointi:** Tämä on aluetukisuuntaviivojen 6 osa:

- [Osa 1: Alkuperä ja perusasiat](/blog/rag-primer) - Historiaa, motivaatiota ja ydinkäsitteitä
- [Osa 2: Arkkitehtuuri ja sisätilat](/blog/rag-architecture) - Tekninen syväsukellus
- [Osa 3: Aluetuki käytännössä](/blog/rag-practical-applications) - Todellisten järjestelmien rakentaminen
- [Osa 4a: ONNX & Qdrantin toteutus](/blog/semantic-search-with-onnx-and-qdrant) - CPU-ystävällinen semanttinen haku
- [Osa 4b: Semanttinen haku toiminnassa](/blog/semantic-search-in-action) - Typeahead, hybridihaku, ja UI
- [Osa 5: Hybridihaku ja autoindeksointi](/blog/rag-hybrid-search-and-indexing) - Tuotantointegraatio
- **Osa 6: GraphRAG** (tämä artikkeli) - Tietokäyrät korporaatiotason ymmärtämiseen

Koko tämän sarjan ajan olemme rakentaneet yhä kehittyneempiä RAG-järjestelmiä. Aloitimme perusvektorihaulla, lisäsimme hybridihakusanan + semanttisen haun ja integroidun autoindeksin. **samankaltaisia kolhuja**, ei **yhdistettyjä konsepteja**. . *Korporaatiotaso* Kysymyksiä (aiheita, jotka kattavat monia asiakirjoja) tarvitset rakennetta.

**Suositeltu reitti:** Jos sinulla on jo Qdrant-pohjainen paikallinen hakutyö (kuten meillä), Python-sivuauton prototyyppi, joka vahvistaa arvon, säilytä vektorit paikalliseen hakuun ja lisää kevyt käyrä maailmanlaajuisiin ja DRIFT-kyselyihin. Käytä "täydellistä GraphRAGia" vasta sitten, kun olet todistanut, että käyttäjät kysyvät näitä kysymyksiä.

[TOC]

# Puhtaan vektorialueen ongelma

Näytän, mitä tarkoitan konkreettisella esimerkillä.

## Mitä Vector RAG tekee hyvin

**Kysymys:** "Miten käytän HTMX:ää alppihevosten kanssa?"

**Vektorilaajennusprosessi:**

1. Lisää kysymykseen: `[0.234, -0.891, 0.567, ...]`
2. Etsi samanlaisia palasia Qdrantista
3. Return top-K -ottelut HTMX:stä ja Alpine.js:stä
4. LLM syntetisoi vastaukset näistä kappaleista

Tämä toimii, koska kysymys ja sisältö ovat **Semanttisesti samanlainen**Uppoumat kuvaavat samankaltaisuutta.

```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
```

## Missä Vector Regage kamppailee

**Kysymys:** "Mitkä ovat tärkeimmät teknologiat, joista kirjoitan, ja miten ne liittyvät toisiinsa?"

**Mitä vektori RAG palauttaa:**

```
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..."
```

Se mainitsee Dockerin, PostgreSQL:n, HTMX:n ja ONNX:n, mutta ei ryhmittele tai selitä, miten ne liittyvät toisiinsa.

Ongelma: Tämä kysymys vaatii **aggregaatio ja suhdeymmärrys** Sinun täytyy:

- Tunnista kaikki mainitut teknologiat
- Ymmärrä, mitä käytetään yhdessä
- Ryhmittele ne yhtenäisiksi teemoiksi

Vektorin samankaltaisuus ei yksin anna sinulle tätä. Jos yrität paikata tämän kehotuksella, päädyt keksimään käyrän uudelleen.

# Syötä GraphRAG

[Graflagi](https://microsoft.github.io/graphrag/) on Microsoft Researchin ratkaisu tähän ongelmaan. [Grafaattipaperi](https://arxiv.org/pdf/2404.16130) Havaittiin kaksi kyselytyyppiä, jotka perustason aluetukisuuntaviivojen käsittely on heikkoa (**aistinvaraista** sekä **YDINKIRJOITUS**) ja rakensi järjestelmän, jolla niitä voitaisiin erityisesti käsitellä.

GraphRAG ei vain upota palasia, vaan rakentaa **Osaamiskäyrä** joka vangitsee kokonaisuuksia ja niiden suhteita, sitten ryhmittelee ne yhteisöiksi yhteenvedoilla.

## Grafragin toiminta

**Putket yhdellä silmäyksellä:**

- **Hakemisto:** Chunks → entiteetit/suhteet → kaavio → yhteisöt → tiivistelmät
- **Kysely:** Paikalliset = kolikot + graafinen naapurusto Global = yhteisön yhteenvedot DRIFT = polut + tiivistelmät

GraphRAG lisää RAG-putkeen useita komponentteja, jotka on jaettu kolmeen luokkaan:

1. **Uutto** (yhteisöt + suhteet)
2. **Kaavion rakenne** (tietokäyrän tallennus)
3. **Yhteenveto** (yhteisöhavainnointi + hierarkia)

```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
```

### Vaihe 1: Yksikön ulosotto

LLM lukee jokaisen palan ja otteita **Yhteisöt** (keskustelun kohteena olevat asiat):

```
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)
```

### Vaihe 2: Suhdeuutuudet

Samassa LLM:ssä määritetään, miten yhteisöt suhtautuvat toisiinsa:

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

### Vaihe 3: Osaamiskaavion rakentaminen

Kaikki yhteisöt ja suhteet muodostavat kaavion:

```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
```

### Vaihe 4: Yhteisön osoittaminen (Leiden algoritmi)

Erytropoietiini [Leiden-algoritmi](https://arxiv.org/pdf/1810.08473.pdf) klusterit, joilla on tiiviit yhteydet yhteisöihin. Tällä on merkitystä, koska se antaa sinulle *vakaa* Ryppäitä yhteenvetona ja noutajana; yhteisöistä tulee hakuyksikköjäsi maailmanlaajuisia kyselyitä varten.

- **Yhteisö 1**"Frontend Stack" (HTMX, Alpine.js, Tailwind)
- **Yhteisö 2**: Container Infrastructure (Docker, Composite, PostgreSQL, Qdrant)
- **Yhteisö 3**: "RAG-putkisto" (ONNX, upotukset, Qdrant, RAG)

Huomaa, miten Qdrant esiintyy kahdessa yhteisössä: se yhdistää infrastruktuurin ja tekoäly/ML:n.

### Vaihe 5: Yhteisön yhteenvedot

LLM laatii tiivistelmiä jokaiselle yhteisölle kullakin hierarkiatasolla:

```
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."
```

## Kyselytilat

GraphRAG tarjoaa kolme kyselytilaa, joista jokainen on optimoitu eri kysymystyypeille:

### Maailmanlaajuinen haku

**Paras:** "Mitkä ovat tärkeimmät teemat?" "Keskeytä keskeiset aiheet."

Käyttää yhteisöllisiä tiivistelmiä (ei yksittäisiä kappaleita) vastatakseen aistinvaraisiin kysymyksiin:

```
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"
```

### Paikallinen haku

**Paras:** "Miten konfiguroidaan X?" "Mikä on Y?"

Yhdistää entiteettipainotteisen käyrätraversaalin perinteiseen vektorihakuun:

```
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
```

### JOHDANNAINEN haku

**Paras:** "Miten X liittyy Y:hen?" "Vertaa A:ta ja B:tä."

DRIFT-haku (Dynamic Reasoning and Inference with Flexible Traversal), [GraphRAGin dokumenteissa kuvattuna](https://microsoft.github.io/graphrag/query/drift_search/), yhdistää paikallisen etsinnän yhteisön kontekstiin. Se käyttää yhä LLM:n päättelyä strukturoituun kontekstiin nähden (ei maagista graafista johtopäätöstä), mutta rakenne auttaa LLM:ää näkemään yhteydet, joita se ei näkisi litteillä palasilla.

```
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."
```

# GraphRAGin vertaaminen nykyiseen järjestelmäämme

Kartoitetaan GraphRAG-käsitteet siihen, mitä meillä jo on. `Mostlylucid.SemanticSearch`:

Komponentti Nykyinen järjestelmä GraphRAG Equivalent
|-----------|---------------|---------------------|
| **Liitännät** ONNX (kaikki MiniLM-L6-v2) Sama (tai OpenAI)
| **Vector Store** Qdrant Qdrant / LanceDB
| **Yksikön otto** Ei mitään LLM-voimalla tapahtuvaa louhintaa
| **Osaamiskaavio** Graafinen tietokanta / in-muisto
| **Yhteisön havainto** Ei mitään Leiden-algoritmia
| **Kysely: Specific** | `SemanticSearchService.SearchAsync()` Paikallinen haku
| **Kysely: Maailmanlaajuinen** Ei tuettu Maailmanlaajuinen haku

Nykyinen toteutus käsittelee **Paikallinen haku** no. GraphRAG lisäisi **Maailmanlaajuinen haku** sekä **JOHDANNAINEN haku** Kykyjä.

```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);
}
```

# Toteutustavat

GraphRAGia voidaan lisätä olemassa olevaan järjestelmään kolmella tavalla.

## Vaihtoehto 1: Python Sidecar (Recommended for Exploration)

Suorita Microsoftin GraphRAG erillisenä palveluna:

```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;
    }
}
```

**Plussat:** Käytä Microsoftin taistelutestattua toteutusta, nopea prototyyppi
**Miinukset:** Python-riippuvuus, LLM-kustannukset indeksoinnista, prosessien välisestä viestinnästä

## Vaihtoehto 2: .NET Native (Tuotantopolku)

Rakenna avainkomponentit C#. BERT-pohjaisia uutto- ja Ollama-kuvioita [DocSummarizer](/blog/docsummarizer-tool) toimi täällä samalla tavalla.

### Yksikön otto

Pyydä LLM:ää tunnistamaan *asioita* (yksikköjä) kussakin osassa – jäsenneltyjä kokonaisuuksia vapaamuotoisten aiheiden sijaan:

```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);
}
```

**Tuotantotarve:** LLM JSON -lähtö *Will* Tauko. Tämä ei ole valinnainen kovettuminen. Tarvitset yhden seuraavista:

- **Scheman rajoittama sukupolvi** (Ollama's `format: json`, OpenAI:n toimintokutsu)
- **Koeta korjaussilmukoita** (Havaitse epämuodostunut JSON, pyydä LLM:ää korjaamaan se)
- **Varaulosotto** (yhteisten yksikkötyyppien mallit)

LLM:t ovat todennäköisiä, mutta poistoputkesi ei saa olla.

### Suhdeuutuudet

Kun olet saanut kokonaisuuksia, kysy LLM:ltä, miten ne liittyvät toisiinsa:

```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));
}
```

### Graafinen säilytys ja Entiteetin normalisointi

Suurin käytännön kipu on **Yksikön peitenimi**: "ASP.NET Core", "ASP.NET" ja "Aspnetcore" pitäisi olla sama solmu. Yksinkertainen normalisointi auttaa:

```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();
}
```

Vakavaa käyttöä varten kannattaa harkita upotettavan kokonaisuuden deduplikaatiota: jos kahdella entiteettinimellä on samanlainen upotus, ne ovat todennäköisesti sama asia.

**Tuotantokipupisteet** (GraphRAGin arvo riippuu kaavion laadusta):

- **Synonyymi/alias-taulukot**: ylläpitää kanonisia nimiä ja tunnettuja aliaksia
- **Suhdelukujen hallinta**: rajoitteet sallittuja predikaatteja estämään hallusinoituneita suhdetyyppejä
- **Luottamuspisteet + karsinta**: kaikki uutetut suhteet eivät ole yhtä luotettavia
- **Laskennallinen uudelleen indeksointi**: Kun asiakirjat päivittyvät, sinun täytyy paikata kaavio, ei rakentaa sitä uudelleen

### Graf Traversal

Asiaan liittyvien tahojen löytäminen on laaja ensimmäinen haku:

```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();
}
```

### Yhteisön havainto

Tämä on yhdistettyjen osien perustaso, **ei** täyden Leidenin. Leiden optimoi modulaarisuuden (keveät sisäiset yhteydet, harvat ulkoiset). Oikeaa toteutusta varten käytä graafista kirjastoa tai algoritmia.

```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;
}
```

### Yhteisön yhteenveto

Jokainen yhteisö saa teemaansa kuvaavan tiivistelmän. Tämä on se, mikä valtaa maailmanlaajuisen haun:

```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);
}
```

## Vaihtoehto 3: Hybridi (käytännöllinen keskikenttä)

Tämä on suositeltu lähestymistapa, jos sinulla on jo toimiva vektorihaku. Pidä Qdrant paikallishaussa, lisää kevyt graafinen kerros Global/DRIFT-kyselyihin.

### Kyselyn luokittelu

Havaitse ensin, minkälainen kysymys tämä on:

```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;
}
```

### Paikallinen haku (tehostettu)

Käytä olemassa olevaa vektorihakua, joka on valinnaisesti rikastettu graafikontekstilla:

```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));
}
```

### Maailmanlaajuinen haku (uusi kyky)

Kartan vähentäminen yhteisön tiivistelmistä (vektorihakua ei tarvita):

```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)));
}
```

### DRIFT-haku (Connecutive Questions)

Yhdistä paikalliset tulokset yhteisön kontekstiin "Miten X liittyy Y:hen" -kysymyksissä:

```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.");
}
```

# Kustannus- ja tulosarviot

GraphRAGilla on merkittäviä kauppoja puhtaaseen vektoripinta-alaan verrattuna.

## Indeksointikustannukset

Entiteettien ja suhteiden erottamiskustannukset vaihtelevat merkittävästi mallien, nopean suunnittelun ja palan koon mukaan. Karkeana tilauksena otaksu **yksi tai kaksi LLM-puhelua per kappale** plus pienempi määrä kehotuksia yhteisöllisten yhteenvetojen esittämiseen.

Operaatio Vektori RAG GraphRAG
|-----------|------------|----------|
| **Upotetaan** Ykköspuhelu/kehotus Samaa
| **Entiteetti/suhde-erotus** Ei yhtään 1-2 LLM-puhelua / chunk-puhelua
| **Yhteisön yhteenveto** Ei yhtään LLM-puhelua/yhteisöä

GraphRAG lisää tuhansia LLM:n haku- ja yhteenvetovaatimuksia. Tarkat kustannukset riippuvat suuresti mallivalinnasta ja nopeasta tehokkuudesta, paikallisten mallien (Ollama laama3.2 tai vastaava) käytöstä poistaa API-kustannukset kokonaan, mikä on suositeltu lähestymistapa kokeiluille.

## Kyselyn kustannukset

Query Type Vector RAG GraphRAG Local GraphRAG Global
|------------|------------|----------------|-----------------|
| **Vektorihaku** Ykköspuhelu Ykköspuhelut Ykköspuhelut
| **Graafinen poikkileikkaus** 1-2 kyselyä
| **LLM kutsuu** 1-2 N (kartta) + 1 (vähennetty)

Maailmanlaajuinen haku on kalliimpaa kyselyä kohden, mutta se vastaa kysymyksiin, joita paikalliseen hakuun ei yksinkertaisesti pysty. Voit myös tallentaa maailmanlaajuisia vastauksia ja virkistää niitä vain, kun corpus muuttuu.

## GraphRAG-häiriötilat

Grafragi ei ole taikaa.

- **Poistovirheet**: LLM:t kaipaavat kokonaisuuksia tai hallusinaatioita
- **Yksikön peitenimi**: "ASP.NET Core" vs "ASP.NET" vs "Aspnetcore" tulevat erillisiksi solmukohdiksi
- **Graafinen poikkeama**: Kun dokumentit päivittyvät, kaaviosta voi tulla tunkkainen
- **Yhteisön yhteenvedot ovat tunkkaisia**: Yhteenvedot eivät automaattisesti päivity, kun kokonaisuudet muuttuvat

Entiteetti normalisointi on suurin käytännön kipu. Tarvitset:

- Kanoniset nimet + peitenimet
- Tapausten taittaminen ja välimerkkien normalisointi
- Valinnaisesti upotettavan yksikön deduplication

# Integroituminen blogihakuumme

Näin GraphRAG voisi parantaa blogin nykyistä semanttista hakua:

## Nykyinen virta

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

## Tehostettu virta

Classifier reitittää kyselyt erilaisiin hakustrategioihin. Näin **maailmanlaajuinen kysely** Virtoja - Huomaa, että se ei koskaan koske vektorikauppaan:

```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..."
```

Vertaa tätä a **paikallinen kysely**, jossa yhdistyvät vektorihaku ja rikkaampien vastausten kaaviokonteksti:

```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..."
```

Avainero: globaalit kyselyt kokoavat yhteen yhteisön yhteenvedot (corpus-tason teemat), kun taas paikalliset kyselyt keräävät tiettyjä kappaleita, jotka on rikastutettu entiteettien suhteella.

> **Yksinkertaisempi vaihtoehto:** GraphRAG on edelleen vahvasti tutkimustyökalu – kokonaisuuden kaivaminen, graafinen rakentaminen ja yhteisöllinen havainnointi lisäävät merkittävästi monimutkaisuutta ja LLM-kustannuksia. **BERT-liitteet + BM25-hakusanat** Toimii paremmin. [Sourcegraphin Cody](https://sourcegraph.com/blog/how-cody-understands-your-codebase) käyttää kooditiedusteluun, ja mitä [DocSummarizer](/blog/docsummarizer-part3) Käyttökohteet asiakirjatiivistykseen. Kaava: hybridihaku käsittelee relevanssia, LLM:n kahvat *kokoonpano*, ei *päätöksenteko*Hyödystä saa 80 prosenttia 20 prosentilla monimutkaisuudesta.

## Toteutussketsi

API on yksinkertainen: luokittele kysely, reitti sopivalle käsittelijälle:

```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
    };
}
```

Vektorihaku ja graafihaku täydentävät toisiaan. Käytä vektoreita "miten teen" -kysymyksiin, kaavioita "mitkä ovat teemat" -kysymyksiin.

# Päätelmät

GraphRAG laajentaa RAG-aluetta "löydä samanlaisia palasia" ja "ymmärtää tietorakennetta". Se ei korvaa vektorihakua, vaan mahdollistaa uudet kyselytyypit.

**GraphRAG lisää:**

- Yksikön ja suhteen erottaminen
- Osaamiskaavion rakenne
- Yhteisön havainto ja hierarkkiset yhteenvedot
- Maailmanlaajuinen aistinvaraisten kysymysten etsiminen
- JOHDANTO Etsi sideperustelua

**Milloin sitä käytetään:**

- Sinulla on merkittävä asiakirjakokoelma
- Käyttäjät kysyvät "mitä teemoja" -tyyppisiä kysymyksiä
- Sisällölläsi on selkeät kokonaisuudet ja suhteet
- Liitännät pintaan automaattisesti

**Toteutuspolku:**

1. Ensinnäkin kysy: Tarvitsetko todella tätä? BERT + BM25 -hybridihaku käsittelee useimpia käyttökohteita
2. Jos vastaus on kyllä, Python-sivuvaunun prototyyppi vahvistaa arvon
3. Rakentaa .NET natiivi, jos kustannukset/myötäisyys
4. Käytä paikallisia LLM:itä (Ollama) indeksointikustannusten hallitsemiseen

## Resurssit

**Graafinen virkamies:**

- [GraphRAG-dokumentaatio](https://microsoft.github.io/graphrag/) - Microsoftin viralliset dokumentit
- [GraphRAG GitHub](https://github.com/microsoft/graphrag) - Lähdekoodi ja esimerkkejä
- [Grafaattipaperi](https://arxiv.org/pdf/2404.16130) - Alkuperäinen tutkimuslehti
- [Leidenin algoritmipaperi](https://arxiv.org/pdf/1810.08473.pdf) - Yhteisön havaintoalgoritmi

**RAG-sarja:**

- [Osa 1: Aluekehitys ja perusasiat](/blog/rag-primer)
- [Osa 2: Arkkitehtuuri ja sisätilat](/blog/rag-architecture)
- [Osa 3: Aluetuki käytännössä](/blog/rag-practical-applications)
- [Osa 4: Semanttinen haku ONNX:n ja Qdrantin kanssa](/blog/semantic-search-with-onnx-and-qdrant)
- [Osa 5: Hybridihaku ja autoindeksointi](/blog/rag-hybrid-search-and-indexing)

**Yksinkertaisempi vaihtoehto (BERT + BM25):**

- [DocSummarizer Osa 3](/blog/docsummarizer-part3) - Hybridihaku ilman yleiskuvaa
- [Lähdekoodi Cody Architecture](https://sourcegraph.com/blog/how-cody-understands-your-codebase) - Tuotantohybridihaku