高级 12 分钟法律

基于判例法的 RAG Légifrance

直接回答

要构建基于判例的 RAG 系统,请下载法国法律与行政信息局(DILA)的开放 XML 档案(CASS 收录刊载于法国最高法院公报的判决,INCA 收录未刊载于该公报的判决),按各判决的章节切分,使用 BGE-M3 在 Qdrant 中建立索引,并要求模型引用上诉案号和 ECLI。全量档案压缩后只有几百 MB,而不是几十 GB。

法国司法判例以开放数据形式发布,但这些数据集有明确的覆盖范围、特定的 XML 结构,以及通用教程忽略的陷阱。本指南将基于这些裁判文书构建本地语义搜索引擎,涵盖下载、XML 读取、按章节分块、索引构建、带筛选条件的搜索,以及需要向用户说明的局限。

作者: Mohamed Meguedmi·更新于 2026-09-30·已在 Windows、macOS 和 Linux 上测试

#什么是法律 RAG,以及我们希望它完成什么

法律 RAG(检索增强生成)系统由针对司法判决的语义搜索引擎和一个根据检索到的段落撰写回答的模型组成。检索过程将问题转换为向量,从判决数据库中找出最相近的文本片段,然后由模型综合这些片段并引用其出处。与单独使用模型相比,这在法律领域具有决定性的优势:模型不是凭记忆作答,而是依据您可以重新查阅的文本回答,并附上审理法院、日期、撤销原判上诉案号和 ECLI 标识符。

本指南使用法国法律与行政信息局(DILA)发布的开放数据,在本地机器上构建这一引擎:法律专业人员提出的任何问题都不会发送到外部服务。典型用法是提出这样的问题:“解除固定期限劳动合同(CDD):法国最高司法法院最近有哪些判决?”,然后获得一份附有来源的文本片段列表。系统不出具法律意见:它负责检索和引用,具体的法律定性由专业人员完成。

i
本指南的独特之处
大多数RAG教程都基于您拥有的文档。而在这里,难点在于:真正理解公开数据集的内容、其XML结构、数据量、更新频率以及覆盖范围的限制。以下部分的每个数字均来自官方来源。

#官方数据集:其实际内容

本地 RAG 套件

你的文档,你的 AI:基于你的 PDF、笔记和邮件的可靠本地 RAG——无需向云端发送任何内容。

  • 在线空间,终身可用
  • PDF + 文件
  • 终身更新

DILA 在不同的数据库中发布司法裁判,每个数据库都有各自的收录范围。有一点很重要,也经常被误解:这些数据库并未收录法国作出的所有司法裁判。以 CASS 名称发布的法国最高法院判决库收录了刊登于《公报》的判决,包括民事庭自 1960 年以来和刑事庭自 1963 年以来的判决,并附有司法官编写的主题标题和摘要。未刊登于《公报》的未公开判决则收录在单独的 INCA 数据库中,该数据库自 1989 年起对外发布。

DILA司法判例库(根据data.gouv.fr提供的资料)
Base内容全量数据归档(压缩版)
CASS法国最高法院刊登于《公报》的判决(民事判决自1960年起,刑事判决自1963年起)约 248 MB
INCA自 1989 年以来法国最高司法法院未刊载于《公报》的判决约655 MB
CAPP上诉法院及一审法院的部分民事和刑事裁判约279 MB
JADE最高行政法院、行政上诉法院、权限争议法院(按法院选择)约1.2 GB

上述大小是2026年9月30日查阅 DILA 服务器时列出的各数据库完整归档文件的大小:每个数据库压缩后只有几百兆字节,远低于有时被引用的数十吉字节。解压后的体积更大,因为每份裁判文书都是一个小型 XML 文件,但一台普通电脑就足够了。规划前,请先在您的磁盘上测量解压后的实际体积。

还有另一种途径:Judilibre API,由法国最高司法法院实施,免费向公众提供一个开放数据库,收录公开作出的裁判,这些裁判可能经过内容补充和假名化处理。它通过需要身份验证的编程接口提供访问,而 DILA 的档案则只是可供下载的文件。对于本地 RAG,档案是最简单的起点;当您需要更完整、更新的资料库时,Judilibre 就值得考虑。

!
个人数据:责任由您承担
DILA 在其资料页中明确指出,提供可能包含个人数据的数据集,并不免除再利用者遵守《信息与自由法》的义务。裁判文书已进行假名化处理(例如用 [V] [S] 这样的标记替代姓名),但绝不要尝试重新识别相关人员的身份,并请与您的数据保护官确认项目的范围。

#下载资料库:先下载存量数据,再下载更新

每个数据库在 DILA 服务器上都采用相同的结构:先是一份全量归档,其名称以 Freemium 开头、以 global 结尾,然后是包含新增内容的增量归档。截至查阅时,最高司法法院的全量归档日期为 2025 年 7 月 13 日,每周归档则补充了此后的内容,直至 2026 年 9 月底。因此,需要先下载全量归档,再按时间顺序应用此后的所有增量归档。

先下载 CASS 全量数据,再下载更新数据
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

DILA 重新生成全量数据归档时,归档名称会发生变化:在代码中写死名称之前,请重新查看目录列表。也要避免反复下载整个目录:一份全量数据归档加上增量文件就足够了,递归镜像会给公共服务器增加不必要的负担。

#读取 DILA XML,避免选错字段

该格式是一组供多个数据库共用的文档类型定义,由 DILA 以 DTD Légifrance 的名称发布。在 2026 年 9 月的一份每周归档中,法国最高法院(Cour de cassation)判决的结构如下:一个通用元数据块(标识符、性质)、一个法律元数据块(标题、判决日期、法院、裁判结果),以及一个专用于普通司法体系的块(案件编号、审判组织、ECLI、是否刊登于公报的标记)。全文位于文本块的内容元素中,换行以 br 标签编码。

教程中常见两个误区。首先,法律信息区块中的NUMERO字段是内部编号;法律专业人士引用的最高法院上诉案件编号位于专用区块的NUMEROS_AFFAIRES之下。其次,用itertext提取文件的全部文本,会将元数据混入判决正文,污染向量:必须定位到内容元素。

解析 CASS 判决(结构已用一份 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)
→
针对每个数据库进行适配
这些路径是在CASS中查得的。JADE、CAPP和INCA数据库共用Légifrance的DTD,但各自的专用区块有所不同:编写解析器前,请先打开每个数据库中的两三个文件,并用包含一百份裁判文书的样本进行测试。

#按裁判文书结构切分,而非按 token 数量

法国最高法院(Cour de cassation)近期的判决遵循一种可识别的结构。在所审查的判决中,可以看到「Faits et procédure」(事实与程序)、「Examen des moyens」(理由审查)这些标题,随后每项理由下都有「Énoncé du moyen」(理由陈述)和「Réponse de la Cour」(法院答复),最后是由「PAR CES MOTIFS」(基于上述理由)引出的裁判主文。每700个token切分一次,会将提交给法院的问题与法院的答复分开,产生含义不明确的片段。请先按这些标题切分,再仅对过长的块进一步拆分。

第二项改进成本低、收益高:在每个片段前加上其头部信息(裁判文书标题和 ECLI)。像“上诉法院颠倒了举证责任”这样的孤立片段,既没有说明它出自哪个法院,也没有说明日期。加上头部信息后,嵌入模型和生成器都能获得上下文。对于结构不同的较早裁判文书,则改用按段落切分、片段之间保留重叠内容的方式。

按章节分块并附带标题
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

#使用 BGE-M3 和 Qdrant 进行索引

根据官方模型卡,BGE-M3 是一个多语言嵌入模型,可生成 1 024 维向量,并接受最长 8 192 token 的输入。在常见用途下,它能较好地处理法律法语;不过,仍应使用您自己的问题评测它。Qdrant 适合存储向量及其元数据。其 Python 客户端提供无需服务器的本地模式,但文档将该模式定位于开发、原型制作和测试:对于数十万个文本片段,请启动服务器(例如使用 Docker)。

常见的教程示例中有一个不易察觉的错误:将裁判文书的索引用作数据点的标识符,会覆盖同一份裁判文书中除最后一个片段以外的所有片段。每个片段都需要唯一的标识符。另一个细节是:要按日期筛选,请在元数据中存储 YYYYMMDD 格式的整数(四位年份、两位月份、两位日期),这样就可以按日期范围筛选。

批量索引
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()

检索时,使用同一个模型对问题进行编码,再向 Qdrant 请求最相近的文本片段,必要时通过过滤条件限定范围。按审判庭、裁判结果或日期过滤,相较于传统全文检索是一项实质优势:“社会庭,2023 年以来”会转化为两个元数据条件,而不是仅在查询中多加一个词。

过滤搜索
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))

#为什么在法律领域仅靠语义检索还不够

法律专业人士经常需要查找精确的信息:向最高法院提起上诉的案件编号、法条编号或惯用法律术语。语义相似度检索难以找到这些内容,因为两个数值相近的编号在含义上并无关联。BGE-M3 的作者也在模型说明中建议,RAG 采用以下流程:先进行混合检索,再进行重排序。因此,请在向量检索之外增加 BM25 一类的词法检索,合并两个结果列表,再将排名靠前的候选项输入重排序模型。

对于法律文献库,除了良好的文本切分之外,这项补充是最重要的改进。关于混合检索和重排序的指南详细介绍了实施方法;本指南仅讨论判例资料所特有的问题。

#生成、更新与引用核查

生成时,请将五到八个最佳摘录及其来源信息传给模型,并要求它仅依据这些摘录回答。像 Qwen 3.5 9B 这样的 90 亿参数模型(在 Ollama 模型库中大小为 6.6 GB)足以概括摘录;Mistral Small 24B(14 GB)则需要 16 GB 显存。提示词必须要求模型为每项陈述注明上诉案号和 ECLI,并在摘录无法回答问题时回复“未找到任何裁判”。

用于总结的系统提示词
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.

在更新方面,根据查阅的清单,DILA 大约每周为 CASS 和 INCA 发布一次增量归档。定时任务应下载尚未获取的归档,对其进行解析并添加摘录,同时使用稳定的标识符来避免重复。最后,请通过程序验证回答中引用的每条参考是否都出现在所提供的摘录中:这与合同分析指南中的核查逻辑相同。

#需向用户说明的局限

部分覆盖
CASS 仅包含刊登在公报中的判决,INCA 包含未刊载的判决,CAPP 提供上诉法院判决的精选:数据库中未收录某项裁决,并不意味着该裁决不存在。
尚未收录或作出时间过近的裁判文书
一项刚作出的裁判可能尚未收录在最新的增量归档中。处理敏感案件时,请与 Légifrance 上的信息交叉核对。
法律演变
较早的判决可能已因判例立场的改变或法律改革而不再适用。系统能检索到文本,却无法判断其中的法律结论如今是否仍具有权威性。
假名化
姓名已隐藏。绝不要试图查明当事人的身份。
不提供法律意见
该工具帮助查找和引用资料。事实的法律定性、将法律适用于具体案件以及提供咨询,仍由专业人士负责。
FAQ
什么是法律 RAG?+
这是一种系统:先在裁判文书库或文本库中检索与问题相关的片段,再让模型仅依据这些片段撰写回答,并附上相应的引用来源。它在法律领域的价值在于可追溯性:每一项陈述都指向一份专业人士可以重新查阅的裁判文书。
在哪里可以找到最高司法法院(Cour de cassation)的开放判例数据?+
可以从 DILA 服务器获取:刊登于《公报》的判决收录在 CASS 档案中,未刊登的判决收录在 INCA 档案中;也可以通过法国最高司法法院(Cour de cassation)的 Judilibre API 获取。相关数据集的介绍页面位于 data.gouv.fr。这些档案只是简单的 XML 文件,很容易在本地处理。
数据集是否完整?+
不完整。CASS 收录刊登在《公报》上的判决,INCA 收录未刊载的判决,CAPP 收录部分上诉法院裁判,JADE 收录部分行政司法裁判。若需要覆盖更广的裁判数据库,法国最高司法法院提供 Judilibre。绝不要仅因某项裁判未收录在您的数据库中,就断言它不存在。
处理法语法律文本该选择哪个嵌入模型?+
BGE-M3 是一个常见选择:支持多语言,生成 1,024 维向量,据其模型说明,输入长度最高可达 8,192 个 token。任何综合排名都不能替代测试:请设计二十个法律专业人士会提出的问题,并列出预期应检索到的司法判决,再用这个样本比较多个模型的召回率。
为裁判文书建立索引是否需要 GPU?+
GPU 并非必需,但使用显卡对数十万个片段进行编码会快得多。没有 GPU 也能完成索引:请安排在夜间分批执行,并且只做一次。数据库建好后,针对单个问题的检索在 CPU 上仍然很快,每周更新的负担也很小。
这些司法裁判可以用于商业产品吗?+
data.gouv.fr上的数据集说明页面列出了各数据集的许可证,通常采用开放许可证,允许在注明来源的前提下再利用。不过,DILA提醒,再利用者仍须遵守《信息与自由法》。请核实许可证,并请您的数据保护官审核批准该项目。
这份指南对您有帮助吗?

有反馈、发现了错误,或想补充说明?请告诉我们,让这份指南对每个人都更有帮助。