Gemini API File Search Toolを使ってみた #google #ai #gemini #rag #llm

はじめに

2025年11月初旬、「Introducing the File Search Tool in Gemini API」にてGemini API ファイル検索が発表されました。これによりRAG (Retrieval Augmented Generation; 検索拡張生成) システムが簡単に構築できるとのことです。RAGについては次の過去記事もご覧ください。

本稿では「Introducing the File Search Tool in Gemini API」に挙げられているサンプルプログラムを通して、Gemini API ファイル検索の仕組みを見ていきます。

前提条件

  • Google Workspaceを使用。
    • Geminiが含まれているのでそれを利用します。
  • Google Cloudを使用。
    • あらかじめプロジェクトを作成しておきます。
    • Generative Language API を利用できるAPIキーを作成しておきます。
  • ローカルPC
    • Python 3.13.9
    • あらかじめGoogle Cloudに作成したプロジェクトにアクセスできるようにしておきます。

公式サンプルプログラム

公式ブログ「Introducing the File Search Tool in Gemini API」に挙げられているサンプルプログラムは次の通りです。

import time
from google import genai
from google.genai import types

client = genai.Client()
store = client.file_search_stores.create()

upload_op = client.file_search_stores.upload_to_file_search_store(
    file_search_store_name=store.name,
    file='path/to/your/document.pdf'
)

while not upload_op.done:
  time.sleep(5)
  upload_op = client.operations.get(upload_op)

# Use the file search store as a tool in your generation call
response = client.models.generate_content(
    model='gemini-2.5-flash',
    contents='What does the research say about ...',
    config=types.GenerateContentConfig(
        tools=[types.Tool(
            file_search=types.FileSearch(
                file_search_store_names=[store.name]
            )
        )]
    )
)
print(response.text)

# Support your response with links to the grounding sources.
grounding = response.candidates[0].grounding_metadata
if not grounding:
 print('No grounding sources found')
else:
 sources = {c.retrieved_context.title for c in grounding.grounding_chunks}
 print('Sources:', *sources)

扱いやすいようにpython-dotenvを使って環境変数 GOOGLE_API_KEY でAPIキーを取得するように変更し、「みんなで育むスキルアップ支援制度・正式導入を決定しました」でも紹介された弊社社内の「スキルアップ支援制度」の説明PDFを対象にするよう変更します。差分は次の通りです。

--- official.py    2025-11-19 16:11:20.951251206 +0900
+++ google-official.py    2025-11-17 18:22:31.281478357 +0900
@@ -1,13 +1,18 @@
+# -*- coding: utf-8 -*-
 import time
+import os
+from dotenv import load_dotenv
 from google import genai
 from google.genai import types

-client = genai.Client()
+load_dotenv()
+
+client = genai.Client(api_key=os.getenv('GOOGLE_API_KEY'))
 store = client.file_search_stores.create()

 upload_op = client.file_search_stores.upload_to_file_search_store(
     file_search_store_name=store.name,
-    file='path/to/your/document.pdf'
+    file='document.pdf',
 )

 while not upload_op.done:
@@ -17,7 +22,7 @@
 # Use the file search store as a tool in your generation call
 response = client.models.generate_content(
     model='gemini-2.5-flash',
-    contents='What does the research say about ...',
+    contents='スキルアップ支援制度の申請手順を教えてください。',
     config=types.GenerateContentConfig(
         tools=[types.Tool(
             file_search=types.FileSearch(

これを実行すると次のようになりました。

スキルアップ支援制度の申請手順は、以下の通りです。

**1. 相談・申請 (本人とチームリーダー)**
*   **本人がチームリーダーに相談:** 会社の事業目標やチーム目標にどのように貢献できるかを具体的に伝え、支援の提案を受けます。
*   **本人が申請内容を記載:** 「スキルアップ支援制度利用一覧」を更新し、必要な情報を1列まで入力します。

**2. 承認 (チームリーダー)**
*   **チームリーダーが判断内容を追記:** 会社の事業目標やチーム目標への貢献度という視点で承認の判断を行い、「スキルアップ支援制度利用一覧」を更新します。
    *   **チームリーダー自身の申請の場合:** 事業責任者の承認が必要です。

**上限金額を上回る支援が必要な場合 (補足)**
*   **チームリーダーが事業責任者に相談:** チームリーダーが事業責任者へ相談します。
*   **経費申請システムの事前申請ワークフローを利用:** 本人が経費申請システムの事前申請ワークフローを利用して「購入物申請」を行い、承認または却下を確認します。
    *   申請経路は「申請者 ⇒ チームリーダー ⇒ 最終承認者」となります。
    *   申請時に承認者を指定し、指定されたメンバーの承認が必要です。

**3. 購入支払日記載 (本人)**
*   チームリーダーの承認後、立替購入を行い、支払日を「スキルアップ支援制度利用一覧」のJ列に記入します。

**4. 請求方法 (本人)**
*   **経費申請システムで経費申請:** 本人が経費申請システムを利用して経費申請を行います。領収書が必須です。
    *   申請日は領収書の日付(支払い日)とします。
    *   費目は以下のいずれかを指定します。
        *   スキルアップ支援制度 研修
        *   スキルアップ支援制度 書籍
        *   スキルアップ支援制度 資格取得
    *   上限金額を上回る申請で事前申請を行っている場合は、事前申請と関連付けて経費申請を行います。

**5. 請求承認 (チームリーダー)**
*   チームリーダーが申請内容を確認し、承認します。

**6. 支給方法 (経理)**
*   経費として支給されます。

**注意事項:**
*   チームリーダーの承認がないまま先に経費精算はできません。承認後に購入と経費精算を行ってください。
*   購入支払月に必ず経費申請をしてください。経費申請を忘れた場合、精算はできません。
*   スキルアップ支援制度で購入した書籍は返却不要です。
*   研修受講にかかる移動費用はスキルアップ支援制度の10万円には含まれませんが、別に経費精算が可能です。
*   領収書を紛失した場合、精算はできません。
Sources: document.pdf

このように、まとめられた手順が生成されました。

どのように格納・検索されているのか

さて、アップロードしたPDFはどのように格納・検索されているのでしょうか? 仕組みを見てみましょう(2025年11月19日現在)。

  1. ファイル検索ストアを作成する: ファイル検索ストアには、ファイルから処理されたデータが含まれます。これは、セマンティック検索が動作するエンベディングの永続コンテナです。

これはサンプルプログラムのソースコードの

store = client.file_search_stores.create()

に相当します。

  1. ファイルをアップロードしてファイル検索ストアにインポートする: ファイルをアップロードすると同時に、結果をファイル検索ストアにインポートします。これにより、未加工ドキュメントへの参照である一時的な File オブジェクトが作成されます。このデータはチャンク化され、ファイル検索エンベディングに変換されて、インデックスが作成されます。 File オブジェクトは 48 時間後に削除されますが、ファイル検索ストアにインポートされたデータは、削除するまで無期限に保存されます。

これはサンプルプログラムのソースコードの

upload_op = client.file_search_stores.upload_to_file_search_store(
    file_search_store_name=store.name,
    file='document.pdf',
)

while not upload_op.done:
  time.sleep(5)
  upload_op = client.operations.get(upload_op)

に相当します。この時点で、

  • データのチャンク化
  • ファイル検索エンベディングに変換
  • インデックス作成

が行われます。

別途、データストアやベクトルDBなどを準備しなくてもよいのです。しかも料金によれば、このエンベディングの料金だけがかかり、ファイル検索ストアの合計サイズが1GBまでなら無料のようです。構成要素を別々に用意するのは管理が大変になりますし、合計でどれだけ料金がかかるのか・かかっているのかわかりにくいため、手を出すのに躊躇してしまいます。一方、このGemini API File Search Toolならば1つにまとめられているので、とても手軽です。

ところで、

これにより、未加工ドキュメントへの参照である一時的な File オブジェクトが作成されます。このデータはチャンク化され、ファイル検索エンベディングに変換されて、インデックスが作成されます。 File オブジェクトは 48 時間後に削除されますが、

の部分ですが、この「48時間で消える File オブジェクト」は内部的なものなのか、ユーザ側からは確認できませんでした。ファイル検索ストアと別に書いてあるので別に見えるのかと思ったのですが…。もし「こうしたら確認できる」という方法をご存知の方はお知らせいただければ幸いです。これに限らずGemini API File Search ToolはウェブUIや gcloud ツールで操作・閲覧できないことも多く、そういう仕様なのか出たばかりで未提供なのか、判断に困るところが多いです(2025年11月19日現在)。

  1. ファイル検索でクエリを実行する: 最後に、 generateContent 呼び出しで FileSearch ツールを使用します。ツール構成で、検索する FileSearchStore を指す FileSearchRetrievalResource を指定します。これにより、モデルは特定のファイル検索ストアでセマンティック検索を実行し、回答のグラウンディングに関連する情報を検索します。

これはサンプルプログラムのソースコードの

response = client.models.generate_content(
    model='gemini-2.5-flash',
    contents='スキルアップ支援制度の申請手順を教えてください。',
    config=types.GenerateContentConfig(
        tools=[types.Tool(
            file_search=types.FileSearch(
                file_search_store_names=[store.name]
            )
        )]
    )
)

に相当します。そして結果を、

print(response.text)

# Support your response with links to the grounding sources.
grounding = response.candidates[0].grounding_metadata
if not grounding:
 print('No grounding sources found')
else:
 sources = {c.retrieved_context.title for c in grounding.grounding_chunks}
 print('Sources:', *sources)

で表示しています。

生成AIで作成する内容を、検証可能な情報ソースに関連づけることを「グラウンディング」と言います。

先の結果では、

Sources: document.pdf

という情報ソースを表示していたので、これを元に回答を生成した、ということがわかります。

ファイル検索ストアの一覧表示

いま作成したファイル検索ストアを表示してみましょう。次のスクリプトを使います。なお、これはClaude Codeで作成しました。

# -*- coding: utf-8 -*-
import os
from dotenv import load_dotenv
from google import genai

load_dotenv()

client = genai.Client(api_key=os.getenv('GOOGLE_API_KEY'))

# すべてのFile Search Storeを取得
print("=== File Search Stores ===\n")

stores = client.file_search_stores.list()

store_count = 0
for store in stores:
    store_count += 1
    print(f"Store #{store_count}")
    print(f"  Name: {store.name}")

    # display_nameがある場合のみ表示
    if hasattr(store, 'display_name') and store.display_name:
        print(f"  Display Name: {store.display_name}")

    # サイズ情報
    if hasattr(store, 'size_bytes') and store.size_bytes is not None:
        size_mb = store.size_bytes / (1024 * 1024)
        print(f"  Size: {store.size_bytes:,} bytes ({size_mb:.2f} MB)")
    elif hasattr(store, 'size_bytes'):
        print(f"  Size: 0 bytes (empty store)")

    # 作成時刻
    if hasattr(store, 'create_time'):
        print(f"  Created: {store.create_time}")

    # 更新時刻
    if hasattr(store, 'update_time'):
        print(f"  Updated: {store.update_time}")

    # その他のすべての属性を表示
    print(f"  All Store attributes:")

    # Pydanticモデルの場合はmodel_dump()を使用
    if hasattr(store, 'model_dump'):
        data = store.model_dump()
        for key, value in data.items():
            print(f"    {key}: {value}")
    else:
        # 通常のオブジェクトの場合
        for attr in dir(store):
            # プライベート属性やメソッドを除外
            if not attr.startswith('_') and not callable(getattr(store, attr)):
                try:
                    value = getattr(store, attr)
                    print(f"    {attr}: {value}")
                except Exception:
                    # アクセスできない属性はスキップ
                    pass

    # ストア内のドキュメントを一覧表示
    print(f"\n  Documents in this store:")
    try:
        documents = client.file_search_stores.documents.list(parent=store.name)
        doc_count = 0
        for doc in documents:
            doc_count += 1
            print(f"\n    Document #{doc_count}:")
            print(f"      Name: {doc.name}")

            # ドキュメントの基本情報
            if hasattr(doc, 'display_name') and doc.display_name:
                print(f"      Display Name: {doc.display_name}")

            if hasattr(doc, 'size_bytes') and doc.size_bytes is not None:
                size_mb = doc.size_bytes / (1024 * 1024)
                print(f"      Size: {doc.size_bytes:,} bytes ({size_mb:.2f} MB)")

            if hasattr(doc, 'create_time'):
                print(f"      Created: {doc.create_time}")

            if hasattr(doc, 'update_time'):
                print(f"      Updated: {doc.update_time}")

            # ドキュメントのすべてのメタデータを表示
            print(f"      All Document attributes:")
            if hasattr(doc, 'model_dump'):
                doc_data = doc.model_dump()
                for key, value in doc_data.items():
                    print(f"        {key}: {value}")
            else:
                for attr in dir(doc):
                    if not attr.startswith('_') and not callable(getattr(doc, attr)):
                        try:
                            value = getattr(doc, attr)
                            print(f"        {attr}: {value}")
                        except Exception:
                            pass

        if doc_count == 0:
            print(f"    (No documents)")
        else:
            print(f"\n    Total documents: {doc_count}")

    except Exception as e:
        print(f"    Error listing documents: {e}")

    print("-" * 60)

if store_count == 0:
    print("No File Search Stores found.")
else:
    print(f"\nTotal: {store_count} store(s)")

これを実行した結果は次の通りです。一部、IDと思われるところは伏せ字にしています。

=== File Search Stores ===

Store #1
  Name: fileSearchStores/★★★★-●●●●
  Size: 395,734 bytes (0.38 MB)
  Created: 2025-11-19 08:56:07.028915+00:00
  Updated: 2025-11-19 08:56:07.028915+00:00
  All Store attributes:
    name: fileSearchStores/★★★★-●●●●
    display_name: None
    create_time: 2025-11-19 08:56:07.028915+00:00
    update_time: 2025-11-19 08:56:07.028915+00:00
    active_documents_count: 1
    pending_documents_count: None
    failed_documents_count: None
    size_bytes: 395734

  Documents in this store:

    Document #1:
      Name: fileSearchStores/★★★★-●●●●/documents/documentpdf-▲▲▲▲
      Display Name: document.pdf
      Size: 395,734 bytes (0.38 MB)
      Created: 2025-11-19 08:56:11.159178+00:00
      Updated: 2025-11-19 08:56:14.083813+00:00
      All Document attributes:
        name: fileSearchStores/★★★★-●●●●/documents/documentpdf-▲▲▲▲
        display_name: document.pdf
        state: DocumentState.STATE_ACTIVE
        size_bytes: 395734
        mime_type: application/pdf
        create_time: 2025-11-19 08:56:11.159178+00:00
        custom_metadata: None
        update_time: 2025-11-19 08:56:14.083813+00:00

    Total documents: 1
------------------------------------------------------------

Total: 1 store(s)

「ファイル検索ストア」という入れ物の中に「ドキュメント」という実際のファイルを格納している、という仕組みのようです。

ファイル検索ストアには何か独自の名前がつくようです。ここでは伏せ字込みで「fileSearchStores/★★★★-●●●●」で表している箇所です。サンプルプログラムでは

store = client.file_search_stores.create()

として作成し、

     file_search_store_name=store.name,

などでファイル検索ストアの名前を取得しています。

つまりサンプルプログラムは実行するたびに新しくファイル検索ストアを作成していることになります。サンプルプログラムは「ファイル検索ストアの作成」「ドキュメントをファイル検索ストアにアップロード」「ファイル検索ストア内を検索」が一体になっているので、実際にシステム構築する際は最低限これらを分離するべきでしょう。

ファイル検索ストアの情報の詳細表示

サンプルプログラムでは検索しても最低限の情報しか表示していなかったので、さらに情報を表示させてみましょう。次のスクリプトを使います。これもClaude Codeで作成しました。

# -*- coding: utf-8 -*-
import os
import sys
import json
from dotenv import load_dotenv
from google import genai
from google.genai import types

load_dotenv()

client = genai.Client(api_key=os.getenv('GOOGLE_API_KEY'))

# ストア名の指定(コマンドライン引数または対話式で取得)
if len(sys.argv) > 1:
    store_name = sys.argv[1]
else:
    # ストアの一覧を表示
    print("=== Available File Search Stores ===")
    stores = client.file_search_stores.list()
    store_list = []

    for idx, store in enumerate(stores, 1):
        store_list.append(store)
        size_info = "empty"
        if hasattr(store, 'size_bytes') and store.size_bytes:
            size_mb = store.size_bytes / (1024 * 1024)
            size_info = f"{size_mb:.2f} MB"

        print(f"{idx}. {store.name}")
        print(f"   Size: {size_info}")
        if hasattr(store, 'create_time'):
            print(f"   Created: {store.create_time}")

    if not store_list:
        print("\nNo stores found. Please upload files first using:")
        print("  python3 google-official-upload-only.py")
        sys.exit(1)

    # ストアを選択
    print("\n" + "=" * 50)
    choice = input(f"Select store number (1-{len(store_list)}): ").strip()

    try:
        idx = int(choice) - 1
        if 0 <= idx < len(store_list):
            store_name = store_list[idx].name
        else:
            print("Invalid store number.")
            sys.exit(1)
    except ValueError:
        print("Invalid input.")
        sys.exit(1)

print(f"\nUsing store: {store_name}")

# クエリの入力
if len(sys.argv) > 2:
    query = sys.argv[2]
else:
    print("\nEnter your query (or press Ctrl+C to exit):")
    query = input("> ").strip()

    if not query:
        print("No query provided.")
        sys.exit(1)

print(f"\nQuery: {query}")
print("Searching...")

# File Searchを使った質問応答
response = client.models.generate_content(
    model='gemini-2.5-flash',
    contents=query,
    config=types.GenerateContentConfig(
        tools=[types.Tool(
            file_search=types.FileSearch(
                file_search_store_names=[store_name]
            )
        )]
    )
)

# レスポンステキストの表示(読みやすさ優先)
print("\n" + "=" * 70)
print("RESPONSE TEXT")
print("=" * 70)
print(response.text)

# 完全なレスポンス構造の表示
print("\n" + "=" * 70)
print("COMPLETE RESPONSE STRUCTURE (JSON)")
print("=" * 70)

if hasattr(response, 'model_dump'):
    try:
        response_data = response.model_dump()
        print(json.dumps(response_data, indent=2, ensure_ascii=False, default=str))
    except Exception as e:
        print(f"Error dumping response: {e}")
        # フォールバック: 主要な属性を個別に表示
        print("\nFalling back to attribute display:")
        for attr in dir(response):
            if not attr.startswith('_') and not callable(getattr(response, attr)):
                try:
                    value = getattr(response, attr)
                    print(f"{attr}: {value}")
                except Exception:
                    pass
else:
    print("response.model_dump() not available")

これを実行した結果は次の通りです。一部、伏せ字にしたり省略したりしています。

Using store: fileSearchStores/★★★★-●●●●

Query: スキルアップ支援制度の申請手順を教えてください。
Searching...

======================================================================
RESPONSE TEXT
======================================================================
スキルアップ支援制度の申請手順は以下の通りです。

**1. 相談・申請**
*   本人がチームリーダーに相談します。この際、会社の事業目標やチーム目標にどのように貢献できるかという点を伝えます。
*   チームリーダーから支援に関する提案を受けることもあります。
*   本人が「スキルアップ支援制度利用一覧」に申請内容を1列まで記載して更新します。

**2. 承認**
*   チームリーダーが、会社の事業目標やチーム目標への貢献度を考慮して承認を判断します。
*   チームリーダーは「スキルアップ支援制度利用一覧」に判断内容を追記し、K列以降を入力します。
    *   **チームリーダー自身が申請する場合**は、事業責任者の承認が必要です。

**上限金額を上回る支援が必要な場合(補足)**
*   まずチームリーダーが事業責任者に相談します。
*   その後、本人が経費申請システムの事前申請ワークフローを利用し、「購入物申請」として承認または却下を確認します。
    *   申請経路は「申請者 ⇒ チームリーダー ⇒ 最終承認者」となります。
    *   申請時に承認者を指定し、指定されたメンバーの承認が必要となります。

**承認後の請求手順**

**3. 購入支払日記載**
*   チームリーダーの承認後、立替購入を行い、その支払日を「スキルアップ支援制度利用一覧」のJ列に記載して更新します。

**4. 請求方法**
*   本人が立替払いをした後、経費申請システムで請求申請を行います。
*   領収書が必須で、申請日は領収書の日付(支払日)とします。
*   費目は以下のいずれかを選択します。
    *   スキルアップ支援制度 研修
    *   スキルアップ支援制度 書籍
    *   スキルアップ支援制度 資格取得
*   上限金額を上回る申請で、事前申請を行っている場合は、事前申請と関連付けて経費申請を行います。

**5. 請求承認**
*   チームリーダーが申請内容を確認し、承認します。

**6. 支給方法**
*   経費として支給されます。

**重要な注意点**
*   チームリーダーの承認がない申請は精算できません。承認後に購入と経費精算を行ってください。
*   購入支払月に必ず経費申請を行ってください。経費申請を忘れた場合は精算できません。
*   スキルアップ支援制度で購入した書籍は返却不要です。
*   研修受講にかかる移動費用は、スキルアップ支援制度の10万円には含まれず、別途経費精算が可能です。移動費用はスキルアップ支援制度一覧に記載不要です。
*   送料や代引き手数料も、領収書に含まれていれば精算金額に含めることができます。
*   領収書を紛失した場合は、通常の経費精算と同様に精算対象外となります。

ご不明な点があれば、人事あてフォームよりお問い合わせください。

======================================================================
COMPLETE RESPONSE STRUCTURE (JSON)
======================================================================
{
  "sdk_http_response": {
    "headers": {
      "content-type": "application/json; charset=UTF-8",
      "vary": "Origin, X-Origin, Referer",
      "content-encoding": "gzip",
      "date": "Thu, 20 Nov 2025 07:14:55 GMT",
      "server": "scaffolding on HTTPServer2",
      "x-xss-protection": "0",
      "x-frame-options": "SAMEORIGIN",
      "x-content-type-options": "nosniff",
      "server-timing": "gfet4t7; dur=6869",
      "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000",
      "transfer-encoding": "chunked"
    },
    "body": null
  },
  "candidates": [
    {
      "content": {
        "parts": [
          {
            "media_resolution": null,
            "code_execution_result": null,
            "executable_code": null,
            "file_data": null,
            "function_call": null,
            "function_response": null,
            "inline_data": null,
            "text": "スキルアップ支援制度の申請手順は以下の通りです。\n\n**1. 相談・申請**\n*   本人がチームリーダーに相談します。この際、会社の事業目標やチーム目標にどのように貢献できるかという点を伝えます。\n*   チームリーダーから支援に関する提案を受けることもあります。\n*   本人が「スキルアップ支援制度利用一覧」に申請内容を1列まで記載して更新します。\n\n**2. 承認**\n*   チームリーダーが、会社の事業目標やチーム目標への貢献度を考慮して承認を判断します。\n*   チームリーダーは「スキルアップ支援制度利用一覧」に判断内容を追記し、K列以降を入力します。\n    *   **チームリーダー自身が申請する場合**は、事業責任者の承認が必要です。\n\n**上限金額を上回る支援が必要な場合(補足)**\n*   まずチームリーダーが事業責任者に相談します。\n*   その後、本人が経費申請システムの事前申請ワークフローを利用し、「購入物申請」として承認または却下を確認します。\n    *   申請経路は「申請者 ⇒ チームリーダー ⇒ 最終承認者」となります。\n    *   申請時に承認者を指定し、指定されたメンバーの承認が必要となります。\n\n**承認後の請求手順**\n\n**3. 購入支払日記載**\n*   チームリーダーの承認後、立替購入を行い、その支払日を「スキルアップ支援制度利用一覧」のJ列に記載して更新します。\n\n**4. 請求方法**\n*   本人が立替払いをした後、経費申請システムで請求申請を行います。\n*   領収書が必須で、申請日は領収書の日付(支払日)とします。\n*   費目は以下のいずれかを選択します。\n    *   スキルアップ支援制度 研修\n    *   スキルアップ支援制度 書籍\n    *   スキルアップ支援制度 資格取得\n*   上限金額を上回る申請で、事前申請を行っている場合は、事前申請と関連付けて経費申請を行います。\n\n**5. 請求承認**\n*   チームリーダーが申請内容を確認し、承認します。\n\n**6. 支給方法**\n*   経費として支給されます。\n\n**重要な注意点**\n*   チームリーダーの承認がない申請は精算できません。承認後に購入と経費精算を行ってください。\n*   購入支払月に必ず経費申請を行ってください。経費申請を忘れた場合は精算できません。\n*   スキルアップ支援制度で購入した書籍は返却不要です。\n*   研修受講にかかる移動費用は、スキルアップ支援制度の10万円には含まれず、別途経費精算が可能です。移動費用はスキルアップ支援制度一覧に記載不要です。\n*   送料や代引き手数料も、領収書に含まれていれば精算金額に含めることができます。\n*   領収書を紛失した場合は、通常の経費精算と同様に精算対象外となります。\n\nご不明な点があれば、人事あてフォームよりお問い合わせください。",
            "thought": null,
            "thought_signature": null,
            "video_metadata": null
          }
        ],
        "role": "model"
      },
      "citation_metadata": null,
      "finish_message": null,
      "token_count": null,
      "finish_reason": "STOP",
      "avg_logprobs": null,
      "grounding_metadata": {
        "google_maps_widget_context_token": null,
        "grounding_chunks": [
          {
            "maps": null,
            "retrieved_context": {
              "document_name": null,
              "rag_chunk": null,
              "text": "★省略★",
              "title": "document.pdf",
              "uri": null
            },
            "web": null
          },
          {
            "maps": null,
            "retrieved_context": {
              "document_name": null,
              "rag_chunk": null,
              "text": "★省略★",
              "title": "document.pdf",
              "uri": null
            },
            "web": null
          }
        ],
        "grounding_supports": [
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 72,
              "part_index": null,
              "start_index": null,
              "text": "スキルアップ支援制度の申請手順は以下の通りです。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 96,
              "part_index": null,
              "start_index": 79,
              "text": "相談・申請**"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 269,
              "part_index": null,
              "start_index": 152,
              "text": "この際、会社の事業目標やチーム目標にどのように貢献できるかという点を伝えます。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 361,
              "part_index": null,
              "start_index": 270,
              "text": "*   チームリーダーから支援に関する提案を受けることもあります。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 481,
              "part_index": null,
              "start_index": 362,
              "text": "*   本人が「スキルアップ支援制度利用一覧」に申請内容を1列まで記載して更新します。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 496,
              "part_index": null,
              "start_index": 488,
              "text": "承認**"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 624,
              "part_index": null,
              "start_index": 497,
              "text": "*   チームリーダーが、会社の事業目標やチーム目標への貢献度を考慮して承認を判断します。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 762,
              "part_index": null,
              "start_index": 625,
              "text": "*   チームリーダーは「スキルアップ支援制度利用一覧」に判断内容を追記し、K列以降を入力します。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 871,
              "part_index": null,
              "start_index": 767,
              "text": "*   **チームリーダー自身が申請する場合**は、事業責任者の承認が必要です。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 937,
              "part_index": null,
              "start_index": 873,
              "text": "**上限金額を上回る支援が必要な場合(補足)**"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1008,
              "part_index": null,
              "start_index": 938,
              "text": "*   まずチームリーダーが事業責任者に相談します。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1165,
              "part_index": null,
              "start_index": 1009,
              "text": "*   その後、本人が経費申請システムの事前申請ワークフローを利用し、「購入物申請」として承認または却下を確認します。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1265,
              "part_index": null,
              "start_index": 1170,
              "text": "*   申請経路は「申請者 ⇒ チームリーダー ⇒ 最終承認者」となります。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1373,
              "part_index": null,
              "start_index": 1270,
              "text": "*   申請時に承認者を指定し、指定されたメンバーの承認が必要となります。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1403,
              "part_index": null,
              "start_index": 1375,
              "text": "**承認後の請求手順**"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1433,
              "part_index": null,
              "start_index": 1410,
              "text": "購入支払日記載**"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1604,
              "part_index": null,
              "start_index": 1434,
              "text": "*   チームリーダーの承認後、立替購入を行い、その支払日を「スキルアップ支援制度利用一覧」のJ列に記載して更新します。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1625,
              "part_index": null,
              "start_index": 1611,
              "text": "請求方法**"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1710,
              "part_index": null,
              "start_index": 1626,
              "text": "*   本人が立替払いをした後、経費申請システムで請求申請を行います。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1799,
              "part_index": null,
              "start_index": 1711,
              "text": "*   領収書が必須で、申請日は領収書の日付(支払日)とします。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1855,
              "part_index": null,
              "start_index": 1800,
              "text": "*   費目は以下のいずれかを選択します。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1901,
              "part_index": null,
              "start_index": 1860,
              "text": "*   スキルアップ支援制度 研修"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1947,
              "part_index": null,
              "start_index": 1906,
              "text": "*   スキルアップ支援制度 書籍"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 1999,
              "part_index": null,
              "start_index": 1952,
              "text": "*   スキルアップ支援制度 資格取得"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 2142,
              "part_index": null,
              "start_index": 2000,
              "text": "*   上限金額を上回る申請で、事前申請を行っている場合は、事前申請と関連付けて経費申請を行います。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 2163,
              "part_index": null,
              "start_index": 2149,
              "text": "請求承認**"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 2237,
              "part_index": null,
              "start_index": 2164,
              "text": "*   チームリーダーが申請内容を確認し、承認します。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 2258,
              "part_index": null,
              "start_index": 2244,
              "text": "支給方法**"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 2299,
              "part_index": null,
              "start_index": 2259,
              "text": "*   経費として支給されます。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              1
            ],
            "segment": {
              "end_index": 2460,
              "part_index": null,
              "start_index": 2400,
              "text": "承認後に購入と経費精算を行ってください。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              1
            ],
            "segment": {
              "end_index": 2585,
              "part_index": null,
              "start_index": 2528,
              "text": "経費申請を忘れた場合は精算できません。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 2665,
              "part_index": null,
              "start_index": 2586,
              "text": "*   スキルアップ支援制度で購入した書籍は返却不要です。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 2885,
              "part_index": null,
              "start_index": 2810,
              "text": "移動費用はスキルアップ支援制度一覧に記載不要です。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 3004,
              "part_index": null,
              "start_index": 2886,
              "text": "*   送料や代引き手数料も、領収書に含まれていれば精算金額に含めることができます。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              0
            ],
            "segment": {
              "end_index": 3111,
              "part_index": null,
              "start_index": 3005,
              "text": "*   領収書を紛失した場合は、通常の経費精算と同様に精算対象外となります。"
            }
          },
          {
            "confidence_scores": null,
            "grounding_chunk_indices": [
              1
            ],
            "segment": {
              "end_index": 3206,
              "part_index": null,
              "start_index": 3113,
              "text": "ご不明な点があれば、人事あてフォームよりお問い合わせください。"
            }
          }
        ],
        "retrieval_metadata": null,
        "retrieval_queries": null,
        "search_entry_point": null,
        "source_flagging_uris": null,
        "web_search_queries": null
      },
      "index": 0,
      "logprobs_result": null,
      "safety_ratings": null,
      "url_context_metadata": null
    }
  ],
  "create_time": null,
  "model_version": "gemini-2.5-flash",
  "prompt_feedback": null,
  "response_id": "★省略★",
  "usage_metadata": {
    "cache_tokens_details": null,
    "cached_content_token_count": null,
    "candidates_token_count": 678,
    "candidates_tokens_details": null,
    "prompt_token_count": 12,
    "prompt_tokens_details": [
      {
        "modality": "TEXT",
        "token_count": 12
      }
    ],
    "thoughts_token_count": 38,
    "tool_use_prompt_token_count": 67,
    "tool_use_prompt_tokens_details": [
      {
        "modality": "TEXT",
        "token_count": 67
      }
    ],
    "total_token_count": 795,
    "traffic_type": null
  },
  "automatic_function_calling_history": [],
  "parsed": null
}

ファイル検索ストアの削除

次に、ファイル検索ストアを削除してみましょう。次のスクリプトを使います。これもClaude Codeで作成しました。

# -*- coding: utf-8 -*-
import os
import sys
from dotenv import load_dotenv
from google import genai

load_dotenv()

client = genai.Client(api_key=os.getenv('GOOGLE_API_KEY'))

def list_stores():
    """すべてのストアを一覧表示"""
    print("=== Available File Search Stores ===\n")
    stores = client.file_search_stores.list()

    store_list = []
    for idx, store in enumerate(stores, 1):
        store_list.append(store)
        size_info = "empty"
        if hasattr(store, 'size_bytes') and store.size_bytes:
            size_mb = store.size_bytes / (1024 * 1024)
            size_info = f"{size_mb:.2f} MB"

        print(f"{idx}. {store.name}")
        print(f"   Size: {size_info}")
        if hasattr(store, 'display_name') and store.display_name:
            print(f"   Display Name: {store.display_name}")
        if hasattr(store, 'create_time'):
            print(f"   Created: {store.create_time}")
        print()

    return store_list

def delete_documents_in_store(store_name):
    """ストア内のすべてのドキュメントを削除"""
    try:
        documents = client.file_search_stores.documents.list(parent=store_name)
        doc_count = 0
        for doc in documents:
            try:
                # forceパラメータをconfigで渡す
                client.file_search_stores.documents.delete(
                    name=doc.name,
                    config={"force": True}
                )
                doc_count += 1
                print(f"  ✓ Deleted document: {doc.name}")
            except Exception as e:
                print(f"  ✗ Failed to delete document: {doc.name}")
                print(f"    Error: {e}")
        return doc_count
    except Exception as e:
        print(f"  Warning: Could not list documents: {e}")
        return 0

def delete_store(store_name):
    """ストアを削除(中のドキュメントも含めて)"""
    try:
        # まず、ストア内のドキュメントを削除
        print(f"Deleting documents in {store_name}...")
        doc_count = delete_documents_in_store(store_name)
        if doc_count > 0:
            print(f"  Deleted {doc_count} document(s)")

        # ストア自体を削除
        client.file_search_stores.delete(name=store_name)
        print(f"✓ Successfully deleted store: {store_name}")
        return True
    except Exception as e:
        print(f"✗ Failed to delete store: {store_name}")
        print(f"  Error: {e}")
        return False

def main():
    stores = list_stores()

    if not stores:
        print("No stores found.")
        return

    print(f"Total: {len(stores)} store(s)")
    print("\n" + "=" * 50)

    # 削除方法の選択
    print("\nDelete options:")
    print("1. Delete specific store by number")
    print("2. Delete all stores")
    print("3. Cancel")

    choice = input("\nEnter your choice (1-3): ").strip()

    if choice == "1":
        # 特定のストアを削除
        store_num = input(f"Enter store number (1-{len(stores)}): ").strip()
        try:
            idx = int(store_num) - 1
            if 0 <= idx < len(stores):
                store = stores[idx]
                confirm = input(f"Delete '{store.name}'? (yes/no): ").strip().lower()
                if confirm == "yes":
                    delete_store(store.name)
                else:
                    print("Cancelled.")
            else:
                print("Invalid store number.")
        except ValueError:
            print("Invalid input.")

    elif choice == "2":
        # すべてのストアを削除
        confirm = input(f"Delete ALL {len(stores)} store(s)? This cannot be undone! (yes/no): ").strip().lower()
        if confirm == "yes":
            print("\nDeleting all stores...")
            success_count = 0
            for store in stores:
                if delete_store(store.name):
                    success_count += 1
            print(f"\nDeleted {success_count}/{len(stores)} store(s)")
        else:
            print("Cancelled.")

    else:
        print("Cancelled.")

if __name__ == "__main__":
    main()

最初に削除プログラムを作成したときは、いきなりファイル検索ストアを削除しようとすると、中のドキュメントが残っていると削除できないエラーになってしまいました。そのためこの削除プログラムでは、先に中身を消してから入れ物を消す、という形になっています。

まとめ

本稿では「Gemini API ファイル検索」のサンプルプログラムを動かしてみて、そこからさらにファイル検索ストアの一覧表示・詳細情報表示・削除のためのプログラムをClaude Codeで作らせてみました。

このGemini API File Search Toolを使うとRAGシステムの構築が簡単かつ安価にできるので、これまで手が出せなかった層にも届きうると考えます。ただ、2025年11月19日現在、Gemini API File Search Toolは登場したばかりであり、まだ機能が十分ではないのか、それとも仕様通りなのか、まだまだ情報が足りません。しかし、出揃ってからシステムを作っていくよりは、途中で仕様変更があったりするかもしれませんが、現状の情報でシステムを作り始めてみて慣れていったほうがいいようにも思います。

今後もGemini API File Search Toolについて継続して調査していきます。

Author

Chef・Docker・Mirantis製品などの技術要素に加えて、会議の進め方・文章の書き方などの業務改善にも取り組んでいます。「Chef活用ガイド」共著のほか、Debian Official Developerもやっています。

Daisuke Higuchiの記事一覧

新規CTA