「画像を渡したのに As an AI, I cannot see images. と返ってきた」「I don't see an attachment in your message と言われた」— Gemini API でマルチモーダル入力を試したとき、最初にぶつかる壁がこれではないでしょうか。
私自身、商品画像を解析するスクリプトを書いたときに丸一日溶かした経験があります。原因はたいていモデル側ではなくクライアント側にあって、4つのポイントを順に潰せば必ず切り分けられます。ここではその診断手順と修正コードを、実際にハマりやすい順に並べてお伝えします。
まず疑うべきは「画像が届いていない」可能性
エラーメッセージが「画像が見えない」と読める場合でも、実際には画像データそのものが API に届いていないケースが大半です。Gemini はリクエスト構造を見て画像パートが存在しないと判断したら、テキストとして「画像はないですよ」と素直に返してきます。
最初に確認すべきは次の3点です。
- リクエストの
contents配列に 画像パートとmime_typeが両方含まれているか - 画像のサイズが 20MB 以下か(インライン送信の上限)
- 使っているモデルが マルチモーダル対応か(
gemini-2.5-flash/gemini-2.5-proなどは対応、text-embedding-004などは非対応)
ここで「mime_type を渡していなかった」が見つかれば、それだけで解決することが多いです。
落とし穴①: mime_type は自動推定されない
新しい統合 SDK google-genai も含め、画像を bytes で渡すときは mime_type を明示しないと API 側で正しく認識してくれません。拡張子から推定してくれる、という思い込みは捨てた方が安全です。
# ❌ よくある間違い: bytes だけを渡す
from google import genai
from pathlib import Path
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
image_bytes = Path("product.jpg").read_bytes()
# このまま渡すと「画像が見えません」と返ることがある
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=["この商品の特徴を教えてください", image_bytes],
)正しくは types.Part.from_bytes() で mime_type を必ず付けます。
# ✅ 正しい書き方: Part を作って mime_type を明示
from google import genai
from google.genai import types
from pathlib import Path
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
image_bytes = Path("product.jpg").read_bytes()
image_part = types.Part.from_bytes(
data=image_bytes,
mime_type="image/jpeg", # PNG なら "image/png", WebP なら "image/webp"
)
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=["この商品の特徴を教えてください", image_part],
)
print(response.text)
# 期待出力例:
# このバッグは A4 サイズの書類が入る大きさで、ショルダーストラップが…PIL.Image オブジェクトを直接渡すルートも公式に対応していますが、それは pillow がインストールされていてかつ画像が壊れていない場合に限ります。本番運用では bytes + mime_type の方が事故が起きにくいです。
落とし穴②: 20MB を超えたら Files API に切り替える
インラインで画像を送る方法は、リクエスト全体(テキスト + 画像のすべて)が 20MB 以下 に収まるときだけ有効です。スマホで撮った高解像度写真や、PDF を画像化した複数ページのデータはあっさりこの上限を超えます。
20MB を超える場合は黙って失敗するわけではなく、リクエスト自体が 400 Bad Request か 413 Payload Too Large で弾かれます。受信側が画像を見られないのではなく、そもそも届いていない状態です。
# ✅ 20MB 超や複数枚を扱うなら Files API を使う
from google import genai
from google.genai import types
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
# アップロード(File オブジェクトが返る)
uploaded = client.files.upload(file="large_photo.jpg")
print(f"uploaded: name={uploaded.name}, uri={uploaded.uri}, state={uploaded.state.name}")
# 期待出力例:
# uploaded: name=files/abc123, uri=https://generativelanguage.googleapis.com/v1beta/files/abc123, state=ACTIVE
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=[
"写っている被写体の特徴を3つ挙げてください",
uploaded, # File オブジェクトをそのまま contents に入れる
],
)
print(response.text)Files API は無料枠でも 20GB / プロジェクトの保存領域があり、48 時間でファイルが自動削除される運用です。アップロード直後に state が PROCESSING のままだと推論が走らないので、画像以外(動画・PDF)の場合は ACTIVE になるまで軽く待つ処理を入れておくと安定します。
詳しい使い分けは Gemini API Files API 使い方完全ガイド と Gemini API Files API トラブルシューティングガイド を読むと、運用上の注意点まで一気にわかります。
落とし穴③: 古い SDK と新 SDK で書き方が違う
2025 年に Google が google-generativeai(旧 SDK)から google-genai(新統合 SDK)への移行を進め、画像入力の書き方が変わりました。古い記事のコードをそのままコピペすると、エラーは出ないのに「画像が見えません」と返るパターンが起きます。
旧 SDK(google-generativeai)の書き方は以下でした。
# 旧 SDK(参考・新規プロジェクトでは推奨しない)
import google.generativeai as genai
from PIL import Image
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-1.5-flash")
img = Image.open("product.jpg")
response = model.generate_content(["説明してください", img])これを新 SDK 環境で実行すると、google.generativeai モジュールが見つからないか、見つかっても挙動が変わっていて意図通りに画像が認識されないことがあります。新 SDK では先述の client.models.generate_content() + types.Part.from_bytes() がスタンダードです。
両 SDK の差分を網羅的に確認したい方は google-genai Python SDK 完全移行ガイド を参照してください。
画像が届いているか、ログで切り分ける
ここまでの3つを直してもまだ「画像が見えない」と返るときは、実際にリクエストへ画像パートが入ったかをログで確かめます。contents の中身を出力して、ちゃんと Part(inline_data=...) が含まれているかを目視するだけでも切り分け精度が上がります。
from google import genai
from google.genai import types
from pathlib import Path
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
image_bytes = Path("product.jpg").read_bytes()
image_part = types.Part.from_bytes(data=image_bytes, mime_type="image/jpeg")
contents = ["この画像を50字以内で説明してください", image_part]
# デバッグログ: 各パートの種類を出力
for i, c in enumerate(contents):
if isinstance(c, str):
print(f"[{i}] text: {c[:30]}...")
elif isinstance(c, types.Part):
if c.inline_data:
print(f"[{i}] inline_data: mime={c.inline_data.mime_type}, "
f"bytes={len(c.inline_data.data)}")
else:
print(f"[{i}] part: {c}")
response = client.models.generate_content(model="gemini-2.5-flash", contents=contents)
print("---")
print(response.text)
# 期待出力例:
# [0] text: この画像を50字以内で説明してください...
# [1] inline_data: mime=image/jpeg, bytes=384921
# ---
# 革製のショルダーバッグ。落ち着いたブラウンで、金具はゴールド。inline_data: mime=image/jpeg, bytes=... がログに出ているのに「画像が見えない」と返るなら、画像コンテンツ自体が真っ白・破損しているか、Safety Filter で弾かれている可能性が高くなります。Safety Filter のブロックを疑うときは、応答オブジェクトの response.prompt_feedback を見ると block_reason が確認できます。
マルチ画像のときに「一部だけ見えない」現象
もうひとつ気付きにくいのが、複数枚の画像のうち 一部だけ認識されない ケースです。1枚が壊れていたり、上限を超えていたり、対応外の形式だったりすると、Gemini はそれを黙って無視して、生きている画像だけ処理します。普段はありがたい挙動ですが、デバッグ中は混乱します。
防御策は、contents を組み立てる前に各画像をバリデーションすることです。
from pathlib import Path
from google.genai import types
SUPPORTED = {"image/jpeg", "image/png", "image/webp", "image/heic", "image/heif"}
MAX_INLINE_BYTES = 19 * 1024 * 1024 # 20MB制限の手前で止める
def to_part(path: str, mime_type: str) -> types.Part:
if mime_type not in SUPPORTED:
raise ValueError(f"未対応の mime_type {mime_type}: {path}")
data = Path(path).read_bytes()
if len(data) == 0:
raise ValueError(f"{path} が空です")
if len(data) > MAX_INLINE_BYTES:
raise ValueError(f"{path} は {len(data)} バイト(Files API推奨)")
return types.Part.from_bytes(data=data, mime_type=mime_type)
parts = [
to_part("front.jpg", "image/jpeg"),
to_part("back.png", "image/png"),
]
contents = ["2枚の商品写真を比較し、相違点を要約してください"] + partsAPI に投げる前にローカルで弾いておけば、エラーメッセージから問題画像を一発で特定できます。「画像が見えません」という曖昧な返答に悩まされずに済む、地味ですが効く対策です。
Vertex AI を使っている場合の補足
AI Studio の API キー方式から Vertex AI に切り替えている場合(リージョン要件や高い割り当てが理由のことが多いはずです)も、ここまでの落とし穴はそのまま当てはまります。違うのはクライアント初期化だけで、types.Part.from_bytes() も Files API もまったく同じ書き方です。
# Vertex AI 版の同じ修正
from google import genai
from google.genai import types
from pathlib import Path
client = genai.Client(
vertexai=True,
project="your-gcp-project-id",
location="us-central1",
)
image_bytes = Path("product.jpg").read_bytes()
image_part = types.Part.from_bytes(data=image_bytes, mime_type="image/jpeg")
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=["この商品の特徴を教えてください", image_part],
)
print(response.text)Vertex AI 固有のハマりポイントは、generativelanguage.googleapis.com 用の API キーが Vertex AI(aiplatform.googleapis.com)では一切通らない、という点です。「画像が見えない」ではなく 401 Unauthenticated が出ているなら、コードがクライアントモードを取り違えていないかを最初に疑ってください。
Node.js / TypeScript を使っている場合
@google/genai Node.js SDK でも同じ問題が起きますが、特有の落とし穴が2つあります。1つ目はブラウザ環境では Buffer が使えないこと、2つ目はプロパティ名がキャメルケースで mimeType になることです。
// ✅ Node.js / TypeScript の正しい書き方
import { GoogleGenAI } from "@google/genai";
import { readFileSync } from "node:fs";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const imageBytes = readFileSync("product.jpg");
const response = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: [
{ text: "この商品の特徴を教えてください" },
{
inlineData: {
mimeType: "image/jpeg", // ← snake_case ではなく camelCase
data: imageBytes.toString("base64"), // Buffer ではなく base64 文字列
},
},
],
});
console.log(response.text);data は base64 エンコード済みの文字列を渡す必要があります。Buffer をそのまま渡すと Python 版で mime_type を忘れたときと同じ症状(黙って画像なしリクエストになる)が出ます。
最終チェックリスト
ここまでで解決しないときの確認順序を、検出しやすい順に並べておきます。
- 使用モデルが
gemini-2.5-flash/gemini-2.5-proなど 画像対応モデル か Part.from_bytes(data=..., mime_type=...)の mime_type を明示 しているか- 画像の 総容量が 20MB 以下 か(超えるなら Files API へ)
- ファイルが JPEG / PNG / WebP / HEIC / HEIF のいずれか(GIF・SVG は不可)
- 古い
google-generativeaiではなくgoogle-genaiを使っているか - レスポンスの
prompt_feedback.block_reasonがSAFETYで 弾かれていないか - 一度 Google AI Studio に同じ画像を貼って モデル側で見えるか を確認
このうち上から3つを潰すだけで、私の経験では9割の「画像が見えない」現象が解決しました。マルチモーダル入力の基本は Gemini API マルチモーダル完全ガイド でも丁寧に整理しているので、合わせて目を通しておくと再発を防げます。
切り分けの順番を1枚のメモに書き出して手元に置いておくと、次に同じ症状が出ても3分で原因を特定できるはずです。今日のうちに、最近詰まっていた画像入力スクリプトに mime_type を1行足してみてください。多くの場合、それだけで「見えなかった画像」が見えるようになります。