GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-05-17中級

Gemini API で画像バッチ分析の INVALID_ARGUMENT エラーを診断する

Gemini API で複数画像を一括分析するとき INVALID_ARGUMENT エラーが出て詰まった経験はありませんか。MIMEタイプ・サイズ制限・リクエスト構造という3つの原因を診断し、File API と inline data を使い分けて根本解決する実践ガイドです。

gemini-api279troubleshooting57image5python103invalid-argument

Beautiful HD Wallpapers(iOS/Android、累計5,000万DL超)の壁紙カテゴリ自動分類機能を実装していたとき、Gemini API の画像バッチ処理で繰り返し INVALID_ARGUMENT エラーに直面しました。最初は「APIキーが間違っているのかな」と疑ったのですが、実際の原因はまったく別のところにありました。

2014年に個人でスマートフォンアプリ開発を始めてから12年、こういうエラーの原因調査に費やした時間を思うと少し苦くなりますが、今回は同じ壁に当たる方のために診断のステップをまとめておきます。

INVALID_ARGUMENT はなぜ「曖昧」なのか

Gemini API の INVALID_ARGUMENT(HTTP 400)は、Google Cloud の慣習通り「リクエストの何かが間違っている」という大カテゴリのエラーです。具体的に何が間違っているかはエラーメッセージの details フィールドに入っていることがありますが、画像関連だと以下の3パターンがほぼ全てを占めます。

最初のパターンは MIMEタイプの指定ミス です。ファイル拡張子と実際の画像フォーマットが一致していない場合に起きます。たとえばファイル名が .jpg でも、中身が WebP や PNG であることは珍しくありません。次が インラインデータのサイズ超過 です。base64 エンコード後のデータが 20MB を超えると API が拒否します。そして contents 配列の構造エラー です。マルチパートリクエストの書き方を間違えると、同じエラーコードで弾かれます。

エラーコードが同じなので、一見するとどれが原因か分かりません。ここで大切なのは e.message を必ず確認することです。メッセージを読むだけで原因が9割絞れます。

原因1: MIMEタイプの指定ミス

最も見落としやすいのがこれです。ファイル拡張子で MIME タイプを決め打ちしているコードは非常に危険です。壁紙アプリでは様々なソースから画像を扱うため、.jpg という拡張子なのに中身は WebP、という状況が頻繁に起きます。実際に Beautiful HD Wallpapers の分類パイプラインでも、この問題が何百件もの INVALID_ARGUMENT を生んでいました。

解決策は拡張子ではなくファイルの中身(マジックバイト)から MIME タイプを検出することです。

import imghdr
from pathlib import Path
 
def get_mime_type(image_path: str) -> str:
    """拡張子ではなくファイルの中身から MIME タイプを判定する"""
    detected = imghdr.what(image_path)
    
    mime_map = {
        "jpeg": "image/jpeg",
        "png": "image/png",
        "webp": "image/webp",
        "gif": "image/gif",
    }
    
    if detected and detected in mime_map:
        return mime_map[detected]
    
    # フォールバック: 拡張子から推定
    path = Path(image_path)
    ext = path.suffix.lower()
    fallback = {
        ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
        ".png": "image/png", ".webp": "image/webp"
    }
    return fallback.get(ext, "image/jpeg")

imghdr は Python 標準ライブラリなので追加インストール不要です。ただし Python 3.13 以降では非推奨になる予定のため、新しいプロジェクトでは python-magic ライブラリを使うことをおすすめします。この関数を導入してから、MIMEタイプ起因の INVALID_ARGUMENT は完全になくなりました。

原因2: インラインデータのサイズ超過

Gemini API のインラインデータ(inline_data)は 1リクエストあたり 20MB の上限があります。base64 エンコードは元のバイナリより約 33% 大きくなるため、実質的な画像ファイルの上限は約 15MB です。

高解像度の壁紙画像(4K UHD など)は 1枚で 10〜20MB になることがあります。バッチで10枚送ろうとしたら 1リクエストで 100MB 超——これが INVALID_ARGUMENT の原因になっていました。しかも途中まで処理してから弾かれるため、どの画像が原因か特定するのにも時間がかかります。

事前にサイズチェックを走らせておくことで、この問題は予防できます。

import os
 
def check_image_size(image_path: str) -> dict:
    """インライン送信可否を事前チェックする"""
    file_size = os.path.getsize(image_path)
    base64_size = file_size * 4 / 3  # base64 後の推定サイズ
    
    return {
        "file_path": image_path,
        "file_size_mb": round(file_size / (1024 * 1024), 2),
        "estimated_base64_mb": round(base64_size / (1024 * 1024), 2),
        "use_file_api": base64_size > 15 * 1024 * 1024,
    }

use_file_api: True になった画像は File API 経由でアップロードします。バッチ処理の前にこのチェックを走らせておくことで、実行途中でエラーが出て中断する事態を防げます。特に夜間バッチのような無人実行では、事前チェックで弾いておく設計が重要です。

原因3: contents 配列の構造エラー

複数画像を contents に詰め込む際、パーツの順序や形式を間違えると INVALID_ARGUMENT になります。REST API を直接叩いているケースで特に多いミスです。Python SDK を経由している場合は SDK が自動変換してくれますが、HTTP クライアントで直接呼び出している場合は注意が必要です。

# ❌ よくある間違い:text と image_data が別の dict に分かれている
contents = [
    {"text": "以下の画像のカテゴリを分類してください"},
    {"inline_data": {"mime_type": "image/jpeg", "data": encoded_img}},
]
 
# ✅ 正しい書き方:1つの content dict の中に parts リストを作る
contents = [
    {
        "parts": [
            {"text": "以下の画像のカテゴリを分類してください"},
            {"inline_data": {"mime_type": "image/jpeg", "data": encoded_img}},
        ]
    }
]

私が実際に詰まったのは、パフォーマンス測定のために Python SDK を一時的にバイパスして REST API を直接叩いた場面でした。SDK が裏でやってくれていた構造変換を自分でやり忘れていたためです。

File API と inline data を使い分ける実装

3つの原因を踏まえた上で、サイズに応じて自動的に File API とインラインデータを切り替える実装例を示します。

import google.generativeai as genai
import base64
import time
import os
 
def upload_or_encode_image(image_path: str, mime_type: str) -> dict:
    """サイズに応じて File API またはインラインデータを選択する"""
    file_size = os.path.getsize(image_path)
    
    if file_size <= 15 * 1024 * 1024:
        with open(image_path, "rb") as f:
            data = base64.b64encode(f.read()).decode("utf-8")
        return {"inline_data": {"mime_type": mime_type, "data": data}}
    
    else:
        uploaded = genai.upload_file(image_path, mime_type=mime_type)
        
        while uploaded.state.name == "PROCESSING":
            time.sleep(5)
            uploaded = genai.get_file(uploaded.name)
        
        if uploaded.state.name == "FAILED":
            raise ValueError(f"File upload failed: {uploaded.name}")
        
        return {"file_data": {"mime_type": mime_type, "file_uri": uploaded.uri}}
 
 
def analyze_images_batch(image_paths: list, prompt: str) -> str:
    """複数画像を安全にバッチ分析する"""
    model = genai.GenerativeModel("gemini-2.5-flash")
    parts = [{"text": prompt}]
    
    for path in image_paths:
        mime = get_mime_type(path)
        part = upload_or_encode_image(path, mime)
        parts.append(part)
    
    response = model.generate_content({"parts": parts})
    return response.text

Beautiful HD Wallpapers では、このパターンを使って約3万枚の壁紙を自動分類しました。4K 画像は File API 経由、サムネイルはインライン——と切り替えることで、INVALID_ARGUMENT はほぼゼロになりました。処理速度も改善し、大きな画像の File API アップロードを先行して走らせておくことで全体のスループットが上がりました。

エラーメッセージで原因を絞る

問題が起きたとき、エラーメッセージの中身を確認する習慣を付けると診断が格段に早くなります。

import google.api_core.exceptions
 
try:
    response = model.generate_content(contents)
except google.api_core.exceptions.InvalidArgument as e:
    print(f"Message: {e.message}")
    # "Request payload size exceeds the limit" → サイズ問題
    # "Invalid MIME type"                       → MIME タイプ問題
    # "Invalid value at 'contents[0].parts'"    → 構造エラー
    # "Could not process Image:"                → 画像の読み込み失敗

このメッセージを見るだけで、上記3つのうちどれが原因かほぼ判定できます。e.message を出力せずにいると、同じエラーコード INVALID_ARGUMENT に対して全部を疑い続けることになり、調査時間が伸びます。

バッチサイズの最適化とリトライ戦略

画像バッチ処理でもう一つ気をつけたいのが、1リクエストに送る画像の枚数です。枚数が多すぎると API のタイムアウトが起きたり、レスポンスが遅くなったりします。壁紙アプリの実装では、1リクエスト最大5枚を上限として設定しました。この枚数は試行錯誤で決めたものですが、4K 画像が混在する場合は3枚程度の方が安定します。

エラーが出たときのリトライも考慮しておく必要があります。INVALID_ARGUMENT は設定ミスなので基本的にリトライしても意味がありませんが、RESOURCE_EXHAUSTED(429)や UNAVAILABLE(503)はリトライが有効です。google.api_core.exceptions の例外クラスを使って適切に分岐することで、バッチ処理全体の信頼性が上がります。File API でアップロードしたファイルは48時間で自動削除されるため、長期間使い回す場合は再アップロードの仕組みも必要です。また、バッチ処理を並列で走らせるときは、同じ画像を複数スレッドからアップロードしないよう、ファイル名をキーにしたキャッシュを持つと効率的です。これらの細かい設計を積み重ねることで、個人開発のスケールでも安定した画像分類パイプラインが作れます。

全体を振り返って:診断の順序

INVALID_ARGUMENT が出たら、次の順で確認するのが最も効率的です。まずエラーキャッチで e.message を出力して原因のヒントを読む。次に全画像を check_image_size() で事前チェックし、15MB 超のものを File API ルートに振り分ける。それでも解消しない場合は get_mime_type() で MIME タイプをファイルの中身から取得しているか確認します。REST API を直接叩いているなら contents[0].parts リスト形式の構造を見直す。

まず小さな画像(1MB 以下)1枚で動作確認してから徐々にサイズと枚数を増やすアプローチが、バッチ処理のデバッグで最も安全です。

壁紙アプリの開発では、画像の種類や解像度が千差万別なため、「1つの設定で全部うまくいく」という期待は禁物です。インラインが向くケースと File API が向くケースを明確に分け、それぞれに適したエラーハンドリングを書いておくことが、長期的な安定稼働につながると感じています。個人開発を12年続けてきた経験から言うと、こういった細かい設計の積み重ねがサービスの信頼性を作るものだと実感しています。同じエラーで時間を溶かしている方の参考になれば幸いです。

シェア

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

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

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

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

関連記事

API / SDK2026-04-30
Gemini API に画像を渡しても「画像が見えません」と返るときの診断と対処
Gemini API に画像を入力したのに『画像は添付されていません』と返ってくる現象の原因を、mime_type・サイズ・SDK差・モデル選択の4視点で切り分け、コピペで動く修正コードまで一気通貫で解説します。
API / SDK2026-06-14
Gemini API の本文が途中で切れる — finish_reason: MAX_TOKENS を検知して続きを継ぎ直す実装メモ
長文生成で末尾が文の途中でぷつりと切れる。原因の多くは finish_reason: MAX_TOKENS です。例外も 200 のまま静かに混入するこの事故を検知し、続きを継ぎ足して全文を取り戻す実装を、思考トークンの落とし穴とあわせてまとめました。
API / SDK2026-06-12
Gemini API の空応答を finish_reason から逆引きする — 診断フロー・再試行分類・監視の実務
response.text が空になる問題は candidates・prompt_feedback・finish_reason の3層で診断できます。思考トークンによる出力枯渇の検出、再試行可否の分類器、空応答率の監視まで、本番で運用している実装をまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →