Claude APIを毎日運用して遭遇したエラーと対処(自動リトライが効かない罠)

Claude APIで記事を毎日自動生成していて、構造化出力のときだけ出る特定のエラーに何度もぶつかりました。SDKの自動リトライが効かない理由と、実際にどう対処したかをまとめます。エラーログの書き方で失敗した話も添えます。

400で返ってくるのに、投げ直せば通るエラー

JSON Schemaを指定した構造化出力を使っていると、ときどき Grammar compilation timed out. というメッセージが返ってきます。厄介なのは、これがHTTP 400、つまりクライアントエラーとして返る点です。

400は「あなたの送ったリクエストがおかしい」という意味なので、普通ならスキーマを直すべきサインです。ところが実際には、同じリクエストをそのままもう一度投げれば通ります。一時的な混雑で起きているだけで、リクエスト自体は壊れていません。

注意

なぜこのタイミングでタイムアウトするのか、内部で何が起きているのかは私には分かっていません。分かっているのは「400で返る」「再送すると通る」という観測結果だけです。

SDKの max_retries は助けてくれない

SDKには max_retries がありますが、400番は再試行の対象になりません。リクエストが悪いという扱いなので、何度投げても結果は変わらないという前提で設計されているからです。設計としては正しいのですが、今回のケースではその前提が成り立ちません。

結果として、max_retries を上げても状況は改善せず、毎日の自動生成が朝になって「失敗していた」と気づく、ということが起きます。

メッセージが一致したときだけ、自前で再試行する

採った対処はシンプルです。エラーメッセージが Grammar compilation timed out. のときに限って、自分で待って投げ直します。

実測

20秒 → 40秒 → 60秒 / 最大4回
この待ち方で、日次の生成が落ちる頻度は実用上の問題にならない水準になりました
  1. APIを呼ぶ。400が返ってきたらエラーメッセージを見る。
  2. Grammar compilation timed out. を含まないなら、即座に失敗として扱う(握りつぶさない)。
  3. 含むなら20秒待って再送。だめなら40秒、次は60秒。
  4. 4回目でも通らなければ、そこで失敗としてログに残す。
ポイント

再試行するのは「このメッセージのときだけ」です。ここを妥協して400番を丸ごと再試行対象にすると、本当にリクエストが壊れているときに、それが4回のリトライに埋もれて気づけなくなります。

長い入出力ならストリーミングのほうが安全

もうひとつ、運用していて分かったのは、長い入力・長い出力・大きな max_tokens のいずれかに当てはまる場合はストリーミングを使うほうが安全だということです。この3つのどれかを踏むリクエストは、非ストリーミングのまま押し通そうとしないほうが結果的に安定します。

str(e) だけ記録していて、原因が追えなくなった

エラーの記録側でも失敗しました。ログに str(e) だけを書いていたのです。

失敗したこと

中身が空の例外に遭遇しました。ログには空文字だけが残り、何が起きたのか一切分からない状態になりました。再現もできず、その日の失敗は原因不明のまま終わっています。

いまは、例外の型名と、トレースバックの最後の1行を必ず添えて記録しています。メッセージが空でも、型名だけで当たりがつきますし、最後の1行があればどこで落ちたかは分かります。

  • 例外の型名を残す
  • トレースバックの最後の1行を残す
  • str(e) だけに頼る

おまけ:YouTube Data APIは「探す」ほうが高い

別の自動化で使っているYouTube Data APIでは、コストの重心が直感と逆でした。

操作 消費ユニット
動画検索 1回 100
コメント投稿 1回 50

書き込みより検索のほうが2倍高いということです。呼び出し回数を減らしたいなら、まず削るべきは検索側になります。

うまくいったこと

「SDKに任せる範囲」と「自分で条件を絞って再試行する範囲」を分けたこと。全部を自動リトライに寄せないほうが、壊れたときに壊れたと分かります。

まとめ

  • 構造化出力で出る Grammar compilation timed out. は400で返るが、実態は一時的な混雑で、再送すれば通る
  • SDKの max_retries は400を再試行しないので、このメッセージのときだけ自前で再試行する(20秒・40秒・60秒、最大4回)
  • 400を全部再試行対象にしてはいけない。本当に壊れたリクエストに気づけなくなる
  • 長い入力・長い出力・大きな max_tokens のどれかに当てはまるならストリーミングのほうが安全
  • エラーログは str(e) だけでは足りない。空の例外に当たると何も分からなくなるので、型名とトレースバック最終行を必ず添える
  • YouTube Data APIは動画検索100ユニット、コメント投稿50ユニットで、探すほうが高い