前回の記事では、response.usage からトークン数を取り出して料金を概算しました。ログに「成功/失敗」を残しておこう、と書いたところで終わっています。今回はその「失敗」のほうを扱います。
APIを繰り返し使い始めると、必ずこうなります。
401 認証エラー
429 レート制限・残高不足
5xx サーバー側のエラー
タイムアウト
このとき、いちばんやってはいけないのが「エラーが出たから、もう一度送る」を全部のエラーに対してやることです。APIキーが間違っているなら、100回送っても101回目も失敗します。そのあいだ、失敗したリクエストもレート制限にカウントされていきます。
先に、この記事の結論を3つ書いておきます。
- 再試行そのものは、すでにSDKがやっています。 自分で書き直す前に、何をどう再試行しているかを知るのが先です
- それでも自分で書く部分があります。 「待っても直らないエラーを、待たずにやめる」判断です
- 上限は回数ではなく時間で決めます。 回数で決めると、SDKの再試行と掛け算になって読めなくなります
この記事を読み終えると、次のことができるようになります。
- 401と429の違いを説明できる
- 再試行してよいエラーと、してはいけないエラーを分けられる
- SDKが既定で何をどう再試行しているかを説明できる
timeoutとmax_retriesを意図をもって設定できる- 同じ429でも「待っても直らない4種類」を見分けられる
- 再試行に締切(デッドライン)を設けられる
- 二重リトライを避けられる
- 本物のエラーを待たずに、再試行の動作を手元で確かめられる
- request ID をログへ残せる
先に、この記事で出てくる用語を整理します
エラーの話は、用語が分からないだけで急に難しく感じます。この記事で使う言葉を先にまとめておきます。いま全部覚える必要はありません。 本文中でも初出のたびに説明しますので、分からなくなったらここへ戻ってきてください。
| 用語 | この記事での意味 |
|---|---|
| HTTPステータス | 401、429、503 のような3桁の数字。おおまかな失敗の種類を表します |
| error code | ステータスより細かい、文字列の識別子。slow_down のような形です |
| SDK | OpenAIが配布しているPythonのライブラリ。pip install openai で入るもの |
| 再試行(リトライ) | 失敗したリクエストを、もう一度送ること |
| 指数バックオフ | 失敗するほど待ち時間を倍にしていく方法。1秒→2秒→4秒→8秒 |
| ジッター | 待ち時間に加える短いランダムのゆらぎ。複数の処理が同時刻に再開しないようにするもの |
| Retry-After | 「最低これだけ待ってから来てください」とサーバーが返すHTTPヘッダー |
| レート制限 | 一定時間内に送れるリクエスト数・トークン数の上限 |
| タイムアウト | 「何秒待っても返ってこなければ諦める」という設定 |
| デッドライン | 再試行を含めて「何秒までなら粘ってよいか」という締切 |
| 冪等(べきとう) | 同じ操作を2回やっても結果が1回分と同じであること |
| request ID | リクエスト1件ごとに振られる識別子。問い合わせや調査に使います |
エラーは「待てば直るか」で2つに分ける
エラーコードを全部覚える必要はありません。最初の判断は1つだけです。
これは、待てば直る可能性があるか。それとも、こちらが何かを直さない限り直らないか。
待てば直る可能性があるもの
| 状況 | ステータス | SDKの例外クラス |
|---|---|---|
| ネットワークがつながらなかった | (なし) | APIConnectionError |
| 時間内に返ってこなかった | 408 | APITimeoutError |
| 一時的な競合 | 409 | ConflictError |
| レート制限に当たった | 429 | RateLimitError |
| サーバー側のエラー・モデルの過負荷 | 5xx | InternalServerError |
一時的な混雑やネットワークの問題なら、少し待ってからやり直すと成功する可能性があります。
503 も
InternalServerErrorです。 SDKにはServiceUnavailableErrorのようなクラスはなく、500以上はすべてInternalServerErrorにまとめられます。503を個別に扱いたい場合は、例外のstatus_codeを見てください。
直さない限り直らないもの
| 状況 | ステータス | SDKの例外クラス |
|---|---|---|
| APIキーが違う・読めていない | 401 | AuthenticationError |
| 権限がない・地域が非対応 | 403 | PermissionDeniedError |
| モデル名やIDが間違っている | 404 | NotFoundError |
| リクエストの中身が不正 | 400 | BadRequestError |
| 残高不足・利用上限に到達 | 429 | RateLimitError |
最後の行に注目してください。429は両方の表に出てきます。 ここがこの記事のいちばん細かい話になるので、後でまた戻ってきます。
401は、何回送っても401
いちばん分かりやすい例が401です。
401 が返った ↓ 1秒待つ もう一度、同じリクエストを送る ↓ また 401 ↓ 2秒待つ また送る … 以下同じ
APIキーが間違っているなら、待っても変わりません。確認するのは次の4点です。
OPENAI_API_KEYが設定されているか- そのキーが無効化されていないか
- 別のプロジェクトのキーを読んでいないか
- キーに必要な権限が付いているか
環境変数の設定については、以前の記事で扱っています。
SDKは、すでに再試行している
ここが、この記事でいちばん先に知っておいてほしいところです。
自分で再試行を書き始める前に、SDKがすでに何をしているかを確認してください。
次のコードには、再試行の記述が1行もありません。
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5.6-luna",
input="先週の売上を2文以内で要約してください。",
)
それでも、一時的なエラーはSDKの内部で自動的に再試行されています。
既定で2回、何を再試行するか
SDKが自動で再試行するのは次の5種類です。これ以外は再試行されず、そのまま例外として上がってきます。
接続エラー(ネットワーク到達不能など) 408 Request Timeout 409 Conflict 429 Rate Limit 5xx サーバー側のエラー
回数は既定で 2回 です。つまり最初の1回と合わせて、最大3リクエストが飛びます。
待ち時間は0.5秒から、上限8秒
待ち方も決まっています。0.5秒 × 2の累乗で増えていき、8秒で頭打ちになります。
| 何回目の失敗の後か | 基準の待ち時間 | 実際の待ち時間(ジッター込み) |
|---|---|---|
| 1回目 | 0.5秒 | 0.38〜0.50秒 |
| 2回目 | 1.0秒 | 0.75〜1.00秒 |
| 3回目 | 2.0秒 | 1.50〜2.00秒 |
| 4回目 | 4.0秒 | 3.00〜4.00秒 |
| 5回目以降 | 8.0秒(上限) | 6.00〜8.00秒 |
ジッターは、待ち時間に加えるランダムなゆらぎのことです。複数の処理が同時に失敗したとき、全員が同じ秒数だけ待つと、また同時に再開してまた混雑します。それを避けるためのものです。SDKでは基準の待ち時間を0〜25%だけ短くする形で入っています。
Retry-After があれば、そちらに従う
ここが重要です。 レート制限(429)やモデルの過負荷(503)のレスポンスには、Retry-After というHTTPヘッダーが付くことがあります。
Retry-After: 56
これは「少なくとも56秒待ってから再試行してください」という、サーバー側からの指定です。公式ガイドでも、このヘッダーがある場合はその指定を最小の待機時間として扱うよう案内されています。
SDKは、このヘッダーを読んで従います。 しかも、かなり丁寧に処理しています。
- 秒数(
Retry-After: 56)だけでなく、ミリ秒(retry-after-ms)とHTTP日付形式も解釈します - ヘッダーがあれば、自前の指数バックオフより優先します
- 指定が120秒を超えていたら、再試行せずに諦めます(長すぎる待機でプロセスを止めないため)
x-should-retryというヘッダーでサーバーが明示的に指示していれば、それを最優先します
ここから分かることがあります。SDKの自動再試行を止めて自分で指数バックオフを書くと、この
Retry-Afterへの対応をいったん捨てることになります。 書き直すなら、同じ処理を自分で実装し直す覚悟が要ります。
それでも自前のラッパーが要る理由
ではSDKに全部任せればよいかというと、1点だけ足りません。
「待っても直らない429」が4種類ある
429は「送りすぎ」だけを意味しません。公式のエラーコード一覧を見ると、429には複数の原因があります。
| error code | 意味 | 待てば直る? |
|---|---|---|
| (レート制限) | 短時間に送りすぎた | ○ |
slow_down |
リクエストの増やし方が急すぎた | ○ |
credit_balance_exhausted |
前払いクレジットの残高がない | × |
organization_spend_limit_exceeded |
組織の支出上限に到達 | × |
project_spend_limit_exceeded |
プロジェクトの月次支出上限に到達 | × |
organization_usage_limit_exceeded |
組織の月次利用上限に到達 | × |
下の4つは、支払いや上限設定を変えない限り、何秒待っても直りません。
SDKは、この4つも再試行してしまう
SDKの判定はHTTPステータスだけを見ています。429ならすべて再試行対象です。error code までは見ていません。
つまり、残高が尽きた状態でAPIを呼ぶと、こうなります。
1回目 → 429 credit_balance_exhausted 0.5秒待つ 2回目 → 429 credit_balance_exhausted 1.0秒待つ 3回目 → 429 credit_balance_exhausted 例外が上がる 3リクエスト・約1.5秒を、確実に失敗すると分かっている処理に使った
1回なら1.5秒です。しかし100店舗ぶんのバッチで全件がこれになると、300リクエスト・2分半が無駄になります。しかも失敗したリクエストもレート制限にカウントされます。
これが、自前のラッパーを書く理由です。 指数バックオフを書き直すためではありません。SDKが見ていない error code を見て、待たずにやめるためです。
429になる前に気づく方法もある
ついでに触れておきます。レスポンスのヘッダーには、レート制限の残りが入っています。
x-ratelimit-limit-requests 1分あたりのリクエスト上限 x-ratelimit-remaining-requests 残りのリクエスト数 x-ratelimit-reset-requests 上限が回復するまでの時間 x-ratelimit-limit-tokens 1分あたりのトークン上限 x-ratelimit-remaining-tokens 残りのトークン数 x-ratelimit-reset-tokens 上限が回復するまでの時間
429になってから対処するのではなく、残りが減ってきたら送信の間隔を空けるという設計もできます。大量に流すバッチでは、こちらのほうが効きます。
まず timeout を決める
ラッパーを書く前に、timeout を設定します。
既定は10分
SDKの既定のタイムアウトは600秒(10分)です。接続だけは5秒で切られますが、応答を待つ時間は10分あります。
短い分析コメントを1つ作るだけの処理で、10分待つ設計にはしないはずです。
from openai import OpenAI
client = OpenAI(
timeout=30.0, # 1回のリクエストを30秒で打ち切る
) # max_retries は既定の 2 のまま
時間内に返ってこなければ APITimeoutError が発生します。
タイムアウトした分も再試行される
注意点が1つあります。timeout=30.0 にしても、処理全体が30秒で終わるとは限りません。
タイムアウトも再試行の対象なので、SDKの既定(2回)が効いていれば、最悪の場合は次のようになります。
30秒(1回目)+ 0.5秒 + 30秒(2回目)+ 1秒 + 30秒(3回目) = 約91.5秒
1回あたりの上限と、処理全体の上限は別物です。 後で扱うデッドラインは、この「全体」のほうを抑えるものです。
APITimeoutErrorはAPIConnectionErrorのサブクラスです。except openai.APIConnectionErrorと書くと、タイムアウトもそこで捕まります。両方を別々のexceptに並べると、後ろに書いたほうは実行されないので注意してください。
待っても直らない429を止める
いよいよラッパーを書きます。やることは1つだけです。「待っても直らない429なら、待たずに諦める」。
4つのコードを定数にする
import time
import openai
from openai import OpenAI
# 待っても直らない429(公式のエラーコード一覧より)
待っても直らない = {
"credit_balance_exhausted",
"organization_spend_limit_exceeded",
"project_spend_limit_exceeded",
"organization_usage_limit_exceeded",
}
client = OpenAI(timeout=30.0) # max_retries は既定の 2 のまま
集合(set)にしているのは、in で確認するためです。error code が増えたときも、ここへ1行足すだけで済みます。
ラッパーを書く
def 呼ぶ(入力, 制限秒=60.0):
期限 = time.monotonic() + 制限秒
待ち = 2.0
while True:
try:
return client.responses.create(
model="gpt-5.6-luna",
input=入力,
)
# 「残高不足」や「上限超過」などのエラーコードに該当する場合
except openai.RateLimitError as exc:
if exc.code in 待っても直らない:
raise # リトライせず即座に例外を発生
直前のエラー = exc
# 通信遮断やサーバー側エラーの場合(リトライ対象)
except (openai.APIConnectionError,
openai.InternalServerError) as exc:
直前のエラー = exc
# 残りの猶予時間を計算
残り = 期限 - time.monotonic()
if 残り <= 待ち:
raise 直前のエラー # 締切までに次の再試行が収まらない
time.sleep(待ち)
待ち = min(待ち * 2, 30.0)
exc.codeで error code が取れます。 SDKはレスポンスのerror.codeをここへ入れてくれます。exc.status_code(429)だけでは、この4つを見分けられません- 401や400の
exceptは書いていません。 捕まえないものは自動的に抜けていきます。exceptを、書いても書かなくても動作が同じです 直前のエラー = excと代入しているのは、except ... as excのexcがブロックを抜けた時点で消えるからです。ブロックの外でraiseし直すには、別の名前へ移しておく必要があります- 待ち時間の計算が1箇所にまとまっています。 429の分岐と5xxの分岐で同じコードを2回書くと、片方だけ直して食い違う原因になります
回数ではなく、締切で止める
最大試行回数だけでは足りない
「最大4回まで」と決めても、実際に何秒かかるかは決まりません。1回のリクエストに30秒かかるかもしれないからです。
業務側の要件は、たいてい回数ではなく時間で来ます。
- 画面で人が待っている → 30秒まで
- 夜間バッチ → 5分まで
だから上のコードでは、回数ではなく 制限秒 を引数にしています。
デッドラインを渡す
time.monotonic() は、途中で時刻が変更されても巻き戻らない時計です。経過時間を測るときは、time.time() ではなくこちらを使います。
締切を変えると、送信回数は自動的に決まります。
| 制限秒 | API送信の回数 | 待機の累計 | 向いている用途 |
|---|---|---|---|
| 10秒 | 3回 | 6秒 | 画面で人が待っている |
| 30秒 | 4回 | 14秒 | 対話型の上限 |
| 60秒 | 5回 | 30秒 | 1件ずつの定期処理 |
| 300秒 | 13回 | 270秒 | 夜間バッチ |
初回の待ちを2秒、上限を30秒とした場合の値です。「何回にすればよいか」を考えなくてよくなるのが、この書き方の利点です。
二重リトライに気をつける
送信回数は掛け算になる
ここで、上のコードに1つ落とし穴があります。SDKの自動再試行を既定のまま残しているので、自前のループの1周が「1リクエスト」とは限りません。
SDKの max_retries |
1周あたりの送信 | 自前のループ | 最大のAPI送信回数 |
|---|---|---|---|
| 2(既定) | 3 | 5回 | 15回 |
| 2(既定) | 3 | 3回 | 9回 |
| 0 | 1 | 3回 | 3回 |
| 5 | 6 | 5回 | 30回 |
公式のレート制限ガイドでも、アプリ側で再試行を管理するならSDKの再試行を無効にするか、SDK側も含めて上限を管理するよう案内されています。
どちらかに寄せる
対処は2つあります。
| やり方 | 設定 | 向いている場面 |
|---|---|---|
| SDKに再試行を任せる | max_retries は既定のまま。自前のループはデッドラインで止める |
Retry-After への対応をSDKに任せられる。ほとんどの場合こちら |
| 自前に寄せる | max_retries=0。待ち方もすべて自分で書く |
待ち方を完全に制御したい場合。Retry-After の処理も自分で書く必要がある |
この記事では前者を採っています。 回数ではなく締切で止めているので、SDKの再試行が何回入っても全体の時間は締切を超えません。掛け算が怖いのは「回数で管理しているとき」です。
max_retries=0にする場合は、Retry-Afterの読み取りを忘れないでください。exc.response.headers.get("retry-after")で取れます。これを実装しないと、サーバーが「56秒待って」と言っているのに2秒で再送することになり、かえって状況を悪くします。
擬似的に429を起こして確かめる
ここまで書いたコードが本当に意図どおり動くか、本物のエラーを待たずに確かめます。
偽のクライアントを作る
やり方は単純です。決めた順番で例外を投げるだけの、偽の client を作って差し替えます。
import httpx2
import openai
def 偽エラー(status, code=None):
"""本物と同じ形の例外オブジェクトを作る"""
req = httpx2.Request("POST", "https://api.openai.com/v1/responses")
body = {"error": {"message": "test", "type": "x", "code": code}}
res = httpx2.Response(
status,
headers={"x-request-id": "req_test"},
json=body,
request=req,
)
return openai.OpenAI(api_key="dummy")._make_status_error(
"test", body=body, response=res
)
class 偽クライアント:
"""決めた順に例外を投げる。None なら成功を返す"""
def __init__(self, 例外列):
self.例外列 = list(例外列)
self.回数 = 0
self.responses = self
def create(self, **kwargs):
self.回数 += 1
e = self.例外列.pop(0) if self.例外列 else None
if e is None:
class R:
output_text = "成功しました"
return R()
raise e
httpx2 はSDKが内部で使っているHTTPライブラリで、openai を入れると一緒に入ります。_make_status_error はSDKがレスポンスから例外を作るときに通る関数そのものなので、本物とまったく同じクラス・同じ code の例外が手に入ります。
6パターンを流す
ケース = [
("429 レート制限 → 2回目で成功", [偽エラー(429, "rate_limit_exceeded"), None]),
("429 slow_down → 3回目で成功", [偽エラー(429, "slow_down")] * 2 + [None]),
("429 残高不足", [偽エラー(429, "credit_balance_exhausted")] * 9),
("429 支出上限", [偽エラー(429, "organization_spend_limit_exceeded")] * 9),
("503 過負荷が続く", [偽エラー(503, "server_is_overloaded")] * 9),
("401 認証エラー", [偽エラー(401)] * 9),
]
for 名前, 列 in ケース:
c = 偽クライアント(列)
try:
呼ぶ(c, "テスト", 制限秒=10.0)
結果 = "成功"
except Exception as exc:
結果 = type(exc).__name__
print(f"{名前} → {結果}(送信 {c.回数} 回)")
呼ぶ() の第1引数に client を受け取るよう、少しだけ書き換えて実行します。結果はこうなります。
429 レート制限 → 2回目で成功 → 成功(送信 2 回) 429 slow_down → 3回目で成功 → 成功(送信 3 回) 429 残高不足 → RateLimitError(送信 1 回) 429 支出上限 → RateLimitError(送信 1 回) 503 過負荷が続く → InternalServerError(送信 6 回) 401 認証エラー → AuthenticationError(送信 1 回)
見るべきは3行目と4行目の「送信 1 回」です。同じ429でも、待っても直らないと分かっているものは1回でやめています。これが狙いどおり動いている証拠です。
そして6行目の401も「送信 1 回」です。except を1つも書いていないのに、再試行されずに上へ抜けています。
request ID をログへ残す
成功時とエラー時
エラーを調べるとき、いちばん役に立つのが request ID です。リクエスト1件ごとに振られる識別子で、レスポンスの x-request-id ヘッダーに入っています。
# 成功したとき
print(response._request_id) # req_abc123...
# エラーになったとき
try:
response = client.responses.create(...)
except openai.APIStatusError as exc:
print("request_id:", exc.request_id)
print("status:", exc.status_code)
print("code:", exc.code)
raise
APIStatusError は4xx・5xxの例外すべての親クラスです。これ1つを捕まえれば、401でも429でも503でも request_id が取れます。
成功時のほうは
response._request_idとアンダースコアが付いています。読みにくいですが、これが公式ドキュメントに載っている書き方です。APIConnectionError(接続できなかった場合)にはrequest_idがありません。 リクエストがサーバーへ届いていないので、IDも振られていないためです。
残す項目と、残してはいけない項目
| 残す | 何に使うか |
|---|---|
| 日時、処理名、モデル | いつ・どこで起きたかの特定 |
| 試行回数、待機した秒数 | 再試行が効いていたかの確認 |
| 例外クラス名、HTTPステータス、error code | 再試行してよかったのかの検証 |
| request ID | サポートへの問い合わせ、個別調査 |
| 最終的に成功したか失敗したか | 再実行の要否 |
| 残さない | 理由 |
|---|---|
| APIキー | ログは共有されます。キーが漏れる最も多い経路の1つです |
| 個人情報 | ログの保管期間と、個人情報の保管ルールは別物です |
| 送信したデータの全文 | 必要なら件数やハッシュだけを残します |
前回の記事でトークン数と概算料金をログへ残しました。そこへ「試行回数」と「成功/失敗」を足すと、「今月APIの請求が増えたのは、リトライが増えたからではないか」まで後から追えるようになります。
エラーの切り分け表
最初の判断に使う表です。手元に置いておいてください。
| 状況 | ステータス | 例外クラス | 同じ内容を再試行? | 最初にすること |
|---|---|---|---|---|
| 接続できない | — | APIConnectionError |
○ | 待って再試行。ネットワークも確認 |
| 時間内に返らない | 408 | APITimeoutError |
○ | timeout の値を見直す |
| 競合 | 409 | ConflictError |
○ | 待って再試行 |
| レート制限 | 429 | RateLimitError |
条件次第 | exc.code と Retry-After を見る |
| 残高・上限 | 429 | RateLimitError |
× | 支払い設定・利用上限を確認 |
| サーバー障害・過負荷 | 5xx | InternalServerError |
○ | 待って再試行 |
| 認証 | 401 | AuthenticationError |
× | APIキーと環境変数を確認 |
| 権限・地域 | 403 | PermissionDeniedError |
× | プロジェクトと権限を確認 |
| 見つからない | 404 | NotFoundError |
× | モデル名・IDを確認 |
| 入力が不正 | 400 | BadRequestError |
× | リクエストの中身を直す |
この表は最初の切り分け用です。429の行が2つあることからも分かるとおり、ステータスだけでは決まりません。exc.code まで見てください。
この記事で扱っていないこと
ストリーミング
ストリーミングは、回答を少しずつ受け取りながら表示する方式です。すでに一部を受け取った後でエラーになることがあります。
このとき最初から自動で再実行すると、すでに画面へ出した文章と、やり直した後の文章が二重に出る可能性があります。公式ガイドでも、ストリームが始まって出力を処理した後は、リクエスト全体を安易に再実行しないよう注意されています。
この記事はストリーミングを使わない呼び出しを前提にしています。
冪等性とジョブの再実行
冪等(べきとう)とは、同じ操作を2回やっても結果が1回分と同じになることです。文章を生成するだけなら問題になりませんが、生成した結果をデータベースへ書き込む処理まで含めて再試行すると、同じレポートが2件保存されることがあります。
再試行の対象はAPI呼び出しだけに限る、というのが基本です。保存まで含めた再実行は、ジョブ管理の話になります。
用途で変わる:対話型と夜間バッチ
「何回再試行すればよいか」に正解はありません。API側の都合ではなく、業務として何秒まで待てるかから決めます。
画面で人が待っている場合
店舗スタッフが画面で質問しているなら、待たせられるのはせいぜい30秒です。
client = OpenAI(timeout=15.0) 呼ぶ(質問, 制限秒=30.0) → 失敗したら「一時的に混み合っています。少し待ってお試しください」
粘るより、早めに諦めて、もう一度押せることを伝えるほうが親切です。
100店舗の夜間バッチ
人がその場で待っていないので、長めに粘れます。ただし1件の失敗で全体を止めないことのほうが重要です。
| 結果 | どうするか |
|---|---|
| 成功した店舗 | そのまま保存 |
| 一時エラーで終わった店舗 | 一覧へ記録し、後でまとめて再処理 |
| 401・400などの店舗 | 設定の問題。再処理せず通知 |
| 429の残高・上限 | バッチ全体を止める。他店舗も必ず失敗するため |
最後の行が、この記事の内容がそのまま効くところです。残高が尽きたと分かった時点で、残り99店舗を試す意味はありません。 exc.code を見ていれば、1件目で判断できます。
1件失敗しただけで100店舗すべてを最初からやり直す必要はありません。
まとめ
APIエラーは、まず「待てば直る可能性があるか」で分けます。
そして、再試行そのものはすでにSDKが書いています。
| 覚えておくこと | |
|---|---|
| SDKがやること | 接続エラー・408・409・429・5xx を既定で2回再試行。Retry-After にも従う |
| 自分で書くこと | 待っても直らない429(4種類)で、待たずにやめる |
| 上限の決め方 | 回数ではなく時間(デッドライン)。回数だとSDKの分と掛け算になる |
| 確かめ方 | 偽のクライアントで擬似的にエラーを起こして流す |
| ログ | request ID・試行回数・error code。APIキーは絶対に残さない |
下書きの段階でよくあるのが、SDKの自動再試行を止めて、自分で指数バックオフを書き直すことです。その時点で Retry-After への対応が消えます。 書き直すなら、SDKが何をしていたかを知ったうえで、同じことを実装してください。
