Concepts fondamentaux des graphes de connaissances
Face à des documents volumineux — rapports scientifiques,文档 administratifs, littérature technique — la lecture séquentielle devient inefficace. Les graphes de connaissances offrent une alternative structurée : ils représentent l'information sous forme de réseau où les antités et leurs relations sont explicitement modélisées.
Un graphe de connaissances repose sur deux éléments :
- Nœuds (Nodes) : entités significatives extractionnées du texte (personnes, lieux, concepts, organisations)
- Arêtes (Edges) : relations typées entre ces entités (créé_par, localisé_dans, cause, etc.)
Exemple : la phrase « Marie Curie a découvert le radium en 1898 » produit deux nœuds (Marie Curie, radium) reliés par une arête « a_découvert », plus un nœud temporel « 1898 » relié par « en ».
Architecture d'un pipeline de génération automatique
Le projet open-source AI-Knowledge-Graph implémente un pipeline en cinq étapes qui transforme du texte non structuré en graphe interactif. Voici une reconstruction de ce pipeline avec une approche modifiée.
Étape 1 : Segmentation du texte
Les modèles de langage ont une fenêtre de contexte limitée. La segmentation découpe le document source en unités traitables avec un chevauchement pour préserver la continuité sémantique.
import re
from typing import List
from dataclasses import dataclass
@dataclass
class SegmentConfig:
max_tokens: int = 250
overlap_ratio: float = 0.1
def segment_document(raw_text: str, cfg: SegmentConfig) -> List[str]:
"""Découpe un document en segments avec recouvrement."""
tokens = raw_text.split()
step = max(1, int(cfg.max_tokens * (1 - cfg.overlap_ratio)))
segments = []
cursor = 0
while cursor < len(tokens):
end = min(cursor + cfg.max_tokens, len(tokens))
segment_text = ' '.join(tokens[cursor:end])
segments.append(segment_text)
if end >= len(tokens):
break
cursor += step
return segments
# Utilisation
doc = "Votre texte long ici..."
cfg = SegmentConfig(max_tokens=250, overlap_ratio=0.1)
parts = segment_document(doc, cfg)
print(f"Document segmenté en {len(parts)} unités")
Étape 2 : Extraction des triplets RDF
Chaque segment est soumis à un LLM qui extrait des triplets (sujet, prédicat, objet). Le prompt est conçu pour forcer une sortie JSON valide et éviter les pronoms ambiguës.
import json
from typing import Dict, List, Optional
class TripletExtractor:
EXTRACTION_TEMPLATE = """Tu es un extracteur de relations sémantiques.
Analyse le texte suivant et retourne UNIQUEMENT un tableau JSON.
Format: [{"s": "entité_source", "p": "relation", "o": "entité_cible"}]
Contraintes:
- Pas de pronoms, uniquement des entités nominales
- Relations en 1-3 mots max
- Faits explicites uniquement
Texte: {passage}
"""
def __init__(self, llm_client):
self.llm = llm_client
def extract(self, passage: str) -> List[Dict[str, str]]:
prompt = self.EXTRACTION_TEMPLATE.format(passage=passage)
raw_response = self.llm.complete(prompt)
try:
parsed = json.loads(raw_response)
return [t for t in parsed if self._validate_triplet(t)]
except (json.JSONDecodeError, TypeError):
return []
@staticmethod
def _validate_triplet(t: dict) -> bool:
return all(k in t and t[k] for k in ('s', 'p', 'o'))
# Exemple d'extraction
texte = "Gutenberg a inventé l'imprimerie vers 1440 à Mayence."
# Résultat attendu:
# [{"s": "Gutenberg", "p": "a_inventé", "o": "imprimerie"},
# {"s": "Gutenberg", "p": "actif_vers", "o": "1440"},
# {"s": "imprimerie", "p": "inventée_à", "o": "Mayence"}]
Étape 3 : Désambiguïsation et fusion d'entités
Le même référent peut apparaître sous différentes formes : « IA », « Intelligence Artificielle », « intelligence artificielle ». Une étape de normalisation regroupe ces variantes.
from collections import defaultdict
from difflib import SequenceMatcher
class EntityResolver:
SIMILARITY_THRESHOLD = 0.85
def __init__(self):
self._alias_map: Dict[str, str] = {}
def build_canonical_forms(self, triplets: List[Dict]) -> None:
entities = set()
for t in triplets:
entities.add(t['s'])
entities.add(t['o'])
unique = list(entities)
groups: List[List[str]] = []
consumed = set()
for i, ent_a in enumerate(unique):
if ent_a in consumed:
continue
cluster = [ent_a]
for ent_b in unique[i+1:]:
if ent_b in consumed:
continue
ratio = SequenceMatcher(None, ent_a.lower(), ent_b.lower()).ratio()
if ratio >= self.SIMILARITY_THRESHOLD:
cluster.append(ent_b)
consumed.add(ent_b)
consumed.add(ent_a)
canonical = max(cluster, key=len)
for variant in cluster:
self._alias_map[variant.lower()] = canonical
def apply(self, triplets: List[Dict]) -> List[Dict]:
resolved = []
for t in triplets:
resolved.append({
's': self._alias_map.get(t['s'].lower(), t['s']),
'p': t['p'],
'o': self._alias_map.get(t['o'].lower(), t['o']),
})
return resolved
Étape 4 : Inférence de relations transitives
Certaines relations ne sont pas explicites mais déductibles. Si A « fabrique » B et B « réduit » C, alors A « contribue à réduire » C.
class TransitiveInferer:
def __init__(self):
self._rules = [
{
'chain': [('fabrique',), ('réduit',)],
'derived': 'contribue_à_réduire'
},
{
'chain': [('appartient_à',), ('situé_dans',)],
'derived': 'localisé_dans'
},
]
def infer(self, triplets: List[Dict]) -> List[Dict]:
derived_facts = []
lookup = {(t['s'], t['p']): t['o'] for t in triplets}
for rule in self._rules:
pred_a, pred_b = rule['chain']
for t1 in triplets:
if t1['p'] in pred_a:
intermediate = t1['o']
target = lookup.get((intermediate, list(pred_b)[0]))
if target and target != t1['s']:
derived_facts.append({
's': t1['s'],
'p': rule['derived'],
'o': target,
'inferred': True
})
return derived_facts
Étape 5 : Rendu visuel interactif
Les triplets — explicites et inférés — sont projetés dans un graphe navigable. Les nœuds sont disposés par force-directed layout et les arêtes colorées par type de relation.
Déploiement local
Prérequis
- Python 3.11 ou supérieur
- Ollama pour l'inférence locale de LLM
- Modèle
gemma2oullama3téléchargé via Ollama
# Clonage du dépôt
git clone https://github.com/robert-mcdermott/ai-knowledge-graph
cd ai-knowledge-graph
# Installation des dépendances
pip install -r requirements.txt
# Alternative avec uv :
uv sync
# Récupération du modèle local
ollama pull gemma2
Configuration
Le fichier config.toml pilote tous les paramètres du pipeline :
[llm]
model_name = "gemma2"
endpoint = "http://localhost:11434/v1/chat/completions"
max_output_tokens = 8192
sampling_temp = 0.2
[segmentation]
window_size = 200
overlap_tokens = 20
[entity_resolution]
enabled = true
use_llm_clustering = true
[reasoning]
transitive_enabled = true
llm_inference_enabled = true
Le paramètre sampling_temp influence fortement la qualité des résultats :
- 0.1–0.3 : extraction factuelle rigoureuse, idéal pour documents techniques
- 0.4–0.6 : compromis entre précision et découverte de liens indirects
- 0.7+ : génère des inférences créatives mais augmente le risque d'hallucinations
Exécution
# Génération depuis un fichier texte
python generate-graph.py --input document.txt --output graphe.html
# Options disponibles
# --debug : logs détaillés du processus d'extraction
# --no-standardize : désactive la fusion d'entités
# --no-inference : extraction littérale uniquement
# --test : test rapide avec données d'exemple
Paramètres de configuration recommandés
| Scénario | taille segment | température | inférence | Résultat |
|---|---|---|---|---|
| Analyse précise (juridique, scientifique) | 150–200 mots | 0.1–0.2 | OFF | Graphe dense et vérifiable |
| Vue d'ensemble (rapport long) | 400–500 mots | 0.3–0.4 | ON | Connexions larges, hiérarchie visible |
| Découverte de liens cachés | 200 mots | 0.4–0.5 | ON | Réseau riche, liens inférés signalés |
Limites et précautions
Hallucinations : le LLM peut générer des triplets plausibles mais absents du texte source. Une validation manuelle reste nécessaire, particulièrement pour les faits marqués inferred: true.
Volumétrie : pour les documents excédant 100 pages, segmenter d'abord en cahpitres et générer des sous-graphes, puis fusionner.
Langue : les performances optimales sont observées avec l'anglais. Les textes multilingues ou très idiomatiques peuvent produire des entités fragmentées.
Cas d'usage concrets
Analyse littéraire : cartographie des personnages et lieux d'un roman, révélant la structure narrative et les réseaux de relations.
Documentation d'entreprise : transformation d'un manuel de procédures en carte navigable reliant politiques, départements et processus.
Synthèse scientifique : visualisation des chaînes causales dans un article de recherche — facteurs, effets, méthodes et conclusions connectés explicitement.