データサイズが大きいPDFファイルをどのようにGeminiに読み込ませるか

https://cloud.google.com/vertex-ai/generative-ai/docs/long-context?hl=ja

Geminiは100万、モデルによっては200万トークンまで読み込むことができることに興奮した人は多いのではないでしょうか（私もその一人でした）。
またマルチモーダルという特徴もあることからPDFファイルであれば数百ページ、動画であれば映画1本分など大量のデータを1度に処理ができるのでさまざまな分野への活用が拡がりました。

一方でGeminiが参照できるデータのサイズには以下のような制限があります。
* 個人が利用できるGemini(Gemini Advanced)でアップロードできるファイルサイズは100MB
* Google CloudのVertex AI Studioのプロンプト上でのアップロードファイルサイズは7MB
* Google CloudのVertex AI StudioでCloud Storageから参照できるファイルのサイズは50MB
* Google CloudのVertex AI Searchが参照できるファイルのサイズは最大で200MB

マルチモーダルが売りであるにも関わらず上記のようなファイル制限があるために映画やカタログファイルなどのデータサイズが大きいものをどのように読み込ませるかが気になったので調べました。

# 案１：データを細かく分割する
上記のファイルサイズの上限に合うようにファイルを分割しそれぞれ読み込ませるという方法を思いつきました。
問題点としては1つのデータを分割することでそれぞれのデータの前後の文脈（コンテキスト）が失われることです。

複数のファイルから特定の情報を探してくる検索のように使用する場合にはこちらでも良いかもしれません。

# 案2：Gemini APIのFiles API
https://ai.google.dev/gemini-api/docs/files?hl=ja

20MBを超える場合には必ずFiles APIを使用して欲しいとのこと。
特徴としては以下となります。
* ファイルは48時間後に自動で削除
* プロジェクトごとに最大20GBのファイルを保存可能
* ファイルあたりの最大サイズは2GB
* メタデータの取得が可能
* 複数のファイルのアップロードも可能
* 無料で利用可能

## 検証
今回は動画ファイルを読み込みそれを要約するプログラムを実装しました。
以下を考慮してコードを記述しました。
* 大きいサイズのアップロードには時間がかかるのでアップロード完了後にファイルを読み込む
* 1時間ほどの動画ファイルなので100万トークンを読み込めるgemini 2.5 pro(試用運用版)を使用
    * [API料金のご注意ください](https://ai.google.dev/gemini-api/docs/pricing?hl=ja#gemini-2.5-pro-preview)
* 要約の内容をWordファイル（docx）として出力

```python:main.py
# 標準的なインポート方法
import google.generativeai as genai
import os
import time
import docx

# --- 設定項目 ---

# [必須] アップロードするローカルファイルのパス
local_file_path = "path/to/your/file" # ここにアップロードするファイルのパスを入力

# [必須] Geminiモデルの設定 (動画/音声処理に対応したモデルを指定)
# 例: "gemini-1.5-flash-latest" や "gemini-1.5-pro-latest"
model_name = "gemini-2.5-pro-exp-03-25"

# [必須] 認証設定 (環境変数 GOOGLE_API_KEY を推奨)
api_key = os.getenv("GOOGLE_API_KEY")
if not api_key:
    # 環境変数が設定されていない場合、以下の行を編集
    api_key = "GOOGLE_API_KEY" # ここにAPIキーを入力

# APIキーを設定
if api_key and api_key != "YOUR_API_KEY": # 初期値でないことを確認
    try:
        genai.configure(api_key=api_key)
        print("APIキーによる認証を設定しました。")
    except Exception as e:
        print(f"エラー: APIキーの設定に失敗しました: {e}")
        exit() # 認証できない場合は終了
else:
    print("エラー: 有効なAPIキーが設定されていません。")
    print("環境変数 GOOGLE_API_KEY を設定するか、コード内の 'YOUR_API_KEY' を置き換えてください。")
    exit()

# [必須] ファイルに対する指示 (プロンプト)
# ユーザー提供のプロンプトを使用
prompt = """ファイルは打ち合わせの動画となります。上司に報告する必要があるため要約してください。"""

# [必須] 出力DOCXファイル名の設定 (オプション)
# デフォルトでは入力ファイル名に基づいて作成
output_docx_filename = os.path.splitext(os.path.basename(local_file_path))[0] + "_summary.docx"

# --- 設定項目ここまで ---

# 戻り値の型ヒントを | を使う形式に変更
def upload_and_process_file(file_path: str, model_id: str, user_prompt: str) -> str | None:
    """
    ファイルをアップロードし、ACTIVE状態になるのを待ってからGemini APIで処理する関数。

    Args:
        file_path (str): アップロードするローカルファイルのパス。
        model_id (str): 使用するGeminiモデル名。
        user_prompt (str): Gemini APIに送信するプロンプト。

    Returns:
        str | None: APIからのレスポンス（処理結果テキスト）。エラー時はNone。
    """
    uploaded_file_object = None # スコープ外でも参照できるよう初期化
    try:
        # 1. ファイルをアップロード
        print(f"ファイルをアップロード中: {file_path}...")
        # display_name は省略可能
        uploaded_file_object = genai.upload_file(path=file_path)
        print(f"ファイル '{uploaded_file_object.display_name}' をアップロードしました。")
        print(f"  File Name (Resource Name): {uploaded_file_object.name}")
        print(f"  現在の状態: {uploaded_file_object.state.name}")

        # 2. ファイルの状態を確認・待機
        file_name = uploaded_file_object.name # 後で使用するためファイル名を取得
        print(f"ファイル '{file_name}' の処理完了を待機中...")
        wait_time = 0
        # 処理時間はファイルサイズによるため、長めに設定 (例: 10分)
        max_wait_time = 600
        sleep_interval = 15 # 15秒ごとに確認

        # ループ中も uploaded_file_object を更新する
        current_file_state = uploaded_file_object.state.name
        while current_file_state == "PROCESSING" and wait_time < max_wait_time:
            print(f"  状態: PROCESSING。{sleep_interval}秒待機します...")
            time.sleep(sleep_interval)
            wait_time += sleep_interval
            # 最新の状態を取得
            uploaded_file_object = genai.get_file(name=file_name)
            current_file_state = uploaded_file_object.state.name


        if current_file_state != "ACTIVE":
            print(f"エラー: ファイル '{file_name}' がACTIVE状態になりません (状態: {current_file_state})。")
            # 必要に応じてアップロードしたファイルを削除する処理を追加しても良い
            # genai.delete_file(name=file_name)
            return None

        print(f"ファイル '{file_name}' はACTIVEです。")
        # MIMEタイプは内部で解釈されることを期待するため、ここでは明示的に使用しない

        # 3. モデルを準備
        model = genai.GenerativeModel(model_id)

        # 4. ファイル参照を作成せず、ファイルオブジェクトを直接渡す
        #    <-- ここで Part オブジェクトの作成を削除 -->

        # 5. コンテンツ生成リクエストを送信
        print("Gemini APIにリクエストを送信中...")
        response = model.generate_content(
            [user_prompt, uploaded_file_object], # プロンプトとファイルオブジェクトをリストで渡す
            request_options={"timeout": 600} # タイムアウトを10分に設定
        )

        # 6. 結果を返す
        print("API処理完了。")
        return response.text

    except AttributeError as ae:
        # Part 関連のエラーは発生しないはずだが、念のため残す
        if "'Part'" in str(ae):
             print(f"エラー: 'Part' クラスの利用で属性エラーが発生しました: {ae}")
             print("  ライブラリのバージョンやインストール状態を確認してください ('pip install --upgrade google-generativeai')。")
        elif "'upload_file'" in str(ae):
             print(f"エラー: 'upload_file' 関数の利用で属性エラーが発生しました: {ae}")
             print("  ライブラリのバージョンが古い可能性があります。")
        else:
             print(f"エラー: APIリクエスト中に属性エラーが発生しました: {ae}")
        return None
    except Exception as e:
        # APIがファイルオブジェクトを直接受け付けない場合のエラーを捕捉する可能性
        print(f"エラー: 処理中にエラーが発生しました: {e}")
        return None
    finally:
        # オプション: 処理完了後やエラー発生時にファイルを削除する場合
        # if uploaded_file_object:
        #     try:
        #         print(f"ファイルを削除中: {uploaded_file_object.name}")
        #         genai.delete_file(name=uploaded_file_object.name)
        #         print("ファイルを削除しました。")
        #     except Exception as delete_e:
        #         print(f"エラー: ファイルの削除中にエラーが発生しました: {delete_e}")
        pass # 必要に応じて削除処理を有効化

# --- メイン処理 ---
if __name__ == "__main__":
    if not os.path.exists(local_file_path):
         print(f"エラー: 指定されたローカルファイルが見つかりません: {local_file_path}")
    elif api_key == "YOUR_API_KEY" and not os.getenv("GOOGLE_API_KEY"):
        print("エラー: スクリプト上部の `api_key` を設定するか、環境変数 `GOOGLE_API_KEY` を設定してください。")
    else:
        # 関数を実行して結果を取得
        result_text = upload_and_process_file(
            file_path=local_file_path,
            model_id=model_name,
            user_prompt=prompt
        )

        # ★★★ 変更点: 結果をDOCXファイルに保存 ★★★
        if result_text:
            try:
                # 新しいDOCXドキュメントを作成
                document = docx.Document()
                # 結果テキストを段落として追加
                document.add_paragraph(result_text)
                # DOCXファイルを保存
                document.save(output_docx_filename)
                print(f"\n--- 処理結果を '{output_docx_filename}' に保存しました ---")
            except Exception as e:
                print(f"\nエラー: DOCXファイルへの保存中にエラーが発生しました: {e}")
                # 保存に失敗した場合でも、結果をコンソールに出力する (オプション)
                print("\n--- 処理結果 (コンソール出力) ---")
                print(result_text)
                print("--- 処理結果ここまで ---")
        else:
            print("\n処理に失敗しました。上記のエラーメッセージを確認してください。")
```

以下が設定項目となります。
* `local_file_path`：ファイルのパスを記述
* `api_key`：Google CloudのAPIキーを記述
* `model_name`：Geminiのモデルを記述
* `prompt`：Geminiへのプロンプトを記述

コードの説明は以下となります。
* `def upload_and_process_file`
    * ファイルのアップロード
    * 動画がアップロード完了（ACTIVE）まで待機
    * Geminiモデルの呼び出しとプロンプトの送信
* `if __name__ == "__main__":`
    * 上の関数の結果をWordファイルに保存
### 結果
今回はHubspotというCRMのウェビナーの動画を要約してみましたのでその一部を掲載します。
Geminiに与えるプロンプトを工夫することでWordファイルに記載する内容を変更することもできます。
![スクリーンショット 2025-05-01 21.54.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2778864/51b84dc7-1d08-49f5-9a00-2cccf4cafd1a.png)



# 結論
Gemini APIのFiles APIを利用することでより大きいデータサイズのファイルを扱えることができます。またデータのアップロードなどは無料なので気軽に使える点も良いですね。
ただしデータサイズが大きいファイルをGeminiが参照する場合、相対的に**読み込むトークン数も多くなるのでGeminiのAPI料金にはご注意ください**

https://ai.google.dev/gemini-api/docs/pricing?hl=ja