Connecter un LLM à un graphe de connaissances

Créez des agents IA intelligents en combinant LLM et graphes de connaissances

01

📖 Introduction

Connecter un LLM (Large Language Model) à un graphe de connaissances permet de créer des systèmes d'IA plus fiables, plus précises et plus explicables. Le LLM apporte la compréhension et la génération de texte, le graphe apporte les faits vérifiés et les relations sémantiques.

💡 Ce que vous allez apprendre :
  • ✅ Choisir le bon LLM pour votre cas d'usage
  • ✅ Configurer LangChain avec Neo4j ou RDF
  • ✅ Implémenter GraphRAG (RAG sur graphe)
  • ✅ Créer un agent IA qui navigue dans le graphe
  • ✅ Optimiser les performances et la qualité
📊 Architecture GraphRAG :

[Question] → [LLM analyse] → [Requête SPARQL/Cypher] → [Graphe] → [Résultats] → [LLM synthèse] → [Réponse]
02

🤖 Étape 1 : Choisir son LLM

ModèleAvantagesInconvénientsUsage recommandé
GPT-4 Très bonne compréhension, multilingue Payant, API seulement Production, haute qualité
Mistral / Mixtral Open source, bon français, gratuit possible Moins puissant que GPT-4 On-premise, budget limité
Llama 3 Open source, performant Anglais principalement Usage local, anglais
Claude 3 Très bon français, contexte long Payant, API seulement Grands contextes, analyse documentaire
💡 Notre recommandation : Pour un projet en français, commencez avec Mistral (open source, bon rapport qualité/prix) ou GPT-4 (meilleure qualité).
03

🔧 Étape 2 : Configuration LangChain

Installation

pip install langchain langchain-community langchain-openai
pip install neo4j rdflib SPARQLWrapper

Avec Neo4j (Cypher)

from langchain_community.graphs import Neo4jGraph
from langchain.chains import GraphCypherQAChain
from langchain_openai import ChatOpenAI

# Connexion à Neo4j
graph = Neo4jGraph(
    url="bolt://localhost:7687",
    username="neo4j",
    password="password"
)

# LLM
llm = ChatOpenAI(model="gpt-4", temperature=0)

# Chaîne Cypher QA
chain = GraphCypherQAChain.from_llm(
    graph=graph,
    llm=llm,
    verbose=True,
    return_intermediate_steps=True
)

Avec RDF / SPARQL

from langchain_community.graphs import RdfGraph
from langchain.chains import GraphSparqlQAChain

# Connexion au triple store
graph = RdfGraph(
    query_endpoint="http://localhost:3030/ds/sparql",
    update_endpoint="http://localhost:3030/ds/update"
)

# Chaîne SPARQL QA
chain = GraphSparqlQAChain.from_llm(
    graph=graph,
    llm=llm,
    verbose=True
)
04

🕸️ Étape 3 : Implémenter GraphRAG

Version avancée avec embeddings + graphe

from langchain.vectorstores import FAISS
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor

class GraphRAG:
    def __init__(self, graph, vectorstore, llm):
        self.graph = graph
        self.vectorstore = vectorstore
        self.llm = llm
        
        # Retriever hybride
        self.retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
        
    def query(self, question):
        # 1. Recherche vectorielle pour la similarité sémantique
        vector_results = self.retriever.get_relevant_documents(question)
        
        # 2. Requête graphe pour les relations précises
        graph_query = self._generate_sparql(question)
        graph_results = self.graph.query(graph_query)
        
        # 3. Fusion des résultats
        context = self._merge_results(vector_results, graph_results)
        
        # 4. Génération de la réponse
        prompt = f"""Contexte: {context}
Question: {question}
Réponse:"""
        
        return self.llm.invoke(prompt)
    
    def _generate_sparql(self, question):
        # Transformer la question naturelle en SPARQL
        # (implémentation avec LLM)
        pass
💡 GraphRAG vs RAG classique :
  • RAG classique : similarité sémantique, documents entiers
  • GraphRAG : relations précises, faits structurés, inférences
  • Hybride : le meilleur des deux mondes
05

🧠 Étape 4 : Créer un agent intelligent

from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import Tool

# Définir les outils
graph_tool = Tool(
    name="query_graph",
    func=lambda q: chain.run(q),
    description="Interroge le graphe de connaissances avec SPARQL/Cypher"
)

vector_tool = Tool(
    name="semantic_search",
    func=lambda q: vectorstore.similarity_search(q, k=3),
    description="Recherche sémantique dans les documents"
)

# Agent
agent = create_react_agent(
    llm=llm,
    tools=[graph_tool, vector_tool],
    prompt=prompt
)

agent_executor = AgentExecutor(
    agent=agent,
    tools=[graph_tool, vector_tool],
    verbose=True,
    max_iterations=3
)

# Exemple d'utilisation
response = agent_executor.invoke({
    "input": "Quels discours de Charles de Gaulle parlent de la résistance ?"
})
📝 Exemple de raisonnement agent :
Input : "Compare les discours de De Gaulle et Macron sur l'Europe"
1. Thought : Je dois trouver les discours des deux orateurs sur l'Europe.
2. Action : query_graph("SELECT ?titre WHERE { ... }")
3. Observation : [Discours de De Gaulle: "Discours de Bayeux", ...]
4. Action : query_graph("SELECT ?titre WHERE { ... }")
5. Observation : [Discours de Macron: "Discours de la Sorbonne", ...]
6. Final Answer : Synthèse des deux...
06

⚡ Étape 5 : Optimisation

1. Cache des requêtes

import hashlib
import pickle

class QueryCache:
    def __init__(self, cache_file="sparql_cache.pkl"):
        self.cache = self._load(cache_file)
    
    def get(self, question):
        key = hashlib.md5(question.encode()).hexdigest()
        return self.cache.get(key)
    
    def set(self, question, answer):
        key = hashlib.md5(question.encode()).hexdigest()
        self.cache[key] = answer

2. Prompt engineering

SYSTEM_PROMPT = """Tu es un expert en histoire des discours.
Utilise le graphe de connaissances pour répondre.
Si la réponse n'est pas dans le graphe, dis-le honnêtement.
Cite toujours tes sources (les URI des entités trouvées).
Sois précis et concis."""

3. Gestion des erreurs

def safe_query(question):
    try:
        response = chain.invoke(question)
        if not response.get('result'):
            return "Je n'ai pas trouvé d'information dans le graphe."
        return response['result']
    except Exception as e:
        return f"Erreur: {str(e)}"
07

🎯 Exemple complet : Assistant discours historiques

class HistoricalSpeechAssistant:
    def __init__(self):
        # Connexion au graphe Neo4j
        self.graph = Neo4jGraph(
            url="bolt://localhost:7687",
            username="neo4j",
            password="password"
        )
        
        # LLM local (Mistral via Ollama)
        self.llm = Ollama(model="mistral", temperature=0)
        
        # Chaîne QA
        self.chain = GraphCypherQAChain.from_llm(
            graph=self.graph,
            llm=self.llm,
            verbose=False
        )
        
        # Historique des conversations
        self.history = []
    
    def ask(self, question):
        # Ajouter à l'historique
        self.history.append({"role": "user", "content": question})
        
        # Interroger le graphe
        result = self.chain.invoke({"query": question})
        
        # Stocker la réponse
        self.history.append({"role": "assistant", "content": result['result']})
        
        return result['result']
    
    def get_conversation(self):
        return self.history

# Utilisation
assistant = HistoricalSpeechAssistant()
print(assistant.ask("Quels sont les discours de Victor Hugo ?"))
print(assistant.ask("Et sur quel thème parle-t-il ?"))
🚀 Prochain pilier : Comparatifs 📊 Comparer les technologies →