Avancé 12 minJuridique

RAG sur la jurisprudence Légifrance

Réponse directe

Pour un RAG sur la jurisprudence, téléchargez les archives XML ouvertes de la DILA (CASS pour les arrêts publiés au Bulletin de la Cour de cassation, INCA pour les inédits), découpez chaque arrêt selon ses sections, indexez avec BGE-M3 dans Qdrant et faites citer le pourvoi et l'ECLI. Les archives de stock pèsent quelques centaines de Mo compressées, pas des dizaines de Go.

La jurisprudence française est publiée en données ouvertes, mais ses jeux de données ont un périmètre précis, une structure XML particulière et des pièges que les tutoriels génériques ignorent. Ce guide construit un moteur de recherche sémantique local sur ces décisions : téléchargement, lecture du XML, découpage par sections, indexation, recherche filtrée, et limites à afficher aux utilisateurs.

Par Mohamed Meguedmi·Màj 2026-09-30·Testé sur Windows, macOS, Linux

#Ce qu'est un RAG juridique, et ce qu'on lui demande

Un RAG juridique est un moteur de recherche sémantique sur des décisions de justice, couplé à un modèle qui rédige une réponse à partir des passages retrouvés. La recherche transforme la question en vecteur, retrouve les extraits les plus proches dans une base de décisions, puis le modèle les synthétise en citant leurs références. L'intérêt par rapport à un modèle seul est décisif en droit : le modèle ne répond pas de mémoire, il répond à partir de textes que vous pouvez relire, avec juridiction, date, numéro de pourvoi et identifiant ECLI.

Ce guide construit ce moteur avec les données ouvertes publiées par la Direction de l'information légale et administrative (DILA), sur une machine locale : aucune question de juriste ne part vers un service externe. Le cas d'usage est une question comme « rupture d'un CDD : quelles décisions récentes de la Cour de cassation ? », avec en retour une liste de passages sourcés. Le système ne rend pas un avis : il retrouve et cite, et c'est au professionnel de qualifier.

i
Ce qui distingue ce guide
La plupart des tutoriels de RAG partent de documents que vous possédez. Ici, la difficulté est ailleurs : comprendre ce que contiennent réellement les jeux de données publics, leur structure XML, leur volume, leur fréquence de mise à jour, et leurs limites de couverture. Chaque chiffre de la section suivante a été relevé sur les sources officielles.

#Les jeux de données officiels : ce qu'ils contiennent vraiment

Le kit RAG Local

Tes documents, ton IA : un RAG local fiable sur tes PDF, notes et mails — sans rien envoyer dans le cloud.

  • Espace en ligne à vie
  • PDF + fichiers
  • Mises à jour à vie

La DILA diffuse les décisions dans des bases distinctes, chacune avec son périmètre. Point important, souvent mal compris : ces bases ne contiennent pas toutes les décisions rendues en France. Le fonds de la Cour de cassation publié sous le nom CASS regroupe les arrêts publiés au Bulletin, ceux des chambres civiles depuis 1960 et de la chambre criminelle depuis 1963, avec titrages et sommaires rédigés par les magistrats. Les arrêts inédits, non publiés au Bulletin, sont dans une base séparée, INCA, diffusée depuis 1989.

Les bases de jurisprudence de la DILA (d'après les fiches data.gouv.fr)
BaseContenuArchive de stock (compressée)
CASSArrêts de la Cour de cassation publiés au Bulletin (civil depuis 1960, criminel depuis 1963)environ 248 Mo
INCAArrêts inédits de la Cour de cassation, non publiés au Bulletin, depuis 1989environ 655 Mo
CAPPSélection de décisions civiles et pénales des cours d'appel et juridictions du premier degréenviron 279 Mo
JADEConseil d'État, cours administratives d'appel, Tribunal des conflits (sélection selon la juridiction)environ 1,2 Go

Les tailles ci-dessus sont celles des archives globales listées sur le serveur de la DILA lors de la consultation du 30 septembre 2026 : quelques centaines de mégaoctets compressés par base, très loin des dizaines de gigaoctets que l'on trouve parfois cités. Le volume décompressé est plus important, car chaque décision est un petit fichier XML, mais un poste standard suffit. Mesurez-le sur votre disque avant de planifier.

Il existe une seconde voie : l'API Judilibre, mise en œuvre par la Cour de cassation pour mettre à disposition du public, gratuitement, une base ouverte alimentée par les décisions rendues publiquement, éventuellement enrichies et pseudonymisées. Elle passe par une interface de programmation avec authentification, alors que les archives de la DILA sont de simples fichiers à télécharger. Pour un RAG local, les archives sont le point de départ le plus simple ; Judilibre devient pertinent quand vous voulez un fonds plus complet et plus à jour.

!
Données personnelles : la responsabilité vous revient
La DILA précise sur ses fiches que la mise à disposition de jeux de données pouvant contenir des données personnelles n'affranchit pas le réutilisateur du respect de la loi Informatique et Libertés. Les décisions sont pseudonymisées (des passages comme [V] [S] remplacent des noms), mais ne tentez jamais de ré-identifier des personnes, et vérifiez le périmètre de votre projet avec votre délégué à la protection des données.

#Télécharger le fonds : stock puis mises à jour

Chaque base suit le même schéma sur le serveur de la DILA : une archive de stock, dont le nom commence par Freemium et se termine par global, puis des archives incrémentales qui apportent les nouveautés. À la date de consultation, l'archive de stock de la Cour de cassation datait du 13 juillet 2025 et des archives hebdomadaires la complétaient jusqu'à la fin de septembre 2026. Il faut donc télécharger le stock, puis appliquer toutes les archives incrémentales postérieures dans l'ordre.

Télécharger le stock CASS puis les mises à jour
mkdir -p dila/CASS && cd dila/CASS
curl -O https://echanges.dila.gouv.fr/OPENDATA/CASS/Freemium_cass_global_20250713-140000.tar.gz
tar xzf Freemium_cass_global_20250713-140000.tar.gz

# Puis chaque archive incrémentale, dans l'ordre chronologique
curl -s https://echanges.dila.gouv.fr/OPENDATA/CASS/ | grep -o 'CASS_2026[0-9-]*\.tar\.gz' | sort -u > maj.txt
for f in $(cat maj.txt); do curl -sO https://echanges.dila.gouv.fr/OPENDATA/CASS/$f && tar xzf $f; done

Le nom de l'archive de stock change quand la DILA la régénère : relisez la liste du dossier avant de coder un nom en dur. Évitez aussi de télécharger le dossier en boucle : une seule archive de stock et les incréments suffisent, et un miroir récursif surcharge inutilement le serveur public.

#Lire le XML DILA sans se tromper de champ

Le format est une famille de définitions de types de documents communes à plusieurs bases, publiée par la DILA sous le nom DTD Légifrance. Sur une archive hebdomadaire de septembre 2026, la structure d'un arrêt de la Cour de cassation est la suivante : un bloc de métadonnées commun (identifiant, nature), un bloc de métadonnées juridiques (titre, date de décision, juridiction, solution) et un bloc spécifique à la justice judiciaire (numéro d'affaire, formation, ECLI, indicateur de publication au Bulletin). Le texte intégral se trouve dans le bloc textuel, sous l'élément de contenu, avec des sauts de ligne codés en balises br.

Deux pièges courent dans les tutoriels. D'abord, le champ NUMERO du bloc juridique est un numéro interne ; le numéro de pourvoi, celui que citent les juristes, est dans le bloc spécifique sous NUMEROS_AFFAIRES. Ensuite, récupérer tout le texte du fichier avec itertext mélange les métadonnées au corps de l'arrêt et pollue les vecteurs : il faut cibler l'élément de contenu.

Parser un arrêt CASS (structure vérifiée sur une archive de 2026)
from lxml import etree
from pathlib import Path
import re

def parser(chemin):
    racine = etree.parse(str(chemin)).getroot()

    def val(xp):
        e = racine.find(xp)
        return (e.text or '').strip() if e is not None else None

    contenu = racine.find('.//TEXTE/BLOC_TEXTUEL/CONTENU')
    if contenu is None:
        return None
    for br in contenu.iter('br'):
        br.tail = '\n' + (br.tail or '')
    texte = ''.join(contenu.itertext())
    texte = re.sub(r'[ \t]+', ' ', re.sub(r'\n\s*\n+', '\n', texte)).strip()
    return {
        'id': val('.//META_COMMUN/ID'),
        'titre': val('.//META_JURI/TITRE'),
        'date': val('.//META_JURI/DATE_DEC'),
        'juridiction': val('.//META_JURI/JURIDICTION'),
        'solution': val('.//META_JURI/SOLUTION'),
        'pourvoi': val('.//META_JURI_JUDI/NUMEROS_AFFAIRES/NUMERO_AFFAIRE'),
        'formation': val('.//META_JURI_JUDI/FORMATION'),
        'ecli': val('.//META_JURI_JUDI/ECLI'),
        'texte': texte,
    }

def decisions(dossier):
    for chemin in Path(dossier).rglob('*.xml'):
        try:
            d = parser(chemin)
            if d:
                yield d
        except etree.XMLSyntaxError as e:
            print('ignoré', chemin, e)
→
Adapter à chaque base
Ces chemins ont été relevés sur CASS. Les bases JADE, CAPP et INCA partagent la DTD Légifrance mais ont des blocs spécifiques différents : ouvrez deux ou trois fichiers de chaque base avant d'écrire le parseur, et testez sur un échantillon de cent décisions.

#Découper par structure de décision, pas par nombre de tokens

Une décision de la Cour de cassation récente suit une trame reconnaissable. Dans les arrêts examinés, on trouve les titres « Faits et procédure », « Examen des moyens », puis pour chaque moyen « Énoncé du moyen » et « Réponse de la Cour », et enfin le dispositif introduit par « PAR CES MOTIFS ». Couper toutes les 700 tokens sépare la question posée à la Cour de sa réponse et produit des extraits ambigus. Découpez d'abord selon ces titres, puis fractionnez seulement les blocs trop longs.

Deuxième amélioration, qui coûte peu et rapporte beaucoup : préfixez chaque extrait par son en-tête (titre de la décision et ECLI). Un extrait isolé du type « la cour d'appel a inversé la charge de la preuve » ne dit ni de quelle juridiction, ni de quelle date il provient. Avec l'en-tête, le modèle d'embedding et le générateur disposent tous deux du contexte. Pour les décisions plus anciennes, dont la trame diffère, retombez sur un découpage par paragraphes avec chevauchement.

Découpage par sections avec en-tête
import re

COUPURE = re.compile(r"\n(?=(?:Faits et procédure|Examen des moyens|Sur le |Énoncé du moyen|Enoncé du moyen|Réponse de la Cour|PAR CES MOTIFS))")

def extraits(d, max_car=2200):
    en_tete = f"{d['titre']} ({d['ecli']})\n"
    sortie, tampon = [], ''
    for bloc in COUPURE.split(d['texte']):
        for para in bloc.split('\n'):
            if len(tampon) + len(para) > max_car and tampon:
                sortie.append(en_tete + tampon)
                tampon = ''
            tampon += para + '\n'
    if tampon.strip():
        sortie.append(en_tete + tampon)
    return sortie

#Indexer avec BGE-M3 et Qdrant

BGE-M3 est un modèle d'embedding multilingue qui produit des vecteurs de 1 024 dimensions et accepte des entrées jusqu'à 8 192 tokens, selon sa fiche officielle. Il gère le français juridique correctement dans les usages courants ; mesurez-le tout de même sur vos questions. Qdrant convient pour stocker les vecteurs avec leurs métadonnées. Son client Python propose un mode local sans serveur, mais sa documentation le destine au développement, au prototypage et aux tests : pour plusieurs centaines de milliers d'extraits, lancez le serveur (par exemple avec Docker).

L'exemple courant de tutoriel contient un bogue silencieux : utiliser l'indice de la décision comme identifiant d'un point écrase tous les extraits d'une même décision sauf le dernier. Il faut un identifiant unique par extrait. Autre détail : pour filtrer par date, stockez un entier de la forme AAAAMMJJ dans les métadonnées, ce qui permet un filtre par intervalle.

Indexation par lots
from sentence_transformers import SentenceTransformer
from qdrant_client import QdrantClient, models

emb = SentenceTransformer('BAAI/bge-m3', device='cuda')
emb.max_seq_length = 1024
client = QdrantClient(host='localhost', port=6333)
if not client.collection_exists('cass'):
    client.create_collection('cass', vectors_config=models.VectorParams(
        size=1024, distance=models.Distance.COSINE))

def indexer(dossier, taille_lot=64):
    n, lot = 0, []
    def vider():
        vecs = emb.encode([x[0] for x in lot], batch_size=32, normalize_embeddings=True)
        client.upsert('cass', points=[models.PointStruct(id=x[2], vector=v.tolist(), payload=x[1])
                                       for x, v in zip(lot, vecs)])
        lot.clear()
    for d in decisions(dossier):
        for texte in extraits(d):
            n += 1
            payload = {k: d[k] for k in ('titre', 'ecli', 'pourvoi', 'juridiction', 'formation', 'solution')}
            payload['date_int'] = int(d['date'].replace('-', ''))
            payload['texte'] = texte
            lot.append((texte, payload, n))
            if len(lot) >= taille_lot:
                vider()
    if lot:
        vider()

La recherche encode la question avec le même modèle et demande à Qdrant les extraits les plus proches, éventuellement restreints par un filtre. Les filtres sur la formation, la solution ou la date sont un vrai avantage sur une recherche plein texte classique : « chambre sociale, depuis 2023 » se traduit par deux conditions sur les métadonnées, pas par un mot de plus dans la requête.

Recherche filtrée
def chercher(question, depuis=None, formation=None, k=8):
    conds = []
    if depuis:
        conds.append(models.FieldCondition(key='date_int', range=models.Range(gte=depuis)))
    if formation:
        conds.append(models.FieldCondition(key='formation', match=models.MatchValue(value=formation)))
    vec = emb.encode(question, normalize_embeddings=True).tolist()
    res = client.query_points('cass', query=vec, limit=k,
                              query_filter=models.Filter(must=conds) if conds else None)
    return res.points

for p in chercher('rupture anticipée du CDD par l employeur', depuis=20230101):
    print(p.payload['titre'], p.payload['pourvoi'], round(p.score, 3))

#Pourquoi la recherche seulement sémantique ne suffit pas en droit

Un juriste cherche souvent des éléments exacts : un numéro de pourvoi, un numéro d'article, une expression consacrée. La similarité sémantique les retrouve mal, car deux numéros proches n'ont aucun rapport de sens. Les auteurs de BGE-M3 recommandent d'ailleurs, dans la fiche du modèle, le pipeline suivant pour le RAG : recherche hybride puis reclassement. Ajoutez donc une recherche lexicale de type BM25 à côté des vecteurs, fusionnez les deux listes, puis passez les meilleurs candidats dans un modèle de reclassement.

Sur un fonds juridique, cet ajout est l'amélioration qui compte le plus après un bon découpage. Les guides sur la recherche hybride et sur le reclassement détaillent la mise en œuvre ; le présent guide se limite à ce qui est propre à la jurisprudence.

#Génération, mises à jour et contrôle des citations

Pour la génération, transmettez au modèle les cinq à huit meilleurs extraits avec leurs références et imposez de ne répondre qu'à partir d'eux. Un modèle de 9 milliards de paramètres comme Qwen 3.5 9B (6,6 Go dans la bibliothèque Ollama) suffit pour résumer des extraits ; Mistral Small 24B (14 Go) demande 16 Go de mémoire vidéo. Le prompt doit exiger de citer, pour chaque affirmation, le numéro de pourvoi et l'ECLI, et de répondre « aucune décision retrouvée » quand les extraits ne répondent pas à la question.

Prompt système de synthèse
Tu es un assistant de recherche en jurisprudence. Tu réponds uniquement à partir des
extraits fournis. Pour chaque affirmation, cite entre crochets le numéro de pourvoi
et l'ECLI de la décision. Si les extraits ne permettent pas de répondre, écris :
aucune décision retrouvée. Tu ne donnes pas d'avis juridique.

Côté mises à jour, la DILA publie des archives incrémentales, environ chaque semaine pour CASS et INCA d'après les listes consultées. Un traitement planifié doit télécharger celles qui manquent, les analyser et ajouter les extraits, avec des identifiants stables pour éviter les doublons. Vérifiez enfin par programme que chaque référence citée dans la réponse figure dans les extraits fournis : c'est la même logique de contrôle que dans le guide sur l'analyse de contrats.

#Limites à afficher aux utilisateurs

Couverture partielle
CASS ne contient que les arrêts publiés au Bulletin, INCA les inédits, CAPP une sélection des cours d'appel : l'absence d'une décision dans la base ne prouve pas qu'elle n'existe pas.
Décisions non versées ou trop récentes
Une décision très récente peut ne pas être encore dans la dernière archive incrémentale. Recoupez avec Légifrance pour un dossier sensible.
Évolution du droit
Un arrêt ancien peut avoir été dépassé par un revirement ou une réforme. Le système retrouve du texte, il ne mesure pas l'autorité actuelle d'une solution.
Pseudonymisation
Les noms sont masqués. Ne cherchez jamais à retrouver l'identité des parties.
Aucun avis juridique
L'outil aide à trouver et citer. La qualification des faits, l'application au dossier et le conseil restent du ressort du professionnel.
FAQ
Qu'est-ce qu'un RAG juridique ?+
C'est un système qui retrouve, dans une base de décisions ou de textes, les extraits proches d'une question, puis fait rédiger une réponse par un modèle à partir de ces seuls extraits, avec leurs références. Sa valeur en droit tient à la traçabilité : chaque affirmation renvoie à une décision que le professionnel peut relire.
Où trouver la jurisprudence de la Cour de cassation en données ouvertes ?+
Sur le serveur de la DILA, dans les archives CASS pour les arrêts publiés au Bulletin et INCA pour les inédits, ou via l'API Judilibre de la Cour de cassation. Les fiches sont sur data.gouv.fr. Les archives sont de simples fichiers XML, faciles à traiter en local.
Le fonds est-il complet ?+
Non. CASS contient les arrêts publiés au Bulletin, INCA les arrêts inédits, CAPP une sélection de décisions des cours d'appel, JADE une sélection pour la justice administrative. Pour un fonds plus étendu, la Cour de cassation propose Judilibre. N'affirmez jamais qu'une décision n'existe pas parce qu'elle manque de votre base.
Quel modèle d'embedding choisir pour le français juridique ?+
BGE-M3 est un choix courant : multilingue, vecteurs de 1 024 dimensions, entrées jusqu'à 8 192 tokens selon sa fiche. Aucun classement général ne remplace un test : construisez vingt questions de juristes avec les décisions attendues, et comparez le rappel de plusieurs modèles sur cet échantillon.
Faut-il un GPU pour indexer les décisions ?+
Ce n'est pas obligatoire, mais l'encodage de centaines de milliers d'extraits est nettement plus rapide avec une carte graphique. Sans GPU, l'indexation fonctionne : planifiez-la de nuit, par lots, et faites-la une seule fois. Une fois la base construite, la recherche d'une question isolée reste rapide sur processeur, et les mises à jour hebdomadaires sont légères.
Peut-on utiliser ces décisions dans un produit commercial ?+
Les fiches data.gouv.fr indiquent la licence de chaque jeu, généralement une licence ouverte qui permet la réutilisation avec mention de la source. La DILA rappelle toutefois que le réutilisateur reste soumis à la loi Informatique et Libertés. Vérifiez la licence et faites valider le projet par votre délégué à la protection des données.
Ce guide vous a aidé ?

Un retour, une erreur, une précision ? Faites-nous signe, ça améliore le guide pour tout le monde.