- 01何を作っていたか
- 0210件まとめて投げたら本文が空だった
- 03原因は思考にも出力枠を使うこと
- 04空で返るときの切り分け表
- 05usageMetadata の読み方
- 06thinkingBudget の指定方法
- 07モデル別の thinkingBudget の範囲
- 08Gemini 3 系は thinkingLevel で指定する
- 092.5 系を使えるかは先に確認する
- 10思考トークンの課金
- 11構造化出力(JSON)の指定方法
- 125件ずつに分割して通した
- 13本文に無い締切日が作られることがある
- 14日付文字列が本文に無い締切は捨てる
- 15定型処理に組み込むときの勘所
- 16よくある失敗と落とし穴
- 17始める前に確認すること
- 18よくある質問
- 19参考にした公式情報
- 20まとめ
おはようございます。まさきんです。
Gemini の API に記事を10件まとめて投げたら、本文が空で返ってきました。原因と直し方が公式ドキュメントで説明できる内容だったので、メモとして残しておきます。
※記事公開時点の情報です。最新の条件は公式サイトでご確認ください。
- Gemini API のレスポンスが空のときは、まず
finishReasonとusageMetadataを見る - 公式では、出力トークン上限(maxOutputTokens)は思考トークンを含む合計にかかるとされている
- gemini-2.5-flash は
thinkingBudgetを 0 にすると思考を止められる(範囲は 0〜24576) - 僕は thinkingBudget=0 にして、10件一括を5件ずつに分けたら通った
- 本文に無い締切日が作られることがあるので、日付は後処理で本文と照合している
何を作っていたか
ある会員向けサイトの新着記事を、毎日取りに行く仕組みを作りました。記事ごとに「お得情報かどうか」の判定と要約を Gemini に任せています。
通知は家族のチャットに送る形です。全部を流すと読まれなくなるので、お得情報と判定されたものだけに絞りました。
モデルは gemini-2.5-flash です。要約と分類という定型の処理なので、速くて安いモデルが合うと考えました。
10件まとめて投げたら本文が空だった
最初は、新着の10件を1回のリクエストにまとめていました。呼び出しの回数を減らしたかったからです。
ところが、返ってきたレスポンスの本文が空でした。エラーで落ちたわけではありません。応答は返ってくるのに中身が無い、という状態です。
エラーなら例外で気づけます。空の応答は静かに通ってしまうので、少し厄介かもしれません。定型処理に組み込むときは、ここが最初の落とし穴だと思います。
原因は思考にも出力枠を使うこと
公式ドキュメントでは、Gemini 3 系と 2.5 系は「思考プロセス」を使うモデルだと説明されています。応答の前に、モデルが内部で推論をする仕組みです。
この思考は、既定でオンになっています。公式の表現では「動的思考」で、依頼の複雑さに合わせて量が自動で変わります。
ポイントは、この思考にもトークンが使われることです。公式には、max_output_tokens は思考トークンを含めた上限だと書かれています。
上限に思考の途中で届くと、finish_reason は MAX_TOKENS になります。そして出力は途中で切れるか、空で返るとされています。10件分の判定を一度に考えさせると、思考と出力の合計がふくらみやすいのだと思います。
公式ドキュメントには、課金についての注意も書かれています。上限に達して出力が空でも、生成された思考トークンは課金対象です。空で返ってくる状態は、結果が無いうえに費用だけ出ていく形になりえます。
空で返るときの切り分け表
空の応答には、いくつか種類があります。API リファレンスにある項目をもとに、見る場所を整理しました。
| 症状 | レスポンスで見る場所 | 考えられる状況 | 対処の方向 |
|---|---|---|---|
| 本文が空・途中で切れる | finishReason が MAX_TOKENS | 指定した最大トークン数に到達 | 思考を減らす・1回の件数を減らす・上限を見直す |
| 本文が空で思考が多い | usageMetadata.thoughtsTokenCount が大きい | 思考が出力枠を使っている | thinkingBudget を下げる(2.5系) |
| 本文が空で理由が別 | finishReason が SAFETY など | 安全性などの理由で停止 | 入力の内容とプロンプトを見直す |
| JSON は返るが中身がおかしい | finishReason は STOP | 形式は正しいが値が誤り | アプリ側で値を検証する |
finishReason の STOP は、自然な終了点で止まったという意味です。MAX_TOKENS は、リクエストで指定された最大トークン数に達したという意味だと定義されています。
なので、空のときに最初に見るのは finishReason だと思います。STOP 以外が入っていたら、本文は使わない前提にしておくと安全かもしれません。
usageMetadata の読み方
レスポンスには usageMetadata が付いてきます。API リファレンスでは、次のように説明されています。
| 項目 | 意味 |
|---|---|
promptTokenCount | プロンプト(入力)のトークン数 |
candidatesTokenCount | 生成された応答候補のトークン数の合計 |
thoughtsTokenCount | 思考モデルの思考に使われたトークン数 |
totalTokenCount | プロンプト+思考+応答候補の合計 |
合計が「プロンプト+思考+応答」と定義されているのが分かりやすいところです。思考と応答は、別々の項目として数えられています。
思考の数字が大きくて応答の数字が小さいなら、思考に枠を使っている可能性があります。この2つをログに残しておくと、後から原因を追いやすいと思います。
thinkingBudget の指定方法
2.5 系では、thinkingBudget というパラメータで思考の量を指定します。公式では、思考に使うトークン数の目安をモデルに与えるものだと説明されています。
0 にすると思考を無効にできます。-1 にすると動的思考になります。Python の SDK では、公式の例に沿うとこういう書き方です。
from google import genai
from google.genai import types
client = genai.Client()
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(thinking_budget=0)
),
)
REST で呼ぶ場合は、generationConfig の中に thinkingConfig を置きます。その中で thinkingBudget を数値で渡す形です。
ただ、公式には注意書きもあります。プロンプトによっては、モデルが予算を超えたり下回ったりすることがあるそうです。厳密な上限というより、目安として扱うのがよさそうです。
モデル別の thinkingBudget の範囲
範囲と既定値は、モデルごとに違います。公式ドキュメントの表を、2.5 系の主なモデルに絞って写しました。
| モデル | 未指定のとき | 指定できる範囲 | 思考の無効化 | 動的思考 |
|---|---|---|---|---|
| 2.5 Pro | 動的思考 | 128〜32768 | できない | -1(既定) |
| 2.5 Flash | 動的思考 | 0〜24576 | 0 を指定 | -1(既定) |
| 2.5 Flash Lite | 思考しない | 512〜24576 | 0 を指定 | -1 |
2.5 Pro は思考を止められない、というのが大事な違いだと思います。「Flash で 0 にしていた設定」をそのまま Pro に持っていくと、前提が崩れるかもしれません。
2.5 Flash Lite は、何も指定しなければ思考しないモデルです。要約や分類だけなら、こちらを選ぶ考え方もあると思います。
Gemini 3 系は thinkingLevel で指定する
記事公開時点の公式ドキュメントでは、Gemini 3 系以降の指定方法が変わっています。推奨されているのは thinkingLevel です。minimal・low・medium・high といった段階で指定する形です。
2.5 系は逆に thinkingLevel に対応していません。公式には、2.5 系では thinkingBudget を使うようにと書かれています。
Gemini 3 系の注意点も載っていました。Gemini 3.1 Pro は思考を無効にできません。Gemini 3 の Flash と Flash-Lite も、完全な思考オフには対応していないとされています。minimal も「思考オフを保証するものではない」という説明です。
つまり、モデルを新しくすると「0 にして止める」という手は使えなくなる可能性があります。乗り換えるときは、設定の書き換えが要ると考えておくのがよさそうです。
2.5 系を使えるかは先に確認する
公式のモデル一覧には、2.5 系について注記があります。過去に実際に使っていたユーザーに利用を限定している、という内容です。
非推奨ではなく、当面は API で提供を続けるとも書かれています。ただ、新しいプロジェクトでは最新のモデルを使うよう案内されていました。
僕の仕組みは gemini-2.5-flash で動いています。これから新しく作る方は、まず自分のプロジェクトで使えるモデルを確認するのが先かもしれません。
押すと楽天のログインページが開きます。ログイン後にキャンペーン内容が表示されます。
思考トークンの課金
料金ページでは、出力の料金が「思考トークンを含む」と明記されています。gemini-2.5-flash の欄も同じ表記でした。
思考のガイドにも、同じ趣旨の説明があります。思考がオンのとき、応答の料金は出力トークンと思考トークンの合計になるというものです。
思考の要約だけを受け取る設定でも、課金は元の思考トークン全体が対象だとされています。見えている量より多く数えられることがある、という理解でよさそうです。
要約や分類のような定型処理は、毎日くり返し動きます。1回あたりは小さくても、思考の分が積み重なる構造は知っておいて損はないと思います。
構造化出力(JSON)の指定方法
判定と要約をプログラムで受け取るなら、JSON で返してもらうのが扱いやすいです。Gemini API には、そのための構造化出力の仕組みがあります。
API リファレンスでは、generationConfig に2つの項目を置く形です。responseMimeType に application/json を指定します。あわせて responseJsonSchema にスキーマを渡します。
"generationConfig": {
"responseMimeType": "application/json",
"responseJsonSchema": {
"type": "object",
"properties": {
"is_deal": {"type": "boolean"},
"summary": {"type": "string"},
"deadline": {"type": "string"}
}
}
}
項目名は説明用に僕が置いた例です。リファレンスには、スキーマを渡すときは responseMimeType が必須だと書かれています。
なお、SDK のガイドでは書き方が少し違う例も載っていました。使っている SDK の版に合わせて、公式の例を見直すのがよいと思います。
公式のベストプラクティスには、大事な一文があります。構造化出力は構文として正しい JSON を保証しますが、値が意味として正しいことは保証しません。使う前にアプリ側で検証するように、と書かれています。
5件ずつに分割して通した
僕が実際にやった対処は2つです。thinkingBudget を 0 にしました。そして10件の一括を、5件ずつに分けました。
この2つを入れたら、本文が返ってくるようになりました。どちらか片方だけで足りたのかは、切り分けていません。
| 比べる点 | 10件を一括 | 5件ずつに分割 |
|---|---|---|
| 呼び出しの回数 | 少ない | 増える |
| 1回の出力の長さ | 長くなる | 短くなる |
| 上限に届く心配 | 大きくなりやすい | 小さくなりやすい |
| 失敗したときの影響 | 全件がやり直し | その束だけやり直し |
| 記事同士の取り違え | 起きうる | 減らしやすい |
一括のほうが効率はよさそうに見えます。ただ、失敗すると全件がやり直しになるんですね。分割しておくと、失敗の範囲が束の中に収まります。
何件ずつが最適かは、記事の長さや出力の形で変わると思います。5件という数は、僕の用途でたまたま通った数です。
本文に無い締切日が作られることがある
もう一つ困ったのが、締切日です。Gemini が、本文に書かれていない締切日を作ることがありました。
お得情報の通知で、締切が間違っているのは困ります。早すぎれば機会を逃しますし、遅すぎれば期限切れに気づけません。
これは、構造化出力の注意書きとつながる話だと思います。JSON の形は正しくても、値が正しいとは限りません。スキーマに「締切」の欄があると、埋めようとする力が働くのかもしれません。
日付文字列が本文に無い締切は捨てる
対策として、後処理を入れました。考え方はシンプルです。
- Gemini に締切日を JSON の項目として返してもらう
- その日付にあたる文字列が、元の記事本文にあるかを調べる
- 本文に見つからなければ、締切の項目を捨てる
- 締切なしのお得情報として通知する
モデルに「作らないで」と頼むだけでは、確実ではないと感じました。なので、プログラム側で本文と突き合わせる形にしています。
この方法の弱点もあります。本文の表記とモデルの返す表記が違うと、正しい締切まで捨ててしまいます。「間違った締切を出すより、締切なしのほうがまし」と割り切った設計です。
定型処理に組み込むときの勘所
今回の件で、定型処理に生成AIを入れるときの見方が少し整理できました。
- 空の応答をエラーとして扱う(本文の長さと finishReason を見る)
- usageMetadata をログに残して、思考と出力の内訳を追えるようにする
- 要約や分類のような作業では、思考を減らす設定を先に検討する
- 1回に詰め込みすぎず、失敗しても束だけやり直せる大きさにする
- モデルの返した値は、元の本文と照合してから使う
公式のベストプラクティスにも、近い例があります。事実の取り出しや分類のような単純な作業は、思考を最小限か低めにするというものです。難しい推論が要らない作業に、深い思考は必要ないのかもしれません。
仕事でも、数字は出どころと突き合わせてから使います。AI の出力も同じ扱いにするのが、いちばん落ち着く形だと思います。
毎朝の情報をAIにまとめさせる話は、AIに今日のブリーフィングを作らせている記事にも書きました。無人で動かすときの権限まわりは、claude -p でMCPが拒否される原因の記事が近い話題です。
よくある失敗と落とし穴
つまずきやすい点を並べておきます。
- 応答が返ってきたことだけを見て、本文が空なのを見逃す
- maxOutputTokens を小さくして、費用を抑えようとする
- 2.5 Flash 用の設定を、思考を止められないモデルにそのまま使う
- JSON で返ってきたので、中身も正しいと思い込む
- 件数をまとめすぎて、1件の失敗で全件をやり直す
二つ目は、公式でもはっきり注意されています。費用や待ち時間を減らしたいときは、上限を小さくするより思考を下げるという案内です。上限を絞ると、応答が切れる原因になります。
僕が踏んだのは、一つ目と五つ目でした。API の利用と、アプリ版の有料プランは別の話です。アプリ側の料金は Google AI Pro の機能と料金を整理した記事にまとめています。
始める前に確認すること
同じような仕組みを作る方向けに、確認項目をまとめました。
- 使うモデルが自分のプロジェクトで利用できるか確認した
- そのモデルで思考を無効にできるか公式の表で確認した
- finishReason が STOP 以外のときの扱いを決めた
- usageMetadata の思考と出力のトークン数をログに残す
- 1回に投げる件数を失敗時にやり直せる大きさにした
- モデルが返した日付や数字を本文と照合する処理を入れた
三つ目と六つ目が、特に効くと思います。空の応答と作られた値は、どちらも静かに通ってしまうからです。
よくある質問
Q. Gemini API のレスポンスが空のとき、最初に何を見ますか?
finishReason を見るのがよいと思います。MAX_TOKENS なら、指定された最大トークン数に達しています。あわせて thoughtsTokenCount を見ると、思考に使われた量が分かります。
Q. thinkingBudget を 0 にできないモデルはありますか?
公式の表では、2.5 Pro は思考を無効にできないとされています。範囲は 128〜32768 です。Gemini 3 系は thinkingLevel で指定する形で、完全な思考オフに対応しないモデルもあります。
Q. gemini-2.5-flash の thinkingBudget の範囲は?
公式の表では 0〜24576 です。未指定だと動的思考になります。0 で無効、-1 で動的思考という指定です。
Q. 思考トークンにも料金はかかりますか?
かかります。料金ページでは、出力の料金が「思考トークンを含む」と書かれています。上限に達して出力が空でも、生成された思考の分は課金されるとされています。
Q. maxOutputTokens を大きくすれば解決しますか?
上限に届いて切れているなら、余裕を持たせるのは一つの手だと思います。ただ、思考が多いままだと費用と待ち時間は増えます。公式は、思考の設定を下げる方法を案内しています。
Q. 構造化出力にすれば、内容の間違いは防げますか?
防げません。公式には、保証されるのは構文として正しい JSON までだと書かれています。値の意味の正しさは保証されません。アプリ側で検証する処理が要ります。
Q. 何件ずつに分割するのがよいですか?
決まった正解は無いと思います。記事の長さと出力の形で変わるからです。僕は10件を5件ずつにしたら通りましたが、これは僕の用途での結果です。
Q. 思考を止めると、判定の質は落ちませんか?
作業の内容によると思います。公式では、分類や事実の取り出しは低い思考で足りる例とされています。僕は質の変化を数字では比べていないので、気になる方は手元の記事で試すのがよさそうです。
参考にした公式情報
- Gemini thinking(Google AI for Developers)
- Gemini thinking・generateContent 版(Google AI for Developers)
- Generating content・API リファレンス(Google AI for Developers)
- Structured outputs・generateContent 版(Google AI for Developers)
- Gemini models(Google AI for Developers)
- Gemini 2.5 Flash(Google AI for Developers)
- Gemini Developer API pricing(Google AI for Developers)
- Troubleshooting guide(Google AI for Developers)
まとめ
空で返ってきた理由は、思考にも出力の枠が使われることでした。thinkingBudget を 0 にして、5件ずつに分けたら通りました。
とはいえ、設定で直せるのは「返ってくるかどうか」までです。返ってきた中身が正しいかは、別に確かめる必要がありました。締切日を本文と照合する後処理は、そのための仕組みです。
定型処理に生成AIを入れている方は、空の応答をどう扱っているか一度見てみてはいかがでしょうか。
押すと楽天のログインページが開きます。ログイン後にキャンペーン内容が表示されます。
本記事にはアフィリエイト広告を含みます。当サイトのリンクを経由して商品・サービスへお申し込みいただいた場合、提携先企業から成果報酬を受け取ることがあります。 また、運営者は楽天グループに所属する社員であり、紹介プログラムを通じた報酬を受け取る場合があります。記事内容・評価は広告の有無にかかわらず、運営者の実体験と調査に基づき作成していますが、 上記の関係性をご理解のうえお読みください。詳しくは免責事項・アフィリエイト表記をご覧ください。
楽天モバイルの通信エリア・電波状況について気になることがある方は、公式の電波改善・調査依頼フォームからご相談いただけます。