Agents SDKを使った判例検索 (7)

Agents SDKを使った判例検索コード( database_query.py )解析の6回目。前回に引き続き「 本文取得ツール 」のコードについて、1行ずつ、その働きを見ていく。

    header = (
        f"doc_id: {row['doc_id']}\n"
        f"court: {row['court']}\n"
        f"date: {row['date']}\n"
        f"case_number: {row['case_number']}\n"
        f"case_name: {row['case_name']}\n"
    )

データベースから取り出した1件の判例データ(row)を、AIが読みやすい判例のプロフィール(ヘッダー情報)として整形する処理。判決の本文という長い文章に入る前に、その判例の基本スペックを整理して提示する役割を担っている。

情報の構造化

ここでは、Pythonの f-string(フォーマット済み文字列) を使って、バラバラだったデータを1つのテキストにまとめている。

  • 項目のラベル付けdoc_id:court: といったラベルを付けることで、AIが、この値が何を意味するのかを正確に理解できるようにしている。
  • 改行(\n)の活用:各項目の末尾に改行を入れることで、人間が読む名刺やカルテのように、1行1項目で整理されたリスト形式にしている。

なぜこのヘッダーが重要なのか

AIにとって、このヘッダー情報は以下のようなメリットを生む。

  • コンテキスト(文脈)の把握:長い本文を読み始める前に「これは東京地裁の、令和○年の事件である」という前提知識をAIに与える。これにより、要約や分析の精度が向上する。
  • 引用の正確性:AIがユーザーに回答する際、「ID: {doc_id} の事件では〜」と正確に引用できるようになる。

各項目の内訳

項目名内容AIにとっての価値
doc_id固有の管理番号他の判例と混同しないための絶対的な指標。
court裁判所名裁判所のレベル(地裁・高裁など)を判断する材料。
date判決日その判例が最新のものか、当時の法体系での判断かを知る鍵。
case_number事件番号正式な文献として参照・引用する際の必須情報。
case_name事件名どんな争点(不当利得、損害賠償など)の事件かを一目で把握。
    body = row["main_text"] or ""

判例の核心部分である主文などを変数に格納する際、データが空っぽ(None)だったとしてもプログラムを停止させないための処理。

Pythonの「or」の賢い使い方

  • row["main_text"]:データベースの main_text カラムから、判決の主文を取り出そうとしている。
  • or "":もしデータベースの該当項目が空(None)だった場合、Pythonは or の右側にある空の文字列 "" を採用する。

なぜこれが必要なのか?

Pythonでは、文字列(String)と None を連結しようとすると、「型が違うので計算できません」というエラー(TypeError)が発生してプログラムが止まってしまう。この1行で、最低でも空の文字列であることを保証することで、この後のテキスト結合処理を安全に行えるようになる。

AIエージェントへの配慮

AIに情報を渡す際、この空文字への変換は地味ながら大きな効果を発揮する。

  1. 処理の継続:一部のデータが欠損している判例があったとしても、エラーで検索全体が失敗するのを防ぎ、「この項目は空ですが、他の情報はあります」とAIが回答できる余地を残す。
  2. ノイズの除去:AIに None というプログラム用語をそのまま渡すと、AIが回答の中で「主文はNoneです」と不自然な言い方をしてしまうことがある。””(空)にしておくことで、AIは自然に「主文の記載はありません」と判断しやすくなる。
    reasons = row["facts_and_reasons"] or ""

判例の中でも特に重要な事実及び理由(判決に至るまでの詳しい経緯や裁判所の考え方)をデータベースから取り出す処理。body(主文)と同様に、データが空っぽだった場合の備え(or "")が含まれている。

「事実及び理由」とは何か?

データベースの facts_and_reasons カラムには、その裁判のドラマのすべてが詰まっている。

  • 争点:何が争われているのか。
  • 当事者の主張:原告と被告がそれぞれ何を言っているのか。
  • 裁判所の判断:なぜそのような結論(主文)に至ったのかという論理的な説明。

AIエージェントが「この判例はなぜ損害賠償を認めたの?」といった高度な質問に答えるためには、この部分のデータが不可欠となる。

「or “”」による二重の安全策

仕組みは前回の body と同じだが、このフィールドにおいて or "" が特に重要な理由が2つある。

  1. 古い判例データの欠損への対応:古い判例や簡略化されたデータでは、この「理由」部分が未入力だったり、別の形式で保存されていたりすることがある。その際、None ではなく「空の文字」として扱うことで、プログラム全体がクラッシュするのを防ぐ。
  2. 結合の準備:この後の工程で、この reasons を他のテキストと合体させる。Pythonでは「文字 + 何もない(None)」はエラーになるが、「文字 + 空の文字(“”)」ならエラーにならず、スムーズに処理を続けられる。
    if q.include_facts_and_reasons and reasons.strip():
        return header + "\n[main_text]\n" + body + "\n\n[facts_and_reasons]\n" + reasons

ユーザー(またはAI)の希望に合わせて、詳細な理由(フルコース)を含めた最終的なレポートを組み立てる条件分岐と、その合体処理を行っている部分。

二重のチェック

if q.include_facts_and_reasons
  • 役割: CaseGet モデルで定義した「理由部分も返すか」というスイッチを確認している。
  • 意味: ユーザーが「詳しい理由まで知りたい」と指示した(デフォルトはTrue)場合にのみ、次のステップに進む。
and reasons.strip()
  • 役割: reasons(事実及び理由)の中身が、空白や改行だけでないかをチェックしている。
  • 意味: たとえスイッチがONでも、データベース側に理由の記載がなければ、無駄に空の項目を付け足さないようにする。

レポートの組み立て図

条件をクリアした場合、これまでに準備した具材を一つの長い文字列に合体させる。

構成要素役割
header事件番号や裁判所名などの表紙情報。
"\n[main_text]\n"ここからが「主文」であることを示すラベル。
body判決の結論部分。
"\n\n[facts_and_reasons]\n"ここからが「理由」であることを示す目立つ見出し。
reasons裁判のドラマが詰まった「理由」の全文。

なぜ「ラベル」を付けるのか?

[main_text][facts_and_reasons] といった括弧付きのラベルをわざわざ挿入しているのには、以下の理由がある。

  1. AIの構造理解:AIは、情報の塊が「どこからどこまでが何なのか」を明示されることで、要約や分析の精度が劇的に向上する。
  2. ハルシネーションの防止:「理由」と「結論(主文)」が混ざってしまうと、AIが結論を誤読するリスクがある。境界線を引くことで、AIは「これは結論ではなく、当事者の主張部分だな」と正しくコンテキストを把握できる。
    return header + "\n[main_text]\n" + body

関数の最後を締めくくる標準(ライト版)レポートの返却処理。前のステップ(if 文)で「詳細な理由」を含める条件に当てはまらなかった場合、最終的にこの「基本情報 + 主文」というコンパクトな形でAIエージェントにデータが渡される。

シンプル・イズ・ベスト

この処理は、いわば「情報の最小ユニット」を構築している。

  • ヘッダー(header:裁判所名や事件番号などの「誰が・いつ・どこで」という基本情報。
  • ラベル(\n[main_text]\n):AIに対して「ここから下が判決の核心(結論)ですよ」という目印。
  • ボディ(body:判決の結論(主文)。

なぜこの控えめな返却が必要なのか

すべてを詰め込むフル版(理由付き)だけでなく、このライト版があることで、システム全体に以下のようなメリットが生まれる。

  • トークン(コストと速度)の節約:判決の理由は数万文字に及ぶこともある。もしユーザーが「結論だけ知りたい」と思っているなら、この短いレポートを返すだけで済むため、AIの処理が速くなり、APIの利用料金も安く抑えられる。
  • 情報の整理:AIエージェントが複数の判例を比較する場合、最初から全文を読み込ませるよりも、まずはこのライト版で要点を確認させるほうが、AIが混乱しにくくなる。

これで「本文取得ツール」の解析は終わり。次回からは「参照解決ツール」の解析に移る。