上級 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という名称で公開されている破毀院の判例データには、判例集「Bulletin」に掲載された判決が収録されており、民事部の判決は1960年以降、刑事部の判決は1963年以降が対象です。これらには、裁判官が作成した見出しと要旨が付いています。「Bulletin」に掲載されていない未公刊の判決は、1989年から公開されている別のデータベースINCAに収録されています。

DILAの判例データベース(data.gouv.frのデータセット紹介に基づく)
Baseコンテンツ全件アーカイブ(圧縮済み)
CASSフランス破毀院の判例集(Bulletin)に掲載された判決(民事は1960年以降、刑事は1963年以降)約248 MB
INCA1989年以降のフランス破毀院の未公刊判決(判例集Bulletinに未掲載)約655MB
CAPP控訴裁判所および第一審裁判所による民事・刑事の裁判の抜粋約279MB
JADE国務院(Conseil d'État)、行政控訴裁判所、権限争議裁判所(Tribunal des conflits)(裁判機関に応じた選択)約1.2GB

上記のサイズは、2026年9月30日に確認した際にDILAのサーバーに掲載されていた、各データベースの全体アーカイブのサイズです。圧縮状態ではデータベースごとに数百MBで、時折引用される数十GBという数字を大きく下回ります。各判決が小さなXMLファイルになっているため、展開後の容量は大きくなりますが、一般的なパソコンで十分です。計画を立てる前に、ご自身のディスク上で実際の容量を測定してください。

もう一つの方法は、Judilibre APIです。フランスの破毀院が提供するこのAPIでは、公開の場で言い渡された判決を収録し、必要に応じて情報の補足や仮名化を施したオープンなデータベースを、誰でも無料で利用できます。認証付きのプログラミングインターフェースを通じて利用する点が、単にダウンロードするファイルであるDILAのアーカイブとは異なります。ローカルRAGでは、アーカイブが最も手軽な出発点です。より網羅的で最新の資料が必要になったときに、Judilibreが有用になります。

!
個人データ:責任は利用者にあります
DILAは、個人データを含む可能性のあるデータセットが公開されていても、再利用者がフランスの情報処理・ファイル・自由に関する法律(loi Informatique et Libertés)を遵守する義務は免除されないと、各データセットの説明ページに明記しています。判決は仮名化されており、[V] [S]のような表記が氏名の代わりに使われていますが、個人の再識別は決して試みないでください。また、データ保護責任者とプロジェクトの対象範囲を確認してください。

#資料群をダウンロードする:既存データ、次に更新データ

各データベースはDILAのサーバー上で同じ構成になっています。まず、名前がFreemiumで始まりglobalで終わる全件アーカイブがあり、その後に新しいデータを追加する差分アーカイブが続きます。閲覧時点では、フランス破毀院(Cour de cassation)の全件アーカイブは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 が再生成すると、ストックアーカイブの名前が変わります:名前をハードコードする前に、フォルダのリストを再確認してください。また、フォルダをループでダウンロードすることも避けてください。ストックアーカイブ 1 つと増分データで十分であり、再帰的なミラーリングはパブリックサーバーに不要な負荷をかけます。

#フィールドを取り違えずにDILAのXMLを読む

この形式は、複数のデータベースに共通する文書型定義の一群で、DILA が DTD Légifrance という名称で公開しています。2026年9月の週次アーカイブでは、Cour de cassation(破毀院)の判決は、共通メタデータブロック(識別子、種別)、法的メタデータブロック(表題、判決日、裁判所、判決結果)、司法裁判に固有のブロック(事件番号、裁判体、ECLI、Bulletin への掲載の有無を示す指標)で構成されています。全文はテキストブロック内のコンテンツ要素の配下にあり、改行は 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を共有していますが、固有のブロックが異なります。パーサーを書く前に、各データベースから2、3つのファイルを開き、100件の決定のサンプルでテストしてください。

#トークン数ではなく、判決の構造に沿って分割する

近年のフランス破毀院の判決には、一定の構成が見られます。検討した判決には、「Faits et procédure」、「Examen des moyens」という見出しがあり、続いて各上告理由について「Énoncé du moyen」と「Réponse de la Cour」が置かれ、最後に「PAR CES MOTIFS」で始まる主文が続きます。700トークンごとに区切ると、裁判所に提示された問いとその回答が分離され、曖昧な抜粋になってしまいます。まずこれらの見出しに沿って分割し、その後、長すぎるブロックだけをさらに分割してください。

2つ目は、少ないコストで大きな効果が得られる改善です。各抜粋の先頭に見出し(裁判例のタイトルと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トークンの入力を受け付けます。一般的な用途ではフランス語の法律文書を適切に扱えますが、それでも自分の質問で性能を測定してください。Qdrantは、ベクトルをメタデータと一緒に保存するのに適しています。Pythonクライアントにはサーバー不要のローカルモードがありますが、ドキュメントでは開発、プロトタイピング、テスト向けとされています。数十万件の抜粋を扱う場合は、サーバーを起動してください(例えばDockerを使います)。

チュートリアルでよく見かける例には、エラーとして表面化しないバグがあります。判決のインデックスをポイントのIDとして使うと、同じ判決からの抜粋が上書きされ、最後の抜粋だけが残ります。抜粋ごとに一意のIDが必要です。もう一つの注意点として、日付で絞り込むには、メタデータに年月日をAAAAMMJJ形式の整数として保存してください。これにより、日付の範囲で絞り込めます。

バッチ単位でのインデックス作成
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年以降」は、クエリに単語を追加するのではなく、メタデータに対する2つの条件として指定できます。

フィルタ付き検索
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))

#法的文脈において、意味検索だけでは不十分な理由

法律の専門家は、上告事件番号、条文番号、定型表現など、正確な情報を探すことがよくあります。意味的類似性による検索では、こうした情報をうまく見つけられません。数値の近い2つの番号に、意味上の関連はないからです。実際、BGE-M3の作者もモデルカードで、RAGにはハイブリッド検索の後にリランキングを行うパイプラインを推奨しています。そのため、ベクトル検索にBM25のような語彙ベースの検索を併用し、2つの結果リストを統合してから、有力な候補をリランキングモデルに渡してください。

法律文書のコレクションでは、この追加は、適切なチャンク分割に次いで最も重要な改善です。ハイブリッド検索とリランキングのガイドでは実装方法を詳しく説明しています。本ガイドでは、判例に特有の内容に絞って扱います。

#生成、更新、引用の検証

生成時には、最も関連性の高い5〜8件の抜粋を参照情報とともにモデルに渡し、それらだけに基づいて回答するよう指示してください。Qwen 3.5 9B(Ollamaライブラリでは6.6 GB)のような90億パラメータのモデルで、抜粋の要約には十分です。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に含まれるのは判例公報(Bulletin)に掲載された判決のみで、INCAには同公報に未掲載の判決、CAPPには控訴院の判決の一部が収録されています。データベースにある判決が見当たらなくても、その判決が存在しない証拠にはなりません。
未収録、またはごく最近の裁判例
ごく最近の裁判判断は、最新の差分アーカイブにまだ含まれていない可能性があります。慎重な扱いが必要な案件では、Légifranceの情報と照合してください。
法制度の変化
古い判決は、判例の変更や法改正によって、すでに通用しなくなっている場合があります。システムはテキストを検索しますが、その法的判断が現在どの程度の権威を持つかを評価するものではありません。
仮名化
名前は伏せられています。当事者の身元を特定しようとすることは、絶対にしないでください。
法的助言は行いません
このツールは、必要な情報を探して引用するのを支援します。事実の法的評価、個別の案件への適用、助言は、引き続き専門家が担います。
FAQ
法律分野のRAGとは何ですか?+
裁判例や文書のデータベースから質問に関連する抜粋を探し、その抜粋だけをもとに、参照元を添えた回答をモデルに作成させるシステムです。法律分野での価値は、根拠を追跡できることにあります。各主張は、専門家が読み直して確認できる裁判例を参照しています。
破毀院の判例をオープンデータで入手するには、どこを探せばよいですか?+
DILAのサーバー上で、公報に掲載された判決はCASSアーカイブ、公報未掲載の判決はINCAアーカイブから取得できます。また、破毀院のJudilibre API経由でも取得できます。データセットの紹介ページはdata.gouv.frにあります。アーカイブは単純なXMLファイルなので、ローカルで簡単に処理できます。
収録されている判例は網羅的ですか?+
いいえ。CASS には判例公報に掲載された判決、INCA には判例公報に掲載されていない判決、CAPP には控訴院の裁判例の一部、JADE には行政裁判の裁判例の一部が収録されています。より広範な資料については、フランスの破毀院が Judilibre を提供しています。ご自身のデータベースに裁判例がないからといって、その裁判例が存在しないと決して断言しないでください。
フランス語の法的文脈向けにどの埋め込みモデルを選ぶべきですか?+
BGE-M3 は一般的な選択肢です。多言語に対応し、1,024次元のベクトルを扱い、仕様書によると入力長は最大8,192トークンです。一般的なランキングでテストを代替することはできません。法務専門家が扱う質問を20件用意し、それぞれに期待される判断を定めたうえで、そのサンプルを使って複数モデルの再現率を比較してください。
裁判例のインデックス作成にはGPUが必要ですか?+
必須ではありませんが、数十万件の抜粋をエンコードする処理は、グラフィックカードを使うと大幅に速くなります。GPUがなくてもインデックス作成はできます。夜間にバッチ単位で行うように計画し、一度だけ実行してください。データベースを構築した後は、個別の質問に対する検索はCPUでも高速で、毎週の更新も負荷は小さいです。
これらの裁判例を商用製品で利用できますか?+
data.gouv.frの各データセットの情報ページには、そのライセンスが記載されています。通常は、出典を明記すれば再利用できるオープンライセンスです。ただしDILAは、再利用者にもフランスの「情報処理・ファイル・自由に関する法律」(Informatique et Libertés)が適用されると説明しています。ライセンスを確認し、データ保護責任者にプロジェクトの承認を得てください。
このガイドは役に立ちましたか?

ご意見、誤りのご指摘、補足はありますか?ぜひお知らせください。皆さんにとってより良いガイドにするために役立ちます。