5/13 ローンチ予定!
PiloTube

PiloTube 開発日誌

← 「ひとり社長のAI開発記」一覧へ

GPT-5系で空応答返ってきた
reasoning処理がトークン全部食べた

約7分で読めます

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 で指定できる。デフォルトは mediumhigh 寄りの設定になっていて、内部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_tokensoutput_tokens の内訳がここに出る。空応答が返ってきたとき、まずここを確認する。


PiloTubeの開発は、こういう「知らないとハマる仕様」との戦いでもある。エラーが出ないバグが一番厄介だ。静かに失敗して、静かに課金される。

6.28円の授業料で気づけたのは、まあ安かったと思うことにする。

チャプター生成AI

URL貼るだけ。AIがチャプターを自動生成。

1

YouTubeのURLをコピーして貼る

2

「生成する」を押す

3

概要欄にコピペして完了

無料でチャプターを生成する →

月3回まで無料 · クレジットカード不要