◉GEMINI LABEN
●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/API / SDK
◈ API / SDK/2026-05-12中級

Gemini File APIのファイルが「PROCESSING」のまま止まる:タイムアウト処理とリトライ設計

Gemini File APIでアップロードしたファイルがPROCESSING状態のまま進まない問題を解説。ポーリングの正しい実装・タイムアウト設計・失敗時のクリーンアップまで、実際のコードで解決します。

gemini-api288file-api6troubleshooting59python107error-handling8

個人で壁紙アプリを開発していたころ、画像の自動分類機能に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はとても使いやすいツールです。長期運用の中で安定して動き続けるコードを目指していきましょう。

同じ問題で詰まっている方の参考になれば幸いです。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

◈ API / SDK2026-10-16
models は 200 なのに generateContent だけ 403 になる日 — 切り分けの順番を先に決めておく
モデル一覧の取得は 200 で通るのに、生成リクエストだけが 403 permission_denied で止まるときの切り分け手順です。キー・プロジェクト・請求の順に確かめる小さな診断スクリプトと、リトライしてはいけない理由、サポートへ渡す材料の揃え方をまとめました。
◈ API / SDK2026-05-17
Gemini API で画像バッチ分析の INVALID_ARGUMENT エラーを診断する
Gemini API で複数画像を一括分析するとき INVALID_ARGUMENT エラーが出て詰まった経験はありませんか。MIMEタイプ・サイズ制限・リクエスト構造という3つの原因を診断し、File API と inline data を使い分けて根本解決する実践ガイドです。
◈ API / SDK2026-04-27
Gemini API のファイル URI が突然 404 になる問題 — 48時間 TTL を前提にした設計の組み立て方
昨日まで動いていた Gemini API の画像/動画認識が、サーバー再起動後に急に動かなくなることがあります。原因は File API の48時間 TTL でした。検出方法と、TTL を前提にしたアプリ設計の組み立て方を実例で解説します。