# GraphRAG: Por qué la búsqueda de vectores se rompe en el nivel Corpus

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

<!-- category -- ASP.NET, Semantic Search, ONNX, Qdrant, Machine Learning, Vector Search, RAG -->
Su sistema RAG es genial en preguntas de "necesidad": recuperar algunos trozos relevantes y sintetizar una respuesta. Lucha con dos tipos de consulta comunes:

- **Sensibilización**: "¿Cuáles son los principales temas de este corpus?"
- **Conectar**: "¿Cómo se relaciona X con Y a través de diferentes documentos?"

Esos no son contestados por un solo trozo. **cobertura + agrupación + vinculación**.

**Por qué falla la búsqueda de vectores aquí:**

- Se clasifica en trozos **independientemente** por similitud a la consulta
- La similitud optimiza la relevancia, no **Cobertura mundial**
- Los empotrados capturan "lo que suena similar", no "lo que conecta con lo que"

Usted puede forzar esto con el impulso y el post-procesamiento, pero usted termina reconstruyendo una solución en forma de gráfico.

**La visión clave:** GraphRAG cambia la unidad de recuperación. Para preguntas de corpus no quieres "piezas similares en la parte superior-K"; quieres **comunidades conceptuales conectadas** (y sus resúmenes), por lo que el modelo ve la estructura, no fragmentos.

[GraphRAG](https://microsoft.github.io/graphrag/) viene de Microsoft Research [papel](https://arxiv.org/pdf/2404.16130) y está disponible como un [Aplicación del código abierto](https://github.com/microsoft/graphrag). Mantiene la búsqueda vectorial de preguntas específicas, pero agrega un gráfico de conocimiento y resúmenes comunitarios para el razonamiento a nivel de corpus.

## Cuándo NO usar GraphRAG

Antes de bucear, seamos claros sobre cuando esto es exagerar:

- **Conjuntos de documentos pequeños** (bajo ~50 documentos): sólo tiene que utilizar la búsqueda de vectores
- **Sólo preguntas de "cómo lo hago"**: GraphRAG no ayuda
- **Contenido uniforme** (sin variedad de entidades): no hay estructura gráfica para explotar
- **Limitación de los costos**: la indexación requiere muchas llamadas LLM

Si sus usuarios sólo hacen preguntas específicas, se adhieren a [búsqueda semántica](/blog/semantic-search-with-onnx-and-qdrant). GraphRAG brilla cuando los usuarios necesitan el *panorama general*, y ese es un público más pequeño de lo que sugieren los vendedores.

# Introducción

**Navegación de la serie:** Esta es la parte 6 de la serie RAG:

- [Parte 1: Orígenes y fundamentos](/blog/rag-primer) - Historia, motivación y conceptos básicos
- [Parte 2: Arquitectura e Interiores](/blog/rag-architecture) - Inmersión técnica profunda
- [Parte 3: Los GCR en la práctica](/blog/rag-practical-applications) - Construcción de sistemas reales
- [Parte 4a: Aplicación de ONNX y Qdrant](/blog/semantic-search-with-onnx-and-qdrant) - Búsqueda semántica amigable con la CPU
- [Parte 4b: Búsqueda Semántica en Acción](/blog/semantic-search-in-action) - Tipoahead, búsqueda híbrida, y interfaz de usuario
- [Parte 5: Búsqueda híbrida y auto-indexación](/blog/rag-hybrid-search-and-indexing) - Integración de la producción
- **Parte 6: GraphRAG** (este artículo) - Gráficos de conocimiento para la comprensión a nivel de corpus

A lo largo de esta serie, hemos construido sistemas RAG cada vez más sofisticados. Empezamos con la búsqueda vectorial básica, añadido palabras clave híbridas + recuperación semántica, y auto-indexación integrada. Pero todos estos enfoques comparten una limitación fundamental: encuentran **trozos similares**, no **conceptos conectados**A favor *nivel de corpus* preguntas (temas que abarcan muchos documentos) que necesita estructura.

**La ruta recomendada:** Si ya tiene una búsqueda local basada en Qdrant trabajando (como nosotros), prototipo con el sidecar de Python para validar el valor, mantener vectores para la búsqueda local y añadir un gráfico ligero para las consultas globales/DRIFT. Sólo vaya "completo GraphRAG" una vez que haya demostrado que los usuarios hacen esas preguntas.

[TOC]

# El problema con el vector puro RAG

Permítanme mostrarles lo que quiero decir con un ejemplo concreto.

## Lo que Vector RAG hace bien

**Pregunta:** "¿Cómo puedo usar HTMX con Alpine.js?"

**Proceso de Vector RAG:**

1. Incorpore la pregunta: `[0.234, -0.891, 0.567, ...]`
2. Encontrar trozos similares en Qdrant
3. Devuelve los mejores partidos K sobre HTMX y Alpine.js
4. LLM sintetiza la respuesta de esos trozos

Esto funciona porque la pregunta y el contenido relevante son **Semánticamente similar**. Las incrustaciones captan esa similitud.

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

## Donde lucha Vector RAG

**Pregunta:** "¿Cuáles son las principales tecnologías sobre las que escribo y cómo se relacionan entre sí?"

**¿Qué vector RAG devuelve:**

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

Menciona Docker, PostgreSQL, HTMX, ONNX... pero no los agrupa ni explica cómo se conectan. Obtienes fragmentos, no perspicacia.

El problema: Esta pregunta requiere **agregación y comprensión de las relaciones** Tienes que:

- Identificar todas las tecnologías mencionadas
- Comprender cuáles se utilizan juntos
- Agruparlas en temas coherentes

La similitud vectorial por sí sola no le da esto. Si usted intenta parchear esto con la petición, usted termina reinventando un gráfico.

# Introduzca GraphRAG

[GraphRAG](https://microsoft.github.io/graphrag/) es la solución de Microsoft Research a este problema. [Papel GraphRAG](https://arxiv.org/pdf/2404.16130) identificó los dos tipos de consulta que RAG basal maneja mal (**sensemaking** y **conjuntivo**) y construyó un sistema específico para abordarlos.

En lugar de simplemente incrustar trozos, GraphRAG construye un **Gráfica de conocimiento** que captura entidades y sus relaciones, luego las agrupa en comunidades con resúmenes.

## Cómo funciona GraphRAG

**Pipeline de un vistazo:**

- **Indización:** Trozos → entidades/relaciones → gráfico → comunidades → resúmenes
- **Consulta:** Local = trozos + gráfico barrio  Global = resúmenes de la comunidad  DRIFT = caminos + resúmenes

GraphRAG añade varios componentes a la tubería RAG, agrupados en tres categorías:

1. **Extracción** (entidades + relaciones)
2. **Construcción de gráficos** (almacenamiento de gráficos de conocimiento)
3. **Resumen** (detección comunitaria + jerarquía)

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

### Paso 1: Extracción de entidades

Un LLM lee cada trozo y extrae **entidades** (las cosas que se discuten):

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

### Paso 2: Extracción de relaciones

El mismo LLM identifica cómo las entidades se relacionan entre sí:

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

### Paso 3: Construcción del gráfico del conocimiento

Todas las entidades y relaciones forman un gráfico:

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

### Paso 4: Detección comunitaria (algoritmo de Leiden)

Los [Algoritmo de Leiden](https://arxiv.org/pdf/1810.08473.pdf) clusters densamente conectados nodos en las comunidades. Esto importa porque le da *estable* clusters para resumir y recuperar; las comunidades se convierten en sus unidades de recuperación para consultas globales.

- **Comunidad 1**: "Frontend Stack" (HTMX, Alpine.js, Tailwind)
- **Comunidad 2**: "Infraestructura de contenedores" (Docker, Compose, PostgreSQL, Qdrant)
- **Comunidad 3**: "RAG Pipeline" (ONNX, Embeddings, Qdrant, RAG)

Observe cómo Qdrant aparece en dos comunidades: un puente entre la infraestructura y la IA/ML.

### Paso 5: Resúmenes comunitarios

Un LLM genera resúmenes para cada comunidad en cada nivel jerárquico:

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

## Modos de consulta

GraphRAG proporciona tres modos de consulta, cada uno optimizado para diferentes tipos de preguntas:

### Búsqueda global

**Lo mejor para:** "¿Cuáles son los temas principales?" "Resumir los temas clave."

Utiliza resúmenes comunitarios (no trozos individuales) para responder a preguntas de sensatez:

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

### Búsqueda local

**Lo mejor para:** "¿Cómo configuro X?" "¿Qué es Y?"

Combina el gráfico centrado en la entidad transversal con la búsqueda vectorial tradicional:

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

### Búsqueda de DRIFT

**Lo mejor para:** "¿Cómo se relaciona X con Y?" "Comparar A y B".

Búsqueda DRIFT (Razonamiento dinámico e inferencia con Traversal flexible), [como se describe en los documentos GraphRAG](https://microsoft.github.io/graphrag/query/drift_search/), combina la búsqueda local con el contexto comunitario. Todavía está utilizando el razonamiento LLM sobre el contexto estructurado recuperado (no la inferencia mágica del gráfico), pero la estructura ayuda al LLM a ver conexiones que faltaría con trozos planos.

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

# Comparando GraphRAG con nuestro sistema actual

Vamos a mapear los conceptos de GraphRAG a lo que ya tenemos en `Mostlylucid.SemanticSearch`:

Componente  Sistema actual  GraphRAG Equivalente
|-----------|---------------|---------------------|
| **Incrustaciones** ONNX (all-MiniLM-L6-v2)  Mismo (o OpenAI)
| **Tienda vectorial** Qdrant Qdrant / LanceDB
| **Extracción de entidades** # Ninguna # # Extracción alimentada por LLM #
| **Gráfico de conocimiento** Ninguno  Base de datos de gráficos / en memoria
| **Detección comunitaria** # Ninguno # # Algoritmo de Leiden #
| **Consulta: Específica** | `SemanticSearchService.SearchAsync()` Búsqueda local
| **Consulta: Global** No soportado  Búsqueda Global

Nuestros manejadores de implementación actuales **Búsqueda local** bien. GraphRAG añadiría **Búsqueda global** y **Búsqueda de DRIFT** capacidades.

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

# Enfoques de aplicación

Hay tres maneras de añadir GraphRAG a un sistema existente.

## Opción 1: Python Sidecar (recomendado para exploración)

Ejecute GraphRAG de Microsoft como un servicio separado:

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

**Pros:** Utilice la implementación probada de batalla de Microsoft, rápido al prototipo
**Contras:** Dependencia de Python, costes LLM para la indexación, comunicación entre procesos

## Opción 2: .NET Native (Ruta de producción)

Construir los componentes clave en C#. La extracción basada en BERT y los patrones de Ollama de [DocSummarizer](/blog/docsummarizer-tool) trabajar de manera similar aquí.

### Extracción de entidades

Pida a un LLM que lo identifique *cosas* (entidades) en cada trozo - entidades estructuradas en lugar de temas de forma libre:

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

**Requisito de producción:** Salida LLM JSON *will* Esto no es un endurecimiento opcional, necesitas uno de:

- **Generación constreñida por esquemas** (Ollama's `format: json`, la función de OpenAI llamando)
- **Loops de reintentar con reparación** (Detectar JSON malformado, pedir LLM para arreglarlo)
- **Extracción por retroactividad** (patrones regex para los tipos de entidades comunes)

Los LLM son probabilísticos; su tubería de extracción no debe serlo.

### Extracción de relaciones

Una vez que tenga entidades, pregunte al LLM cómo se conectan:

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

### Almacenamiento de gráficos con normalización de entidades

El mayor dolor práctico es **alias de entidad**: "ASP.NET Core", "ASP.NET", y "aspnetcore" deben ser el mismo nodo. La normalización simple ayuda a:

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

Para un uso serio, considere la deduplicación de entidades basadas en la integración: si dos nombres de entidades tienen incrustaciones similares, probablemente sean lo mismo.

**Puntos de dolor en la producción** (El valor de GraphRAG depende de la calidad del gráfico):

- **Cuadros sinónimos/alias**: mantener nombres canónicos y alias conocidos
- **Control del esquema de relación**: limitar los predicados permitidos para prevenir tipos de relaciones alucinadas
- **Puntuación de confianza + poda**: no todas las relaciones extraídas son igualmente confiables
- **Reindexación incremental**: cuando los documentos se actualizan, es necesario parchear el gráfico, no reconstruirlo

### Traversal del gráfico

Encontrar entidades relacionadas es una búsqueda amplia:

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

### Detección comunitaria

Esta es una línea de base de componentes conectados, **no** completo Leiden. Leiden optimiza para modularidad (conexiones internas densas, externas escasas). Para una implementación adecuada, utilice una biblioteca de gráficos o porte el 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;
}
```

### Resumen comunitario

Cada comunidad obtiene un resumen describiendo su tema. Esto es lo que potencia Global Search:

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

## Opción 3: Híbrido (terreno medio pragmático)

Este es el enfoque recomendado si usted ya tiene la búsqueda de vectores de trabajo. Mantenga Qdrant para la búsqueda local, agregue una capa de gráfico ligera para las consultas de Global / DRIFT.

### Clasificación de las consultas

En primer lugar, detectar qué tipo de pregunta es esta:

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

### Búsqueda local (mejorada)

Utilice la búsqueda vectorial existente, opcionalmente enriquecida con el contexto del gráfico:

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

### Búsqueda global (nueva capacidad)

Mapa-reducir sobre los resúmenes de la comunidad (no se necesita búsqueda de vectores):

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

### Búsqueda DRIFT (Connective Queries)

Combine los resultados locales con el contexto comunitario para "cómo se relaciona X con 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.");
}
```

# Consideraciones sobre los costos y el rendimiento

GraphRAG tiene compensaciones significativas en comparación con el vector RAG puro.

## Costes de indización

Los costos de extracción de entidad/relación varían significativamente según el modelo, el diseño rápido y el tamaño del trozo. **una o dos llamadas LLM por trozo** más un número menor de llamadas para resúmenes comunitarios.

Operación Vector RAG GraphRAG
|-----------|------------|----------|
| **Incorporación** 1 llamada/hunk  Mismo
| **Entidad/Extracción de relaciones** Ninguno  1-2 llamadas LLM / chunk
| **Resumen comunitario** Ninguno  1 LLM llamada/comunidad

Para un corpus de 1.000 publicaciones de blog con 5 trozos cada una (5.000 pedazos en total), la indexación vectorial es esencialmente sólo costos de integración. GraphRAG añade miles de llamadas LLM para extracción y resumen. El costo exacto depende en gran medida de su elección de modelo y la eficiencia rápida; el uso de modelos locales (Ollama con llama3.2 o similar) elimina los costos API por completo, que es el enfoque recomendado para la experimentación.

## Costos de las consultas

Tipo de consulta Vector RAG GraphRAG Local GraphRAG Global
|------------|------------|----------------|-----------------|
| **Búsqueda de vectores** 1 llamada  1 llamada  0 llamadas
| **Traversal del gráfico** 0  1-2 consultas  0
| **Llamadas LLM** 1  1-2  N (mapa) + 1 (reducir)

La búsqueda global es más cara por consulta, pero responde a preguntas que la búsqueda local simplemente no puede. También puede guardar en caché las respuestas globales y actualizarlas sólo cuando el corpus cambia.

## Modos de fallo de GraphRAG

GraphRAG no es magia.

- **Errores de extracción**: Los LLM echan de menos las entidades o alucinan las relaciones
- **Alias de entidad**: "ASP.NET Core" vs "ASP.NET" vs "aspnetcore" se convierten en nodos separados
- **Deriva del gráfico**: Cuando la actualización docs, el gráfico puede convertirse en rancio
- **Resúmenes comunitarios rancios**: Los resúmenes no se actualizan automáticamente cuando las entidades cambian

La normalización de la entidad es el mayor dolor práctico.

- Nombres canónicos + alias
- Presentación de casos y normalización de puntuación
- Deduplicación de la entidad basada en la integración opcional

# Integración con la búsqueda de nuestro blog

Así es como GraphRAG podría mejorar la búsqueda semántica existente en el blog:

## Flujo actual

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

## Flujo mejorado

El clasificador dirige las consultas a diferentes estrategias de búsqueda. **Consulta global** flujos - tenga en cuenta que nunca toca la tienda de vectores:

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

Compare esto con una **consulta local**, que combina búsqueda vectorial con contexto gráfico para respuestas más ricas:

```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 diferencia clave: consultas globales agregan resúmenes comunitarios (temas a nivel de cuerpo), mientras que las consultas locales recuperan fragmentos específicos enriquecidos con relaciones de entidad.

> **Una alternativa más sencilla:** GraphRAG sigue siendo en gran medida una herramienta de investigación - extracción de entidades, construcción de gráficos y detección de comunidades añadir complejidad significativa y costos LLM. Para la mayoría de los casos de uso, **Incrustaciones BERT + coincidencia de palabras clave BM25** Esto es lo que funciona mejor. [Cody de Sourcegraph](https://sourcegraph.com/blog/how-cody-understands-your-codebase) utiliza para la inteligencia de código, y lo que [DocSummarizer](/blog/docsummarizer-part3) El patrón: recuperación híbrida maneja la relevancia; la LLM maneja *montaje*, no *adopción de decisiones*. Obtienes el 80% del beneficio con el 20% de la complejidad.

## Sketch de implementación

La API es sencilla: clasificar la consulta, la ruta al manejador apropiado:

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

La búsqueda vectorial y la búsqueda gráfica se complementan entre sí. Utilice vectores para "cómo hago" preguntas, gráficos para "cuáles son los temas" preguntas.

# Conclusión

GraphRAG extiende RAG de "encontrar trozos similares" a "entender la estructura del conocimiento". No es un reemplazo para la búsqueda de vectores; es una mejora que permite nuevos tipos de consulta.

**Lo que GraphRAG añade:**

- Extracción de entidades y relaciones
- Construcción de gráficos de conocimiento
- Detección comunitaria y resúmenes jerárquicos
- Global Search for sensemaking issues
- DRIFT Búsqueda de razonamiento conectivo

**Cuándo usarlo:**

- Usted tiene una colección de documentos sustancial
- Los usuarios preguntan "cuáles son los temas" tipo preguntas
- Su contenido tiene entidades y relaciones claras
- Quiere que las conexiones de superficie automáticamente

**Vía de aplicación:**

1. En primer lugar, pregunte: ¿realmente necesita esto? BERT + BM25 recuperación híbrida maneja la mayoría de los casos de uso
2. En caso afirmativo, prototipo con sidecar de Python para validar el valor
3. Construir .NET nativo si los costos / materia de la latencia
4. Utilizar LLM locales (Ollama) para controlar los costos de indexación

## Recursos

**GraphRAG Oficial:**

- [Documentación GraphRAG](https://microsoft.github.io/graphrag/) - Documentos oficiales de Microsoft
- [GraphRAG GitHub](https://github.com/microsoft/graphrag) - Código fuente y ejemplos
- [Papel GraphRAG](https://arxiv.org/pdf/2404.16130) - El artículo de investigación original
- [Papel de Algoritmo de Leiden](https://arxiv.org/pdf/1810.08473.pdf) - Algoritmo de detección comunitaria

**Serie RAG:**

- [Parte 1: Orígenes y fundamentos de la GCR](/blog/rag-primer)
- [Parte 2: RAG Arquitectura e Interiores](/blog/rag-architecture)
- [Parte 3: Los GCR en la práctica](/blog/rag-practical-applications)
- [Parte 4: Búsqueda semántica con ONNX y Qdrant](/blog/semantic-search-with-onnx-and-qdrant)
- [Parte 5: Búsqueda híbrida y auto-indexación](/blog/rag-hybrid-search-and-indexing)

**Alternativa más sencilla (BERT + BM25):**

- [DocSummarizer Parte 3](/blog/docsummarizer-part3) - Recuperación híbrida sin cabeza gráfica
- [Sourcegraph Cody Architecture](https://sourcegraph.com/blog/how-cody-understands-your-codebase) - Búsqueda híbrida de producción