ask_gpt が空応答返してきた事件 — GPT-5系は reasoning_effort を制御しないと最大トークン全部消費する
6.28円。たったそれだけの損失だが、原因がわからないまま同じことを繰り返すのは嫌だった。
PiloTube(パイロチューブ)の開発中、GPTセカンドオピニオン用に自作したCLIラッパーが突然空応答を返してきた。エラーでもなく、タイムアウトでもなく、ただ「何も返ってこない」。APIは正常に叩けている。でも出力がゼロ。しばらく意味がわからなかった。
何が起きていたか
PiloTubeの開発では、Claudeをメインの思考エンジンとして使いつつ、重要な判断や設計の方向性については「GPTにもセカンドオピニオンを聞く」という運用をしている。そのために ask_gpt という簡単なCLIラッパーを自作していた。
ask_gpt "この設計で問題ないか確認してくれ"
こんな感じで使う、シンプルなツールだ。プロンプトを渡してGPTの返答をターミナルに出力するだけ。特別なことは何もしていない。
その日もいつも通り使おうとしたら、レスポンスが返ってきた。でも中身が空だった。
Response:
以上。それだけ。
最初は自分のコードを疑った
まず疑ったのは自分のラッパーのコードだ。パースの処理がおかしいのか、レスポンスのどこかを読み間違えているのか。コードを見直した。問題なさそうだった。
次にAPIのレスポンスそのものをダンプして確認した。すると、確かにAPIはちゃんと返ってきていた。HTTPステータスも200。でも choices[0].message.content が空文字列だった。
「なんで?」
エラーじゃないのに中身がない。これは詰まった。
気づきは「usage」の中にあった
レスポンスの usage フィールドをよく見たときに、ようやく気づいた。
{
"usage": {
"prompt_tokens": 312,
"completion_tokens": 0,
"total_tokens": 312,
"completion_tokens_details": {
"reasoning_tokens": 4096,
"output_tokens": 0
}
}
}
reasoning_tokens が4096。output_tokens が0。
これだ。
GPT-5系のモデル(o3やo4-miniなど、内部でreasoning処理を行うモデル群)は、回答を生成する前に「内部で考える」フェーズがある。このreasoning処理にもトークンを消費する。そしてそのreasoning tokenも max_tokens の制限に含まれる。
つまり何が起きていたかというと——
reasoning処理だけで max_tokens の上限を全部使い切ってしまい、実際の出力を生成するトークンが残っていなかった。
だから応答は空。APIはエラーを返さない。「ちゃんと考えた。でも答えを書く余裕がなかった」という状態だ。
reasoning_effort という制御パラメータを知らなかった
GPT-5系のreasoningモデルには reasoning_effort というパラメータがある。low / medium / high で指定できる。デフォルトは medium か high 寄りの設定になっていて、内部reasoning処理に積極的にトークンを割り当てる。
自分は ask_gpt を作ったとき、このパラメータの存在を知らなかった。というか、「GPT-5系は内部でreasoning処理をする」という仕様をちゃんと把握していなかった。
GPT-4系までは max_tokens を指定すれば「その分だけ出力が返ってくる」という感覚で使えていた。でもGPT-5系のreasoningモデルは違う。max_tokens の中でreasoningと出力が競合する。しかもreasoningが優先される。
これは正直、知らないとハマる。
実際に直した内容
修正は単純だった。reasoning_effort を明示的に指定して、reasoningに使うトークンを抑える。
response = client.chat.completions.create(
model="o4-mini",
max_tokens=4096,
messages=[{"role": "user", "content": prompt}],
reasoning_effort="low" # これを追加
)
セカンドオピニオン用途なら low で十分だ。深く考えてほしいわけじゃなく、別の視点から一言もらいたいだけ。reasoning処理に全トークンを費やされても困る。
もう一つ、max_tokens の値も見直した。4096では、reasoningモデルにとっては「ちょっと考えたら使い切れる量」だった。用途に応じて適切なサイズに調整する必要がある。
修正後は正常に返答が返ってくるようになった。同じプロンプトで試したら、今度はちゃんと数百トークンの回答が出てきた。
6.28円の内訳
気になって計算した。空応答が返ってきた呼び出しが数回あって、その合計が6.28円だった。
金額としては大したことない。でも「何も返ってこないのにお金だけ取られた」という構造が嫌だった。しかもエラーが出ないから気づきにくい。静かに消えていく。
これが大規模なバッチ処理や自動化の中に組み込まれていたら、気づかないまま同じことを何度も繰り返していた可能性がある。
読者への持ち帰り
GPT-5系のreasoningモデル(o3、o4-mini等)を使うときに知っておいてほしいことをまとめる。
① reasoning_effort を必ず明示する
指定しないとデフォルトで高めのreasoning処理が走る。用途に応じて low / medium / high を選ぶ。軽いセカンドオピニオン用途なら low で十分。
② max_tokens はreasoningとoutputの合計枠だと理解する
GPT-4系の感覚で max_tokens=4096 と指定すると、reasoning処理だけで使い切られる可能性がある。reasoningモデルを使うなら max_tokens をもっと大きく取るか、reasoning_effort で消費量を抑えるか、どちらかが必要。
③ 空応答はエラーにならない
choices[0].message.content が空文字列でも、HTTPステータスは200で返ってくる。自動化処理に組み込む場合は、空応答チェックを必ず入れること。if not content: raise ValueError("Empty response") くらいの防御コードは最低限入れておく。
④ usage.completion_tokens_details を見ると何が起きたかわかる
reasoning_tokens と output_tokens の内訳がここに出る。空応答が返ってきたとき、まずここを確認する。
PiloTubeの開発は、こういう「知らないとハマる仕様」との戦いでもある。エラーが出ないバグが一番厄介だ。静かに失敗して、静かに課金される。
6.28円の授業料で気づけたのは、まあ安かったと思うことにする。
チャプター生成AI
URL貼るだけ。AIがチャプターを自動生成。
YouTubeのURLをコピーして貼る
「生成する」を押す
概要欄にコピペして完了
月3回まで無料 · クレジットカード不要