Avanzato 12 minGiuridico

RAG sulla giurisprudenza Légifrance

Risposta diretta

Per un RAG sulla giurisprudenza, scarica gli archivi XML aperti della DILA (CASS per le sentenze pubblicate nel Bollettino della Corte di cassazione francese, INCA per quelle inedite), suddividi ogni sentenza secondo le sue sezioni, indicizza con BGE-M3 in Qdrant e fai citare il numero del ricorso per cassazione e l'ECLI. Gli archivi completi pesano alcune centinaia di MB compressi, non decine di GB.

La giurisprudenza francese è pubblicata come dati aperti, ma i suoi dataset hanno un ambito preciso, una struttura XML particolare e insidie che i tutorial generici ignorano. Questa guida costruisce un motore di ricerca semantica locale su tali decisioni: download, lettura dell'XML, suddivisione in sezioni, indicizzazione, ricerca filtrata e limiti da mostrare agli utenti.

Di Mohamed Meguedmi·Agg. 2026-09-30·Testato su Windows, macOS e Linux

#Cos'è un RAG giuridico e cosa gli si chiede

Un RAG giuridico è un motore di ricerca semantica su decisioni giudiziarie, abbinato a un modello che redige una risposta a partire dai passaggi recuperati. La ricerca trasforma la domanda in un vettore, recupera gli estratti più vicini in una banca dati di decisioni, poi il modello li sintetizza citandone i riferimenti. Il vantaggio rispetto a un modello da solo è decisivo in ambito giuridico: il modello non risponde a memoria, ma a partire da testi che puoi rileggere, con l'indicazione dell'organo giudicante, della data, del numero di ricorso e dell'identificativo ECLI.

Questa guida costruisce questo motore con i dati aperti pubblicati dalla Direzione dell'informazione legale e amministrativa (DILA), su una macchina locale: nessuna domanda di un giurista viene inviata a un servizio esterno. Il caso d'uso è una domanda come «risoluzione di un CDD: quali decisioni recenti della Corte di cassazione?», che restituisce un elenco di passaggi corredati delle relative fonti. Il sistema non esprime un parere: recupera e cita, e spetta al professionista effettuare la qualificazione giuridica.

i
Cosa distingue questa guida
La maggior parte dei tutorial sul RAG parte da documenti che possiedi. Qui, la difficoltà è altrove: capire cosa contengono realmente i dataset pubblici, la loro struttura XML, il loro volume, la loro frequenza di aggiornamento e i loro limiti di copertura. Ogni numero della sezione successiva è stato rilevato nelle fonti ufficiali.

#I dataset ufficiali: cosa contengono veramente

Il kit RAG Local

I tuoi documenti, la tua IA: un RAG locale affidabile sui tuoi PDF, sulle tue note e sulle tue email — senza inviare nulla nel cloud.

  • Spazio online a vita
  • PDF + file
  • Aggiornamenti a vita

La DILA diffonde le decisioni in banche dati distinte, ciascuna con il proprio ambito. Un punto importante, spesso frainteso: queste banche dati non contengono tutte le decisioni pronunciate in Francia. La raccolta della Corte di cassazione pubblicata con il nome CASS riunisce le sentenze pubblicate nel Bollettino, quelle delle sezioni civili dal 1960 e della sezione penale dal 1963, con titoli e sintesi redatti dai magistrati. Le sentenze inedite, non pubblicate nel Bollettino, si trovano in una banca dati separata, INCA, diffusa dal 1989.

I database di giurisprudenza della DILA (secondo le schede data.gouv.fr)
BaseContenutoArchivio completo (compresso)
CASSSentenze della Corte di cassazione pubblicate nel Bollettino (civili dal 1960, penali dal 1963)circa 248 MB
INCASentenze inedite della Corte di cassazione, non pubblicate nel Bollettino, dal 1989circa 655 MB
CAPPSelezione di decisioni civili e penali delle corti d'appello e degli organi giudiziari di primo gradocirca 279 MB
JADEConsiglio di Stato, corti amministrative di appello, Tribunale dei conflitti (selezione in base alla giurisdizione)circa 1,2 GB

Le dimensioni indicate sopra sono quelle degli archivi globali elencati sul server della DILA al momento della consultazione del 30 settembre 2026: alcune centinaia di megabyte compressi per banca dati, ben lontane dalle decine di gigabyte che a volte vengono citate. Il volume decompresso è maggiore, perché ogni decisione è un piccolo file XML, ma un computer standard è sufficiente. Misuralo sul tuo disco prima di pianificare.

Esiste una seconda via: l'API Judilibre, implementata dalla Cour de cassation per mettere gratuitamente a disposizione del pubblico una banca dati aperta alimentata dalle decisioni pronunciate pubblicamente, eventualmente arricchite e pseudonimizzate. L'accesso avviene tramite un'interfaccia di programmazione con autenticazione, mentre gli archivi della DILA sono semplici file da scaricare. Per un RAG locale, gli archivi sono il punto di partenza più semplice; Judilibre diventa pertinente quando vuoi una raccolta più completa e più aggiornata.

!
Dati personali: la responsabilità è tua
La DILA precisa nelle sue schede che la messa a disposizione di insiemi di dati che possono contenere dati personali non esonera chi li riutilizza dal rispetto della legge Informatique et Libertés. Le decisioni sono pseudonimizzate (passaggi come [V] [S] sostituiscono nomi), ma non tentare mai di reidentificare le persone e verifica l'ambito del tuo progetto con il tuo responsabile della protezione dei dati.

#Scaricare l'archivio: prima il corpus completo, poi gli aggiornamenti

Ogni banca dati segue lo stesso schema sul server della DILA: un archivio completo, il cui nome inizia con Freemium e termina con global, seguito da archivi incrementali che aggiungono le novità. Alla data di consultazione, l'archivio completo della Corte di cassazione risaliva al 13 luglio 2025 e gli archivi settimanali lo integravano fino alla fine di settembre 2026. È quindi necessario scaricare l'archivio completo, poi applicare tutti gli archivi incrementali successivi in ordine.

Scaricare l'archivio completo CASS, poi gli aggiornamenti
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

Il nome dell'archivio completo cambia quando la DILA lo rigenera: rileggi l'elenco della cartella prima di inserire un nome fisso nel codice. Evita anche di scaricare ripetutamente la cartella: bastano un solo archivio completo e gli aggiornamenti incrementali, e un mirror ricorsivo sovraccarica inutilmente il server pubblico.

#Leggere l'XML DILA senza confondere i campi

Il formato è una famiglia di definizioni di tipi di documento comuni a diversi database, pubblicata dalla DILA con il nome DTD Légifrance. In un archivio settimanale di settembre 2026, la struttura di una sentenza della Corte di cassazione è la seguente: un blocco di metadati comuni (identificativo, natura), un blocco di metadati giuridici (titolo, data della decisione, organo giudicante, esito) e un blocco specifico della giustizia ordinaria (numero della causa, composizione del collegio, ECLI, indicatore di pubblicazione nel Bulletin). Il testo integrale si trova nel blocco testuale, sotto l'elemento di contenuto, con interruzioni di riga codificate tramite tag br.

Nei tutorial ricorrono due insidie. Innanzitutto, il campo NUMERO del blocco giuridico è un numero interno; il numero del ricorso in cassazione, quello citato dai giuristi, si trova nel blocco specifico sotto NUMEROS_AFFAIRES. Inoltre, recuperare tutto il testo del file con itertext mescola i metadati al corpo della sentenza e contamina i vettori: bisogna selezionare l'elemento che contiene il testo.

Analizzare una sentenza CASS (struttura verificata su un archivio del 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)
→
Adattare a ogni base
Questi percorsi sono stati rilevati su CASS. I database JADE, CAPP e INCA condividono la DTD Légifrance ma hanno blocchi specifici diversi: apri due o tre file di ciascun database prima di scrivere il parser e testalo su un campione di cento decisioni.

#Suddividere in base alla struttura della decisione, non al numero di token

Una decisione recente della Corte di cassazione segue una struttura riconoscibile. Nelle sentenze esaminate si trovano i titoli « Faits et procédure », « Examen des moyens », poi, per ogni motivo, « Énoncé du moyen » e « Réponse de la Cour », e infine il dispositivo introdotto da « PAR CES MOTIFS ». Suddividere il testo ogni 700 token separa la questione sottoposta alla Corte dalla sua risposta e produce estratti ambigui. Suddividi prima il testo in base a questi titoli, poi fraziona soltanto i blocchi troppo lunghi.

Secondo miglioramento, che costa poco e rende molto: anteponi a ogni estratto la sua intestazione (titolo della decisione ed ECLI). Un estratto isolato del tipo «la corte d'appello ha invertito l'onere della prova» non indica né da quale organo giurisdizionale provenga né a quale data risalga. Con l'intestazione, sia il modello di embedding sia il generatore dispongono del contesto. Per le decisioni più datate, la cui struttura è diversa, torna a una suddivisione in paragrafi con sovrapposizione.

Divisione per sezioni con intestazione
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

#Indicizzare con BGE-M3 e Qdrant

BGE-M3 è un modello di embedding multilingue che genera vettori di 1.024 dimensioni e accetta input fino a 8.192 token, secondo la sua scheda ufficiale. Gestisce correttamente il francese giuridico negli usi comuni; valutalo comunque sulle tue domande. Qdrant è adatto per memorizzare i vettori insieme ai loro metadati. Il suo client Python offre una modalità locale senza server, ma la documentazione la indica per lo sviluppo, la prototipazione e i test: per diverse centinaia di migliaia di estratti, avvia il server (ad esempio con Docker).

L'esempio comunemente usato nei tutorial contiene un bug silenzioso: utilizzare l'indice della decisione come identificatore di un punto sovrascrive tutti gli estratti di una stessa decisione tranne l'ultimo. È necessario un identificatore unico per ogni estratto. Un altro dettaglio: per filtrare per data, salva un intero nella forma AAAAMMJJ nei metadati, il che permette un filtro per intervallo.

Indicizzazione a lotti
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 ricerca codifica la domanda con lo stesso modello e chiede a Qdrant gli estratti più vicini, eventualmente limitati da un filtro. I filtri sulla composizione del collegio, sull'esito o sulla data sono un vero vantaggio rispetto a una ricerca full-text classica: «sezione sociale, dal 2023» si traduce in due condizioni sui metadati, non in una parola in più nella query.

Ricerca filtrata
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))

#Perché la sola ricerca semantica non basta nel diritto

Un giurista cerca spesso elementi esatti: un numero di ricorso in cassazione, un numero di articolo, un'espressione consolidata. La similarità semantica fatica a ritrovarli, perché due numeri vicini non hanno alcuna relazione di significato. Gli autori di BGE-M3 raccomandano del resto, nella scheda del modello, la seguente pipeline per il RAG: ricerca ibrida seguita da riordinamento dei risultati. Aggiungi quindi una ricerca lessicale di tipo BM25 accanto ai vettori, unisci le due liste, poi passa i migliori candidati a un modello di riordinamento.

Su un corpus giuridico, questa aggiunta è il miglioramento più importante dopo una buona suddivisione in segmenti. Le guide sulla ricerca ibrida e sul riordinamento dei risultati spiegano in dettaglio l'implementazione; questa guida si limita agli aspetti specifici della giurisprudenza.

#Generazione, aggiornamenti e controllo delle citazioni

Per la generazione, invia al modello da cinque a otto dei migliori estratti con i relativi riferimenti e imponi di rispondere soltanto basandosi su di essi. Un modello da 9 miliardi di parametri come Qwen 3.5 9B (6,6 GB nella libreria Ollama) basta per riassumere estratti; Mistral Small 24B (14 GB) richiede 16 GB di VRAM. Il prompt deve richiedere di citare, per ogni affermazione, il numero del ricorso e l'ECLI e di rispondere «nessuna decisione trovata» quando gli estratti non rispondono alla domanda.

Prompt di sistema per la sintesi
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.

Per quanto riguarda gli aggiornamenti, la DILA pubblica archivi incrementali, circa ogni settimana per CASS e INCA secondo gli elenchi consultati. Un processo pianificato deve scaricare quelli mancanti, analizzarli e aggiungere gli estratti, con identificatori stabili per evitare duplicati. Verifica infine tramite un programma che ogni riferimento citato nella risposta sia presente negli estratti forniti: è la stessa logica di controllo della guida sull'analisi dei contratti.

#Limiti da mostrare agli utenti

Copertura parziale
CASS contiene solo le sentenze pubblicate nel Bollettino, INCA quelle inedite, CAPP una selezione delle sentenze delle corti d'appello: l'assenza di una decisione nel database non dimostra che non esista.
Decisioni non ancora inserite o troppo recenti
Una decisione molto recente potrebbe non essere ancora presente nell'ultimo archivio incrementale. Per un dossier sensibile, verifica le informazioni anche su Légifrance.
Evoluzione del diritto
Una vecchia sentenza può essere stata superata da un cambiamento di orientamento giurisprudenziale o da una riforma. Il sistema recupera del testo, non valuta l'autorità attuale di una soluzione giuridica.
Pseudonimizzazione
I nomi sono nascosti. Mai cercare di ricostruire l'identità delle parti.
Nessun parere giuridico
Lo strumento aiuta a trovare e citare. La qualificazione dei fatti, l'applicazione al caso e la consulenza restano di competenza del professionista.
FAQ
Che cosa è un RAG giuridico?+
È un sistema che trova, in una banca dati di decisioni o testi, gli estratti pertinenti a una domanda, poi fa redigere a un modello una risposta basata esclusivamente su quegli estratti, corredata dei relativi riferimenti. Il suo valore in ambito giuridico risiede nella tracciabilità: ogni affermazione rimanda a una decisione che il professionista può rileggere.
Dove trovare la giurisprudenza della Corte di cassazione in formato open data?+
Sul server della DILA, negli archivi CASS per le sentenze pubblicate nel Bollettino e INCA per quelle inedite, oppure tramite l'API Judilibre della Corte di cassazione. Le schede sono su data.gouv.fr. Gli archivi sono semplici file XML, facili da elaborare in locale.
È completo il fondo?+
No. CASS contiene le sentenze pubblicate nel Bulletin, INCA le sentenze inedite, CAPP una selezione di decisioni delle corti d'appello, JADE una selezione per la giustizia amministrativa. Per una raccolta più ampia, la Corte di cassazione propone Judilibre. Non affermare mai che una decisione non esiste perché manca dalla tua banca dati.
Quale modello di embedding scegliere per il francese giuridico?+
BGE-M3 è una scelta comune: multilingue, vettori di 1.024 dimensioni, input fino a 8.192 token secondo la sua scheda. Nessuna classifica generale sostituisce un test: prepara venti domande di giuristi con le decisioni attese e confronta il recall di diversi modelli su questo campione.
Serve una GPU per indicizzare le decisioni?+
Non è obbligatorio, ma la codifica di centinaia di migliaia di estratti è molto più veloce con una scheda grafica. Senza GPU, l'indicizzazione funziona: programmala di notte, a lotti, ed eseguila una sola volta. Una volta costruito il database, la ricerca relativa a una singola domanda resta veloce sulla CPU, e gli aggiornamenti settimanali sono leggeri.
È possibile utilizzare queste decisioni in un prodotto commerciale?+
Le schede di data.gouv.fr indicano la licenza di ogni dataset, generalmente una licenza aperta che consente il riutilizzo con citazione della fonte. La DILA ricorda tuttavia che chi riutilizza i dati resta soggetto alla legge Informatique et Libertés. Verifica la licenza e fai approvare il progetto dal tuo responsabile della protezione dei dati.
Questa guida ti è stata utile?

Un feedback, un errore, una precisazione? Facci sapere, così la guida migliora per tutti.