個人で壁紙アプリを開発していたころ、画像の自動分類機能にGemini File APIを組み込んだことがあります。アップロード後に file.state を確認すると PROCESSING と表示されるのは正常な流れのはずでした。ところが何分待っても状態が変わらず、次のリクエストに進めないまま処理が止まってしまう。
はじめはネットワークの問題かと思いましたが、原因はポーリングのロジックにありました。同じ問題に何度か直面するうちに、対処のパターンが整理されてきたので記録しておきます。
File APIの PROCESSING 状態とは何か
Gemini File APIにファイルをアップロードすると、即座に使えるわけではありません。特に動画ファイルや大きなPDFは、Googleのサーバーが内部でトランスコードや解析処理を行い、その間 PROCESSING 状態が続きます。
処理が完了すると ACTIVE に変わり、はじめてモデルへのリクエストに使えるようになります。FAILED になった場合は再アップロードが必要です。
import google.generativeai as genai
genai.configure(api_key="YOUR_GEMINI_API_KEY")
# ファイルをアップロード
uploaded_file = genai.upload_file("sample_video.mp4", mime_type="video/mp4")
print(uploaded_file.state) # → FileState.PROCESSING状態の遷移は PROCESSING → ACTIVE または PROCESSING → FAILED の2択です。ACTIVE になるまで待たずにモデルに渡すと、次のエラーが発生します。
google.api_core.exceptions.FailedPrecondition: 400 File is not yet ready
止まる原因は3パターンある
PROCESSING のまま進まない問題には、実際には3つの独立した原因があります。症状が似ているので混同しがちです。
パターン1:ポーリングが一度しか実行されていない
最もよくあるミスです。file.state を一度確認して PROCESSING だったら、そのまま処理を止めてしまうコードです。
# ❌ 悪い例:一度確認して諦める
file = genai.get_file(uploaded_file.name)
if file.state == genai.protos.File.State.PROCESSING:
print("まだ処理中")
# ここで止まってしまう処理完了まで繰り返し確認するループが必要です。
パターン2:ポーリング間隔が短すぎる
time.sleep(1) のような短い間隔で確認し続けると、APIのレート制限に引っかかります。File APIのリクエスト制限は比較的厳しく、短時間に大量のGET要求を送ると429エラーが返ってきます。
パターン3:ファイル自体が処理できない形式
MIMEタイプの指定ミスや、対応していないコーデックの動画を渡した場合、PROCESSING のまま長時間停滞し最終的に FAILED に遷移します。この場合はいくら待っても ACTIVE にはなりません。
正しいポーリング実装:指数バックオフ付きの待機ループ
実際に使えるポーリング関数です。間隔を徐々に広げる指数バックオフと、全体のタイムアウトを組み合わせています。
import google.generativeai as genai
import time
import math
def wait_for_file_active(
file_name: str,
max_wait_seconds: int = 300, # 最大5分待機
initial_interval: float = 5.0, # 最初の待機間隔
max_interval: float = 30.0, # 最大待機間隔
) -> genai.protos.File:
"""
ファイルがACTIVE状態になるまで待機する。
Args:
file_name: アップロードしたファイルのname(例: "files/abc123")
max_wait_seconds: この時間を超えたらTimeoutErrorを投げる
initial_interval: 最初のポーリング間隔(秒)
max_interval: 最大ポーリング間隔(秒)
Returns:
ACTIVE状態のFileオブジェクト
Raises:
TimeoutError: max_wait_seconds以内にACTIVEにならなかった場合
RuntimeError: ファイルがFAILED状態になった場合
"""
start_time = time.time()
interval = initial_interval
attempt = 0
while True:
elapsed = time.time() - start_time
if elapsed > max_wait_seconds:
raise TimeoutError(
f"ファイル {file_name} が {max_wait_seconds}秒以内にACTIVEになりませんでした"
)
file = genai.get_file(file_name)
attempt += 1
print(f" [{attempt}] {file.name}: {file.state} ({elapsed:.0f}秒経過)")
if file.state == genai.protos.File.State.ACTIVE:
print(f"✅ ACTIVE になりました({elapsed:.0f}秒)")
return file
if file.state == genai.protos.File.State.FAILED:
raise RuntimeError(
f"ファイル {file_name} の処理が失敗しました: {getattr(file, 'error', '不明なエラー')}"
)
# 指数バックオフ:間隔を徐々に広げる
time.sleep(interval)
interval = min(interval * 1.5, max_interval)使い方はシンプルです。
# アップロード
uploaded = genai.upload_file("document.pdf", mime_type="application/pdf")
print(f"アップロード完了: {uploaded.name}")
try:
# ACTIVE になるまで待機
active_file = wait_for_file_active(uploaded.name, max_wait_seconds=180)
# モデルにリクエスト
model = genai.GenerativeModel("gemini-2.5-flash")
response = model.generate_content(["この書類を要約してください。", active_file])
print(response.text)
except TimeoutError as e:
print(f"タイムアウト: {e}")
# 必要に応じてファイルを削除
genai.delete_file(uploaded.name)
except RuntimeError as e:
print(f"処理失敗: {e}")タイムアウト時のクリーンアップ
タイムアウトや失敗が発生したとき、アップロードしたファイルはGoogleのサーバーに残ります。File APIの保持期限は48時間ですが、大量のファイルを扱う処理では使い終わったファイルを明示的に削除する習慣をつけることをお勧めします。
import contextlib
@contextlib.contextmanager
def managed_file(file_path: str, mime_type: str):
"""アップロードしたファイルを自動的にクリーンアップするコンテキストマネージャー"""
uploaded = genai.upload_file(file_path, mime_type=mime_type)
try:
active = wait_for_file_active(uploaded.name)
yield active
finally:
# 正常終了・例外発生どちらの場合でも削除
try:
genai.delete_file(uploaded.name)
print(f"🗑️ ファイル削除: {uploaded.name}")
except Exception:
pass # 削除失敗は無視
# 使い方
with managed_file("video.mp4", "video/mp4") as file:
response = model.generate_content(["動画の内容を説明してください。", file])
print(response.text)
# withブロックを抜けた時点で自動削除MIMEタイプのミスで止まる場合
FAILED になるまでに時間がかかるケースの多くは、MIMEタイプの指定が正しくないことが原因です。たとえば .mp4 ファイルを video/mpeg で送ると、処理は開始されますが最終的に失敗します。
Gemini File APIがサポートするMIMEタイプの主な対応は以下の通りです。
- 動画:
video/mp4、video/mpeg、video/mov、video/avi、video/webm - 音声:
audio/mpeg、audio/wav、audio/ogg、audio/flac - 画像:
image/jpeg、image/png、image/gif、image/webp - ドキュメント:
application/pdf、text/plain、text/html
ファイルの拡張子からMIMEタイプを自動判定するユーティリティを使うと、ミスが減ります。
import mimetypes
def get_mime_type(file_path: str) -> str:
"""ファイルパスからMIMEタイプを判定する"""
mime_type, _ = mimetypes.guess_type(file_path)
if mime_type is None:
raise ValueError(f"MIMEタイプを判定できませんでした: {file_path}")
return mime_type
# 使い方
path = "sample_video.mp4"
mime = get_mime_type(path) # → "video/mp4"
uploaded = genai.upload_file(path, mime_type=mime)バッチ処理での並列アップロード
壁紙アプリの画像分類では、多数の画像を一括で処理する必要がありました。ファイルを順番にアップロードして一つずつ待機するのは非効率です。並列アップロードと非同期ポーリングを組み合わせると、処理時間を大幅に短縮できます。
import asyncio
import google.generativeai as genai
async def async_wait_for_active(file_name: str, max_wait: int = 300) -> genai.protos.File:
"""非同期版のファイル待機関数"""
start = asyncio.get_event_loop().time()
interval = 5.0
while True:
elapsed = asyncio.get_event_loop().time() - start
if elapsed > max_wait:
raise TimeoutError(f"{file_name} がタイムアウト")
# genai.get_file は同期関数なのでexecutorで実行
loop = asyncio.get_event_loop()
file = await loop.run_in_executor(None, genai.get_file, file_name)
if file.state == genai.protos.File.State.ACTIVE:
return file
if file.state == genai.protos.File.State.FAILED:
raise RuntimeError(f"{file_name} の処理が失敗")
await asyncio.sleep(min(interval, 30.0))
interval *= 1.5
async def batch_upload_and_wait(file_paths: list[str]) -> list[genai.protos.File]:
"""複数ファイルを並列アップロードし、全て ACTIVE になるまで待機"""
loop = asyncio.get_event_loop()
# 順番にアップロード(upload_fileは同期)
uploaded_files = []
for path in file_paths:
mime = get_mime_type(path)
f = await loop.run_in_executor(None, lambda p=path, m=mime: genai.upload_file(p, mime_type=m))
uploaded_files.append(f)
print(f"アップロード: {f.name}")
# 全ファイルの完了を並列待機
active_files = await asyncio.gather(
*[async_wait_for_active(f.name) for f in uploaded_files]
)
return list(active_files)
# 実行例
# files = asyncio.run(batch_upload_and_wait(["img1.jpg", "img2.jpg", "img3.jpg"]))2014年からアプリ開発を続けてきた経験上、バッチ処理のAPIコストは積み重なります。並列化で時間を短縮しつつ、不要なファイルは即座に削除する設計が、長期的な運用コスト削減につながります。
トラブルシューティングチェックリスト
PROCESSING で止まると感じたときに確認する順序です。
ポーリングのループが実装されているか確認してください。一度だけ確認するコードでは必ず止まります。次に、ポーリング間隔が5秒以上あるか見てください。短すぎると429エラーが混入して状況が複雑になります。MIMEタイプが正しいかも確認ポイントです。mimetypes ライブラリで自動判定する方法が確実です。最後に、max_wait_seconds を設定しているか確認してください。無限ループは本番環境で確実に問題を起こします。
File APIの詳しいアップロード手順や対応ファイル形式については、Gemini File API完全ガイドも参照してください。非同期処理に絡むタイムアウト問題全般については、Gemini APIエラーハンドリングガイドが参考になります。
ポーリングの設計を一度きちんと整えてしまえば、File APIはとても使いやすいツールです。長期運用の中で安定して動き続けるコードを目指していきましょう。
同じ問題で詰まっている方の参考になれば幸いです。