Avanzado 12 minJurídico

RAG sobre la jurisprudencia Légifrance

Respuesta directa

Para un RAG sobre jurisprudencia, descarga los archivos XML abiertos de la DILA (CASS para las sentencias publicadas en el Boletín del Tribunal de Casación, INCA para las no publicadas), divide cada sentencia según sus secciones, indéxalas con BGE-M3 en Qdrant y haz que se citen el número del recurso de casación y el ECLI. Los archivos completos de cada base pesan unos cientos de MB comprimidos, no decenas de GB.

La jurisprudencia francesa se publica como datos abiertos, pero sus conjuntos de datos tienen un alcance preciso, una estructura XML particular y dificultades que los tutoriales genéricos pasan por alto. Esta guía construye un motor de búsqueda semántica local sobre estas resoluciones: descarga, lectura del XML, división por secciones, indexación, búsqueda filtrada y límites que deben mostrarse a los usuarios.

Por Mohamed Meguedmi·Actualización 2026-09-30·Probado en Windows, macOS y Linux

#¿Qué es un RAG jurídico y qué se le pide?

Un RAG jurídico es un motor de búsqueda semántica sobre resoluciones judiciales, combinado con un modelo que redacta una respuesta a partir de los pasajes recuperados. La búsqueda transforma la pregunta en un vector, recupera los fragmentos más cercanos en una base de resoluciones y luego el modelo los sintetiza citando sus referencias. La ventaja frente a un modelo por sí solo es decisiva en derecho: el modelo no responde de memoria, sino a partir de textos que puedes volver a leer, con el órgano jurisdiccional, la fecha, el número del recurso de casación y el identificador ECLI.

Esta guía construye este motor con los datos abiertos publicados por la Dirección de Información Legal y Administrativa (DILA), en una máquina local: ninguna pregunta de un jurista se envía a un servicio externo. El caso de uso es una pregunta como «Rescisión de un CDD: ¿qué decisiones recientes hay del Tribunal de Casación?», que devuelve una lista de pasajes con sus fuentes. El sistema no emite un dictamen: recupera y cita, y corresponde al profesional realizar la calificación jurídica.

i
Lo que distingue esta guía
La mayoría de los tutoriales de RAG parten de documentos que tú posees. Aquí, el problema está en otro lugar: comprender realmente qué contienen los conjuntos de datos públicos, su estructura XML, su volumen, su frecuencia de actualización y sus limitaciones de cobertura. Cada cifra de la sección siguiente se ha obtenido de fuentes oficiales.

#Conjuntos de datos oficiales: lo que realmente contienen

El kit RAG Local

Tus documentos, tu IA: un RAG local fiable sobre tus PDF, notas y correos — sin enviar nada a la nube.

  • Espacio en línea de por vida
  • PDF + archivos
  • Reembolsado 30 j

La DILA difunde las decisiones en bases separadas, cada una con su ámbito. Un punto importante, a menudo mal entendido: estas bases no contienen todas las decisiones dictadas en Francia. El fondo de la Corte de Casación publicado bajo el nombre CASS reúne las sentencias publicadas en el Boletín, las de las salas civiles desde 1960 y las de la sala penal desde 1963, con títulos y resúmenes redactados por los magistrados. Las sentencias inéditas, no publicadas en el Boletín, están en una base separada, INCA, difundida desde 1989.

Bases de jurisprudencia de la DILA (según las fichas data.gouv.fr)
BaseContenidoArchivo del conjunto completo de datos (comprimido)
CASSSentencias del Tribunal de Casación publicadas en el Boletín (civiles desde 1960, penales desde 1963)aproximadamente 248 MB
INCASentencias inéditas del Tribunal de Casación, no publicadas en el Boletín, desde 1989alrededor de 655 MB
CAPPSelección de resoluciones civiles y penales de los tribunales de apelación y de los órganos judiciales de primera instanciaaproximadamente 279 MB
JADEConsejo de Estado, tribunales administrativos de apelación, Tribunal de Conflictos (selección según la jurisdicción)aproximadamente 1,2 GB

Los tamaños indicados arriba corresponden a los archivos comprimidos globales que figuraban en el servidor de la DILA cuando se consultó el 30 de septiembre de 2026: unos cientos de megabytes comprimidos por base de datos, muy lejos de las decenas de gigabytes que a veces se citan. El volumen descomprimido es mayor, porque cada resolución es un pequeño archivo XML, pero basta con un equipo estándar. Mídelo en tu disco antes de planificar.

Existe una segunda vía: la API Judilibre, implementada por el Tribunal de Casación para poner a disposición del público, de forma gratuita, una base abierta alimentada por las resoluciones dictadas públicamente, que pueden estar enriquecidas y seudonimizadas. Se accede a ella a través de una interfaz de programación con autenticación, mientras que los archivos de la DILA son simples ficheros para descargar. Para un RAG local, los archivos son el punto de partida más sencillo; Judilibre resulta pertinente cuando quieres una colección más completa y actualizada.

!
Datos personales: la responsabilidad recae en ti
La DILA especifica en sus fichas que poner a disposición conjuntos de datos que puedan contener datos personales no exime a quien los reutiliza de cumplir la ley francesa Informatique et Libertés. Las decisiones están seudonimizadas (fragmentos como [V] [S] sustituyen nombres), pero nunca intentes reidentificar a las personas y verifica el alcance de tu proyecto con tu delegado de protección de datos.

#Descargar el corpus: datos iniciales y luego actualizaciones

Cada base sigue el mismo esquema en el servidor de la DILA: un archivo con el conjunto completo de datos, cuyo nombre empieza por Freemium y termina en global, seguido de archivos incrementales que aportan las novedades. En la fecha de consulta, el archivo con el conjunto completo de datos del Tribunal de Casación databa del 13 de julio de 2025 y los archivos semanales lo completaban hasta finales de septiembre de 2026. Por tanto, hay que descargar el archivo completo y luego aplicar todos los archivos incrementales posteriores en orden.

Descargar el conjunto completo de datos CASS y después las actualizaciones
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

El nombre del archivo de datos completos cambia cuando la DILA lo regenera: revisa el listado del directorio antes de escribir un nombre fijo en el código. Evita también descargar el directorio en bucle: basta con un único archivo de datos completos y las actualizaciones incrementales, y una réplica recursiva sobrecarga innecesariamente el servidor público.

#Leer el XML DILA sin equivocarse en el campo

El formato es una familia de definiciones de tipos de documentos comunes a varias bases, publicada por la DILA con el nombre DTD Légifrance. En un archivo semanal de septiembre de 2026, la estructura de una sentencia del Tribunal de Casación es la siguiente: un bloque de metadatos comunes (identificador, naturaleza), un bloque de metadatos jurídicos (título, fecha de la decisión, órgano jurisdiccional, sentido del fallo) y un bloque específico de la justicia ordinaria (número de asunto, composición del tribunal, ECLI, indicador de publicación en el Boletín). El texto íntegro se encuentra en el bloque textual, dentro del elemento de contenido, con saltos de línea codificados mediante etiquetas br.

Hay dos errores habituales en los tutoriales. Primero, el campo NUMERO del bloque jurídico es un número interno; el número del recurso de casación, el que citan los juristas, está en el bloque específico bajo NUMEROS_AFFAIRES. Segundo, recuperar todo el texto del archivo con itertext mezcla los metadatos con el cuerpo de la sentencia y contamina los vectores: hay que seleccionar el elemento de contenido.

Analizar una sentencia CASS (estructura verificada en un archivo 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)
→
Ajustar a cada base
Estas rutas se han identificado en CASS. Las bases JADE, CAPP e INCA comparten la DTD de Légifrance, pero tienen bloques específicos diferentes: abre dos o tres archivos de cada base antes de escribir el analizador y pruébalo con una muestra de cien decisiones judiciales.

#Dividir según la estructura de la resolución, no por el número de tokens

Una resolución reciente del Tribunal de Casación sigue una estructura reconocible. En las sentencias examinadas, aparecen los títulos «Faits et procédure», «Examen des moyens», luego, para cada motivo, «Énoncé du moyen» y «Réponse de la Cour», y finalmente el fallo, introducido por «PAR CES MOTIFS». Dividir cada 700 tokens separa la cuestión planteada al Tribunal de su respuesta y produce fragmentos ambiguos. Divide primero según estos títulos y luego subdivide solo los bloques demasiado largos.

Segunda mejora, que cuesta poco y aporta mucho: antepón a cada extracto su encabezado (título de la resolución y ECLI). Un extracto aislado como «el tribunal de apelación invirtió la carga de la prueba» no indica de qué órgano jurisdiccional procede ni cuál es su fecha. Con el encabezado, tanto el modelo de embeddings como el generador disponen del contexto. Para las resoluciones más antiguas, cuya estructura es distinta, recurre a una segmentación por párrafos con solapamiento.

División por secciones con encabezado
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

#Indexar con BGE-M3 y Qdrant

BGE-M3 es un modelo de embeddings multilingüe que produce vectores de 1 024 dimensiones y acepta entradas de hasta 8 192 tokens, según su ficha oficial. Maneja correctamente el francés jurídico en los usos habituales; aun así, evalúalo con tus propias preguntas. Qdrant es adecuado para almacenar los vectores junto con sus metadatos. Su cliente de Python ofrece un modo local sin servidor, pero su documentación lo destina al desarrollo, al prototipado y a las pruebas: para varios cientos de miles de fragmentos, inicia el servidor (por ejemplo, con Docker).

El ejemplo habitual de los tutoriales contiene un error silencioso: usar el índice de la decisión como identificador de un punto sobrescribe todos los extractos de una misma decisión excepto el último. Se necesita un identificador único por extracto. Otro detalle: para filtrar por fecha, almacena un entero en formato AAAAMMDD en los metadatos, lo que permite filtrar por intervalos.

Indexación por lotes
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 búsqueda codifica la pregunta con el mismo modelo y pide a Qdrant los fragmentos más cercanos, restringidos por un filtro si procede. Los filtros por composición del tribunal, sentido del fallo o fecha ofrecen una verdadera ventaja frente a una búsqueda de texto completo tradicional: «sala de lo social, desde 2023» se traduce en dos condiciones sobre los metadatos, no en una palabra más en la consulta.

Búsqueda filtrada
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))

#¿Por qué una búsqueda exclusivamente semántica no basta en derecho?

Un jurista suele buscar elementos exactos: un número de recurso de casación, un número de artículo, una expresión consagrada. La similitud semántica no los recupera bien, porque dos números cercanos no guardan ninguna relación de significado. De hecho, los autores de BGE-M3 recomiendan en la ficha del modelo el siguiente pipeline para el RAG: búsqueda híbrida y después reordenamiento. Añade, por tanto, una búsqueda léxica de tipo BM25 junto a los vectores, fusiona las dos listas y pasa los mejores candidatos por un modelo de reordenamiento.

En un corpus jurídico, este añadido es la mejora más importante después de una buena segmentación. Las guías sobre búsqueda híbrida y sobre reordenación de resultados detallan la implementación; esta guía se limita a lo que es específico de la jurisprudencia.

#Generación, actualizaciones y control de citas

Para la generación, envía al modelo los cinco a ocho mejores extractos con sus referencias y exige que responda solo a partir de ellos. Un modelo de 9 mil millones de parámetros como Qwen 3.5 9B (6,6 GB en la biblioteca Ollama) basta para resumir extractos; Mistral Small 24B (14 GB) requiere 16 GB de VRAM. El prompt debe exigir que se cite, para cada afirmación, el número del recurso de casación y el ECLI, y que se responda «no se ha encontrado ninguna resolución» cuando los extractos no respondan a la pregunta.

Prompt de sistema de síntesis
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.

En cuanto a las actualizaciones, la DILA publica archivos incrementales, aproximadamente cada semana para CASS y INCA según las listas consultadas. Un proceso programado debe descargar los que faltan, analizarlos y añadir los extractos, con identificadores estables para evitar duplicados. Por último, verifica mediante un programa que cada referencia citada en la respuesta figure en los extractos proporcionados: es la misma lógica de control que en la guía sobre el análisis de contratos.

#Limitaciones que debes mostrar a los usuarios

Cobertura parcial
CASS solo incluye los fallos publicados en el Boletín, INCA los no publicados, CAPP una selección de sentencias de apelación: la ausencia de una decisión en la base no demuestra que no exista.
Resoluciones no incorporadas o demasiado recientes
Una resolución muy reciente puede no estar todavía en el último archivo incremental. Contrasta la información con Légifrance si se trata de un expediente delicado.
Evolución del derecho
Una sentencia antigua puede haber quedado superada por un cambio de jurisprudencia o una reforma. El sistema recupera texto; no evalúa la autoridad actual de una solución jurídica.
Seudonimización
Los nombres están ocultos. Nunca busques identificar a las partes.
Sin asesoramiento jurídico
La herramienta ayuda a encontrar y citar. La calificación de los hechos, su aplicación al caso y el consejo siguen siendo responsabilidad del profesional.
FAQ
¿Qué es un RAG jurídico?+
Es un sistema que recupera, de una base de resoluciones judiciales o textos, los fragmentos relacionados con una pregunta y luego hace que un modelo redacte una respuesta basada únicamente en esos fragmentos, con sus referencias. Su valor en el ámbito jurídico radica en la trazabilidad: cada afirmación remite a una resolución judicial que el profesional puede volver a leer.
¿Dónde encontrar la jurisprudencia de la Corte de Casación en datos abiertos?+
En el servidor de la DILA, en los archivos CASS para las sentencias publicadas en el Boletín e INCA para las no publicadas, o a través de la API Judilibre de la Corte de Casación. Las fichas están en data.gouv.fr. Los archivos son simples ficheros XML, fáciles de procesar en local.
¿Está completo el fondo documental?+
No. CASS contiene las sentencias publicadas en el Boletín, INCA las sentencias inéditas, CAPP una selección de resoluciones de los tribunales de apelación y JADE una selección de resoluciones de la justicia administrativa. Para una colección más amplia, el Tribunal de Casación ofrece Judilibre. Nunca afirmes que una resolución no existe porque no esté en tu base de datos.
¿Qué modelo de embedding elegir para el francés jurídico?+
BGE-M3 es una opción común: multilingüe, vectores de 1.024 dimensiones y entradas de hasta 8.192 tokens según su ficha. Ninguna clasificación general sustituye una prueba: prepara veinte preguntas de juristas con las resoluciones judiciales esperadas y compara la exhaustividad (recall) de varios modelos en esta muestra.
¿Se necesita un GPU para indexar las resoluciones judiciales?+
No es obligatorio, pero la codificación de cientos de miles de fragmentos es mucho más rápida con una tarjeta gráfica. Sin GPU, la indexación funciona: planifícala por la noche, por lotes, y hazla una sola vez. Una vez construida la base, la búsqueda para una pregunta aislada sigue siendo rápida con el procesador, y las actualizaciones semanales son ligeras.
¿Se pueden usar estas decisiones en un producto comercial?+
Las fichas de data.gouv.fr indican la licencia de cada conjunto de datos, generalmente una licencia abierta que permite la reutilización con mención de la fuente. La DILA recuerda, sin embargo, que quien reutiliza los datos sigue sujeto a la ley francesa de Informática y Libertades. Revisa la licencia y pide a tu delegado de protección de datos que valide el proyecto.
¿Esta guía te ha ayudado?

¿Un comentario, un error, una precisión? Avísanos, eso mejora la guía para todos.