RAG en production pour la recherche juridique : le playbook JurIA
Ce qu'il faut réellement pour construire une récupération ancrée sur des corpus juridiques multilingues — chunking, recherche hybride, citations ancrées et évaluation. Les leçons de JurIA.
Un chatbot qui répond à des questions de droit est un tour de passe-passe. Un système de récupération qui répond avec des citations vérifiables est un produit. JurIA — l'assistant de recherche juridique que je construis pour le marché marocain — est délibérément du deuxième type. Voici le playbook jusqu'ici, erreurs comprises.
Le problème qui vaut la peine d'être résolu
La recherche juridique au Maroc est douloureuse pour des raisons structurelles. Les textes sources vivent dans le Bulletin Officiel, des codes éparpillés et les conventions OHADA. Ils sont multilingues (français et arabe, avec des termes juridiques qui ne se traduisent pas proprement), fragmentés entre portails et rarement annotés ou normalisés. Avocats, chercheurs et équipes de conformité passent des heures à recouper des documents ; les portails de recherche renvoient des documents, mais personne ne renvoie des réponses.
Cet écart — entre « voici dix documents » et « voici la réponse et l'article qui la soutient » — est là où la récupération ancrée gagne sa place.
L'ingénierie du corpus vient en premier
Avant qu'un modèle ne touche au texte, le corpus doit être rendu récupérable. Le texte juridique ne se découpe pas comme de la prose. Un article de loi est une unité de sens autonome ; un jugement est un récit ; un règlement référence d'autres règlements. Un découpage naïf en blocs de taille fixe (disons 512 tokens) coupe les articles en deux et produit des voisins qui ne répondent à rien.
La règle qui s'est dégagée :
- Découpez aux frontières sémantiques. Pour les codes, découpez par article. Pour les jugements, par section. Pour les règlements, gardez la hiérarchie — partie, chapitre, article — dans les métadonnées du bloc.
- Transportez le contexte avec le bloc. Chaque bloc stocke son document, sa juridiction, sa langue et son chemin de section. Ces métadonnées ne sont pas de la décoration ; c'est le filtre de récupération.
def chunk_legal_document(doc):
chunks = []
for section in split_by_structure(doc):
chunks.append({
"text": section.text,
"meta": {
"jurisdiction": section.jurisdiction, # MA | FR | OHADA
"doc_type": section.doc_type, # code | jugement | règlement
"path": section.path, # Livre > Titre > Article
},
})
return chunks
Récupération hybride avec un reranker
Les embeddings denses seuls échouent sur le texte juridique. Le langage juridique est formulé — « nonobstant », « au cas où », « sous réserve de » — et les synonymes sont létaux : l'article correct utilise des termes que la question n'emploie jamais. La recherche par mots-clés attrape le langage légal exact ; la recherche dense attrape la paraphrase ; aucune des deux ne suffit seule.
JurIA utilise une récupération hybride sur Milvus :
- Passe dense avec des embeddings entraînés (ou affinés) pour le texte juridique français et arabe.
- Passe creuse/mots-clés avec les termes légaux pondérés par fréquence documentaire.
- Fusion des scores, puis un reranker cross-encoder sur les 40 meilleurs candidats pour produire le top-5 final.
Le reranker est le composant le plus sous-estimé de la stack. La récupération vous donne 40 candidats plausibles ; le reranker décide lesquels répondent réellement à la question. En RAG juridique, la précision en tête est non négociable — un mauvais article cité avec assurance est pire que pas de réponse du tout.
Les citations sont le produit
L'étape de génération a une règle dure : le modèle ne peut répondre qu'à partir des passages récupérés, et chaque affirmation porte sa source. Tout ce qui n'est pas ancré est refusé, pas deviné. C'est imposé structurellement, pas par prompt. Le prompt ne contient que les passages récupérés ; les objets de citation sont attachés à la réponse depuis les métadonnées de récupération, pas générés par le modèle.
// chaque réponse embarque des citations lisibles par machine
{
answer: "Aux termes de l'article X du code civil marocain…",
citations: [
{ article: "X", source: "C.civ., art. X", jurisdiction: "MA" },
],
}
Ce que cela achète en pratique : un avocat peut vérifier une réponse en moins d'une minute, et un article halluciné ne peut pas être cité car la citation vient des métadonnées du bloc, pas du flux de tokens.
La juridiction comme filtre de première classe
Le Maroc, la France et l'OHADA sont des systèmes juridiques différents avec des sources différentes. Les confondre est pire que de ne rien renvoyer — un article du code français semble plausible et est tout simplement faux en droit marocain. La juridiction n'est donc pas un raffinement de recherche ; c'est un filtre dur appliqué avant la récupération, et elle est exposée dans l'UI comme un sélecteur que l'utilisateur contrôle explicitement.
L'évaluation : le moteur ennuyeux de la confiance
Le niveau d'exigence pour le travail juridique est impitoyable. Nous évaluons sur trois axes :
- Qualité de la récupération : recall@k contre un jeu d'or de paires question→article.
- Ancrage : la fraction des affirmations de la réponse soutenue par le passage cité (vérifiée avec un juge LLM plus une revue humaine sur un échantillon).
- Exactitude juridique : revue par des gens qui pratiquent le droit, pas par des gens qui écrivent des articles de blog sur le RAG.
La leçon : construisez le jeu d'or avant d'ajuster le modèle. Chaque amélioration que vous pensez apporter n'est que du bruit tant qu'il n'y a pas un jeu étiqueté pour la mesurer. C'est la discipline qui sépare une démo d'un produit, et c'est 80 % du temps d'ingénierie réel.
Le piège multilingue
Le français et l'arabe dans le même corpus, c'est là qu'un système RAG s'effondre en silence, car ils échouent dans des directions différentes. Le texte juridique français est dense et latin, avec des termes presque impossibles à récupérer par synonymes (« nullité », « caducité », « inopposabilité » — trois concepts juridiques différents qu'un profane pourrait confondre). Le texte juridique arabe a son propre défi : la morphologie. Une seule racine verbale génère une famille de formes, et la correspondance exacte les manque toutes.
La réponse pratique est d'arrêter de traiter la langue comme un seul problème. Segmentez la récupération par langue, plongez dans des contextes sensibles à la langue et — point crucial — laissez les métadonnées faire le travail lourd que les embeddings ne peuvent pas faire. Quand une question arrive en français, filtrez d'abord sur le corpus français ; la couche de récupération n'a jamais à deviner la langue de la source, car la source l'a dit au moment de l'ingestion. La récupération multilingue n'est pas un problème de modèle ; c'est un problème de structure de données déguisé en problème de modèle.
Le budget de latence que personne ne budgète
Les utilisateurs juridiques n'attendront pas huit secondes pour une réponse, si correcte soit-elle. Le pipeline complet — récupération hybride, reranking, génération — doit tenir dans un budget où l'utilisateur ne remarque jamais qu'il attend. Cela a forcé une ingénierie peu à la mode : cache de requêtes pour les questions répétées, récupération par blocs pour que le reranker ne voie jamais qu'un petit ensemble de candidats, et une étape de génération en streaming pour que le premier token arrive pendant que l'utilisateur lit encore la question. La latence dans un outil professionnel est un signal de confiance ; chaque seconde gaspillée se lit comme de l'incertitude.
Et ensuite
Le pipeline de corpus et la couche de récupération sont le focus actuel ; l'interface de réponse est une app Next.js. Quand le niveau d'exactitude sera atteint, JurIA deviendra un produit live sous l'X-Ecosystem. Si vous construisez du RAG juridique — surtout en français ou en arabe — je serais ravi de comparer nos notes sur le chunking et l'évaluation. Contactez-moi.