アーティスト・クリエイターの廣川政樹です。2014年から個人開発でモバイルアプリを作り続けています。先日、運営している壁紙アプリのアプリ内タグ付け機能で Gemini 2.5 Pro を使っていたとき、応答を少しでも速くしたくて thinkingBudget を 0 に設定したところ、生成そのものが 400 エラーで止まってしまいました。まったく同じ書き方が 2.5 Flash では問題なく動いていたので、最初は SDK 側のバグを疑ったほどです。
結論から書くと、これはバグではなく、Gemini 2.5 Pro が「思考の完全オフ」を許可していないことが理由でした。同じ思考予算(thinking budget)の設定でも、モデルによって受け付ける値の範囲が違います。同じ落とし穴で時間を溶かす方が減るように、症状の切り分けと正しい書き方を整理しておきます。
thinkingBudget を 0 にしたら 400 INVALID_ARGUMENT が返ってきた
まず再現コードです。Python SDK(google-genai)で、Pro に対して思考予算を 0 にすると次のように失敗します。
from google import genai
from google.genai import types
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
# 2.5 Flash では動くが、2.5 Pro では 400 になる書き方
resp = client.models.generate_content(
model="gemini-2.5-pro",
contents="次の説明文に合うタグを5個、日本語で挙げてください。",
config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(thinking_budget=0), # ← Pro は 0 を拒否する
),
)
print(resp.text)返ってくるエラーは、おおよそ次のような内容です。
google.genai.errors.ClientError: 400 INVALID_ARGUMENT
{
"error": {
"code": 400,
"message": "Budget 0 is invalid. Expected a value in the range [128, 32768], or -1 (DYNAMIC) for models/gemini-2.5-pro.",
"status": "INVALID_ARGUMENT"
}
}
ポイントは、メッセージが「0 は無効で、[128, 32768] か -1 を期待している」と明確に言っている点です。つまり API はリクエスト形式を正しく解釈できていて、値そのものを拒否しています。SDK の書き方ミスやネットワークの問題ではありません。
なぜ Pro だけ 0 を拒否するのか — モデルごとの思考予算レンジ
Gemini 2.5 系は内部で「思考(thinking)」に使うトークン量を thinkingBudget で調整できます。ところが、この受け付ける範囲がモデルごとに異なります。本稿執筆時点(2026年5月)では、おおむね次のように整理できます。
- gemini-2.5-pro: 128〜32,768 トークン、または -1(動的割り当て)。思考の完全オフ(0)は不可。難しい推論向けに設計されているため、最低でも 128 は思考に回ります。
- gemini-2.5-flash: 0〜24,576 トークン。0 を指定すると思考を完全にオフにできます。
- gemini-2.5-flash-lite: 既定では思考しません。必要に応じて予算を与えると思考を有効化できます。
この違いを知らないまま「Flash で動いた設定をそのまま Pro に流用する」と、今回のエラーに突き当たります。私の場合も、最初に検証していたのが Flash で、本番でより賢い出力が欲しくなって Pro に差し替えた瞬間に壊れました。モデルを切り替えるときは、思考予算の前提も一緒に切り替える必要があります。
なお -1 は「動的割り当て」で、モデルがタスクの難易度に応じて思考量を自動調整します。とりあえず安全側に倒したいときは、Pro なら 0 ではなく -1 を渡すのが無難です。
対処1: Pro で思考量を最小化する正しい書き方
Pro を使い続けたい、でも思考のオーバーヘッドはできるだけ削りたい、という場合は 0 ではなく最小値の 128 を指定します。
resp = client.models.generate_content(
model="gemini-2.5-pro",
contents="次の説明文に合うタグを5個、日本語で挙げてください。",
config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(thinking_budget=128), # 最小値で思考を絞る
),
)
print(resp.text)
print("思考トークン:", resp.usage_metadata.thoughts_token_count)usage_metadata.thoughts_token_count を見ると、実際に思考へ何トークン使われたかを確認できます。128 に絞っても出力品質が十分かどうかは、必ずこの値と実際の出力を見て判断してください。タグ付けや短い分類のような軽い処理なら、128 でも体感の劣化はほとんどありませんでした。
逆に、要約の一貫性や多段の推論が必要な処理で 128 まで絞ると、結論が雑になることがあります。コストや速度のために思考を削るときは、品質が落ちていないかを出力サンプルで毎回確かめるのが安全です。
対処2: 速度・コスト優先なら 2.5 Flash に切り替える
そもそも「思考を完全に切りたい」と感じている時点で、そのタスクには Pro がオーバースペックなことが多いです。タグ付け、短い分類、定型フォーマットへの整形といった軽い処理は、思考をオフにした Flash で十分こなせます。
resp = client.models.generate_content(
model="gemini-2.5-flash",
contents="次の説明文に合うタグを5個、日本語で挙げてください。",
config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(thinking_budget=0), # Flash は 0 で思考オフ
),
)
print(resp.text)私の壁紙アプリのタグ付けは、最終的に Pro(128)から Flash(思考オフ)へ寄せました。出力の質は実用上ほぼ同等で、応答が体感で1〜2秒短くなり、トークン課金も思考分がまるごと消えた分だけ軽くなりました。AdMob 中心の収益で個人運営しているので、こうした地味なコスト削減が積み重なると効いてきます。
判断の目安としては、賢さより速さ・安さが欲しいなら Flash(必要なら思考オフ)、難しい推論やコード生成で精度が要るなら Pro(128 以上、または -1)、と切り分けています。
SDK 移行で見落としやすい thinkingConfig のフィールド名
このエラーと前後して、もう一つよく踏むのがフィールド名の取り違えです。新しい統合 SDK(google-genai / @google/genai)では、言語によって命名規則が違います。
JavaScript / TypeScript では camelCase です。
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({ apiKey: "YOUR_GEMINI_API_KEY" });
const res = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: "次の説明文に合うタグを5個、日本語で挙げてください。",
config: {
thinkingConfig: { thinkingBudget: 0 }, // JS は camelCase
},
});
console.log(res.text);ありがちな失敗は二つあります。一つは thinkingConfig を config の中ではなくトップレベルに置いてしまい、設定が黙って無視されるパターン。もう一つは、旧 SDK(@google/generative-ai)の記法と混ざってしまうパターンです。設定が効いていないと感じたら、まず thinkingConfig が config の直下にあるかを確認してください。Python は snake_case(thinking_config / thinking_budget)、REST は camelCase(thinkingConfig / thinkingBudget)と、層によって表記が変わる点も覚えておくと混乱しません。
予防策: モデルごとの設定を1か所にまとめる
根本的な再発防止は、「モデル名から思考予算を引く」テーブルを1か所に置くことです。呼び出し側で毎回 0 を直書きしないようにすれば、モデルを差し替えても今回のエラーは起きません。
# モデルごとに許容される思考予算をマッピング
THINKING_BUDGET = {
"gemini-2.5-pro": 128, # 0 は不可。最小で絞る
"gemini-2.5-flash": 0, # 0 で思考オフ
"gemini-2.5-flash-lite": 0, # 既定で思考なし
}
def make_config(model: str):
budget = THINKING_BUDGET.get(model, -1) # 未知のモデルは動的(-1)で安全側に
return types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(thinking_budget=budget),
)
resp = client.models.generate_content(
model="gemini-2.5-pro",
contents="次の説明文に合うタグを5個、日本語で挙げてください。",
config=make_config("gemini-2.5-pro"),
)未知のモデル名には -1(動的)をフォールバックにしておくと、新しいモデルが出て名前を追加し忘れても 400 で落ちません。私自身、複数アプリで Gemini を共通ライブラリ越しに呼んでいるので、こうして1か所に閉じ込めておくと、モデル更新のたびに各所を直す手間がなくなります。
次に同じエラーを見たら、まずはエラーメッセージの Expected a value in the range [...] を読み、使っているモデルが 0 を許すのか(Flash 系)許さないのか(Pro)を確認してみてください。原因のほとんどはここで切り分けられます。