Guide complet GraphRAG : architecture Knowledge Graph + RAG, implémentation Neo4j + LangChain, Microsoft GraphRAG, comparatif vectoriel vs graphe, benchmarks.
Le RAG (Retrieval Augmented Generation) a transformé la manière dont les modèles de langage accèdent à l'information externe, mais cette architecture atteint ses limites face aux requêtes complexes nécessitant un raisonnement multi-hop, une compréhension contextuelle profonde ou une synthèse globale d'un corpus documentaire. GraphRAG, architecture émergente qui fusionne les knowledge graphs avec le RAG classique, apporte une réponse structurée à ces défis. En modélisant explicitement les entités, leurs relations et les hiérarchies conceptuelles sous forme de graphe de connaissances, l'approche knowledge graph RAG permet aux LLM de naviguer dans l'information de manière raisonnée plutôt que de simplement récupérer des fragments textuels par similarité vectorielle. Cet article technique détaillé explore en profondeur l'architecture GraphRAG, ses fondements théoriques, ses implémentations concrètes avec Neo4j, LangChain et le framework Microsoft GraphRAG, ainsi que les comparaisons rigoureuses dans le débat base de données vectorielle vs graphe. Que vous conceviez un système de recherche entreprise, un assistant de conformité réglementaire ou un moteur d'analyse de code, cette analyse vous fournira les clés pour évaluer si GraphRAG constitue la bonne approche pour votre cas d'usage.
Ce qu'il faut retenir
- GraphRAG combine knowledge graphs et RAG pour dépasser les limites du RAG vectoriel classique, notamment le raisonnement multi-hop et la synthèse globale.
- L'architecture repose sur un pipeline d'indexation en 5 étapes : chunking, extraction d'entités, construction de relations, détection de communautés et résumé hiérarchique.
- Deux modes de requête coexistent : local search (précision sur entités spécifiques) et global search (synthèse thématique sur l'ensemble du corpus).
- Le coût d'indexation GraphRAG est 5 à 10 fois supérieur au RAG classique, mais les gains en fidélité et exhaustivité justifient l'investissement pour les cas d'usage complexes.
Limites du RAG classique : pourquoi le vecteur ne suffit plus
Avant de plonger dans l'architecture GraphRAG, il est essentiel de comprendre pourquoi le RAG vectoriel classique, malgré son succès indéniable, atteint des limites structurelles face à certaines catégories de requêtes. Ces limites ne sont pas des bugs corrigeables par une meilleure ingénierie — elles découlent de choix architecturaux fondamentaux qui font du RAG vectoriel un outil intrinsèquement local, incapable de raisonnement structurel. Comprendre ces limites est la clé pour évaluer objectivement si GraphRAG apporte une valeur ajoutée pour un cas d'usage donné.
Le problème du "Lost in the Middle"
Le RAG classique repose sur un mécanisme conceptuellement simple : découper les documents en chunks, les transformer en embeddings vectoriels, puis récupérer les k chunks les plus similaires à la requête utilisateur pour les injecter dans le prompt du LLM. Cette approche fonctionne remarquablement bien pour les requêtes factuelles simples, mais elle souffre de limitations structurelles profondes.
Le phénomène "Lost in the Middle", documenté par Liu et al. (2023), démontre que les LLM peinent à exploiter l'information positionnée au centre d'un contexte long. Lorsqu'un système RAG injecte 10 ou 20 chunks dans le prompt, le modèle accorde une attention disproportionnée aux premiers et derniers fragments, négligeant potentiellement l'information la plus pertinente. Ce biais positionnel est inhérent à l'architecture Transformer et ne peut être complètement éliminé par le fine-tuning ou l'augmentation de la fenêtre de contexte.
En pratique, cela signifie qu'un système RAG classique qui récupère 15 chunks pertinents pour répondre à une question complexe peut produire une réponse incomplète ou biaisée, simplement parce que les chunks critiques se trouvaient en position médiane dans le contexte. Ce problème s'aggrave proportionnellement à la complexité de la requête et au nombre de documents source pertinents.
Hallucinations et fabrication contextuelle
Le RAG réduit considérablement les hallucinations par rapport à un LLM utilisé seul, mais ne les élimine pas. Le problème fondamental réside dans le mécanisme de récupération par similarité vectorielle : deux passages peuvent être sémantiquement proches dans l'espace vectoriel tout en étant contextuellement incompatibles. Un chunk décrivant une politique de sécurité d'une entreprise A peut être récupéré pour répondre à une question sur l'entreprise B, simplement parce que les termes techniques utilisés sont similaires.
Ce phénomène de "confusion contextuelle" est particulièrement dangereux dans les domaines réglementaires ou médicaux, où la précision des attributions est critique. Le RAG classique ne dispose d'aucun mécanisme structurel pour distinguer les entités, leurs attributs et leurs relations mutuelles — il opère sur des fragments textuels désincarnés, sans graphe de contexte.
L'impossibilité du raisonnement multi-hop
Le raisonnement multi-hop — la capacité à chaîner plusieurs inférences pour répondre à une question — constitue la limite la plus fondamentale du RAG vectoriel. Considérons la requête : "Quelles sont les implications réglementaires des acquisitions réalisées par les entreprises dirigées par d'anciens employés de Google dans le secteur de la santé ?"
Pour répondre correctement, le système doit : (1) identifier les anciens employés de Google, (2) retrouver les entreprises qu'ils dirigent, (3) filtrer celles opérant dans le secteur santé, (4) identifier leurs acquisitions, et (5) analyser les implications réglementaires de chacune. Chaque étape dépend du résultat de la précédente, formant une chaîne de raisonnement que la simple similarité vectorielle ne peut capturer.
Le RAG classique récupérerait probablement des chunks mentionnant Google, la santé et les réglementations de manière indépendante, sans jamais établir les connexions causales nécessaires. C'est précisément ce type de requête complexe qui motive l'adoption du graph RAG et de l'architecture knowledge graph RAG.
La synthèse globale : angle mort du RAG vectoriel
Au-delà du raisonnement multi-hop, le RAG classique échoue systématiquement face aux requêtes de synthèse globale. Des questions comme "Quels sont les thèmes principaux abordés dans ce corpus de 10 000 documents ?" ou "Comment les pratiques de cybersécurité ont-elles évolué dans notre organisation au cours des 5 dernières années ?" nécessitent une vue d'ensemble que la récupération de k chunks ne peut fournir.
La similarité vectorielle est par nature une opération locale : elle identifie les passages les plus proches d'une requête donnée, pas les patterns émergents à l'échelle du corpus. Pour les requêtes analytiques ou exploratoires, cette limitation rend le RAG classique essentiellement inutile, forçant les utilisateurs à formuler des requêtes atomiques et à synthétiser manuellement les résultats.
Qu'est-ce qu'un Knowledge Graph ?
Fondamentaux : nœuds, arêtes et propriétés
Un knowledge graph (graphe de connaissances) est une structure de données qui modélise l'information sous forme de réseau de concepts interconnectés. Contrairement aux bases de données relationnelles qui organisent les données en tables et colonnes, ou aux bases vectorielles qui projettent l'information dans des espaces mathématiques à haute dimension, le knowledge graph représente explicitement les entités du monde réel et les relations qui les lient.
La structure fondamentale d'un knowledge graph repose sur trois composants. Les nœuds (ou sommets) représentent les entités : personnes, organisations, concepts, événements, lieux. Chaque nœud possède un type (label) et un ensemble de propriétés (attributs clé-valeur). Les arêtes (ou relations) connectent les nœuds entre eux et portent elles aussi un type et des propriétés. La combinaison d'un nœud source, d'une relation et d'un nœud cible forme un triplet, l'unité atomique de connaissance dans un graphe.
Par exemple, le triplet (ANSSI, régule, cybersécurité_france) encode la relation entre l'entité ANSSI et le concept de cybersécurité en France. La puissance du knowledge graph émerge de l'agrégation de millions de ces triplets, formant un réseau dense où chaque entité est contextualisée par ses relations multiples avec d'autres entités.
Triplets RDF et standards du Web sémantique
Le Resource Description Framework (RDF) constitue le standard W3C pour la représentation de knowledge graphs sur le Web. En RDF, chaque fait est encodé comme un triplet (sujet, prédicat, objet), où chaque composant est identifié par un URI unique. Cette uniformité syntaxique permet l'interopérabilité entre graphes de connaissances hétérogènes.
Un triplet RDF typique ressemble à :
<http://example.org/entity/GraphRAG>
<http://example.org/relation/utilise>
<http://example.org/entity/KnowledgeGraph> .
<http://example.org/entity/GraphRAG>
<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>
<http://example.org/class/Architecture_IA> .
<http://example.org/entity/GraphRAG>
<http://example.org/relation/proposePar>
<http://example.org/entity/Microsoft_Research> .
Le langage de requête SPARQL permet d'interroger ces triplets avec une expressivité comparable à SQL mais adaptée aux structures de graphe. Les requêtes SPARQL excellent dans la traversée de chemins complexes, une opération coûteuse ou impossible en SQL classique.
Ontologies : la structure conceptuelle du graphe
Une ontologie définit le vocabulaire formel utilisé dans un knowledge graph : les types d'entités autorisés, les relations valides entre ces types, et les contraintes de cardinalité. L'ontologie joue le rôle de schéma pour le graphe, guidant à la fois l'extraction d'information et la validation des données.
Les langages ontologiques les plus utilisés sont RDFS (RDF Schema) pour les hiérarchies simples de classes et propriétés, et OWL (Web Ontology Language) pour les ontologies expressives avec raisonnement logique. Une ontologie bien conçue permet l'inférence automatique de nouveaux triplets : si "GraphRAG est un type de RAG" et "RAG est un type d'architecture IA", alors le raisonneur peut inférer que "GraphRAG est un type d'architecture IA".
Dans le contexte de GraphRAG, l'ontologie guide le processus d'extraction d'entités et de relations depuis le texte source. Elle détermine quelles catégories d'entités le LLM doit identifier (personnes, organisations, technologies, concepts) et quelles relations il doit extraire (utilise, développe, remplace, régule). Une ontologie trop restrictive manquera des relations importantes ; une ontologie trop permissive produira un graphe bruité et difficile à exploiter.
Exemples de Knowledge Graphs à grande échelle
Plusieurs knowledge graphs majeurs illustrent la puissance de cette approche à l'échelle. Google Knowledge Graph, lancé en 2012, contient plus de 500 milliards de faits sur 5 milliards d'entités. Il alimente les Knowledge Panels affichés dans les résultats de recherche et constitue une composante fondamentale de l'infrastructure Google.
Wikidata, le knowledge graph collaboratif de la fondation Wikimedia, contient plus de 100 millions d'entités structurées et sert de base de connaissances ouverte pour de nombreux projets d'IA. Sa structure RDF et son API SPARQL en font un terrain d'expérimentation privilégié pour les chercheurs en knowledge graphs. Contrairement aux knowledge graphs propriétaires, Wikidata adopte un modèle de qualification des triplets : chaque relation peut être annotée avec des qualificateurs (date de début, date de fin, source, degré de certitude) qui enrichissent considérablement la granularité de l'information.
DBpedia extrait automatiquement des données structurées depuis Wikipedia, créant un knowledge graph de 6 millions d'entités avec des liens vers d'autres datasets du Linked Open Data (LOD). Le projet illustre la puissance de l'extraction automatisée de connaissances à partir de texte semi-structuré, un processus fondamentalement similaire au pipeline d'extraction de GraphRAG, mais opérant sur des templates Wikipedia plutôt que sur du texte libre.
UMLS (Unified Medical Language System) agrège plus de 200 vocabulaires médicaux en un knowledge graph unifié de 4 millions de concepts et 15 millions de relations, utilisé dans la recherche biomédicale et les systèmes d'aide à la décision clinique. Ces exemples démontrent que les knowledge graphs opèrent efficacement à des échelles massives, un prérequis pour les applications enterprise de GraphRAG.
GraphRAG : principe et architecture fondamentale
Le paper Microsoft Research : "From Local to Global"
Le framework GraphRAG a été formalisé par Microsoft Research dans le paper "From Local to Global: A Graph RAG Approach to Query-Focused Summarization" (Edge et al., 2024). Cette publication propose une architecture qui transcende les limitations du RAG vectoriel en construisant un knowledge graph structuré à partir du corpus documentaire, puis en exploitant la topologie de ce graphe pour répondre aux requêtes.
L'insight fondamental du paper est que les approches RAG existantes sont intrinsèquement "locales" : elles récupèrent des fragments de texte proches de la requête dans l'espace vectoriel, mais ne peuvent jamais fournir une vue "globale" du corpus. GraphRAG résout ce problème en introduisant deux niveaux de traitement : un pipeline d'indexation qui construit le graphe et ses résumés hiérarchiques, et un pipeline de requête qui sélectionne dynamiquement entre recherche locale et globale selon la nature de la question.
Les résultats expérimentaux du paper démontrent que GraphRAG surpasse significativement le RAG classique (naive RAG) sur les tâches de synthèse globale, avec des améliorations de 50 à 70% en termes de comprehensiveness (exhaustivité) et diversity (diversité des aspects couverts), tout en maintenant des performances comparables sur les requêtes factuelles locales.
Détection de communautés : l'algorithme de Leiden
Un composant clé de l'architecture GraphRAG est la détection de communautés, réalisée via l'algorithme de Leiden (Traag et al., 2019). Une fois le knowledge graph construit, l'algorithme de Leiden identifie des groupes d'entités densément interconnectées — les communautés — qui correspondent à des thèmes, des domaines ou des clusters conceptuels au sein du corpus.
L'algorithme de Leiden fonctionne en optimisant itérativement une fonction de modularité : il cherche la partition du graphe qui maximise la densité des connexions intra-communautaires tout en minimisant les connexions inter-communautaires. Contrairement à l'algorithme de Louvain dont il dérive, Leiden garantit que toutes les communautés détectées sont bien connectées, éliminant les communautés "fragmentées" qui réduisent la qualité des résumés.
La détection de communautés produit une hiérarchie multi-niveaux : les communautés de niveau 0 sont les plus granulaires (quelques entités fortement liées), tandis que les niveaux supérieurs agrègent progressivement ces micro-communautés en clusters thématiques plus larges. Cette hiérarchie permet au système de requête de sélectionner le niveau de granularité approprié pour chaque question.
Résumés hiérarchiques : la mémoire structurée du corpus
Pour chaque communauté détectée, GraphRAG génère un résumé textuel qui capture les thèmes, entités clés et relations dominantes au sein du cluster. Ces résumés sont produits par le LLM lui-même, qui reçoit l'ensemble des entités, relations et textes sources associés à la communauté et génère une synthèse cohérente.
Les résumés communautaires constituent la "mémoire structurée" du corpus : ils encodent la connaissance globale sous une forme directement consommable par le LLM lors de la phase de requête. En parcourant les résumés des communautés pertinentes plutôt que des chunks individuels, le système peut répondre à des questions de synthèse globale sans avoir à ingérer l'intégralité du corpus dans le contexte.
Cette approche est fondamentalement différente du map-reduce naïf (qui itère séquentiellement sur tous les documents) : les résumés communautaires sont pré-calculés lors de l'indexation, rendant la phase de requête à la fois rapide et exhaustive. Le coût est transféré de la requête (runtime) vers l'indexation (build time), un compromis généralement acceptable pour les applications enterprise où le corpus évolue lentement.
La qualité des résumés communautaires dépend fortement du prompt utilisé pour les générer. Un prompt trop générique produira des résumés vagues qui diluent l'information spécifique ; un prompt trop directif risque de biaiser les résumés vers certains aspects au détriment d'autres. L'approche recommandée par Microsoft Research consiste à fournir au LLM l'ensemble des entités, relations et extraits textuels de la communauté, en lui demandant d'identifier les thèmes dominants, les entités les plus influentes (par centralité dans le sous-graphe), et les insights non évidents qui émergent des connexions entre entités. Le résumé résultant doit être auto-suffisant : un lecteur qui ne connaît pas le corpus doit pouvoir comprendre le contenu thématique de la communauté en lisant uniquement le résumé.
En production, les résumés communautaires sont souvent enrichis de métadonnées structurées : liste des entités clés, mots-clés thématiques, score de densité de la communauté (nombre de relations / nombre d'entités), et liens vers les résumés des communautés parentes et enfantes dans la hiérarchie. Ces métadonnées accélèrent le filtrage lors de la phase de requête globale, évitant au système de scorer la pertinence de chaque résumé via un appel LLM.
Architecture GraphRAG en bref
GraphRAG transforme un corpus textuel en un knowledge graph structuré, puis applique la détection de communautés (Leiden) pour identifier les clusters thématiques. Chaque communauté reçoit un résumé généré par LLM, formant une hiérarchie de connaissances navigable. Ce pré-traitement coûteux lors de l'indexation permet des réponses rapides et exhaustives lors de la requête, aussi bien pour les questions locales (entités spécifiques) que globales (synthèse thématique).
RAG classique vs GraphRAG : comparaison détaillée
Tableau comparatif des architectures
| Critère | RAG classique (vectoriel) | GraphRAG |
|---|---|---|
| Représentation des données | Chunks textuels → embeddings vectoriels | Entités + relations → knowledge graph + embeddings |
| Mécanisme de récupération | Similarité cosinus dans l'espace vectoriel | Traversée de graphe + similarité vectorielle hybride |
| Raisonnement multi-hop | Impossible nativement (nécessite des hacks comme le query decomposition) | Natif via la traversée de chemins dans le graphe |
| Synthèse globale | Limitée aux k chunks récupérés | Résumés hiérarchiques de communautés pré-calculés |
| Explicabilité | Score de similarité uniquement | Chemin de raisonnement traceable dans le graphe |
| Coût d'indexation | Faible (embedding des chunks) | Élevé (extraction d'entités par LLM + construction du graphe) |
| Latence de requête | Faible (recherche vectorielle ANN) | Moyenne à élevée (traversée de graphe + LLM calls) |
| Maintenance | Simple (réindexation des chunks modifiés) | Complexe (mise à jour incrémentale du graphe) |
| Qualité pour requêtes factuelles | Excellente | Excellente (comparable) |
| Qualité pour requêtes analytiques | Faible | Excellente |
| Complexité d'implémentation | Faible à moyenne | Élevée |
| Maturité de l'écosystème | Mature (LangChain, LlamaIndex, etc.) | Émergent (Microsoft GraphRAG, Neo4j GenAI) |
Quand utiliser le RAG classique
Le RAG vectoriel classique reste le choix optimal dans plusieurs scénarios. Pour les requêtes factuelles simples ("Quelle est la procédure de réponse à incident de notre organisation ?"), la similarité vectorielle identifie efficacement les passages pertinents sans la surcharge d'un knowledge graph. Lorsque le corpus évolue fréquemment (base de tickets, documentation produit mise à jour quotidiennement), la simplicité de réindexation du RAG classique est un avantage déterminant. Enfin, pour les projets avec des contraintes budgétaires serrées, le coût d'indexation GraphRAG (5 à 10 fois supérieur) peut être rédhibitoire.
Le RAG classique excelle également lorsque le corpus est relativement homogène et que les requêtes portent sur des segments de texte bien délimités. Dans ces conditions, l'optimisation du chunking et de la stratégie de retrieval (hybrid search, reranking) suffit généralement à atteindre des performances satisfaisantes.
Quand privilégier GraphRAG
GraphRAG s'impose lorsque les requêtes impliquent des relations complexes entre entités. Les domaines juridiques, réglementaires et médicaux, où les textes font référence à des chaînes d'entités interconnectées (lois qui citent d'autres lois, médicaments qui interagissent, entreprises liées par des contrats), bénéficient massivement de la structuration en graphe.
Les requêtes de synthèse globale constituent le cas d'usage emblématique de GraphRAG. "Quels sont les principaux risques identifiés dans nos 500 rapports d'audit ?" est une question à laquelle le RAG classique ne peut simplement pas répondre de manière exhaustive, tandis que GraphRAG exploite ses résumés communautaires pour fournir une synthèse structurée couvrant l'ensemble du corpus.
L'explicabilité constitue un autre facteur de choix. Dans les contextes réglementaires (conformité RGPD, audit financier), la capacité à tracer le chemin de raisonnement dans le graphe — montrant exactement quelles entités et relations ont contribué à la réponse — peut être une exigence légale.
Matrice de décision pratique
Pour guider le choix entre RAG classique et GraphRAG, voici une grille d'évaluation basée sur cinq critères pondérés. Attribuez un score de 1 à 5 pour chaque critère et calculez le score pondéré total.
| Critère | Poids | Favorise RAG classique (score 1-2) | Favorise GraphRAG (score 4-5) |
|---|---|---|---|
| Complexité des requêtes | 30% | Questions factuelles simples, recherche par mots-clés augmentée | Raisonnement multi-hop, synthèse globale, requêtes analytiques |
| Densité relationnelle du corpus | 25% | Documents indépendants, peu de références croisées | Réseau dense de références, entités interconnectées |
| Exigence d'explicabilité | 20% | Score de confiance suffisant | Traçabilité complète du raisonnement requise |
| Budget disponible | 15% | Contraint, optimisation coûts prioritaire | Flexible, qualité prioritaire sur coût |
| Fréquence de mise à jour | 10% | Mise à jour quotidienne ou plus | Corpus stable, mise à jour hebdomadaire ou moins |
Un score total supérieur à 3.5 indique un cas d'usage favorable à GraphRAG. Entre 2.5 et 3.5, une approche hybride légère (RAG + graphe simplifié) mérite d'être explorée. En dessous de 2.5, le RAG vectoriel classique reste le choix le plus rationnel. Cette matrice doit être complétée par un prototype comparatif sur un échantillon représentatif du corpus et des requêtes cibles.
Architecture technique détaillée de GraphRAG
Pipeline d'indexation : du texte brut au graphe de connaissances
Le pipeline d'indexation GraphRAG transforme un corpus textuel en un knowledge graph structuré et résumé, prêt à être interrogé. Ce pipeline se décompose en cinq étapes séquentielles, chacune ajoutant un niveau de structure et d'abstraction au-dessus du texte brut.
Étape 1 : Chunking intelligent des documents
La première étape consiste à découper les documents source en chunks de taille contrôlée. Contrairement au RAG classique où la stratégie de chunking impacte directement la qualité du retrieval, le chunking dans GraphRAG sert principalement à produire des unités textuelles digestibles par le LLM lors de l'extraction d'entités. La taille optimale se situe entre 300 et 600 tokens, avec un overlap de 100 tokens pour éviter la perte d'entités à cheval sur deux chunks.
from langchain.text_splitter import RecursiveCharacterTextSplitter
def chunk_documents(documents: list[str], chunk_size: int = 1200,
chunk_overlap: int = 200) -> list[str]:
"""
Découpe les documents en chunks adaptés à l'extraction d'entités.
Taille en caractères (≈300-400 tokens pour du texte français).
"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
separators=["\n\n", "\n", ". ", " ", ""],
length_function=len
)
chunks = []
for doc in documents:
doc_chunks = splitter.split_text(doc)
chunks.extend(doc_chunks)
return chunks
Étape 2 : Extraction d'entités par LLM
Chaque chunk est soumis au LLM avec un prompt spécialisé qui lui demande d'identifier toutes les entités mentionnées (personnes, organisations, concepts, technologies, lieux) et de les typer. Cette étape est la plus coûteuse du pipeline en termes d'appels API, car chaque chunk nécessite un appel LLM dédié.
from pydantic import BaseModel, Field
from openai import OpenAI
class Entity(BaseModel):
name: str = Field(description="Nom normalisé de l'entité")
type: str = Field(description="Type: PERSON, ORG, TECH, CONCEPT, LOCATION, EVENT")
description: str = Field(description="Description contextuelle de l'entité")
class EntityExtractionResult(BaseModel):
entities: list[Entity]
ENTITY_EXTRACTION_PROMPT = """Tu es un expert en extraction d'entités nommées.
À partir du texte suivant, identifie TOUTES les entités significatives.
Catégories d'entités :
- PERSON : personnes, chercheurs, dirigeants
- ORG : entreprises, institutions, laboratoires
- TECH : technologies, frameworks, outils, algorithmes
- CONCEPT : concepts abstraits, méthodologies, architectures
- LOCATION : pays, villes, régions
- EVENT : événements, conférences, publications
Pour chaque entité, fournis :
1. Un nom normalisé (forme canonique, sans articles)
2. Le type parmi les catégories ci-dessus
3. Une description contextuelle basée sur le texte
Texte à analyser :
{chunk_text}
"""
client = OpenAI()
def extract_entities(chunk: str) -> list[Entity]:
"""Extrait les entités nommées d'un chunk via LLM."""
response = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "system", "content": "Tu extrais des entités nommées de manière exhaustive."},
{"role": "user", "content": ENTITY_EXTRACTION_PROMPT.format(chunk_text=chunk)}
],
response_format=EntityExtractionResult
)
return response.choices[0].message.parsed.entities
Étape 3 : Extraction de relations
Une fois les entités identifiées dans chaque chunk, le LLM extrait les relations entre elles. Chaque relation est un triplet (entité source, type de relation, entité cible) accompagné d'un poids de confiance et d'une description textuelle. Cette étape peut être réalisée conjointement avec l'extraction d'entités ou dans un second pass dédié.
class Relationship(BaseModel):
source: str = Field(description="Entité source (nom normalisé)")
target: str = Field(description="Entité cible (nom normalisé)")
relation_type: str = Field(description="Type de relation: UTILISE, DÉVELOPPE, CONTIENT, etc.")
description: str = Field(description="Description de la relation en contexte")
weight: float = Field(description="Poids de confiance (0.0 à 1.0)", ge=0.0, le=1.0)
class RelationExtractionResult(BaseModel):
relationships: list[Relationship]
RELATION_EXTRACTION_PROMPT = """À partir du texte et des entités identifiées ci-dessous,
identifie TOUTES les relations significatives entre entités.
Entités identifiées : {entities_json}
Types de relations possibles :
- UTILISE : X utilise/emploie Y
- DÉVELOPPE : X développe/crée Y
- CONTIENT : X contient/inclut Y
- RÉGULE : X régule/gouverne Y
- CONCURRENCE : X concurrence/rivalise avec Y
- AMÉLIORE : X améliore/optimise Y
- DÉPEND_DE : X dépend de / requiert Y
- PRODUIT : X produit/génère Y
- ÉVALUE : X évalue/mesure Y
Texte source :
{chunk_text}
"""
def extract_relationships(chunk: str, entities: list[Entity]) -> list[Relationship]:
"""Extrait les relations entre entités identifiées dans un chunk."""
entities_json = [{"name": e.name, "type": e.type} for e in entities]
response = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "user", "content": RELATION_EXTRACTION_PROMPT.format(
entities_json=entities_json,
chunk_text=chunk
)}
],
response_format=RelationExtractionResult
)
return response.choices[0].message.parsed.relationships
Étape 4 : Détection de communautés (algorithme de Leiden)
Le knowledge graph résultant des étapes précédentes est analysé par l'algorithme de Leiden pour identifier les communautés d'entités thématiquement liées. Cette étape utilise la bibliothèque graspologic de Microsoft, qui implémente Leiden avec support pour la hiérarchie multi-niveaux.
import networkx as nx
from graspologic.partition import hierarchical_leiden
def build_networkx_graph(entities: list[Entity],
relationships: list[Relationship]) -> nx.Graph:
"""Construit un graphe NetworkX à partir des entités et relations extraites."""
G = nx.Graph()
for entity in entities:
G.add_node(entity.name, type=entity.type, description=entity.description)
for rel in relationships:
if G.has_edge(rel.source, rel.target):
# Agrège les poids pour les relations multiples
G[rel.source][rel.target]["weight"] += rel.weight
G[rel.source][rel.target]["descriptions"].append(rel.description)
else:
G.add_edge(
rel.source, rel.target,
weight=rel.weight,
relation_type=rel.relation_type,
descriptions=[rel.description]
)
return G
def detect_communities(G: nx.Graph, max_levels: int = 3) -> dict:
"""
Détecte les communautés hiérarchiques via l'algorithme de Leiden.
Retourne un dictionnaire {level: {node: community_id}}.
"""
community_mapping = hierarchical_leiden(
G,
max_cluster_size=10,
random_seed=42
)
# Organise par niveau hiérarchique
communities_by_level = {}
for item in community_mapping:
level = item.level
node = item.node
cluster = item.cluster
if level not in communities_by_level:
communities_by_level[level] = {}
communities_by_level[level][node] = cluster
return communities_by_level
Étape 5 : Génération des résumés communautaires
Pour chaque communauté détectée, le LLM génère un résumé structuré qui capture les thèmes principaux, les entités clés et les relations dominantes. Ces résumés constituent l'index principal pour les requêtes globales.
COMMUNITY_SUMMARY_PROMPT = """Tu es un analyste expert. Génère un résumé structuré
de la communauté thématique suivante, identifiée dans un corpus documentaire.
Entités de la communauté :
{entities_info}
Relations entre ces entités :
{relationships_info}
Extraits textuels pertinents :
{source_texts}
Ton résumé doit :
1. Identifier le thème central de cette communauté
2. Lister les entités les plus importantes et leur rôle
3. Décrire les relations clés et leurs implications
4. Synthétiser les insights principaux en 3-5 points
Format : paragraphe structuré de 200-400 mots.
"""
def generate_community_summary(community_id: int,
community_entities: list[Entity],
community_relationships: list[Relationship],
source_chunks: list[str]) -> str:
"""Génère un résumé LLM pour une communauté d'entités."""
entities_info = "\n".join(
f"- {e.name} ({e.type}): {e.description}"
for e in community_entities
)
relationships_info = "\n".join(
f"- {r.source} --[{r.relation_type}]--> {r.target}: {r.description}"
for r in community_relationships
)
source_texts = "\n---\n".join(source_chunks[:5]) # Limite à 5 extraits
response = client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": COMMUNITY_SUMMARY_PROMPT.format(
entities_info=entities_info,
relationships_info=relationships_info,
source_texts=source_texts
)
}],
max_tokens=1000
)
return response.choices[0].message.content
Pipeline de requête : local search vs global search
Le pipeline de requête GraphRAG opère selon deux modes distincts, sélectionnés automatiquement ou manuellement selon la nature de la question.
Le local search s'active pour les requêtes portant sur des entités spécifiques ("Quels sont les avantages de Neo4j pour le GraphRAG ?"). Le système identifie d'abord les entités mentionnées dans la requête, puis traverse le graphe pour récupérer les entités voisines, les relations pertinentes et les chunks source associés. Ce mode combine la précision de la traversée de graphe avec la richesse contextuelle des embeddings vectoriels.
Le global search s'active pour les requêtes de synthèse ("Quels sont les principaux défis de l'IA en cybersécurité d'après notre documentation ?"). Le système parcourt les résumés communautaires au niveau hiérarchique approprié, les agrège via un processus map-reduce, et génère une synthèse globale. Ce mode est unique à GraphRAG et n'a pas d'équivalent dans le RAG classique.
from enum import Enum
class SearchMode(Enum):
LOCAL = "local"
GLOBAL = "global"
AUTO = "auto"
def classify_query(query: str) -> SearchMode:
"""Classifie automatiquement une requête en local ou global."""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": f"""Classifie cette requête :
- LOCAL : porte sur des entités spécifiques, des faits précis
- GLOBAL : demande une synthèse, une vue d'ensemble, des tendances
Requête : "{query}"
Réponds uniquement LOCAL ou GLOBAL."""
}],
max_tokens=10
)
result = response.choices[0].message.content.strip().upper()
return SearchMode.LOCAL if "LOCAL" in result else SearchMode.GLOBAL
def local_search(query: str, graph: nx.Graph,
entity_embeddings: dict, top_k: int = 10) -> str:
"""
Recherche locale : identifie les entités pertinentes,
traverse le graphe, et génère la réponse.
"""
# 1. Identifier les entités dans la requête
query_entities = extract_entities(query)
# 2. Trouver les entités les plus proches dans le graphe
matched_entities = match_entities_to_graph(query_entities, graph)
# 3. Traverser le graphe (voisinage à 2 hops)
context_subgraph = extract_subgraph(graph, matched_entities, max_hops=2)
# 4. Récupérer les chunks source associés
source_chunks = get_source_chunks(context_subgraph)
# 5. Construire le contexte et générer la réponse
context = format_graph_context(context_subgraph, source_chunks)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Réponds en te basant sur le contexte fourni."},
{"role": "user", "content": f"Contexte :\n{context}\n\nQuestion : {query}"}
]
)
return response.choices[0].message.content
def global_search(query: str, community_summaries: dict,
level: int = 1) -> str:
"""
Recherche globale : utilise les résumés communautaires
pour une synthèse exhaustive via map-reduce.
"""
summaries = community_summaries.get(level, {})
# Phase MAP : score de pertinence de chaque communauté
relevant_summaries = []
for comm_id, summary in summaries.items():
score = score_relevance(query, summary)
if score > 0.3:
relevant_summaries.append((comm_id, summary, score))
# Trie par pertinence décroissante
relevant_summaries.sort(key=lambda x: x[2], reverse=True)
# Phase REDUCE : synthèse finale
combined_context = "\n\n---\n\n".join(
f"[Communauté {cid} (pertinence: {score:.2f})]\n{summary}"
for cid, summary, score in relevant_summaries[:20]
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": """Tu synthétises l'information de plusieurs
communautés thématiques pour répondre de manière exhaustive et structurée."""},
{"role": "user", "content": f"Résumés des communautés :\n{combined_context}\n\nQuestion : {query}"}
]
)
return response.choices[0].message.content
Implémentation complète avec Neo4j et LangChain
Architecture de la solution
L'implémentation de GraphRAG avec Neo4j et LangChain combine la puissance d'une base de données graphe native avec l'écosystème mature de LangChain pour l'orchestration LLM. Neo4j offre le langage de requête Cypher, optimisé pour la traversée de graphes, tandis que LangChain fournit les abstractions nécessaires pour l'intégration avec les modèles de langage et les stores vectoriels.
Configuration de l'environnement
# requirements.txt
# neo4j==5.19.0
# langchain==0.2.6
# langchain-openai==0.1.14
# langchain-community==0.2.6
# langchain-experimental==0.0.62
import os
from neo4j import GraphDatabase
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.graphs import Neo4jGraph
from langchain_experimental.graph_transformers import LLMGraphTransformer
from langchain_community.vectorstores import Neo4jVector
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema import Document
# Configuration
NEO4J_URI = os.getenv("NEO4J_URI", "bolt://localhost:7687")
NEO4J_USER = os.getenv("NEO4J_USER", "neo4j")
NEO4J_PASSWORD = os.getenv("NEO4J_PASSWORD", "password")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# Initialisation des composants
llm = ChatOpenAI(model="gpt-4o", temperature=0)
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
graph = Neo4jGraph(url=NEO4J_URI, username=NEO4J_USER, password=NEO4J_PASSWORD)
Pipeline d'ingestion complet
from langchain_experimental.graph_transformers import LLMGraphTransformer
from langchain.schema import Document
from typing import List
import hashlib
class GraphRAGPipeline:
"""Pipeline complet d'indexation GraphRAG avec Neo4j + LangChain."""
def __init__(self, graph: Neo4jGraph, llm: ChatOpenAI,
embeddings: OpenAIEmbeddings):
self.graph = graph
self.llm = llm
self.embeddings = embeddings
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1200,
chunk_overlap=200,
separators=["\n\n", "\n", ". ", " "]
)
self.graph_transformer = LLMGraphTransformer(
llm=llm,
allowed_nodes=[
"Person", "Organization", "Technology",
"Concept", "Regulation", "Product", "Event"
],
allowed_relationships=[
"USES", "DEVELOPS", "REGULATES", "COMPETES_WITH",
"DEPENDS_ON", "IMPROVES", "CONTAINS", "PRODUCES"
],
node_properties=["description"],
relationship_properties=["description", "weight"]
)
def ingest_documents(self, documents: list[Document]) -> dict:
"""
Pipeline complet : chunking → extraction → stockage → indexation.
Retourne des statistiques d'ingestion.
"""
stats = {"chunks": 0, "nodes": 0, "relationships": 0}
# Étape 1 : Chunking
chunks = self.text_splitter.split_documents(documents)
stats["chunks"] = len(chunks)
print(f"[1/4] Chunking terminé : {len(chunks)} chunks")
# Étape 2 : Extraction d'entités et relations via LLM
all_graph_documents = []
for i, chunk in enumerate(chunks):
graph_docs = self.graph_transformer.convert_to_graph_documents([chunk])
all_graph_documents.extend(graph_docs)
if (i + 1) % 10 == 0:
print(f"[2/4] Extraction : {i+1}/{len(chunks)} chunks traités")
# Comptage
for gdoc in all_graph_documents:
stats["nodes"] += len(gdoc.nodes)
stats["relationships"] += len(gdoc.relationships)
print(f"[2/4] Extraction terminée : {stats['nodes']} nœuds, "
f"{stats['relationships']} relations")
# Étape 3 : Stockage dans Neo4j
self.graph.add_graph_documents(
all_graph_documents,
baseEntityLabel=True,
include_source=True
)
print("[3/4] Stockage Neo4j terminé")
# Étape 4 : Création de l'index vectoriel sur les nœuds
self._create_entity_embeddings()
print("[4/4] Index vectoriel créé")
return stats
def _create_entity_embeddings(self):
"""Crée des embeddings pour chaque entité du graphe."""
# Récupère toutes les entités avec leurs descriptions
entities = self.graph.query("""
MATCH (n)
WHERE n.description IS NOT NULL
RETURN n.id AS id, n.description AS description,
labels(n) AS labels
""")
for entity in entities:
embedding = self.embeddings.embed_query(
f"{entity['id']}: {entity['description']}"
)
# Stocke l'embedding dans le nœud
self.graph.query("""
MATCH (n {id: $id})
SET n.embedding = $embedding
""", params={"id": entity["id"], "embedding": embedding})
def setup_vector_index(self):
"""Configure l'index vectoriel Neo4j pour la recherche ANN."""
self.graph.query("""
CREATE VECTOR INDEX entity_embeddings IF NOT EXISTS
FOR (n:__Entity__)
ON (n.embedding)
OPTIONS {
indexConfig: {
`vector.dimensions`: 3072,
`vector.similarity_function`: 'cosine'
}
}
""")
Module de requête hybride
from langchain.chains import GraphCypherQAChain
from langchain_community.vectorstores import Neo4jVector
class GraphRAGQueryEngine:
"""Moteur de requête hybride combinant traversée de graphe et search vectoriel."""
def __init__(self, graph: Neo4jGraph, llm: ChatOpenAI,
embeddings: OpenAIEmbeddings):
self.graph = graph
self.llm = llm
self.embeddings = embeddings
# Chain Cypher pour requêtes structurées sur le graphe
self.cypher_chain = GraphCypherQAChain.from_llm(
llm=llm,
graph=graph,
verbose=True,
validate_cypher=True,
top_k=10
)
# Store vectoriel Neo4j pour recherche sémantique
self.vector_store = Neo4jVector.from_existing_index(
embedding=embeddings,
url=NEO4J_URI,
username=NEO4J_USER,
password=NEO4J_PASSWORD,
index_name="entity_embeddings"
)
def query(self, question: str, mode: str = "hybrid") -> dict:
"""
Exécute une requête en mode local, global ou hybride.
Args:
question: La question utilisateur
mode: "local", "global" ou "hybrid"
Returns:
dict avec 'answer', 'sources', 'graph_context'
"""
if mode == "local":
return self._local_search(question)
elif mode == "global":
return self._global_search(question)
else:
return self._hybrid_search(question)
def _local_search(self, question: str) -> dict:
"""Recherche locale : entités proches + voisinage graphe."""
# 1. Recherche vectorielle pour trouver les entités pertinentes
similar_entities = self.vector_store.similarity_search_with_score(
question, k=5
)
# 2. Expansion du contexte via traversée de graphe
entity_names = [doc.metadata.get("id", "") for doc, _ in similar_entities]
graph_context = self.graph.query("""
UNWIND $entities AS entity_name
MATCH (n {id: entity_name})-[r]-(m)
RETURN n.id AS source, type(r) AS relation,
m.id AS target, m.description AS target_desc,
r.description AS rel_description
LIMIT 50
""", params={"entities": entity_names})
# 3. Récupération des chunks source associés
source_chunks = self.graph.query("""
UNWIND $entities AS entity_name
MATCH (n {id: entity_name})<-[:MENTIONS]-(chunk:Chunk)
RETURN chunk.text AS text, n.id AS entity
LIMIT 10
""", params={"entities": entity_names})
# 4. Construction du contexte enrichi
context = self._format_context(graph_context, source_chunks)
# 5. Génération de la réponse
response = self.llm.invoke(
f"""À partir du contexte suivant (knowledge graph + sources),
réponds à la question de manière précise et sourcée.
Contexte du graphe :
{context}
Question : {question}"""
)
return {
"answer": response.content,
"sources": source_chunks,
"graph_context": graph_context,
"mode": "local"
}
def _global_search(self, question: str) -> dict:
"""Recherche globale via résumés communautaires."""
# Récupère les résumés communautaires
community_summaries = self.graph.query("""
MATCH (c:Community)
RETURN c.id AS community_id, c.summary AS summary,
c.level AS level, c.title AS title
ORDER BY c.level ASC
""")
# Filtre par pertinence (scoring rapide)
relevant = self._score_communities(question, community_summaries)
# Map-reduce pour synthèse
map_results = []
for comm in relevant[:15]:
partial = self.llm.invoke(
f"""Extrais de ce résumé communautaire les informations
pertinentes pour répondre à : "{question}"
Résumé : {comm['summary']}
Si non pertinent, réponds "NON_PERTINENT"."""
)
if "NON_PERTINENT" not in partial.content:
map_results.append(partial.content)
# Reduce : synthèse finale
combined = "\n\n---\n\n".join(map_results)
final_response = self.llm.invoke(
f"""Synthétise ces extraits de communautés thématiques pour
répondre de manière exhaustive et structurée à : "{question}"
Extraits :
{combined}"""
)
return {
"answer": final_response.content,
"sources": relevant,
"graph_context": None,
"mode": "global"
}
def _hybrid_search(self, question: str) -> dict:
"""Combine local et global search pour une réponse complète."""
local_result = self._local_search(question)
global_result = self._global_search(question)
# Fusion des réponses
fusion_response = self.llm.invoke(
f"""Combine ces deux perspectives pour une réponse complète :
Perspective locale (entités spécifiques) :
{local_result['answer']}
Perspective globale (synthèse thématique) :
{global_result['answer']}
Question originale : {question}
Produis une réponse unifiée, précise et exhaustive."""
)
return {
"answer": fusion_response.content,
"sources": local_result["sources"] + global_result["sources"],
"graph_context": local_result["graph_context"],
"mode": "hybrid"
}
def _format_context(self, graph_context: list, source_chunks: list) -> str:
"""Formate le contexte graphe + sources pour le prompt LLM."""
lines = ["## Relations du graphe de connaissances"]
for rel in graph_context:
lines.append(
f"- {rel['source']} --[{rel['relation']}]--> {rel['target']}"
f" : {rel.get('rel_description', '')}"
)
lines.append("\n## Extraits sources")
for chunk in source_chunks:
lines.append(f"[Entité: {chunk['entity']}] {chunk['text'][:500]}")
return "\n".join(lines)
Exemple d'utilisation complète
from langchain.schema import Document
# 1. Préparer les documents
documents = [
Document(
page_content=open(f"docs/{f}").read(),
metadata={"source": f}
)
for f in os.listdir("docs")
if f.endswith(".txt") or f.endswith(".md")
]
# 2. Initialiser et lancer le pipeline d'indexation
pipeline = GraphRAGPipeline(graph, llm, embeddings)
pipeline.setup_vector_index()
stats = pipeline.ingest_documents(documents)
print(f"Indexation terminée : {stats}")
# 3. Initialiser le moteur de requête
query_engine = GraphRAGQueryEngine(graph, llm, embeddings)
# 4. Requête locale
result_local = query_engine.query(
"Quels frameworks Python supportent GraphRAG ?",
mode="local"
)
print(f"Réponse locale : {result_local['answer']}")
# 5. Requête globale
result_global = query_engine.query(
"Quels sont les principaux défis de l'IA en entreprise ?",
mode="global"
)
print(f"Réponse globale : {result_global['answer']}")
# 6. Requête hybride
result_hybrid = query_engine.query(
"Comment Neo4j se compare-t-il à Pinecone pour le RAG ?",
mode="hybrid"
)
print(f"Réponse hybride : {result_hybrid['answer']}")
Point clé : Neo4j + LangChain
L'intégration Neo4j + LangChain via LLMGraphTransformer automatise l'extraction d'entités et de relations depuis le texte. Le module Neo4jVector permet de combiner recherche vectorielle (embeddings d'entités) et traversée de graphe (Cypher) dans un même pipeline. Cette architecture hybride offre le meilleur des deux mondes : la précision sémantique des embeddings et la puissance relationnelle du graphe.
Implémentation avec Microsoft GraphRAG
Installation et configuration
Le framework Microsoft GraphRAG fournit une implémentation clé en main de l'architecture décrite dans le paper de recherche. Il gère automatiquement les cinq étapes du pipeline d'indexation et expose les deux modes de requête (local et global) via une CLI et une API Python.
# Installation
pip install graphrag
# Initialisation d'un projet
mkdir mon-projet-graphrag && cd mon-projet-graphrag
graphrag init --root .
# Structure créée :
# ├── settings.yaml # Configuration principale
# ├── .env # Clés API
# ├── prompts/ # Prompts personnalisables
# │ ├── entity_extraction.txt
# │ ├── summarize_descriptions.txt
# │ └── community_report.txt
# └── input/ # Documents source
# └── (placer vos fichiers .txt ici)
Configuration détaillée
# settings.yaml — Configuration Microsoft GraphRAG
llm:
api_key: ${GRAPHRAG_API_KEY}
type: openai_chat
model: gpt-4o
max_tokens: 4000
temperature: 0
top_p: 1
request_timeout: 180
tokens_per_minute: 80000
requests_per_minute: 40
parallelization:
stagger: 0.3
num_threads: 10
async_mode: threaded
embeddings:
llm:
api_key: ${GRAPHRAG_API_KEY}
type: openai_embedding
model: text-embedding-3-small
chunks:
size: 1200
overlap: 200
group_by_columns: [id]
encoding_model: cl100k_base
input:
type: file
file_type: text
base_dir: "input"
file_encoding: utf-8
file_pattern: ".*\\.txt$"
entity_extraction:
prompt: "prompts/entity_extraction.txt"
entity_types: [organization, person, technology, concept, regulation, location]
max_gleanings: 1 # Passes supplémentaires pour extraire les entités manquées
community_reports:
prompt: "prompts/community_report.txt"
max_length: 2000
max_input_length: 8000
claim_extraction:
enabled: true
prompt: "prompts/claim_extraction.txt"
description: "Toute affirmation technique, réglementaire ou factuelle"
max_gleanings: 1
storage:
type: file
base_dir: "output"
reporting:
type: file
base_dir: "output/reports"
snapshots:
graphml: true
raw_entities: true
top_level_nodes: true
Personnalisation des prompts d'extraction
# prompts/entity_extraction.txt (extrait adapté au domaine cybersécurité/IA)
-Goal-
À partir d'un texte potentiellement pertinent et d'une liste de types d'entités,
identifie toutes les entités de ces types et toutes les relations entre elles.
-Steps-
1. Identifie toutes les entités. Pour chaque entité identifiée, extrais :
- entity_name : Nom de l'entité, en majuscules, forme canonique
- entity_type : Un parmi [{entity_types}]
- entity_description : Description complète de l'entité, ses attributs et activités
2. Pour chaque paire d'entités liées, extrais :
- source_entity : nom de l'entité source
- target_entity : nom de l'entité cible
- relationship_description : explication de la relation
- relationship_strength : score numérique (1-10) de force de la relation
- relationship_keywords : mots-clés caractérisant la relation
3. Retourne le résultat en JSON conforme au schema suivant :
{{
"entities": [...],
"relationships": [...]
}}
Exécution du pipeline
# Indexation complète
graphrag index --root .
# Progression typique :
# [2026-04-18 10:00:00] Starting pipeline...
# [2026-04-18 10:00:02] Processing 150 documents...
# [2026-04-18 10:00:05] Chunking: 1,234 chunks created
# [2026-04-18 10:02:30] Entity extraction: 5,678 entities found
# [2026-04-18 10:05:00] Relationship extraction: 12,345 relationships found
# [2026-04-18 10:05:30] Community detection: 89 communities at 3 levels
# [2026-04-18 10:08:00] Community summarization: 89 reports generated
# [2026-04-18 10:08:05] Pipeline completed successfully
# Requête locale
graphrag query --root . --method local \
--query "Quels sont les frameworks Python pour le GraphRAG ?"
# Requête globale
graphrag query --root . --method global \
--query "Quels sont les thèmes principaux de ce corpus ?"
Utilisation via l'API Python
import asyncio
from graphrag.query.indexer_adapters import (
read_indexer_entities,
read_indexer_relationships,
read_indexer_communities,
read_indexer_community_reports,
read_indexer_text_units,
)
from graphrag.query.llm.oai.chat_openai import ChatOpenAI as GraphRAGChatOpenAI
from graphrag.query.llm.oai.typing import OpenaiApiType
from graphrag.query.structured_search.local_search.mixed_context import LocalSearchMixedContext
from graphrag.query.structured_search.local_search.search import LocalSearch
from graphrag.query.structured_search.global_search.community_context import GlobalCommunityContext
from graphrag.query.structured_search.global_search.search import GlobalSearch
import pandas as pd
# Configuration du LLM
llm = GraphRAGChatOpenAI(
api_key=os.environ["GRAPHRAG_API_KEY"],
model="gpt-4o",
api_type=OpenaiApiType.OpenAI,
max_retries=3,
)
# Chargement des données indexées
INPUT_DIR = "output/artifacts"
entity_df = pd.read_parquet(f"{INPUT_DIR}/create_final_entities.parquet")
relationship_df = pd.read_parquet(f"{INPUT_DIR}/create_final_relationships.parquet")
community_df = pd.read_parquet(f"{INPUT_DIR}/create_final_communities.parquet")
report_df = pd.read_parquet(f"{INPUT_DIR}/create_final_community_reports.parquet")
text_unit_df = pd.read_parquet(f"{INPUT_DIR}/create_final_text_units.parquet")
entities = read_indexer_entities(entity_df, community_df, 2) # community level 2
relationships = read_indexer_relationships(relationship_df)
community_reports = read_indexer_community_reports(report_df, community_df, 2)
text_units = read_indexer_text_units(text_unit_df)
# --- LOCAL SEARCH ---
local_context = LocalSearchMixedContext(
community_reports=community_reports,
text_units=text_units,
entities=entities,
relationships=relationships,
entity_text_embeddings=None, # Ou store vectoriel configuré
)
local_search = LocalSearch(
llm=llm,
context_builder=local_context,
token_budget=12000,
llm_params={"max_tokens": 2000, "temperature": 0.0},
)
# Exécution
async def run_local():
result = await local_search.asearch("Qu'est-ce que l'algorithme de Leiden ?")
print(result.response)
print(f"\nSources : {len(result.context_data)}")
asyncio.run(run_local())
# --- GLOBAL SEARCH ---
global_context = GlobalCommunityContext(
community_reports=community_reports,
entities=entities,
)
global_search = GlobalSearch(
llm=llm,
context_builder=global_context,
token_budget=16000,
max_data_tokens=12000,
map_llm_params={"max_tokens": 1000, "temperature": 0.0},
reduce_llm_params={"max_tokens": 2000, "temperature": 0.0},
)
async def run_global():
result = await global_search.asearch(
"Quels sont les principaux défis techniques de l'IA en cybersécurité ?"
)
print(result.response)
asyncio.run(run_global())
Base de données vectorielle vs graphe : comparaison approfondie
Bases vectorielles : forces et faiblesses
La question base de données vectorielle vs graphe revient systématiquement lors de la conception d'un système GraphRAG. Les bases de données vectorielles comme Pinecone, Weaviate, Qdrant ou Milvus sont optimisées pour la recherche par similarité dans des espaces à haute dimension. Leur force réside dans la vitesse de recherche ANN (Approximate Nearest Neighbors) : une requête sur des millions de vecteurs s'exécute en quelques millisecondes grâce aux algorithmes d'indexation HNSW ou IVF.
Les bases vectorielles excellent pour les cas d'usage de recherche sémantique pure : trouver les documents les plus similaires à une requête, identifier les passages pertinents dans un corpus, ou détecter les doublons sémantiques. Leur modèle de données est simple (vecteur + métadonnées) et leur mise en œuvre est rapide.
En revanche, les bases vectorielles souffrent de limitations structurelles pour le GraphRAG : elles ne modélisent pas nativement les relations entre entités, ne supportent pas la traversée de chemins multi-hop, et ne fournissent aucun mécanisme de raisonnement structurel. Un vecteur ne "sait" pas qu'il représente une entité liée à d'autres entités — il existe isolément dans l'espace vectoriel.
Bases graphe : Neo4j, Amazon Neptune, ArangoDB
Les bases de données graphe modélisent nativement les entités (nœuds) et leurs relations (arêtes) avec des propriétés associées. Neo4j, leader du marché avec le langage Cypher, offre une traversée de graphe performante qui s'exécute en temps constant par rapport au nombre de nœuds traversés (O(k) où k est la profondeur de traversée, indépendamment de la taille totale du graphe).
Pour GraphRAG, les bases graphe apportent trois avantages fondamentaux. Le raisonnement multi-hop natif : une requête Cypher peut traverser N relations en une seule opération. Le contexte structurel : chaque nœud est contextualisé par l'ensemble de ses relations, fournissant un contexte riche au LLM. La détection de patterns : des algorithmes de graphe (PageRank, communautés, chemins les plus courts) révèlent des structures latentes dans les données.
Les limitations des bases graphe incluent l'absence native de recherche sémantique (comblée récemment par les index vectoriels de Neo4j 5.x), la complexité du modèle de données (nécessite une ontologie bien conçue), et les défis de scalabilité horizontale (la plupart des bases graphe scalent verticalement).
Tableau comparatif détaillé
| Critère | Pinecone (vectoriel) | Neo4j (graphe) | Weaviate (hybride) |
|---|---|---|---|
| Recherche sémantique | Natif, ultra-optimisé (HNSW) | Via index vectoriel (Neo4j 5.x) | Natif (HNSW + BM25) |
| Relations entre entités | Non supporté | Natif (Cypher) | Cross-references basiques |
| Traversée multi-hop | Impossible | Natif, O(k) | Limité (1-2 hops via refs) |
| Scalabilité | Horizontale (serverless) | Verticale (clustering payant) | Horizontale (sharding) |
| Latence de recherche | <10ms (p99) | 10-100ms selon la complexité | <20ms (p99) |
| Algorithmes de graphe | Non | GDS (PageRank, Leiden, etc.) | Non |
| Facilité d'intégration RAG | Très facile (LangChain natif) | Moyen (nécessite Cypher) | Facile (module generative) |
| Coût opérationnel | $70-300/mois (serverless) | $65-700/mois (Aura) | Self-hosted ou $25-200/mois |
| Adapté au GraphRAG | Non (composant auxiliaire uniquement) | Oui (choix principal) | Partiellement (pour RAG hybride simple) |
Architecture hybride : le meilleur des deux mondes
L'approche la plus performante pour GraphRAG combine une base graphe (Neo4j) pour la structure relationnelle avec une couche vectorielle pour la recherche sémantique. Neo4j 5.x intègre nativement les index vectoriels, rendant cette combinaison possible dans un seul système. Alternativement, une architecture découplée utilise Neo4j pour le graphe et un store vectoriel externe (Pinecone, Qdrant) pour les embeddings.
class HybridGraphVectorStore:
"""
Store hybride combinant Neo4j (graphe) et un store vectoriel
pour une recherche GraphRAG optimale.
"""
def __init__(self, neo4j_driver, vector_store, llm):
self.neo4j = neo4j_driver
self.vectors = vector_store
self.llm = llm
def search(self, query: str, k_vector: int = 10,
graph_depth: int = 2) -> dict:
"""
Recherche hybride en 3 étapes :
1. Recherche vectorielle pour identifier les entités candidates
2. Expansion via traversée de graphe
3. Reranking du contexte combiné
"""
# Étape 1 : Recherche vectorielle
vector_results = self.vectors.similarity_search_with_score(query, k=k_vector)
# Étape 2 : Expansion graphe
seed_entities = [doc.metadata["entity_id"] for doc, _ in vector_results]
with self.neo4j.session() as session:
graph_context = session.run("""
UNWIND $seeds AS seed_id
MATCH path = (start {id: seed_id})-[*1..{depth}]-(connected)
WITH connected, relationships(path) AS rels,
length(path) AS distance
RETURN DISTINCT connected.id AS entity,
connected.description AS description,
connected.type AS type,
collect(DISTINCT {
rel_type: type(rels[-1]),
from: startNode(rels[-1]).id,
to: endNode(rels[-1]).id
}) AS connections,
min(distance) AS min_distance
ORDER BY min_distance ASC
LIMIT 50
""", seeds=seed_entities, depth=graph_depth).data()
# Étape 3 : Reranking
combined_context = self._merge_and_rerank(
vector_results, graph_context, query
)
return combined_context
def _merge_and_rerank(self, vector_results, graph_context, query):
"""Fusionne et rerankse les résultats vectoriels et graphe."""
# Score combiné : 0.6 * score_vectoriel + 0.4 * score_graphe
# Le score graphe est inversement proportionnel à la distance dans le graphe
all_entities = {}
for doc, score in vector_results:
entity_id = doc.metadata.get("entity_id", doc.page_content[:50])
all_entities[entity_id] = {
"text": doc.page_content,
"vector_score": score,
"graph_score": 0.0,
"source": "vector"
}
for item in graph_context:
entity_id = item["entity"]
distance = item["min_distance"]
graph_score = 1.0 / (1 + distance) # Décroissance avec la distance
if entity_id in all_entities:
all_entities[entity_id]["graph_score"] = graph_score
all_entities[entity_id]["source"] = "both"
all_entities[entity_id]["connections"] = item.get("connections", [])
else:
all_entities[entity_id] = {
"text": item.get("description", ""),
"vector_score": 0.0,
"graph_score": graph_score,
"source": "graph",
"connections": item.get("connections", [])
}
# Score combiné
for entity_id, data in all_entities.items():
data["combined_score"] = (
0.6 * data["vector_score"] + 0.4 * data["graph_score"]
)
# Tri par score combiné
ranked = sorted(
all_entities.items(),
key=lambda x: x[1]["combined_score"],
reverse=True
)
return ranked
Cas d'usage concrets de GraphRAG
Enterprise Search : recherche interne augmentée
L'enterprise search constitue le cas d'usage le plus immédiatement rentable pour GraphRAG. Les grandes organisations accumulent des dizaines de milliers de documents internes (politiques, procédures, rapports techniques, comptes-rendus de réunions) qui forment un réseau dense de références croisées. Un employé cherchant à comprendre "la politique de rétention des données pour les clients européens du secteur santé" doit naviguer entre la politique RGPD, les contrats clients spécifiques, les réglementations sectorielles santé et les procédures internes de classification des données.
Le RAG classique récupérerait les chunks les plus similaires à cette requête, probablement issus de la politique RGPD générale, sans établir les connexions avec les spécificités sectorielles ou contractuelles. GraphRAG, en modélisant explicitement les relations entre réglementations, contrats, secteurs et procédures, traverse ces connexions pour construire une réponse complète et traceable.
Les gains mesurés dans les déploiements enterprise de GraphRAG sont significatifs : réduction de 40 à 60% du temps de recherche d'information, amélioration de 35% de la précision des réponses sur les requêtes multi-domaines, et augmentation de la confiance utilisateur grâce à la traçabilité des sources dans le graphe.
L'implémentation enterprise typique modélise le graphe selon une ontologie organisationnelle : les nœuds représentent les départements, les projets, les personnes, les documents et les processus, tandis que les relations capturent les liens hiérarchiques, les responsabilités, les dépendances entre projets et les références documentaires. Ce graphe organisationnel constitue un actif stratégique qui s'enrichit avec chaque document indexé et qui améliore progressivement la qualité des réponses à mesure que le réseau de relations se densifie.
Un aspect souvent négligé est la valeur analytique du knowledge graph enterprise lui-même, indépendamment de son utilisation pour le RAG. Le graphe révèle les silos organisationnels (clusters d'entités faiblement connectés entre départements), les experts non identifiés (nœuds à forte centralité dans le graphe de connaissances), et les risques de perte de connaissance (entités critiques avec peu de connexions alternatives). Ces insights structurels sont un bénéfice collatéral significatif de l'investissement dans GraphRAG.
Conformité réglementaire : naviguer dans les textes de loi
Les corpus réglementaires sont naturellement structurés en graphe : les lois citent d'autres lois, les articles font référence à des définitions contenues dans d'autres textes, les directives européennes sont transposées en lois nationales qui sont elles-mêmes interprétées par la jurisprudence. Cette structure relationnelle est invisible au RAG vectoriel mais parfaitement capturable par un knowledge graph.
Un système GraphRAG appliqué à la conformité permet de répondre à des questions comme "Quelles sont les obligations de notification de breach pour un sous-traitant qui traite des données de santé de citoyens allemands dans le cadre du RGPD, de la directive NIS2 et du code de la santé publique ?". La réponse nécessite de traverser les relations entre ces trois textes réglementaires, d'identifier les dispositions applicables au cas précis (sous-traitant + santé + Allemagne), et de synthétiser les obligations convergentes.
Des cabinets d'avocats pionniers utilisent déjà GraphRAG pour automatiser l'analyse réglementaire croisée, réduisant de plusieurs heures à quelques minutes le temps nécessaire pour identifier les textes applicables à un cas donné. Le knowledge graph réglementaire capture les relations d'applicabilité (quelle réglementation s'applique à quel secteur, quelle juridiction, quelle taille d'organisation), de hiérarchie normative (les règlements européens prévalent sur les directives nationales) et de temporalité (dates d'entrée en vigueur, périodes transitoires, abrogations).
Un exemple concret illustre la puissance de cette approche. Pour répondre à la question "Notre filiale allemande peut-elle transférer les données de santé de patients français vers un sous-traitant cloud américain ?", le système GraphRAG traverse les relations : filiale allemande → droit allemand (BDSG) → RGPD (transferts internationaux, art. 46-49) → décisions d'adéquation → Cloud Act américain → données de santé (art. 9 RGPD) → hébergeur de données de santé (certification HDS française). Chaque maillon de cette chaîne est une relation explicite dans le graphe, et la réponse trace précisément le raisonnement juridique en citant les dispositions applicables. Un RAG classique, confronté à cette même question, mélangerait probablement des chunks sur le RGPD, sur le Cloud Act et sur les données de santé sans établir les connexions juridiques correctes.
Connaissances médicales : drug discovery et aide au diagnostic
Le domaine biomédical est un terrain d'application naturel pour GraphRAG, en raison de l'existence de knowledge graphs médicaux structurés (UMLS, SNOMED CT, DrugBank) et de la complexité des interactions entre entités médicales. Les interactions médicamenteuses, les voies métaboliques, les relations gène-protéine-maladie et les cascades d'effets secondaires forment des graphes de connaissances denses où le raisonnement multi-hop est essentiel.
Un système GraphRAG médical peut répondre à des requêtes comme "Quels sont les traitements alternatifs pour un patient atteint de diabète de type 2 avec une insuffisance rénale chronique, en tenant compte des interactions avec son traitement antihypertenseur actuel ?". Le graphe encode les contre-indications, les interactions médicamenteuses, les adaptations posologiques en fonction de la fonction rénale et les alternatives thérapeutiques validées par les recommandations de pratique clinique.
Dans le domaine du drug discovery, GraphRAG accélère l'identification de cibles thérapeutiques en traversant les relations gène → protéine → voie métabolique → pathologie → médicament existant → effet secondaire. Cette traversée multi-hop permet de découvrir des repositionnements de médicaments (drug repurposing) : un médicament développé pour une indication peut être identifié comme candidat pour une autre pathologie en raison de ses interactions avec des cibles partagées dans le graphe. Des entreprises pharmaceutiques rapportent une réduction de 30 à 50% du temps de revue de littérature pour l'identification de cibles, grâce à la capacité de GraphRAG à synthétiser les résultats de milliers de publications scientifiques interconnectées.
La dimension de sécurité patient est également renforcée : le graphe de connaissances médicales permet de vérifier automatiquement la cohérence des prescriptions avec les contre-indications connues, les interactions médicamenteuses documentées et les spécificités physiologiques du patient. Cette vérification multi-factorielle, impossible avec un RAG vectoriel, constitue un cas d'usage à haute valeur où la qualité supérieure de GraphRAG justifie pleinement son coût additionnel.
Analyse de code : comprendre les dépendances et l'architecture
L'analyse de code à grande échelle bénéficie naturellement de la modélisation en graphe. Les relations entre modules, classes, fonctions, packages et fichiers de configuration forment un graphe de dépendances complexe. GraphRAG appliqué au code permet de répondre à des questions architecturales : "Quels sont les impacts potentiels de la modification de l'interface AuthService sur les composants downstream ?", "Quelles fonctions partagent des dépendances avec le module de paiement ?" ou "Comment les changements récents dans le module de logging ont-ils affecté les performances globales ?".
Le knowledge graph du code capture les relations d'import, d'héritage, d'appel, de dépendance et de configuration qui échappent totalement à la recherche vectorielle. Un développeur cherchant à comprendre l'impact d'un refactoring peut traverser le graphe pour identifier toutes les classes et fonctions affectées, même celles qui ne mentionnent pas directement le composant modifié.
L'intégration de GraphRAG dans les workflows de développement s'avère particulièrement puissante pour l'onboarding de nouveaux développeurs et la documentation vivante. Au lieu de parcourir manuellement des milliers de fichiers pour comprendre l'architecture d'un projet, un développeur peut interroger le système : "Comment le flux de paiement est-il implémenté, de la requête HTTP jusqu'à la notification au client ?" Le graphe traverse les relations contrôleur → service → repository → base de données → event bus → notification service, fournissant une cartographie fonctionnelle complète avec les fichiers source correspondants. Cette capacité de "documentation dynamique" élimine le décalage chronique entre la documentation écrite et le code réel.
Performance et benchmarks
Métriques d'évaluation
L'évaluation rigoureuse d'un système GraphRAG nécessite un ensemble de métriques couvrant la qualité des réponses, la fidélité aux sources et les performances opérationnelles. Les métriques standard du RAG (recall, precision, faithfulness) s'enrichissent de métriques spécifiques aux graphes.
Le recall mesure la proportion d'informations pertinentes effectivement récupérées par le système. Sur les benchmarks multi-hop (HotpotQA, MuSiQue), GraphRAG atteint un recall de 78-85%, contre 55-65% pour le RAG vectoriel classique, grâce à sa capacité à traverser les relations entre entités.
La faithfulness (fidélité) évalue si la réponse générée est effectivement supportée par les sources récupérées. GraphRAG affiche une faithfulness de 0.85-0.92 (mesurée par RAGAS), contre 0.70-0.82 pour le RAG classique. Cette amélioration découle du contexte structuré fourni au LLM : les relations explicites du graphe réduisent l'ambiguïté et les erreurs d'attribution.
La comprehensiveness (exhaustivité), métrique introduite par le paper Microsoft GraphRAG, mesure la proportion des aspects pertinents couverts par la réponse. Sur les tâches de synthèse globale, GraphRAG atteint une comprehensiveness de 0.80-0.90, contre 0.30-0.50 pour le RAG classique — le gain le plus significatif de l'architecture.
Benchmarks comparatifs
| Benchmark | Métrique | Naive RAG | RAG + Reranking | GraphRAG (local) | GraphRAG (global) |
|---|---|---|---|---|---|
| HotpotQA (multi-hop) | F1 Score | 0.52 | 0.61 | 0.78 | 0.72 |
| MuSiQue (4-hop) | Recall | 0.35 | 0.44 | 0.71 | 0.65 |
| NarrativeQA (synthèse) | ROUGE-L | 0.28 | 0.31 | 0.38 | 0.52 |
| Custom Enterprise (global) | Comprehensiveness | 0.35 | 0.42 | 0.65 | 0.87 |
| Custom Enterprise (local) | Faithfulness | 0.72 | 0.80 | 0.89 | 0.85 |
Analyse des coûts
Le coût constitue le facteur le plus déterminant dans le choix entre RAG classique et GraphRAG. L'indexation GraphRAG nécessite des appels LLM massifs pour l'extraction d'entités et de relations, la génération de résumés communautaires, et la classification des claims.
| Phase | RAG classique (1000 docs) | GraphRAG (1000 docs) |
|---|---|---|
| Chunking | ~$0 (local) | ~$0 (local) |
| Embedding des chunks | ~$2-5 (API embedding) | ~$2-5 (API embedding) |
| Extraction d'entités | N/A | ~$50-150 (GPT-4o API) |
| Extraction de relations | N/A | ~$30-80 (GPT-4o API) |
| Résumés communautaires | N/A | ~$10-30 (GPT-4o API) |
| Infrastructure DB | ~$25/mois (Pinecone starter) | ~$65/mois (Neo4j Aura) |
| Total indexation | ~$5-10 | ~$95-270 |
| Coût par requête | ~$0.01-0.03 | ~$0.05-0.15 |
Le coût d'indexation GraphRAG est donc 10 à 50 fois supérieur au RAG classique, et le coût par requête est 3 à 5 fois plus élevé en raison des appels LLM supplémentaires pour la synthèse. Ces surcoûts doivent être évalués au regard des gains en qualité, en particulier pour les cas d'usage à haute valeur (conformité, médical, enterprise search) où une mauvaise réponse coûte plus cher que l'infrastructure.
Challenges et limites de GraphRAG
Coût et temps d'indexation
Le défi le plus immédiat de GraphRAG est le coût d'indexation. Pour un corpus de 10 000 documents, l'extraction d'entités et de relations peut nécessiter 50 000 à 100 000 appels API LLM, représentant un coût de $500 à $2 000 et un temps de traitement de 4 à 12 heures (même avec parallélisation). Ce coût est acceptable pour des corpus stables (documentation réglementaire, bases de connaissances) mais prohibitif pour des corpus dynamiques mis à jour quotidiennement.
Des stratégies d'optimisation existent : utilisation de modèles plus petits (GPT-4o-mini, Claude Haiku) pour l'extraction d'entités, extraction incrémentale (seuls les documents modifiés sont retraités), et mise en cache des résultats d'extraction. Cependant, ces optimisations réduisent le coût d'un facteur 3 à 5, pas d'un ordre de grandeur.
Il est crucial de planifier le budget d'indexation dès la phase de conception. Un calcul réaliste doit intégrer : le nombre de chunks (taille du corpus / taille de chunk), le nombre d'appels LLM par chunk (extraction d'entités + relations = 2 appels minimum, souvent 3 avec le gleaning), le coût par appel (variable selon le modèle et la longueur du prompt/réponse), et un facteur de overhead de 20 à 30% pour les retries et les erreurs d'extraction. Pour un corpus de 1 000 pages (environ 500 000 tokens), le coût total d'indexation avec GPT-4o se situe entre $80 et $200, tandis que GPT-4o-mini réduit ce chiffre à $15-40 au prix d'une extraction d'entités légèrement moins précise.
Latence de requête
La latence de requête GraphRAG est structurellement supérieure au RAG classique. Une requête vectorielle ANN s'exécute en quelques millisecondes, tandis qu'une requête GraphRAG implique : (1) extraction d'entités de la question, (2) matching d'entités dans le graphe, (3) traversée de graphe, (4) récupération des chunks source, et (5) génération LLM avec contexte enrichi. L'ensemble prend typiquement 2 à 8 secondes pour une requête locale et 10 à 30 secondes pour une requête globale (due au map-reduce sur les résumés communautaires).
Pour les applications interactives (chatbot, assistant en temps réel), cette latence peut être problématique. Les optimisations incluent le pré-calcul des traversées fréquentes, la mise en cache des résultats de requêtes similaires, et l'utilisation d'un mode "streaming" où la réponse partielle est affichée pendant que le système complète la traversée du graphe. Une stratégie efficace consiste à implémenter un routeur de requêtes en amont : les questions simples sont dirigées vers le RAG vectoriel classique (réponse en moins d'une seconde), tandis que seules les requêtes multi-hop ou de synthèse sont routées vers le pipeline GraphRAG complet. Ce routage intelligent, réalisable via un classificateur léger ou un LLM rapide, réduit la latence perçue par l'utilisateur tout en préservant la qualité des réponses complexes.
Maintenance et évolution du graphe
Un knowledge graph n'est jamais "fini" : les entités évoluent, les relations changent, de nouvelles catégories apparaissent. La maintenance du graphe GraphRAG pose des défis spécifiques. La résolution d'entités (entity resolution) — identifier que "Microsoft Research", "MS Research" et "MSR" désignent la même entité — nécessite des heuristiques sophistiquées et une validation humaine régulière.
La mise à jour incrémentale du graphe lors de l'ajout ou de la modification de documents est techniquement complexe : il faut identifier les entités et relations obsolètes, gérer les conflits avec les nouvelles extractions, et recalculer les communautés et résumés affectés. Aucun framework open source ne gère encore cette problématique de manière satisfaisante, bien que Microsoft GraphRAG travaille activement sur ce sujet.
La qualité du graphe se dégrade progressivement si elle n'est pas activement maintenue. Les entités mal typées, les relations erronées et les doublons s'accumulent au fil du temps, réduisant la précision des réponses. Un processus de curation humaine périodique reste nécessaire, en complément des mécanismes automatiques de validation.
Scalabilité et infrastructure
La scalabilité de GraphRAG est contrainte par deux facteurs : la taille du graphe et le coût des traversées. Un graphe de 10 millions d'entités et 50 millions de relations nécessite une infrastructure Neo4j conséquente (32-64 Go de RAM pour les traversées en mémoire), et les requêtes globales qui parcourent des milliers de résumés communautaires consomment des budgets token significatifs.
Le sharding (partitionnement) du graphe est possible mais complexe : une entité peut appartenir à des communautés réparties sur plusieurs shards, nécessitant des traversées inter-shards coûteuses. Les solutions cloud managées (Neo4j Aura, Amazon Neptune) simplifient l'infrastructure mais ajoutent un coût opérationnel mensuel significatif.
Qualité de l'extraction d'entités : le maillon faible
La qualité du knowledge graph dépend directement de la qualité de l'extraction d'entités et de relations par le LLM. Or cette extraction est sujette à plusieurs types d'erreurs systématiques. Les entités ambiguës posent un problème de désambiguïsation : "Python" désigne-t-il le langage de programmation ou le serpent ? Le contexte résout généralement cette ambiguïté pour un humain, mais le LLM peut produire des typages incohérents entre chunks différents.
Les relations implicites constituent un autre défi. Le texte "L'ANSSI a publié ses recommandations sur le cloud en 2023, suite aux incidents impliquant OVHcloud" contient une relation causale implicite (incidents OVHcloud → publication ANSSI) que le LLM peut manquer ou mal interpréter. Les relations négatives ("X n'utilise pas Y") et conditionnelles ("X utilise Y uniquement si Z") sont particulièrement difficiles à extraire correctement.
L'hallucination d'entités est un phénomène spécifique à l'extraction par LLM : le modèle peut "inventer" des relations qui ne sont pas présentes dans le texte source, contaminant le graphe avec des informations fausses. Le mécanisme de "gleaning" (passes supplémentaires d'extraction) réduit les entités manquées mais peut augmenter les hallucinations si le prompt n'est pas soigneusement calibré. Une validation automatique par recoupement avec le texte source est recommandée pour filtrer les extractions hallucinées.
Limites principales à anticiper
GraphRAG n'est pas une solution universelle. Son coût d'indexation (10-50x supérieur au RAG classique), sa latence de requête (2-30 secondes vs millisecondes), et la complexité de maintenance du graphe en font un choix pertinent uniquement pour les cas d'usage à haute valeur où la qualité des réponses multi-hop et des synthèses globales justifie l'investissement. Pour les requêtes factuelles simples sur des corpus homogènes, le RAG vectoriel classique reste plus efficient.
Alternatives et évolutions de GraphRAG
RAPTOR : Recursive Abstractive Processing for Tree-Organized Retrieval
RAPTOR (Sarthi et al., 2024) propose une approche alternative à GraphRAG pour la synthèse multi-niveaux. Au lieu de construire un knowledge graph, RAPTOR organise les chunks en un arbre hiérarchique de résumés : les chunks de base sont regroupés par clustering, chaque cluster est résumé, puis les résumés sont eux-mêmes regroupés et résumés, formant un arbre dont la racine contient un résumé global du corpus.
RAPTOR est significativement moins coûteux que GraphRAG à l'indexation (pas d'extraction d'entités/relations) tout en offrant une capacité de synthèse multi-niveaux. Cependant, il ne capture pas les relations explicites entre entités et ne supporte pas le raisonnement multi-hop structuré. RAPTOR est un excellent compromis pour les cas d'usage nécessitant une synthèse hiérarchique sans la complexité complète d'un knowledge graph. L'arbre de résumés RAPTOR peut également être combiné avec un knowledge graph dans une architecture hybride, où l'arbre fournit la synthèse multi-niveaux et le graphe assure le raisonnement relationnel, offrant une couverture complète des types de requêtes.
HippoRAG : inspiration neuroscientifique
HippoRAG (Gutiérrez et al., 2024) s'inspire du fonctionnement de l'hippocampe humain pour concevoir un système RAG avec mémoire associative. L'architecture simule le processus de pattern separation (encodage de nouvelles informations) et pattern completion (récupération d'informations partielles) de l'hippocampe, en utilisant un knowledge graph comme "index de mémoire" et un processus de Personalized PageRank (PPR) pour la récupération contextuelle.
HippoRAG se distingue de GraphRAG par son mécanisme de récupération : au lieu de traverser explicitement le graphe, il utilise PPR pour propager l'activation depuis les entités de la requête vers les entités contextuellement pertinentes, simulant l'activation neuronale associative. Les résultats montrent des performances supérieures à GraphRAG sur certains benchmarks multi-hop, avec un coût d'indexation comparable.
LightRAG : GraphRAG allégé
LightRAG (Guo et al., 2024) adresse directement le problème du coût d'indexation de GraphRAG en proposant une architecture simplifiée. Au lieu de l'extraction exhaustive d'entités et relations par LLM, LightRAG utilise un modèle d'extraction plus léger (basé sur des heuristiques et un petit modèle fine-tuné) et un graphe de connaissances simplifié sans la couche de détection de communautés.
LightRAG conserve la capacité de raisonnement structuré via le graphe tout en réduisant le coût d'indexation d'un facteur 5 à 10. Le compromis se situe au niveau de la qualité des résumés communautaires (absents) et de la précision de l'extraction d'entités (inférieure à celle de GPT-4o). Pour les cas d'usage où le budget est contraint mais le raisonnement multi-hop reste nécessaire, LightRAG constitue une alternative pertinente.
GraphReader : lecture approfondie guidée par le graphe
GraphReader (Li et al., 2024) propose une approche complémentaire où le knowledge graph ne sert pas de source de récupération mais de carte de navigation pour la lecture approfondie. Le système construit un graphe grossier du corpus, puis utilise un agent LLM qui "navigue" dans ce graphe pour lire séquentiellement les passages pertinents, en suivant les relations entre concepts.
Cette approche agentique simule la lecture humaine d'un document complexe : plutôt que de récupérer des chunks isolés, l'agent suit un fil conducteur dans le graphe, lisant et synthétisant les passages dans un ordre logique. GraphReader atteint des performances remarquables sur les benchmarks de compréhension de documents longs (128k+ tokens), surpassant GraphRAG sur certaines tâches de raisonnement séquentiel. L'approche agentique introduit cependant une latence significativement plus élevée (30 à 120 secondes par requête selon la profondeur de lecture) et un coût par requête proportionnel au nombre de passages lus. GraphReader est donc mieux adapté aux tâches d'analyse approfondie asynchrones qu'aux interactions conversationnelles en temps réel.
Tableau comparatif des alternatives
| Framework | Coût indexation | Multi-hop | Synthèse globale | Latence | Maturité |
|---|---|---|---|---|---|
| RAG classique | Très faible | Non | Non | Très faible | Mature |
| Microsoft GraphRAG | Élevé | Oui | Excellent | Moyenne-haute | Beta |
| RAPTOR | Moyen | Limité | Bon | Faible-moyenne | Recherche |
| HippoRAG | Élevé | Excellent | Moyen | Moyenne | Recherche |
| LightRAG | Faible-moyen | Oui | Limité | Faible | Alpha |
| GraphReader | Moyen | Excellent | Bon | Haute (agentique) | Recherche |
L'écosystème GraphRAG en évolution rapide
GraphRAG n'est qu'une des réponses au défi du raisonnement structuré dans les systèmes RAG. RAPTOR offre une synthèse hiérarchique sans knowledge graph, HippoRAG apporte une récupération bio-inspirée, LightRAG réduit drastiquement les coûts d'indexation, et GraphReader introduit une approche agentique de lecture guidée par le graphe. Le choix optimal dépend du ratio coût/qualité exigé par le cas d'usage spécifique. La convergence de ces approches vers des systèmes hybrides adaptatifs constitue la tendance dominante pour 2026-2027.
FAQ : questions fréquentes sur GraphRAG
Quelle est la différence fondamentale entre RAG classique et GraphRAG ?
Le RAG classique récupère des fragments de texte (chunks) par similarité vectorielle avec la requête, puis les injecte dans le contexte du LLM. GraphRAG ajoute une couche structurelle : il construit un knowledge graph d'entités et de relations à partir du corpus, détecte des communautés thématiques, et génère des résumés hiérarchiques. Lors de la requête, GraphRAG peut traverser le graphe pour un raisonnement multi-hop (local search) ou exploiter les résumés communautaires pour une synthèse globale (global search). La différence clé est que GraphRAG modélise explicitement les relations entre concepts, permettant un raisonnement structuré impossible avec la seule similarité vectorielle.
GraphRAG est-il adapté à tous les cas d'usage ?
Non. GraphRAG est optimisé pour les cas d'usage nécessitant un raisonnement multi-hop (questions impliquant des chaînes de relations entre entités), une synthèse globale (vue d'ensemble d'un large corpus), ou une traçabilité des sources (explicabilité du raisonnement). Pour les requêtes factuelles simples sur des corpus homogènes, le RAG classique avec un bon chunking et du reranking offre un meilleur ratio coût/performance. GraphRAG est particulièrement pertinent dans les domaines juridique, médical, enterprise search et analyse de code.
Quel est le coût réaliste d'un déploiement GraphRAG en production ?
Pour un corpus de 10 000 documents (environ 50 millions de tokens), l'indexation initiale coûte entre $500 et $2 000 en appels API LLM (GPT-4o), plus $65 à $200 par mois pour l'infrastructure Neo4j. Le coût par requête est de $0.05 à $0.15 (contre $0.01 à $0.03 pour le RAG classique). L'utilisation de modèles plus économiques pour l'extraction (GPT-4o-mini, Claude Haiku) réduit le coût d'indexation d'un facteur 3 à 5. Le ROI devient positif dès que la valeur d'une réponse précise (évitement d'erreur de conformité, gain de temps de recherche) dépasse quelques dollars.
Comment gérer la mise à jour du knowledge graph quand le corpus évolue ?
La mise à jour incrémentale du graphe est le défi technique principal de GraphRAG en production. L'approche recommandée consiste à : (1) détecter les documents modifiés ou ajoutés, (2) extraire les entités et relations des nouveaux chunks, (3) résoudre les conflits avec les entités existantes (entity resolution), (4) mettre à jour les communautés affectées via un recalcul partiel de Leiden, et (5) régénérer uniquement les résumés des communautés modifiées. Microsoft GraphRAG travaille activement sur un mode d'indexation incrémentale (update mode) qui automatise ces étapes.
Neo4j est-il le seul choix de base de données graphe pour GraphRAG ?
Non, bien que Neo4j soit le choix le plus mature et le mieux intégré à l'écosystème LangChain. Les alternatives incluent Amazon Neptune (compatible RDF et openCypher, managé AWS), ArangoDB (multi-modèle : document + graphe + clé-valeur), et JanusGraph (open source distribué). Pour les déploiements légers, NetworkX (bibliothèque Python in-memory) suffit pour les prototypes sur des graphes de moins de 100 000 nœuds. Le choix dépend des contraintes d'infrastructure, de la taille du graphe et de l'écosystème cloud existant.
Peut-on combiner GraphRAG avec des bases vectorielles existantes comme Pinecone ?
Oui, l'architecture hybride est même recommandée. Le pattern consiste à utiliser Neo4j pour la structure relationnelle (entités, relations, communautés) et Pinecone (ou Qdrant, Weaviate) pour la recherche vectorielle rapide sur les chunks source et les descriptions d'entités. Les embeddings stockés dans Pinecone servent de point d'entrée pour identifier les entités pertinentes, puis la traversée de graphe dans Neo4j enrichit le contexte avec les relations multi-hop. Cette architecture découplée offre le meilleur des deux mondes : la vitesse de recherche vectorielle et la puissance du raisonnement graphe.
Quels sont les prérequis techniques pour déployer GraphRAG en entreprise ?
Les prérequis techniques incluent : (1) une infrastructure Neo4j ou compatible (minimum 16 Go RAM pour des graphes de taille moyenne), (2) un accès API à un LLM performant (GPT-4o ou Claude Sonnet recommandés pour l'extraction d'entités), (3) des compétences en Python et en Cypher (langage de requête Neo4j), (4) un pipeline de données pour l'ingestion incrémentale des documents, et (5) un processus de curation pour la validation périodique de la qualité du graphe. L'équipe minimum recommandée comprend un data engineer, un ML engineer familier avec les LLM, et un domain expert pour la conception de l'ontologie.
GraphRAG remplacera-t-il le RAG classique à terme ?
Il est plus probable que GraphRAG et le RAG classique convergent vers des architectures hybrides adaptatives. Les systèmes futurs détecteront automatiquement la nature de la requête (factuelle simple, multi-hop, synthèse globale) et activeront le mode de retrieval approprié : vectoriel pur pour les questions simples, graphe pour le raisonnement structuré, résumés communautaires pour la synthèse. Les frameworks émergents (LightRAG, HippoRAG) préfigurent cette convergence en combinant graphes légers et retrieval vectoriel dans une architecture unifiée. Le RAG vectoriel ne disparaîtra pas, mais il sera de plus en plus souvent augmenté d'une couche structurelle graphe.
Conclusion : GraphRAG, un changement de paradigme maîtrisé
GraphRAG représente une évolution architecturale significative dans l'écosystème RAG, apportant des réponses concrètes aux limitations fondamentales du retrieval par similarité vectorielle. En modélisant explicitement les entités et leurs relations sous forme de knowledge graph, en détectant les communautés thématiques via l'algorithme de Leiden, et en pré-calculant des résumés hiérarchiques, GraphRAG ouvre la voie à un raisonnement multi-hop natif et à une synthèse globale de corpus documentaires complexes.
Les résultats expérimentaux sont clairs : GraphRAG surpasse le RAG classique de 30 à 70% sur les métriques d'exhaustivité et de fidélité pour les requêtes complexes, tout en maintenant des performances comparables sur les requêtes factuelles simples. Ces gains ne sont pas gratuits — le coût d'indexation est 10 à 50 fois supérieur et la latence de requête augmente d'un ordre de grandeur — mais ils sont justifiés pour les cas d'usage à haute valeur : conformité réglementaire, recherche médicale, enterprise search multi-domaines et analyse de code à grande échelle.
L'écosystème GraphRAG est encore jeune mais évolue rapidement. Le framework open source de Microsoft, l'intégration native de Neo4j avec LangChain, et les alternatives émergentes (RAPTOR, HippoRAG, LightRAG) offrent un éventail de solutions adaptées à différents points du spectre coût/qualité. La convergence vers des architectures hybrides adaptatives, combinant retrieval vectoriel et raisonnement graphe de manière dynamique, constitue la trajectoire la plus probable pour les 2 à 3 prochaines années.
Pour les équipes techniques évaluant l'adoption de GraphRAG, la recommandation pratique est de commencer par un prototype ciblé sur un cas d'usage à haute valeur (typiquement, un corpus réglementaire ou une base de connaissances technique dense en relations inter-entités), de mesurer rigoureusement les gains en qualité de réponse par rapport au RAG classique, et de dimensionner les coûts d'infrastructure avant un déploiement à plus grande échelle.
Le chemin vers un GraphRAG production-ready passe par plusieurs étapes progressives. Commencez par valider le concept avec un sous-ensemble représentatif du corpus (100-500 documents) en utilisant le framework Microsoft GraphRAG en mode standalone. Mesurez les métriques clés (faithfulness, comprehensiveness, latence) sur un jeu de requêtes de test couvrant les trois modes d'utilisation : factuel simple, multi-hop et synthèse globale. Si les gains sur les requêtes multi-hop et globales justifient l'investissement, passez à une intégration Neo4j + LangChain pour un contrôle fin sur le pipeline. Enfin, industrialisez avec un pipeline d'indexation incrémentale, un monitoring de la qualité du graphe et un processus de curation périodique.
Le knowledge graph n'est pas une fin en soi — c'est un outil de structuration qui amplifie les capacités de raisonnement des LLM lorsque le corpus et les requêtes le justifient. La question n'est pas "faut-il adopter GraphRAG ?" mais plutôt "quelles requêtes de mes utilisateurs ne trouvent pas de réponse satisfaisante avec le RAG classique, et le raisonnement structuré du graphe comblerait-il ce gap ?". Si la réponse implique des chaînes de relations entre entités, des synthèses transversales ou une traçabilité du raisonnement, alors GraphRAG mérite un investissement sérieux en prototypage et en évaluation.
Télécharger cet article en PDF
Format A4 optimisé pour l'impression et la lecture hors ligne
À propos de l'auteur
Ayi NEDJIMI
Auditeur Senior Cybersécurité & Consultant IA
Expert Judiciaire — Cour d'Appel de Paris
Habilitation Confidentiel Défense
ayi@ayinedjimi-consultants.fr
Ayi NEDJIMI est un vétéran de la cybersécurité avec plus de 25 ans d'expérience sur des missions critiques. Ancien développeur Microsoft à Redmond sur le module GINA (Windows NT4) et co-auteur de la version française du guide de sécurité Windows NT4 pour la NSA.
À la tête d'Ayi NEDJIMI Consultants, il réalise des audits Lead Auditor ISO 42001 et ISO 27001, des pentests d'infrastructures critiques, du forensics et des missions de conformité NIS2 / AI Act.
Conférencier international (Europe & US), il a formé plus de 10 000 professionnels.
Domaines d'expertise
Ressources & Outils de l'auteur
Testez vos connaissances
Mini-quiz de certification lié à cet article — propulsé par CertifExpress
Articles connexes
Bases de Données Vectorielles : Comparatif Complet 2026
Comparatif détaillé de 8 bases de données vectorielles : Pinecone, Milvus, Weaviate, Chroma, Qdrant, pgvector, FAISS, LanceDB. Benchmarks, tutoriel Python et guide de choix.
AWQ et GPTQ — Quantization de LLM pour Déploiement On-Premise
Guide expert AWQ et GPTQ : quantization post-entraînement de LLM pour déploiement on-premise. Comparaison GGUF/bitsandbytes, déploiement vLLM/TGI/Ollama et benchmarks reproductibles.
Qdrant vs Milvus vs Weaviate : Bases Vectorielles pour RAG Sécurisé
Comparaison détaillée de Qdrant, Milvus et Weaviate pour les pipelines RAG sécurisés : architecture, benchmarks, sécurité, multi-tenancy, coûts et recommandations pour choisir la base vectorielle adaptée à vos contraintes.
Commentaires
Aucun commentaire pour le moment. Soyez le premier à commenter !
Laisser un commentaire