Retour au blog
Guides

Graph RAG associe les graphes de connaissances au retrieval pour un raisonnement superieur

Racine AI

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.

Newsletter technique

1 article par mois sur l'IA documentaire. Pas de spam.

2 x 2 =

On nous demande souvent

Quand utiliser Graph RAG plutot que le RAG vectoriel classique ?

Graph RAG apporte de la valeur quand les requetes necessitent de connecter des informations reparties sur plusieurs documents, quand les entites et leurs relations sont centrales au domaine, ou quand des questions complexes necessitent un raisonnement multi-sauts. Pour des requetes simples ou l'information est contenue dans un seul passage, le RAG vectoriel reste plus efficace.

Quelle base de donnees graphe choisir pour Graph RAG ?

Neo4j reste la reference avec le langage Cypher optimise pour la traversee de chemins. Pour les equipes utilisant PostgreSQL, Apache AGE ajoute des capacites graphe comme extension. Amazon Neptune et Azure Cosmos DB offrent des solutions cloud managees. Le choix depend de l'infrastructure existante et des besoins de scaling.

Comment extraire automatiquement les entites et relations des documents ?

L'extraction passe par deux etapes : le NER (Named Entity Recognition) identifie les entites avec des modeles comme BERT-NER, puis l'extraction de relations determine les connexions entre entites. Le modele REBEL de Babelscape ou les LLM avec prompts structures permettent d'extraire les triplets sujet-predicat-objet.

Le Graph RAG augmente-t-il significativement le temps d'indexation ?

Oui. L'extraction d'entites et le minage de relations ajoutent un temps d'indexation significatif compare au simple chunking vectoriel. Selon le paper Microsoft GraphRAG, un corpus de 1700 podcasts prend environ 4 heures avec GPT-4 pour l'extraction et la synthese. Les modeles d'extraction open-source reduisent ce cout.

Comment gerer les mises a jour incrementales du graphe de connaissances ?

Les mises a jour incrementales sont plus complexes qu'avec le RAG vectoriel. Chaque nouveau document necessite l'extraction d'entites, le linking vers les noeuds existants, et l'ajout de nouvelles relations. La detection de doublons et la resolution d'entites doivent fonctionner en continu.

Quels sont les domaines ou Graph RAG excelle particulierement ?

L'analyse de documents juridiques beneficie du tracking des parties et obligations entre contrats. La litterature medicale connecte medicaments, conditions et traitements. L'analyse financiere lie entreprises, dirigeants et transactions. Ces domaines ont une structure de graphe inherente que Graph RAG exploite.

Comment combiner Graph RAG et RAG vectoriel dans une architecture hybride ?

Une architecture hybride route les requetes selon leur complexite. Les questions factuelles simples vont au RAG vectoriel pour la rapidite. Les questions analytiques complexes activent la traversee de graphe. Un classifieur de requetes determine le routage, ou les deux retrievers s'executent en parallele.

Quelle latence ajoute la traversee de graphe par rapport au RAG vectoriel ?

Les requetes mono-saut se completent en millisecondes. Le raisonnement multi-sauts avec recherche de chemins peut prendre plusieurs secondes sur de grands graphes sans indexation appropriee. L'indexation des IDs d'entites et le caching des sous-graphes frequents optimisent les temps de reponse.

Les frameworks LangChain et LlamaIndex supportent-ils Graph RAG ?

Oui. Le KnowledgeGraphIndex de LlamaIndex construit des graphes pendant l'indexation et supporte la generation de requetes Cypher. LangChain s'integre a Neo4j, NebulaGraph et autres stores. Ces frameworks abstraient la complexite mais peuvent manquer de flexibilite pour les cas avances.

Comment reduire les hallucinations avec Graph RAG ?

L'ancrage dans le graphe reduit les hallucinations car les reponses peuvent etre tracees jusqu'a des entites et relations specifiques. La verification post-generation compare les faits mentionnes au graphe. Les affirmations non supportees par le contexte fourni peuvent etre signalees ou filtrees.

Discutons de

Votre Projet.

IA Documents, automatisation legacy, inspection terrain. Nous deployons des solutions qui passent en production.

Decrivez votre projet et recevez une reponse sous 48h.

Nous contacter