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-06-01中級

Gemini 2.5/3 で本文が空なのに finish_reason が MAX_TOKENS になるときの原因と対処

プロンプトはほんの数行なのに、maxOutputTokens を絞った gemini-2.5-flash が空文字を返し finish_reason が MAX_TOKENS になる — 犯人は思考トークンです。原因と3通りの対処を実装コードで整理します。

gemini-api279troubleshooting57thinking7python103nodejs2

「プロンプトは10行ほどしかないのに、response.text が空で返ってくる。しかも finish_reason を見たら MAX_TOKENS — いやいや、そんなに長い出力なんて頼んでいないのに」。Gemini 2.5 や 3 系の思考モデルを使い始めて、こう首をかしげた経験はないでしょうか。

私自身、2014年から続けているアプリ事業で、App Store / Google Play のレビューを gemini-2.5-flash で分類するバッチを書いていたとき、まさにこの症状にぶつかりました。コスト最適化のつもりで maxOutputTokens を 256 まで絞ったところ、本文だけが空っぽで返り、原因の特定に半日溶かしてしまったのです。結論を先にお伝えすると、空に見える応答を作っている真犯人は「思考トークン(thinking tokens)」です。

症状 — 短い出力を頼んだのに MAX_TOKENS で止まる

まず再現条件です。思考モデル(gemini-2.5-flashgemini-2.5-progemini-3-* 系)に対して maxOutputTokens を小さめ(数十〜数百)に設定すると、答えが短いはずの依頼でも次のような応答が返ります。

from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_API_KEY")
 
resp = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="このレビューの感情を positive / negative の一語で答えてください。「動作が軽くて満足」",
    config=types.GenerateContentConfig(max_output_tokens=256),
)
 
print(repr(resp.text))                       # '' もしくは None
print(resp.candidates[0].finish_reason)      # FinishReason.MAX_TOKENS

たった一語を返すだけの依頼で MAX_TOKENS。直感に反しますが、ここに思考モデル特有の落とし穴が潜んでいます。

なぜ起きるのか — 思考トークンが出力予算を先に食い尽くす

Gemini 2.5 以降の思考モデルは、最終的な答えを出す前に内部で「思考」を行います。この思考も立派なトークン消費であり、maxOutputTokens は「思考トークン+可視の回答トークン」の合計に対する上限として働きます。

つまり maxOutputTokens=256 のとき、モデルが思考に 256 トークンを使い切ってしまうと、可視の回答に回す予算がゼロになります。その結果、parts にテキストが一つも入らないまま MAX_TOKENS で打ち切られ、response.text は空になるわけです。応答自体は壊れていません。予算配分の問題です。

これは usage_metadata を見ると一目で分かります。

um = resp.usage_metadata
print("prompt:", um.prompt_token_count)
print("thoughts:", um.thoughts_token_count)   # ここが maxOutputTokens 近くまで膨らんでいる
print("candidates:", um.candidates_token_count) # 0 もしくは極端に小さい

thoughts_token_countmaxOutputTokens に張り付いていて、candidates_token_count が 0 — これが「思考に全予算を吸われた」決定的な証拠です。旧来の非思考モデル(gemini-1.5-* 等)では起きなかった挙動なので、移行時に面食らう方が多いところだと思います。

対処1 — maxOutputTokens を思考分込みで確保する

最も素直な対処は、出力上限を思考トークンの分まで含めて広めに取ることです。思考モデルでは maxOutputTokens を「回答に必要な長さ」だけで見積もってはいけません。

resp = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="このレビューの感情を positive / negative の一語で答えてください。「動作が軽くて満足」",
    config=types.GenerateContentConfig(max_output_tokens=2048),  # 思考分を含めて広めに
)
print(resp.text)  # 'positive'

私の感覚では、短い分類・抽出タスクでも思考モデルを使うなら maxOutputTokens は最低 1024、安全側なら 2048 以上を確保しておくと、MAX_TOKENS による空応答はほぼ消えます。出力上限はあくまで「暴走を止める安全弁」と捉え、回答の実長で攻めすぎないのがコツです。

対処2 — thinkingBudget で思考量そのものを制御する

回答が確実に短いタスクなら、思考の量を直接抑えるほうがコストにも効きます。thinking_configthinking_budget で思考トークンの上限を指定できます。

resp = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="このレビューの感情を positive / negative の一語で答えてください。「動作が軽くて満足」",
    config=types.GenerateContentConfig(
        max_output_tokens=256,
        thinking_config=types.ThinkingConfig(thinking_budget=0),  # 思考を無効化
    ),
)
print(resp.text)  # 'positive'

ここで一点、機種差に注意してください。gemini-2.5-flashflash-litethinking_budget=0 で思考を完全に切れますが、gemini-2.5-pro は思考を完全には無効化できず、最小予算が残ります(おおむね 128 以上を指定する必要があります)。Pro でゼロを渡すと INVALID_ARGUMENT になりますので、Pro の場合は「0 で切る」ではなく「対処1で出力上限を広げる」方針を採るのが安全です。thinking_budget=-1 を渡せばモデルがタスクに応じて思考量を自動調整する動的モードになります。

本番で空応答を検知して自動リカバリする

バッチや常駐サービスでは、空応答を握りつぶさずに検知してリトライする層を一枚挟んでおくと、夜間バッチが静かに欠損する事故を防げます。私のレビュー分類パイプラインでは、累計5,000万DL分のアプリにまたがって毎晩これが回っているため、ここの堅牢性は収益の安定に直結しています。

def generate_with_guard(client, model, contents, max_tokens=2048):
    resp = client.models.generate_content(
        model=model,
        contents=contents,
        config=types.GenerateContentConfig(max_output_tokens=max_tokens),
    )
    fr = resp.candidates[0].finish_reason
    text = resp.text or ""
 
    if not text and str(fr) == "FinishReason.MAX_TOKENS":
        # 思考が予算を食い切ったとみなし、上限を倍にして一度だけ再試行
        resp = client.models.generate_content(
            model=model,
            contents=contents,
            config=types.GenerateContentConfig(max_output_tokens=max_tokens * 2),
        )
        text = resp.text or ""
 
    if not text:
        raise RuntimeError(f"空応答が継続: finish_reason={fr}")
    return text

finish_reason を見てから本文に触る、という順序を徹底するだけで、空応答まわりのトラブルは大半が予防できます。Node SDK(@google/genai)でも考え方は同じで、response.candidates?.[0]?.finishReason を確認し、response.usageMetadata?.thoughtsTokenCount で思考消費を観測できます。

思考モデルへ移行したあと最初に maxOutputTokens を見直すこと — まずはそこから試してみてください。同じところで時間を溶かしている方の助けになれば嬉しいです。

シェア

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

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

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

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

関連記事

API / SDK2026-05-30
Gemini 2.5 Pro で thinkingBudget を 0 にすると INVALID_ARGUMENT になる原因と対処
Gemini 2.5 Pro で thinkingBudget を 0 にすると 400 INVALID_ARGUMENT が返る原因を、モデルごとの思考予算レンジの違いから解説します。Pro でレイテンシを抑える正しい書き方と Flash への切り替え判断を Python・JavaScript のコード付きで紹介します。
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駆動開発
※ アフィリエイトリンクを含みます
もっと見る →