This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Friday, 26 December 2025
Su sistema RAG es genial en preguntas de "necesidad": recuperar algunos trozos relevantes y sintetizar una respuesta. Lucha con dos tipos de consulta comunes:
Esos no son contestados por un solo trozo. cobertura + agrupación + vinculación.
Por qué falla la búsqueda de vectores aquí:
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 viene de Microsoft Research papel y está disponible como un Aplicación del código abierto. 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.
Antes de bucear, seamos claros sobre cuando esto es exagerar:
Si sus usuarios sólo hacen preguntas específicas, se adhieren a búsqueda semántica. 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.
Navegación de la serie: Esta es la parte 6 de la serie RAG:
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 conectadosA 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.
Permítanme mostrarles lo que quiero decir con un ejemplo concreto.
Pregunta: "¿Cómo puedo usar HTMX con Alpine.js?"
Proceso de Vector RAG:
[0.234, -0.891, 0.567, ...]Esto funciona porque la pregunta y el contenido relevante son Semánticamente similar. Las incrustaciones captan esa similitud.
// 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
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:
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.
GraphRAG es la solución de Microsoft Research a este problema. Papel GraphRAG 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.
Pipeline de un vistazo:
GraphRAG añade varios componentes a la tubería RAG, agrupados en tres categorías:
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
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)
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
Todas las entidades y relaciones forman un gráfico:
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
Los Algoritmo de Leiden 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.
Observe cómo Qdrant aparece en dos comunidades: un puente entre la infraestructura y la IA/ML.
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."
GraphRAG proporciona tres modos de consulta, cada uno optimizado para diferentes tipos de preguntas:
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"
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
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, 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."
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.
// 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);
}
Hay tres maneras de añadir GraphRAG a un sistema existente.
Ejecute GraphRAG de Microsoft como un servicio separado:
# 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
// 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
Construir los componentes clave en C#. La extracción basada en BERT y los patrones de Ollama de DocSummarizer trabajar de manera similar aquí.
Pida a un LLM que lo identifique cosas (entidades) en cada trozo - entidades estructuradas en lugar de temas de forma libre:
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:
format: json, la función de OpenAI llamando)Los LLM son probabilísticos; su tubería de extracción no debe serlo.
Una vez que tenga entidades, pregunte al LLM cómo se conectan:
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));
}
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:
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):
Encontrar entidades relacionadas es una búsqueda amplia:
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();
}
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.
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;
}
Cada comunidad obtiene un resumen describiendo su tema. Esto es lo que potencia Global Search:
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);
}
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.
En primer lugar, detectar qué tipo de pregunta es esta:
// 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;
}
Utilice la búsqueda vectorial existente, opcionalmente enriquecida con el contexto del gráfico:
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));
}
Mapa-reducir sobre los resúmenes de la comunidad (no se necesita búsqueda de vectores):
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)));
}
Combine los resultados locales con el contexto comunitario para "cómo se relaciona X con Y":
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.");
}
GraphRAG tiene compensaciones significativas en comparación con el vector RAG puro.
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.
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.
GraphRAG no es magia.
La normalización de la entidad es el mayor dolor práctico.
Así es como GraphRAG podría mejorar la búsqueda semántica existente en el blog:
User types in search → SemanticSearchService → Qdrant → Results
El clasificador dirige las consultas a diferentes estrategias de búsqueda. Consulta global flujos - tenga en cuenta que nunca toca la tienda de vectores:
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:
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 utiliza para la inteligencia de código, y lo que DocSummarizer 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.
La API es sencilla: clasificar la consulta, la ruta al manejador apropiado:
[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.
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:
Cuándo usarlo:
Vía de aplicación:
GraphRAG Oficial:
Serie RAG:
Alternativa más sencilla (BERT + BM25):
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.