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

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

    if q.court:

「q」(search_cases関数に渡す引数)に値が入っているか(検索条件として裁判所名が指定されているか)をチェックしている。入っていれば if 文中の処理を行う。
 ◼︎q の中身は、たとえば以下のようになる。
   q = CaseSearch(
    keywords=”発信者情報開示 プロバイダ責任制限法”,
    court=”東京地裁”,
    limit=5
    )

        where_parts.append("court LIKE ?")

if 文中の処理。検索条件として裁判所名が指定されていれば、検索条件を格納しているリスト(where_parts)の末尾に「”court LIKE ?”」という要素を追加(append)する。「court」はデータベース(cases.db)内にある裁判所名が格納された列の名前。「LIKE」は完全一致ではなく、「〜を含む」というあいまい検索を行うための命令。「?」はプレースホルダ(穴埋め用の場所)で、実際の裁判所名(例:%東京地裁%)が安全な方法でここに流し込まれる。

        params.append(f"%{q.court}%")

前行の「?」(プレースホルダ)に検索文字列(裁判所名)を追加する処理。「 f”%{q.court}%” 」により、ユーザー(またはAI)が指定した裁判所名を、SQLのあいまい検索用の形式に変換している。たとえば、q.court"東京地裁" なら、"%東京地裁%" という文字列に変換されて プレースホルダのリスト(params)末尾に格納される。

    sql = f"""
        SELECT doc_id, court, date, case_number, case_name
        FROM cases
        WHERE {" AND ".join(where_parts)}
        ORDER BY date DESC
        LIMIT ?
    """

これまでバラバラに準備してきた検索条件の部品を組み合わせて、データベースに送る最終的な命令書(SQL文)を完成させるための処理をしている。

各要素の意味

◼︎SELECT … FROM cases:データベースの「cases」という表から、画面に表示したい項目(ID、裁判所名、判決日、事件番号、事件名)だけをピックアップして持ってくるという宣言。

◼︎WHERE {” AND “.join(where_parts)} :このプログラムの心臓部。これまでリスト(where_parts)に溜めてきた条件(例:キーワード、裁判所名)を、"AND" でつなげて1つのWHERE句にしている。

直前のコードで、where_parts には、次のような文字列のリストが入っている。

where_parts = [
    "(doc_id LIKE ? OR court LIKE ? OR ... OR facts_and_reasons LIKE ?)",
    "(doc_id LIKE ? OR court LIKE ? OR ... OR facts_and_reasons LIKE ?)",
    "court LIKE ?"
]

” AND “.join(where_parts) は、このリストの要素を" AND " を間に挟んで1つの文字列に連結するという処理を行なっている。この処理により以下のような1つの長いSQL文ができあがる。

(doc_id LIKE ? OR court LIKE ? OR ...)
AND
(doc_id LIKE ? OR court LIKE ? OR ...)
AND
court LIKE ?

これにより、すべての条件に一致する(AND検索)ものだけを絞り込むことができる。

◼︎ORDER BY date DESC:検索結果を判決日の新しい順(降順)に並び替える。最新の判例からチェックしたいというユーザーのニーズに応えるための設定。

◼︎LIMIT ?:最大件数(例:5件)で結果をカットするための処理。上限を設けることでサーバーが重くなるのを防いでいる。

params.append(int(q.limit))

検索結果の件数を制限するための最後の仕上げ。SQL文の最後にある LIMIT ? というプレースホルダに、具体的な数値を流し込む処理を行っている。
◼︎q.limit:前回のステップで作成した CaseSearch モデルの limit フィールドを取得している(デフォルトは5件、最大20件という制約をしている部分)。
◼︎int(…): 数値を確実に整数型として扱うための処理。Pydanticモデルですでに整数としてバリデーションされているが、データベースの種類によっては型に非常に厳格なものがあるため、念のため明示的に変換して安全性を高めている。
◼︎params.append(…):これまでキーワードや裁判所名を追加してきた params リストの一番最後に、q.limit で指定された件数を追加している。

3. 全体の完成図(paramsの中身)

ユーザーが「損害賠償」「東京地裁」で検索し、上限「5件」を指定した場合、最終的な params は以下のような1つの長いリストになる。

["%損害賠償%", ..., "%損害賠償%", "%東京地裁%", 5] (キーワード分7個) + (裁判所分1個) + (件数分1個)

その意味するところは、次のとおりである。

casesという表から、5つの項目を選んで持ってきて。ただし、キーワードに「損害賠償」が含まれていて、かつ(AND)、裁判所名に「東京地裁」が含まれているものに限定して。さらに、日付が新しい順に並べて、最初の5件だけを見せて。

    with _connect() as conn:

この with は、Pythonにおいてリソースの管理を自動化するための構文。データベース操作において、最も避けるべき失敗は接続(コネクション)を開きっぱなしにして、サーバーに負荷をかけ続けてしまうこと。with を使うことで、処理が終わった後(あるいは途中でエラーが起きた時でも)、自動的にデータベースとの接続を閉じてくれるようになる。

  • _connect():データベースへ接続するための関数。実行すると、データベースへの専用の通り道を確保して戻してくれる。
  • as conn:確保された通り道(接続オブジェクト)に conn という名前を付けて、その後の処理で使えるようにしている。
        rows = conn.execute(sql, params).fetchall()

これまで準備してきたSQL文とデータ(params)をデータベースに渡し、検索を実行して結果を受け取る処理をしている。

    if not rows:
        return "該当なし。"

データベースから取得した結果(rows)が空(false)なら「該当なし」を戻り値として処理を終了する。

    lines = []

表示用の文字列をためるリスト(lines)を作成している。

    for i, r in enumerate(rows, 1):

検索結果(rows)を1件ずつ1から始まる番号付きで取得している。「i」には振られた番号(1, 2, 3…)が、「r」にはデータベースから取り出した1件分の判例データ(IDや裁判所名などのセット)が、それぞれ格納されている。

        lines.append(
            f"{i}. doc_id={r['doc_id']} / {r['court']} / {r['date']} / "
            f"{r['case_number']} / {r['case_name']}"
        )

データベースから取り出した生のデータを、AIや人間が一見して理解できる1行の要約レポートに書き換える処理を行なっている。これにより、見やすく整理されたデータが lines リストに格納される。

  • {i}.:前回のステップで振った番号(1, 2, 3…)を冒頭に置く。
  • doc_id={r[‘doc_id’]}:管理用のIDを明記する。AIが後で「このIDの詳しい内容を教えて」と判断しやすくするため。
  • {r[‘court’]} / {r[‘date’]} / …:「裁判所名」「判決日」「事件番号」「事件名」という、判例を特定するために欠かせない5つの重要項目を、スラッシュ(/)で区切って並べている。
    return "\n".join(lines)

整理したデータ(lines)を1つの大きな検索結果レポートとしてAIに返している。

  • “\n”:改行を意味する特殊記号。
  • .join(lines):lines リストに保管されている各判例の1行要約の間に改行を挟み込みながら、すべての要素を1つの長い文字列に合体させる。
  • return:完成した最終的な検索結果を、関数の外にいるAIエージェントへと返している。

最終的に返される検索結果のイメージは以下のようになる。

1. doc_id=AAA / 東京地裁 / 令和7年3月7日 / 令和6年(ワ)70278号 / 発信者情報開示請求事件
2. doc_id=BBB / 大阪地裁 / 令和6年12月1日 / 令和6年(ワ)12345号 / 損害賠償請求事件
3. doc_id=CCC / 東京高裁 / 令和5年5月20日 / 令和5年(ネ)54321号 / 請求異議控訴事件

これで「検索ツール」部分の解析は終わり。次回から「本文取得ツール」部分の解析に着手する。