Derniere mise a jour le 14 janvier 2026
Graph RAG represente une evolution fondamentale du retrieval-augmented generation en remplacant la similarite vectorielle plate par une traversee structuree des connaissances. Au lieu de traiter les documents comme des chunks isoles, Graph RAG extrait les entites et relations pour construire un graphe de connaissances interrogeable qui permet le raisonnement multi-sauts sur des concepts interconnectes.
Le RAG traditionnel atteint des limitations fondamentales quand le raisonnement necessite de connecter plusieurs faits
Les architectures RAG standard recuperent des chunks de documents bases sur la similarite semantique, puis les transmettent directement au modele de langage. Cette approche fonctionne bien pour les requetes mono-fait ou la reponse existe verbatim dans un seul passage. Mais les vraies questions d’entreprise necessitent souvent de synthetiser des informations dispersees dans de nombreux documents.
Considerez cette question posee a un systeme RAG : “Quels fournisseurs ont des contrats expirant dans 90 jours et qui ont aussi eu des problemes de qualite le trimestre dernier ?” Une recherche vectorielle pourrait recuperer quelques documents de contrats et quelques rapports qualite, mais connecter les points entre fournisseurs specifiques, leurs contrats et leurs incidents qualite necessite un suivi explicite des relations.
Le paper RAG original de Lewis et al. (2020) a demontre des resultats impressionnants sur les taches NLP intensives en connaissances, mais les auteurs ont reconnu des limitations sur les chaines de raisonnement complexes. Les recherches subsequentes de Microsoft ont identifie ce fossile, menant a l’approche GraphRAG publiee en 2024 qui a introduit la detection hierarchique de communautes pour la synthese de documents (Microsoft Research, 2024).
Graph RAG adresse ces limitations en construisant un graphe de connaissances pendant l’indexation. Les entites extraites des documents deviennent des noeuds. Les relations entre entites deviennent des aretes. Au moment de la requete, le systeme traverse ce graphe pour rassembler des informations contextuellement pertinentes qui pourraient ne jamais apparaitre dans le meme chunk.
L’extraction d’entites transforme le texte non structure en connaissances prete pour le graphe
La premiere etape des pipelines Graph RAG consiste a identifier les entites dans les documents. Les modeles de Named Entity Recognition (NER) detectent les personnes, organisations, lieux, dates, produits et entites specifiques au domaine. Les approches modernes utilisent des modeles transformer fine-tunes sur des corpus de domaine.
from transformers import AutoTokenizer, AutoModelForTokenClassification
from transformers import pipeline
import spacy
# Charger un modele NER pre-entraine
tokenizer = AutoTokenizer.from_pretrained("dslim/bert-base-NER")
model = AutoModelForTokenClassification.from_pretrained("dslim/bert-base-NER")
ner_pipeline = pipeline("ner", model=model, tokenizer=tokenizer, aggregation_strategy="simple")
def extraire_entites(texte: str) -> list[dict]:
"""
Extrait les entites nommees du texte avec un NER base sur transformers.
Retourne une liste d'entites avec type et position.
"""
entites_brutes = ner_pipeline(texte)
entites = []
for ent in entites_brutes:
entites.append({
"texte": ent["word"],
"type": ent["entity_group"],
"confiance": ent["score"],
"debut": ent["start"],
"fin": ent["end"]
})
return entites
# Exemple d'extraction
document = """
Acme Corporation a signe un contrat de 2,5M euros avec GlobalTech le 15 janvier 2026.
L'accord couvre les services d'infrastructure cloud pour leur datacenter de Paris.
Le PDG Jean Dupont a annonce le partenariat au Tech Summit de Berlin.
"""
entites = extraire_entites(document)
for ent in entites:
print(f"{ent['type']}: {ent['texte']} (confiance: {ent['confiance']:.2f})")
Au-dela du NER basique, les systemes Graph RAG en production implementent la resolution de coreference pour lier les pronoms et references a leurs antecedents. Quand un document mentionne “l’entreprise” ou “ils”, le systeme doit determiner quelle entite est referencee. Le neuralcoref de SpaCy ou les modeles de coreference plus recents bases sur transformers gerent cette desambiguisation.
Le linking d’entites mappe ensuite les mentions extraites aux entites canoniques dans le graphe de connaissances. “Acme Corp”, “Acme Corporation” et “ACME” doivent tous se resoudre vers le meme noeud du graphe. Cette normalisation empeche la fragmentation du graphe et permet une traversee precise.
L’extraction de relations identifie les connexions entre entites
Une fois les entites identifiees, l’extraction de relations determine comment elles se connectent. Cela transforme du texte comme “Acme Corporation a signe un contrat avec GlobalTech” en un triplet structure : (Acme Corporation, A_SIGNE_CONTRAT_AVEC, GlobalTech).
Les approches modernes d’extraction de relations se divisent en deux categories. Les approches pipeline extraient d’abord les entites, puis classifient la relation entre chaque paire d’entites. Les modeles d’extraction conjointe identifient simultanement les entites et leurs relations, atteignant souvent une meilleure precision grace aux representations partagees.
from typing import NamedTuple
from openai import OpenAI
class Triplet(NamedTuple):
sujet: str
predicat: str
objet: str
confiance: float
def extraire_relations_llm(texte: str, entites: list[dict]) -> list[Triplet]:
"""
Extrait les relations entre entites avec un LLM.
Cette approche exploite le raisonnement du modele pour les relations complexes.
"""
client = OpenAI()
liste_entites = ", ".join([e["texte"] for e in entites])
prompt = f"""Extrais toutes les relations entre les entites suivantes trouvees dans le texte.
Entites: {liste_entites}
Texte: {texte}
Pour chaque relation, output en format:
SUJET | PREDICAT | OBJET | CONFIANCE
Utilise des predicats clairs et normalises comme:
- TRAVAILLE_POUR
- SITUE_A
- A_SIGNE_CONTRAT_AVEC
- A_ANNONCE
- PARTENAIRE_DE
Output seulement les relations, une par ligne."""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.1
)
triplets = []
for ligne in response.choices[0].message.content.strip().split("\n"):
parties = [p.strip() for p in ligne.split("|")]
if len(parties) == 4:
triplets.append(Triplet(
sujet=parties[0],
predicat=parties[1],
objet=parties[2],
confiance=float(parties[3])
))
return triplets
# Alternative: utiliser des modeles RE dedies pour plus de controle
from transformers import AutoModelForSeq2SeqLM
def extraire_relations_rebel(texte: str) -> list[Triplet]:
"""
Extrait les relations avec le modele REBEL (Relation Extraction By End-to-end Language generation).
REBEL a ete entraine sur Wikipedia et les relations Wikidata.
Reference: Huguet Cabot and Navigli, 2021
"""
tokenizer = AutoTokenizer.from_pretrained("Babelscape/rebel-large")
model = AutoModelForSeq2SeqLM.from_pretrained("Babelscape/rebel-large")
inputs = tokenizer(texte, return_tensors="pt", max_length=512, truncation=True)
outputs = model.generate(**inputs, max_length=256, num_beams=5)
decoded = tokenizer.decode(outputs[0], skip_special_tokens=False)
# Parser le format de sortie REBEL
triplets = parser_sortie_rebel(decoded)
return triplets
Le modele REBEL de Babelscape (Huguet Cabot and Navigli, 2021) fournit une option open-source competitive pour l’extraction de relations. Entraine sur Wikipedia avec supervision Wikidata, il genere des triplets directement de maniere seq2seq. Pour les relations specifiques au domaine, le fine-tuning sur des exemples annotes de votre corpus ameliore la qualite d’extraction.
Les bases de donnees graphe stockent et interrogent efficacement les connaissances extraites
Les entites et relations extraites necessitent un stockage persistant supportant des requetes de traversee efficaces. Les bases de donnees graphe excellent dans cette tache car elles representent les relations comme des citoyens de premiere classe plutot que des jointures par cle etrangere.
Neo4j reste la base graphe la plus deployee, offrant le langage de requete Cypher optimise pour la traversee de chemins. Pour les equipes utilisant deja PostgreSQL, Apache AGE fournit des capacites graphe comme extension. Amazon Neptune et Azure Cosmos DB offrent des services graphe manages en environnement cloud.
from neo4j import GraphDatabase
from typing import Optional
class GrapheConnaissances:
"""
Interface de graphe de connaissances pour Graph RAG avec Neo4j.
Gere le stockage d'entites, la creation de relations et la recuperation de sous-graphes.
"""
def __init__(self, uri: str, utilisateur: str, mot_de_passe: str):
self.driver = GraphDatabase.driver(uri, auth=(utilisateur, mot_de_passe))
def ajouter_entite(self, entite_id: str, type_entite: str, proprietes: dict):
"""Ajoute ou met a jour un noeud entite dans le graphe."""
with self.driver.session() as session:
session.run(
f"""
MERGE (e:{type_entite} {{id: $id}})
SET e += $proprietes
""",
id=entite_id,
proprietes=proprietes
)
def ajouter_relation(self, sujet_id: str, predicat: str, objet_id: str,
proprietes: Optional[dict] = None):
"""Cree une relation entre deux entites."""
with self.driver.session() as session:
session.run(
f"""
MATCH (s {{id: $sujet_id}})
MATCH (o {{id: $objet_id}})
MERGE (s)-[r:{predicat}]->(o)
SET r += $proprietes
""",
sujet_id=sujet_id,
objet_id=objet_id,
proprietes=proprietes or {}
)
def obtenir_sous_graphe_entite(self, entite_id: str, max_sauts: int = 2) -> dict:
"""
Recupere le sous-graphe local autour d'une entite jusqu'a max_sauts.
Retourne les noeuds et relations pour l'assemblage de contexte.
"""
with self.driver.session() as session:
resultat = session.run(
"""
MATCH chemin = (depart {id: $entite_id})-[*1..$max_sauts]-(connecte)
RETURN chemin
""",
entite_id=entite_id,
max_sauts=max_sauts
)
noeuds = {}
relations = []
for enregistrement in resultat:
chemin = enregistrement["chemin"]
for noeud in chemin.nodes:
noeuds[noeud["id"]] = dict(noeud)
for rel in chemin.relationships:
relations.append({
"source": rel.start_node["id"],
"cible": rel.end_node["id"],
"type": rel.type,
"proprietes": dict(rel)
})
return {"noeuds": noeuds, "relations": relations}
def trouver_chemins(self, debut_id: str, fin_id: str, longueur_max: int = 4) -> list:
"""
Trouve tous les chemins entre deux entites jusqu'a une longueur maximale.
Essentiel pour les requetes de raisonnement multi-sauts.
"""
with self.driver.session() as session:
resultat = session.run(
"""
MATCH chemin = shortestPath((debut {id: $debut_id})-[*1..$longueur_max]-(fin {id: $fin_id}))
RETURN chemin
ORDER BY length(chemin)
LIMIT 10
""",
debut_id=debut_id,
fin_id=fin_id,
longueur_max=longueur_max
)
chemins = []
for enregistrement in resultat:
noeuds_chemin = [dict(n) for n in enregistrement["chemin"].nodes]
rels_chemin = [{"type": r.type} for r in enregistrement["chemin"].relationships]
chemins.append({"noeuds": noeuds_chemin, "relations": rels_chemin})
return chemins
Les strategies d’indexation de graphe comptent pour la performance des requetes. Creer des index sur les IDs d’entites et les proprietes frequemment interrogees accelere les operations MATCH. Pour les tres grands graphes, les strategies de partitionnement basees sur le type d’entite ou la source du document peuvent maintenir des temps de requete sous la seconde.
Le traitement de requete lie les questions utilisateur aux entites du graphe
Quand un utilisateur soumet une requete, les systemes Graph RAG doivent identifier quelles entites du graphe de connaissances sont pertinentes. Ce linking d’entites au moment de la requete imite le processus d’extraction mais opere sur du texte plus court avec moins de contexte.
La requete “Quels contrats Acme a-t-elle qui expirent bientot ?” devrait se lier a l’entite Acme Corporation et au concept d’expiration de contrat. Le retrieval dense peut aider ici : embedder la requete et comparer aux embeddings d’entites identifie les entites candidates meme quand les correspondances exactes de noms echouent.
import numpy as np
from sentence_transformers import SentenceTransformer
class ProcesseurRequete:
"""
Traite les requetes utilisateur pour identifier les entites de graphe pertinentes
et formuler des strategies de traversee.
"""
def __init__(self, gc: GrapheConnaissances, modele_embedding: str = "BAAI/bge-large-en-v1.5"):
self.gc = gc
self.encodeur = SentenceTransformer(modele_embedding)
self.embeddings_entites = {}
self.metadata_entites = {}
def indexer_entites(self, entites: list[dict]):
"""Pre-calcule les embeddings pour toutes les entites du graphe."""
textes = []
ids = []
for ent in entites:
# Creer une representation textuelle riche de l'entite
texte = f"{ent['type']}: {ent['nom']}. {ent.get('description', '')}"
textes.append(texte)
ids.append(ent['id'])
self.metadata_entites[ent['id']] = ent
embeddings = self.encodeur.encode(textes, normalize_embeddings=True)
for i, ent_id in enumerate(ids):
self.embeddings_entites[ent_id] = embeddings[i]
def lier_entites_requete(self, requete: str, top_k: int = 5) -> list[dict]:
"""
Trouve les entites du graphe les plus pertinentes pour la requete.
Utilise le retrieval dense sur les embeddings d'entites.
"""
embedding_requete = self.encodeur.encode(requete, normalize_embeddings=True)
similarites = []
for ent_id, ent_embedding in self.embeddings_entites.items():
sim = np.dot(embedding_requete, ent_embedding)
similarites.append((ent_id, sim))
similarites.sort(key=lambda x: x[1], reverse=True)
resultats = []
for ent_id, score in similarites[:top_k]:
resultats.append({
**self.metadata_entites[ent_id],
"score_pertinence": float(score)
})
return resultats
def etendre_contexte(self, entites_seed: list[str], requete: str) -> dict:
"""
Etend depuis les entites seed a travers le graphe pour rassembler le contexte pertinent.
Utilise les types de relations et proprietes d'entites pour guider l'expansion.
"""
contexte = {
"entites": {},
"relations": [],
"chemins": []
}
# Rassembler les voisinages locaux des entites seed
for ent_id in entites_seed:
sous_graphe = self.gc.obtenir_sous_graphe_entite(ent_id, max_sauts=2)
contexte["entites"].update(sous_graphe["noeuds"])
contexte["relations"].extend(sous_graphe["relations"])
# Trouver les chemins connectants entre entites seed
if len(entites_seed) > 1:
for i, ent1 in enumerate(entites_seed):
for ent2 in entites_seed[i+1:]:
chemins = self.gc.trouver_chemins(ent1, ent2, longueur_max=3)
contexte["chemins"].extend(chemins)
return contexte
La decomposition de requete aide les questions complexes necessitant plusieurs traversees. “Quels dirigeants des entreprises avec des contrats expirant ont aussi assiste a la reunion du conseil le mois dernier ?” se decompose en : (1) trouver les entreprises avec des contrats expirant, (2) trouver les dirigeants de ces entreprises, (3) filtrer par presence a la reunion du conseil. Chaque sous-requete peut traverser differentes parties du graphe.
L’assemblage de contexte formate les connaissances du graphe pour la consommation LLM
Le sous-graphe recupere pendant le traitement de requete doit etre formate pour le modele de langage. Les triplets bruts manquent de la fluidite necessaire pour une bonne generation. Les approches graph-to-text convertissent les connaissances structurees en passages en langage naturel.
Le paper GraphRAG de Microsoft a introduit les resumes de communautes comme couche d’abstraction. L’algorithme de Leiden partitionne le graphe en communautes d’entites densement connectees. Chaque communaute recoit un resume genere par un LLM, creant un contexte hierarchique qui capture la structure locale et globale.
from dataclasses import dataclass
from typing import List
@dataclass
class ContexteGraphe:
"""Contexte formate depuis la traversee de graphe pour le prompt LLM."""
descriptions_entites: List[str]
enonces_relations: List[str]
passages_support: List[str]
def formater_triplets_en_texte(triplets: list[dict]) -> list[str]:
"""
Convertit les triplets de graphe en enonces en langage naturel.
Plus lisible pour la consommation LLM que les donnees structurees brutes.
"""
enonces = []
modeles_predicat = {
"TRAVAILLE_POUR": "{sujet} travaille pour {objet}",
"SITUE_A": "{sujet} est situe a {objet}",
"A_SIGNE_CONTRAT_AVEC": "{sujet} a signe un contrat avec {objet}",
"PARTENAIRE_DE": "{sujet} a un partenariat avec {objet}",
"RAPPORTE_A": "{sujet} rapporte a {objet}",
"FILIALE_DE": "{sujet} est une filiale de {objet}",
"A_ACQUIS": "{sujet} a acquis {objet}",
}
for triplet in triplets:
predicat = triplet.get("type", triplet.get("predicat"))
sujet = triplet.get("source", triplet.get("sujet"))
obj = triplet.get("cible", triplet.get("objet"))
if predicat in modeles_predicat:
enonce = modeles_predicat[predicat].format(
sujet=sujet, objet=obj
)
else:
# Fallback pour predicats inconnus
pred_lisible = predicat.replace("_", " ").lower()
enonce = f"{sujet} {pred_lisible} {obj}"
# Ajouter contexte temporel/proprietes si disponible
if props := triplet.get("proprietes", {}):
if date := props.get("date"):
enonce += f" (au {date})"
if montant := props.get("montant"):
enonce += f" pour {montant}"
enonces.append(enonce)
return enonces
def assembler_prompt(requete: str, contexte: ContexteGraphe) -> str:
"""
Assemble le prompt final combinant requete et contexte de graphe.
Structure pour guider le LLM vers des reponses ancrees dans le graphe.
"""
parties_prompt = [
"Reponds a la question suivante en utilisant le contexte de graphe de connaissances fourni.",
"Base ta reponse uniquement sur les informations donnees. Si le contexte ne contient pas",
"assez d'informations pour repondre completement, reconnais ce qui manque.",
"",
"## Question",
requete,
"",
"## Informations sur les entites",
]
for desc in contexte.descriptions_entites:
parties_prompt.append(f"- {desc}")
parties_prompt.extend([
"",
"## Relations connues",
])
for enonce in contexte.enonces_relations:
parties_prompt.append(f"- {enonce}")
if contexte.passages_support:
parties_prompt.extend([
"",
"## Extraits de documents support",
])
for passage in contexte.passages_support:
parties_prompt.append(f"> {passage}")
parties_prompt.extend([
"",
"## Reponse",
])
return "\n".join(parties_prompt)
Les approches hybrides combinent le contexte de graphe avec les chunks recuperes par vecteur traditionnel. Le graphe fournit des relations structurees tandis que les chunks offrent du texte support verbatim. Cette combinaison ancre la generation dans les connaissances explicites (graphe) et le contexte implicite (passages).
La generation LLM produit des reponses ancrees dans le graphe
Le modele de langage recoit le contexte formate et genere une reponse. L’ancrage dans le graphe reduit les hallucinations parce que la sortie du modele peut etre tracee jusqu’a des entites et relations specifiques dans le sous-graphe recupere.
La verification post-generation compare les faits mentionnes au graphe. Les affirmations sur les proprietes d’entites ou relations sont validees. Si le modele affirme quelque chose non present dans le contexte fourni, le systeme peut signaler ou filtrer ce contenu.
from openai import OpenAI
class GenerateurGraphRAG:
"""
Genere des reponses ancrees dans le contexte du graphe de connaissances.
Inclut le tracking de citations et la detection d'hallucinations.
"""
def __init__(self, modele: str = "gpt-4o"):
self.client = OpenAI()
self.modele = modele
def generer(self, requete: str, contexte: ContexteGraphe) -> dict:
"""Genere une reponse avec tracking de citations."""
prompt = assembler_prompt(requete, contexte)
prompt_systeme = """Tu es un assistant precis qui repond aux questions basees sur
les donnees de graphe de connaissances. Suis ces regles:
1. Utilise uniquement les informations du contexte fourni
2. Cite les entites quand tu fais des affirmations a leur sujet
3. Si des informations manquent, indique explicitement ce que tu ne peux pas determiner
4. Structure ta reponse clairement avec la chaine de raisonnement visible"""
response = self.client.chat.completions.create(
model=self.modele,
messages=[
{"role": "system", "content": prompt_systeme},
{"role": "user", "content": prompt}
],
temperature=0.1
)
reponse = response.choices[0].message.content
# Tracker quelles entites sont mentionnees dans la reponse
entites_citees = self.extraire_citations(reponse, contexte)
return {
"reponse": reponse,
"entites_citees": entites_citees,
"contexte_utilise": {
"entites": len(contexte.descriptions_entites),
"relations": len(contexte.enonces_relations)
}
}
def extraire_citations(self, reponse: str, contexte: ContexteGraphe) -> list[str]:
"""Identifie quelles entites du contexte apparaissent dans la reponse generee."""
citees = []
reponse_lower = reponse.lower()
for desc in contexte.descriptions_entites:
# Extraire le nom d'entite de la description
nom_entite = desc.split(":")[0] if ":" in desc else desc.split()[0]
if nom_entite.lower() in reponse_lower:
citees.append(nom_entite)
return list(set(citees))
def verifier_affirmations(self, reponse: str, contexte: ContexteGraphe) -> dict:
"""
Verifie que les affirmations dans la reponse sont supportees par le contexte.
Retourne le statut de verification et les affirmations non supportees.
"""
prompt_verification = f"""Analyse cette reponse et identifie chaque affirmation faite:
Reponse: {reponse}
Pour chaque affirmation, verifie si elle est supportee par ce contexte:
{chr(10).join(contexte.enonces_relations)}
Output chaque affirmation avec son statut SUPPORTEE ou NON_SUPPORTEE."""
response = self.client.chat.completions.create(
model=self.modele,
messages=[{"role": "user", "content": prompt_verification}],
temperature=0
)
return {"verification": response.choices[0].message.content}
Les considerations de performance faconnent les deploiements Graph RAG en production
Construire et interroger des graphes de connaissances a l’echelle requiert une ingenierie soignee. L’extraction d’entites et le minage de relations ajoutent un temps d’indexation significatif compare au simple chunking. Un corpus qui prend des heures a indexer pour le RAG vectoriel pourrait prendre des jours pour un traitement Graph RAG complet.
Les mises a jour incrementales presentent des defis. Quand de nouveaux documents arrivent, le systeme doit extraire leurs entites, les lier aux noeuds de graphe existants, et ajouter de nouvelles relations. La detection de doublons et la resolution d’entites deviennent des processus continus plutot que des etapes d’indexation ponctuelles.
La latence des requetes depend de la profondeur de traversee du graphe. Les requetes mono-saut se completent en millisecondes. Le raisonnement multi-sauts avec recherche de chemins peut prendre des secondes sur de grands graphes sans indexation appropriee. Mettre en cache les sous-graphes frequemment traverses aide pour les patterns de requetes courants.
Le paper Microsoft GraphRAG rapporte que leur approche hierarchique par communautes traite un corpus de 1 700 podcasts en environ 4 heures avec GPT-4 pour l’extraction d’entites et la synthese. Le cout evolue avec l’utilisation du LLM pendant l’indexation, rendant les modeles d’extraction open-source attractifs pour les grands corpus.
LangChain et LlamaIndex fournissent des abstractions Graph RAG
Les deux frameworks LLM majeurs incluent maintenant des composants Graph RAG. Le KnowledgeGraphIndex de LlamaIndex construit des graphes pendant l’indexation et supporte la generation de requetes Cypher. Les integrations graphe de LangChain se connectent a Neo4j, NebulaGraph et autres stores.
from llama_index.core import KnowledgeGraphIndex, SimpleDirectoryReader
from llama_index.core.graph_stores import Neo4jGraphStore
from llama_index.llms.openai import OpenAI
# Configuration Graph RAG avec LlamaIndex
def creer_graph_rag_llamaindex(chemin_documents: str, config_neo4j: dict):
"""
Cree un index Graph RAG avec LlamaIndex.
Gere l'extraction d'entites et la construction de graphe automatiquement.
"""
# Charger les documents
documents = SimpleDirectoryReader(chemin_documents).load_data()
# Configurer le store graphe
graph_store = Neo4jGraphStore(
username=config_neo4j["utilisateur"],
password=config_neo4j["mot_de_passe"],
url=config_neo4j["uri"],
database=config_neo4j.get("database", "neo4j")
)
# Creer l'index avec extraction d'entites
llm = OpenAI(model="gpt-4o", temperature=0)
index = KnowledgeGraphIndex.from_documents(
documents,
graph_store=graph_store,
llm=llm,
max_triplets_per_chunk=10,
include_embeddings=True
)
return index
def interroger_graph_rag(index: KnowledgeGraphIndex, requete: str):
"""Interroge l'index Graph RAG avec retrieval base sur le graphe."""
moteur_requete = index.as_query_engine(
include_text=True, # Inclure le texte source avec le graphe
response_mode="tree_summarize",
embedding_mode="hybrid" # Combiner retrieval graphe et vecteur
)
reponse = moteur_requete.query(requete)
return {
"reponse": str(reponse),
"noeuds_source": [node.text for node in reponse.source_nodes]
}
Ces frameworks abstraient une complexite significative mais peuvent ne pas exposer toutes les options de tuning. Les deploiements en production personnalisent souvent le pipeline d’extraction, la logique de resolution d’entites et les strategies de traversee au-dela de ce que les parametres par defaut des frameworks fournissent.
Graph RAG excelle sur des cas d’usage specifiques
Toute application RAG ne beneficie pas de la structure de graphe. Graph RAG apporte de la valeur quand les requetes necessitent de connecter des informations entre documents, quand les entites et relations sont centrales au domaine, ou quand des chaines de raisonnement multi-sauts repondent a des questions importantes.
L’analyse de documents juridiques beneficie du tracking des parties, obligations et relations entre contrats. La revue de litterature medicale connecte medicaments, conditions, traitements et resultats. L’analyse financiere lie entreprises, dirigeants, transactions et evenements. Ces domaines ont une structure de graphe inherente que le RAG peut exploiter.
A l’inverse, si la plupart des requetes cherchent des passages verbatim avec des reponses contenues dans des chunks uniques, le RAG vectoriel traditionnel peut suffire. La complexite ajoutee de la construction et maintenance de graphe ne paie que quand les requetes necessitent reellement la traversee.
Les architectures hybrides combinent le meilleur des deux approches
De nombreux systemes en production implementent a la fois le retrieval vectoriel et graphe, selectionnant l’approche selon les caracteristiques de la requete. Les questions factuelles simples sont routees vers le RAG vectoriel pour la vitesse. Les questions analytiques complexes activent la traversee de graphe pour la completude.
La classification de requete determine le routage. Un modele leger predit si une requete necessite une recherche mono-saut ou un raisonnement multi-sauts. Alternativement, les deux retrievers s’executent en parallele avec les resultats fusionnes avant la generation.
from enum import Enum
from dataclasses import dataclass
class ComplexiteRequete(Enum):
SIMPLE = "simple" # Recherche de fait unique
MODEREE = "moderee" # Plusieurs faits, meme entite
COMPLEXE = "complexe" # Raisonnement multi-sauts requis
@dataclass
class ConfigRAGHybride:
poids_vecteur: float = 0.5
poids_graphe: float = 0.5
seuil_complexite: float = 0.7
class RAGHybride:
"""
Systeme RAG hybride combinant retrieval vectoriel et graphe.
Route les requetes selon l'analyse de complexite.
"""
def __init__(self, retriever_vecteur, retriever_graphe, config: ConfigRAGHybride):
self.vecteur = retriever_vecteur
self.graphe = retriever_graphe
self.config = config
def classifier_requete(self, requete: str) -> ComplexiteRequete:
"""
Analyse la requete pour determiner la strategie de retrieval requise.
Utilise des heuristiques et une classification ML optionnelle.
"""
# Heuristiques simples
mots_cles_multi_entite = ["relation", "connecte", "entre", "compare"]
mots_cles_raisonnement = ["pourquoi", "comment", "qu'est-ce qui cause", "impact"]
requete_lower = requete.lower()
if any(mc in requete_lower for mc in mots_cles_multi_entite):
return ComplexiteRequete.COMPLEXE
if any(mc in requete_lower for mc in mots_cles_raisonnement):
return ComplexiteRequete.MODEREE
# Compter les references potentielles a des entites (mots capitalises)
candidats_entite = len([m for m in requete.split() if m[0].isupper()])
if candidats_entite > 2:
return ComplexiteRequete.COMPLEXE
return ComplexiteRequete.SIMPLE
def recuperer(self, requete: str) -> dict:
"""
Recupere le contexte avec la strategie appropriee selon la complexite de requete.
"""
complexite = self.classifier_requete(requete)
if complexite == ComplexiteRequete.SIMPLE:
# Retrieval vecteur uniquement pour requetes simples
return {
"strategie": "vecteur",
"contexte": self.vecteur.recuperer(requete)
}
elif complexite == ComplexiteRequete.COMPLEXE:
# Retrieval graphe principal avec augmentation vectorielle
contexte_graphe = self.graphe.recuperer(requete)
contexte_vecteur = self.vecteur.recuperer(requete, limite=3)
return {
"strategie": "graphe_principal",
"contexte_graphe": contexte_graphe,
"chunks_support": contexte_vecteur
}
else:
# Retrieval parallele avec fusion ponderee
contexte_graphe = self.graphe.recuperer(requete)
contexte_vecteur = self.vecteur.recuperer(requete)
return {
"strategie": "hybride",
"contexte_graphe": contexte_graphe,
"contexte_vecteur": contexte_vecteur,
"poids": {
"graphe": self.config.poids_graphe,
"vecteur": self.config.poids_vecteur
}
}
Progresser avec l’implementation Graph RAG
Demarrer un projet Graph RAG necessite la conception du schema d’entites avant tout code. Quels types d’entites comptent pour votre domaine ? Quelles relations les connectent ? Cette ontologie faconne les pipelines d’extraction et les capacites de requete.
Commencez avec un sous-ensemble de votre corpus pour valider l’approche. Extrayez les entites et relations de documents representatifs. Construisez un graphe de test. Executez des requetes echantillon manuellement pour verifier que la structure capture les connaissances dont vous avez besoin.
L’evaluation reste difficile. Les benchmarks RAG standard testent le retrieval mono-saut. Les capacites multi-sauts de Graph RAG necessitent des ensembles d’evaluation personnalises avec des questions qui ont vraiment besoin de la traversee de relations. Sans evaluation appropriee, vous ne pouvez pas mesurer si la complexite ajoutee apporte de la valeur.
Le paysage Graph RAG continue d’evoluer. La recherche explore la construction neurale de graphes qui apprend les schemas d’entites et relations depuis les donnees. Les embeddings de graphes de connaissances permettent un matching souple au-dela du linking exact d’entites. Le raisonnement temporel trace comment les relations changent dans le temps. Ces avancees rendront Graph RAG plus puissant et plus facile a deployer dans les annees a venir.