「PDFを inline_data で送ったのに、応答が空っぽで返ってきた」「アップロードは成功したのに、本文を一行も認識してくれない」—Gemini API でPDFを扱い始めた方からよく相談を受けるのが、この種の沈黙系の不具合です。エラーが派手に出てくれれば原因にたどり着けるのですが、PDF入力は何も言わずに落ちることが多く、最初の切り分けで時間を取られがちな領域でもあります。
ここではGemini API(gemini-2.5-pro / gemini-2.5-flash)でPDFを処理するときに私自身が踏み抜いてきた落とし穴を、症状別に整理しています。コードはすべて Python 用 google-genai SDK で書いてありますが、Node.js でも考え方はそのまま適用できます。
症状から原因を絞り込むフローチャート
最初に、典型的な4パターンを症状ベースで整理しておきます。これだけでも当たりをつけやすくなります。
- 応答が空、または「PDFが読めない」と返る → スキャン画像PDF / OCR必要 / インラインデータの破損を疑う
400 INVALID_ARGUMENT: request payload size exceeds the limit→ 20MBインライン上限を超過400 The document has too many pages→ 1,000ページ制限を超過、または1ページあたりのトークンが極端に多い- ページの一部だけ反応する、後半が無視される → トークン上限とページ毎トークンコストの問題
それぞれを順に見ていきます。
原因1: スキャンされた画像PDFを送ってしまっている
PDFには大きく分けて「テキストレイヤを持つPDF」と「画像だけのPDF(スキャナや複合機で取り込んだもの)」の2種類があります。前者なら Gemini はテキスト抽出と画像理解の両方を使ってくれますが、後者は事実上「画像の集合」として扱われ、解像度や言語によっては読み取り精度が大きく落ちます。
切り分け方は単純で、ローカルでテキストが取り出せるかを確認することです。
# pip install pypdf
from pypdf import PdfReader
reader = PdfReader("sample.pdf")
text = "".join(page.extract_text() or "" for page in reader.pages)
print(f"抽出文字数: {len(text)}")
# 0 または極端に少ない場合は画像PDFの可能性が高い文字数がほぼゼロなら、Gemini に投げる前に OCR を挟むのがおすすめです。私のプロジェクトでは、簡単な日本語スキャンPDFなら gemini-2.5-flash に「このPDFは画像のみで構成されています。各ページの文字を起こしてください」と明示的に指示するだけで体感がかなり改善しました。指示なしだと「読み取れません」と返ってくることが多いので、システム指示の一言が効きます。
原因2: 20MBのインライン上限に引っかかっている
inline_data でPDFを送る場合、リクエストペイロード全体で20MBまでという制約があります。PDFそのものが小さくても、Base64 エンコード後は約1.33倍にふくらむので、原本15MB前後で警戒した方が安全です。
20MBを超えたら、Files API(client.files.upload)に切り替えてファイル URI を渡す方式が定石になります。Files API なら最大2GBまで扱えますし、複数の呼び出しで使い回せるのも利点です。
from google import genai
client = genai.Client()
# 20MB超のPDFは Files API へ
uploaded = client.files.upload(file="large_report.pdf")
print(f"State: {uploaded.state}") # ACTIVE になるまで待つ必要あり
# state チェックを挟むと安定する
import time
while uploaded.state == "PROCESSING":
time.sleep(2)
uploaded = client.files.get(name=uploaded.name)
response = client.models.generate_content(
model="gemini-2.5-pro",
contents=[uploaded, "このPDFの結論を3つ箇条書きで教えてください。"],
)
print(response.text)state を確認せずに即 generate_content を呼ぶと、たまに FAILED_PRECONDITION で弾かれます。PROCESSING から ACTIVE への遷移を待つガードは入れておくと事故が減ります。なお、Files API のファイルは48時間で自動削除されるので、長期セッションを跨ぐ場合は再アップロードのロジックも必要です(詳しくは Files API のファイルが消える問題の対処法 で別途まとめています)。
原因3: 1,000ページの上限とトークン消費
Gemini API のPDF処理には「1リクエスト最大1,000ページ」という上限があります。さらに、ページ毎にテキストはトークン化、画像領域は約258トークン分として課金されるため、ページ数が増えると入力トークンが爆発しがちです。
実務でよく出会うのが、500ページ前後の長大なPDFを丸ごと投げて応答が極端に短くなったり、後半のページが無視されているように見えるケースです。これは gemini-2.5-pro の100万トークンウィンドウに収まっていても、出力トークン上限(既定で8,192)に対して入力が大きすぎて、応答側で要約が荒くなっているのが正体です。
対策はチャンク分割が王道です。
from pypdf import PdfReader, PdfWriter
def split_pdf(src: str, chunk_size: int = 100):
reader = PdfReader(src)
chunks = []
for start in range(0, len(reader.pages), chunk_size):
writer = PdfWriter()
for i in range(start, min(start + chunk_size, len(reader.pages))):
writer.add_page(reader.pages[i])
out_path = f"{src}.part{start//chunk_size}.pdf"
with open(out_path, "wb") as f:
writer.write(f)
chunks.append(out_path)
return chunks100ページ単位に分けて、章ごとに Gemini に渡し、後でメタ要約を作る—この流れに切り替えるだけで、長文PDFの処理品質は驚くほど安定します。チャンクごとの要約戦略は 長尺PDFを章単位でチャンク要約する設計 でもう少し丁寧に書いています。
原因4: MIME タイプ違いと拡張子だけの一致
意外と見落としやすいのが MIME タイプの問題です。inline_data を組み立てる際、mime_type を application/pdf ではなく application/octet-stream にしてしまうと、Gemini はバイナリブロブとして受け取り、PDFとして解釈しません。SDK 経由で Part.from_bytes を使う場合も、mime_type を必ず明示してください。
from google.genai import types
with open("report.pdf", "rb") as f:
pdf_bytes = f.read()
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=[
types.Part.from_bytes(data=pdf_bytes, mime_type="application/pdf"),
"このPDFの目的と読者像を1段落で要約してください。",
],
)それから、拡張子が .pdf でも実体が暗号化されていたり、PDF/A の特殊な構成で pypdf ですらページを開けないケースもあります。pypdf.errors.PdfReadError が出たら、qpdf --decrypt などで一度きれいに解いてから再アップロードするのが確実です。
原因5: 表組みや数式が崩れて返ってくる
PDFがちゃんと読まれていても、表や数式が崩れて返ってくる相談も多いです。これは「PDFの読み取り失敗」ではなく「LLMの出力フォーマット問題」なので、対処は別系統になります。
私が現場で効果を感じているのは次の3つです。
- Markdown ではなく明示的な構造化を要求する: 「表は
|区切りではなく、行ごとに項目名: 値の箇条書きで返してください」と書くと、再構成しやすい形で安定します response_mime_type="application/json"と組み合わせる: 表構造をスキーマで縛ってしまうと、Markdown崩れに悩まされなくなります- 数式は LaTeX で要求する: 「数式は
$$ ... $$で囲み、TeX 記法で出力してください」と指定すると、画像化された数式も比較的安定して LaTeX 化されます
それでも直らないときの最終チェック
ここまで来ても解決しない場合は、次の3つを順に確認してみてください。
- 同じPDFが Google AI Studio(Web 版)で正しく処理できるか—できなければPDF側の問題、できればAPIリクエスト側の問題と切り分けられます
- 別の小さいPDF(テキストレイヤありの数ページのもの)で同じコードが通るか—コードの最小再現を取るのは原因特定の最短ルートです
usage_metadata.prompt_token_countをログに出して、入力トークンが想定通りか確認する—想像より少なければ、PDFがほとんど認識されていない兆候です
このあたりの基礎は Gemini API でつまずきがちなエラーと解決策 にも別の切り口でまとめてあるので、合わせて参照してみてください。
PDF処理は腰を据えて取り組むほど面白い領域でもあります。
まずは手元のPDFで len(text) を確認するところから始めてみてください。「画像PDFをそのまま投げていた」「mime_type が違っていた」のどちらかで解決するケースが、私の体感では半分以上を占めます。原因が一つに絞れたら、対処はシンプルです。