Il vostro sistema RAG è grande a "ago" domande: recuperare alcuni pezzi rilevanti e sintetizzare una risposta. Si lotta con due tipi di query comuni:
Non hanno risposta da nessuna parte. copertura + cluster + collegamento.
Perché la ricerca vettoriale fallisce qui:
Puoi forzarlo con il prompt e il post-processing, ma finisci per ricostruire una soluzione a forma di grafico.
L'intuizione chiave: GraphRAG cambia l'unità di recupero. Per le domande sul corpus non si desidera "top-K parti simili"; si desidera comunità concettuali collegate (e i loro riassunti), così il modello vede la struttura, non frammenti.
GraphRAG proviene da Microsoft Research carta ed è disponibile come implementazione open source. Continua la ricerca vettoriale di domande specifiche, ma aggiunge un grafico della conoscenza e riassunti della comunità per il ragionamento a livello di corpus.
Prima di immergerci, cerchiamo di essere chiari su quando questo è eccessivo:
Se i tuoi utenti fanno solo domande specifiche, resta con ricerca semantica. GraphRAG brilla quando gli utenti hanno bisogno di quadro generaleE questo e' un pubblico piu' piccolo di quanto suggeriscano i venditori.
Navigazione della serie: Questa è la parte 6 della serie RAG:
Durante questa serie, abbiamo costruito sistemi RAG sempre più sofisticati. Abbiamo iniziato con la ricerca vettoriale di base, aggiunta parola chiave ibrida + recupero semantico, e auto-indicizzazione integrata. Ma tutti questi approcci condividono una limitazione fondamentale: trovano pezzi simili, non concetti connessi. per livello corpus domande (temi che coprono molti documenti) è necessario struttura.
Il percorso consigliato: Se hai già la ricerca locale basata su Qdrant (come facciamo noi), prototipo con il sidecar Python per convalidare il valore, mantenere i vettori per la ricerca locale e aggiungere un grafico leggero per le query globale/DRIFT. Fai solo "Full GraphRAG" una volta che hai dimostrato che gli utenti fanno queste domande.
Lasciate che vi mostri cosa intendo con un esempio concreto.
Domanda: "Come posso usare HTMX con Alpine.js?"
Processo di RAG vettoriali:
[0.234, -0.891, 0.567, ...]Questo funziona perché la domanda e il contenuto sono Simile semanticamenteGli incorniciamenti catturano quella somiglianza.
// 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
Domanda: "Quali sono le principali tecnologie di cui scrivo e come si relazionano tra loro?"
Quale vettore RAG restituisce:
Result 1: "HTMX makes it easy to add AJAX to your pages..."
Result 2: "Docker Compose orchestrates multiple containers..."
Result 3: "PostgreSQL's full-text search is surprisingly capable..."
Result 4: "Alpine.js provides reactive state management..."
Si parla di Docker, PostgreSQL, HTMX, ONNX... ma non li raggruppa o spiega come si connettono. Ottieni frammenti, non intuizioni.
Il problema: questa domanda richiede l'aggregazione e la comprensione delle relazioni; attraverso l'intero corpus. Devi:
La somiglianza vettoriale da sola non ti dà questo. Se provi a patchare questo con il suggerimento, finisci per reinventare un grafico.
GraphRAG è la soluzione di Microsoft Research a questo problema. Carta GraphRAG ha individuato i due tipi di query che gli RAG di base gestiscono male (sensemaking e Connecto) e ha costruito un sistema specifico per affrontarli.
Invece di incorporare solo pezzi, GraphRAG costruisce un grafico della conoscenza che cattura le entità e le loro relazioni, poi le raggruppa in comunità con riassunti.
Conduttura a colpo d'occhio:
GraphRAG aggiunge diversi componenti al gasdotto RAG, raggruppati in tre categorie:
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 legge ogni pezzo ed estratti entità (le cose di cui si discute):
Chunk: "Docker Compose makes it easy to define multi-container applications.
I use it with PostgreSQL for my blog's database layer."
Extracted Entities:
- Docker Compose (technology)
- PostgreSQL (database)
- blog (project)
- database layer (concept)
Lo stesso LLM identifica come le entità si relazionano tra loro:
Relationships:
- Docker Compose --[used_with]--> PostgreSQL
- blog --[has_component]--> database layer
- PostgreSQL --[implements]--> database layer
Tutte le entità e le relazioni formano un grafico:
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
La Algoritmo di Leida cluster densamente collegati nodi in comunità. Questo importa perché ti dà stabile cluster da riassumere e recuperare; le comunità diventano le vostre unità di recupero per le query globali.
Notate come Qdrant appare in due comunità: collega l'infrastruttura e l'AI/ML.
Un LLM genera sommari per ciascuna comunità a ogni livello gerarchico:
Community 1 Summary (Frontend Stack):
"The frontend approach combines HTMX for server-driven interactivity
with Alpine.js for client-side state management, styled using Tailwind CSS.
This stack prioritizes HTML-first development with minimal JavaScript,
focusing on progressive enhancement over SPA complexity."
Community 2 Summary (Container Infrastructure):
"The blog runs on Docker Compose, orchestrating PostgreSQL for persistent
storage, Qdrant for vector search, and the ASP.NET Core application.
This containerized architecture enables consistent local development
and production deployment."
GraphRAG fornisce tre modalità di query, ognuna ottimizzata per diversi tipi di domande:
Meglio per: "Quali sono i temi principali?" "Summarizzare gli argomenti chiave."
Usa i riassunti delle comunità (non singoli pezzi) per rispondere alle domande di sensemaking:
Query: "What technologies does this blog cover most?"
Process:
1. Retrieve all community summaries
2. Map: Ask LLM to extract technology themes from each summary
3. Reduce: Combine partial answers into final response
Response:
"The writing centres on three technology clusters:
1. **Frontend Development** - HTMX, Alpine.js, Tailwind CSS for minimal-JS web UIs
2. **AI/ML Infrastructure** - RAG pipelines, ONNX embeddings, vector search with Qdrant
3. **DevOps/Containerization** - Docker, PostgreSQL, ASP.NET Core deployment"
Meglio per: "Come posso configurare X?" "Che cos'è Y?"
Combina la traversata del grafico focalizzato sull'entità con la ricerca vettoriale tradizionale:
Query: "How do I use Qdrant with ONNX embeddings?"
Process:
1. Identify entities in query: Qdrant, ONNX, embeddings
2. Retrieve graph neighborhood around those entities
3. Also retrieve vector-similar chunks
4. Combine into rich context for LLM
Response includes:
- Direct relationships (ONNX generates embeddings stored in Qdrant)
- Related entities (all-MiniLM-L6-v2 model, cosine similarity)
- Specific code examples from vector-retrieved chunks
Meglio per: "Come fa X a relazionarsi con Y?" "Confrontare A e B."
ricerca DRIFT (Dynamic Reasoning and Inference with Flexible Traversal), come descritto nei documenti GraphRAG, combina la ricerca locale con il contesto comunitario. Sta ancora usando il ragionamento LLM sopra il contesto strutturato recuperato (non l'inferenza del grafico magico), ma la struttura aiuta il LLM vedere le connessioni che perderebbe con pezzi piatti.
Query: "How do the frontend and backend technologies connect?"
Process:
1. Start with entities: HTMX, ASP.NET Core
2. Traverse graph to find connection paths
3. Include community summaries for context
4. Generate answer showing the full picture
Response:
"HTMX makes requests to ASP.NET Core endpoints, which query PostgreSQL
and Qdrant. The connection flows through the API layer, where endpoints
return HTML fragments that HTMX swaps into the DOM. Alpine.js handles
client-side state for interactive components like search typeahead."
Mappare i concetti di GraphRAG a quello che abbiamo già in Mostlylucid.SemanticSearch:
| Componente | Sistema di corrente | Equivalente RAG del grafico |
|---|---|---|
| Incorporazioni | ONNX (all-miniLM-L6-v2) | Stesso (o OpenAI) |
| Store vettoriale | Qdrant | Qdrant / LanceDB |
| Estrazione di entità | Nessuno | LLM-powered extraction |
| Grafico della conoscenza | Nessuno | Banca dati grafico/memoria in memoria |
| Rilevamento comunitario | Nessuno | Algoritmo di Leiden |
| Interrogazione: specifica | SemanticSearchService.SearchAsync() |
Ricerca locale |
| Interrogazione: Globale | Non supportato | Ricerca globale |
La nostra attuale gestione dell'implementazione Ricerca locale Bene. GraphRAG aggiungerebbe Ricerca globale e Ricerca DRIFT capacità.
// 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);
}
Ci sono tre modi per aggiungere GraphRAG a un sistema esistente.
Eseguire Microsoft GraphRAG come un servizio separato:
# 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;
}
}
Pro: Utilizzare l'implementazione testata dalla battaglia di Microsoft, veloce al prototipo Punti negativi: Dipendenza da Python, costi LLM per l'indicizzazione, comunicazione tra processi
Costruisci i componenti chiave in C#. L'estrazione basata su BERT e i modelli di Ollama da DocSummarizer lavorare allo stesso modo qui.
Chiedere a un LLM di identificare cose (enti) in ciascuna parte - entità strutturate piuttosto che soggetti a forma libera:
public async Task<List<Entity>> ExtractEntitiesAsync(string chunk)
{
var prompt = $"""
Extract entities from this text. Return JSON array.
Types: technology, concept, project, person, organization
Text: {chunk}
Format: [{{"name": "Docker", "type": "technology"}}]
""";
var response = await _ollama.GenerateAsync(prompt);
return JsonSerializer.Deserialize<List<Entity>>(response);
}
Fabbisogno di produzione: Uscita LLM JSON Will break. Questo non è indurimento facoltativo. Hai bisogno di uno dei:
format: json, funzione chiamata di OpenAI)I LLM sono probabilistici; la vostra pipeline di estrazione non deve esserlo.
Una volta che si dispone di entità, chiedere al LLM come si collegano:
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));
}
Il più grande dolore pratico è aliasing dell'entità: "ASP.NET Core," "ASP.NET" e "spnetcore" dovrebbero essere lo stesso nodo. La normalizzazione semplice aiuta:
public class KnowledgeGraph
{
private readonly Dictionary<string, Entity> _entities = new();
private readonly List<Relationship> _relationships = new();
public void AddEntity(Entity entity)
{
var key = Normalise(entity.Name); // "ASP.NET Core" → "aspnetcore"
_entities[key] = entity;
}
private string Normalise(string name) =>
name.ToLowerInvariant().Replace(".", "").Replace("-", "").Trim();
}
Per un uso serio, considerare deduplicazione di entità basata sull'embedding: se due nomi di entità hanno embedding simili, probabilmente sono la stessa cosa.
Punti di dolore alla produzione (Il valore di GraphRAG dipende dalla qualità del grafico):
Trovare entità correlate è una ricerca wide-first:
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();
}
Si tratta di una linea di base di componenti collegati, non Leida completa. Leida ottimizza la modularità (connessioni interne dense, quelle esterne sparse). Per una corretta implementazione, utilizza una libreria grafica o porta l'algoritmo.
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;
}
Ogni comunità riceve un riassunto che ne descrive il tema. Questo è ciò che alimenta la Ricerca Globale:
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);
}
Questo è l'approccio consigliato se hai già una ricerca vettoriale funzionante. Mantieni Qdrant per la ricerca locale, aggiungi un livello grafico leggero per le query Global/DRIFT.
In primo luogo, individuare che tipo di domanda questo è:
// 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;
}
Utilizzare la ricerca vettoriale esistente, eventualmente arricchita con il contesto grafico:
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));
}
Riduci mappa sopra i riassunti della comunità (non è necessaria alcuna ricerca vettoriale):
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)));
}
Combina i risultati locali con il contesto comunitario per le domande "come X si riferisce a 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 ha notevoli compromessi rispetto ai RAG vettori puri.
I costi di estrazione dell'entità/della relazione variano in modo significativo in base al modello, alla progettazione rapida e alle dimensioni del pezzo. una o due chiamate LLM per pezzo più un numero minore di richieste di riassunti della comunità.
| Funzionamento | RAG vettoriali | Grafico RAG |
|---|---|---|
| Incorporazione | 1 call/chunk | Stesso |
| Estrazione di entità/relazione | Nessuno | 1-2 chiamate LLM/chunk |
| Sintesi della Comunità | Nessuno | 1 LLM call/community |
Per un corpus di 1.000 post sul blog con 5 pezzi ciascuno (5.000 pezzi totali), l'indicizzazione vettoriale è essenzialmente solo un'integrazione dei costi. GraphRAG aggiunge migliaia di chiamate LLM per l'estrazione e la sommarizzazione. L'esatto costo dipende fortemente dalla scelta del modello e dall'efficienza immediata; l'utilizzo di modelli locali (Ollama con lama3.2 o simili) elimina completamente i costi API, che è l'approccio raccomandato per la sperimentazione.
| Tipo di domanda | RAG vettoriali | Locale grafo RAG | Globale grafico RAG |
|---|---|---|---|
| Ricerca vettoriale | 1 chiamata | 1 chiamata | 0 chiamate |
| Traversale grafico | 0 | 1-2 query | 0 |
| Chiamate LLM | 1 | 1-2 | N (mappa) + 1 (riduce) |
La ricerca globale è più costosa per ogni query, ma risponde a domande che la ricerca locale semplicemente non può. Puoi anche trovare risposte globali nella cache e aggiornarle solo quando il corpus cambia.
Il GraphRAG non e' magico.
La normalizzazione dell'entità è il più grande dolore pratico. Avrete bisogno di:
Ecco come GraphRAG potrebbe migliorare la ricerca semantica esistente del blog:
User types in search → SemanticSearchService → Qdrant → Results
Il classificatore indirizza le query a diverse strategie di ricerca. Ecco come interrogazione globale flussi - si noti che non tocca mai il vettore store:
sequenceDiagram
participant U as User
participant API as Search API
participant C as Query Classifier
participant G as Global Search
participant KG as Knowledge Graph
U->>API: "What topics does this blog cover?"
API->>C: Classify query
C-->>API: QueryMode.Global
API->>G: GlobalSearch(query)
G->>KG: GetCommunitySummaries()
KG-->>G: [Frontend, Infrastructure, AI/ML]
G->>G: MapReduce over summaries
G-->>API: Synthesized answer
API-->>U: "The blog covers three main areas..."
Confronta questo ad un interrogazione locale, che combina la ricerca vettoriale con il contesto grafico per risposte più ricche:
sequenceDiagram
participant U as User
participant API as Search API
participant C as Query Classifier
participant L as Local Search
participant Q as Qdrant
participant KG as Knowledge Graph
U->>API: "How do I use HTMX?"
API->>C: Classify query
C-->>API: QueryMode.Local
API->>L: LocalSearch(query)
L->>Q: Vector search
Q-->>L: Relevant chunks
L->>KG: GetEntityContext("HTMX")
KG-->>L: Related: Alpine.js, Tailwind, ASP.NET
L-->>API: Answer with rich context
API-->>U: "HTMX is used with Alpine.js for..."
La differenza chiave: le query globali sommano i riassunti delle comunità (temi a livello di corpus), mentre le query locali recuperano parti specifiche arricchite da relazioni tra entità.
Un'alternativa più semplice: GraphRAG è ancora molto uno strumento di ricerca - l'estrazione di entità, la costruzione di grafici e la rilevazione della comunità aggiungono complessità significativa e costi LLM. Per la maggior parte dei casi di utilizzo, Abbinamenti BERT + corrispondenza delle parole chiave BM25 funziona meglio. Questo è ciò che Cody della Sourcegraph utilizza per l'intelligenza di codice, e cosa DocSummarizer usi per la sommarizzazione dei documenti. Il modello: il recupero ibrido gestisce la pertinenza; le maniglie LLM montaggio, non processo decisionale. Si ottiene l'80% del beneficio con il 20% della complessità.
L'API è semplice: classificare la query, il percorso al gestore appropriato:
[HttpGet("api/search")]
public async Task<IActionResult> Search([FromQuery] string q, [FromQuery] string mode = "auto")
{
if (mode == "auto")
mode = ClassifyQuery(q);
// global/local return synthesised answers; default returns raw search results
return mode switch
{
"global" => Ok(await _graphRag.GlobalSearchAsync(q)), // synthesised answer
"local" => Ok(await SearchWithGraphContext(q)), // answer with citations
_ => Ok(await _semanticSearch.SearchAsync(q)) // raw ranked results
};
}
Ricerca vettoriale e ricerca grafica completano l'un l'altro. Utilizzare vettori per "come faccio" domande, grafici per "quali sono i temi" domande.
GraphRAG estende RAG da "trovare pezzi simili" a "capire la struttura della conoscenza." Non è una sostituzione per la ricerca vettoriale; è un miglioramento che consente nuovi tipi di query.
Cosa aggiunge GraphRAG:
Quando usarlo:
Percorso di attuazione:
GraphRAG Funzionario:
Serie RAG:
Alternativa più semplice (BERT + BM25):
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.