クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

APIエラーの再試行とレート制限

このレッスンのあとで: 再試行する失敗と、直すべき失敗を見分けられる。

60分のレッスンコマンド5件AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

待てば直る失敗がある

API のエラーとは、ステータスコードが「呼び出しがうまくいかなかった」と伝える返答です。少し待ってもう一度試せば直るエラーもあれば、何度試しても直らないエラーもあります。

よいプログラムは、この二つを見分けます。前者は、待ち時間を延ばしながら再試行し、後者は、そこで止めて知らせます。

この課のゴール

メモ

再試行する失敗と、直すべき失敗を見分けられる。

キーワード

ことばいみれい
ステータスコード呼び出しの結果を表す、返答の中の数429
レート制限一分間にできる呼び出しの数の上限
バックオフ失敗のたびに待ち時間を延ばすこと
ジッター待ち時間のランダムな変化。一斉に再試行しないため
タイムアウト返答を待つ最長の時間

学生が入力するセルは、このプレビューでは空白です。

ステータスコードが次の行動を教える

200 番台のステータスは、呼び出しが成功したことを表します。400 番台のステータスは、たいてい呼び出しの作り方が間違っているので、同じ呼び出しはまた失敗します。

ほかのステータスは、短く一時的な問題を表します。時間切れ、サーバーの混雑、レート制限などです。その場合は、待ってからもう一度試すのが正しい対応です。

例:再試行か、修正か

ステータス意味対応
400リクエストが間違いリクエストを直す
401有効なキーがないキーを直す
403許可なし、または残りの枠なし原因を直す
408、504時間切れ再試行する
429リクエストが多すぎる待ってから再試行する
500、502、503サーバーに問題数回再試行する

学生が入力するセルは、このプレビューでは空白です。

ステータスを分け、記録済みの429を読む

コマンド
docker exec -w /home/node/project agent node -e '/* どの失敗なら、もう一度試す価値があるでしょうか。 */ const kind = (status) => { if (status >= 200 && status < 300) return "ok"; return [408, 429, 500, 502, 503, 504].includes(status) ? "retry" : "fix"; }; const notes = { 200: "成功", 400: "リクエストが間違い", 401: "有効なキーがない", 403: "許可されていない", 408: "時間切れ", 429: "リクエストが多すぎる", 500: "サーバーの故障", 503: "サーバーの準備ができていない" }; for (const status of Object.keys(notes)) { console.log(status + " " + kind(Number(status)).padEnd(5) + " " + notes[status]); } /* 記録済みの429を、データとしてこのスクリプトに書きました。呼び出しはしません。 */ const recorded = { status: 429, headers: { "retry-after": "2" }, body: { error: { message: "リクエストが多すぎます。ゆっくりお願いします。" } } }; console.log("記録済みの429:" + recorded.body.error.message + "サーバーは " + recorded.headers["retry-after"] + " 秒を求めています。");'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

バックオフは毎回長く待つ

すぐ再試行すると、失敗の原因になった負荷をさらに増やします。バックオフは、少し待ち、次にその倍、さらにその倍と、上限まで待ち時間を延ばします。

ジッターも加えます。待ち時間を、半分から全部の間のランダムな量だけ変えます。そうすれば、いっしょに失敗した百台のクライアントが、同じ瞬間に再試行しません。返答に Retry-After ヘッダーがあれば、それを使います。

バックオフの予定表を表示する

コマンド
docker exec -w /home/node/project agent node -e '/* 失敗のたびに待ち時間を延ばし、上限を設け、ばらつき(ジッター)を加えます。 */ const base = 1; const cap = 8; const delays = []; for (let attempt = 0; attempt < 6; attempt++) delays.push(Math.min(cap, base * 2 ** attempt)); console.log("待ち時間(秒):" + delays.join(", ")); const jittered = delays.map((one) => one / 2 + Math.random() * one / 2); console.log("ばらつきあり、ある実行の例:" + jittered.map((one) => one.toFixed(2)).join(", ")); console.log("ばらつきありの待ち時間は、元の半分から元の全部までの間になります。");'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

よくある間違い

注意

どのエラーも再試行すべきだと考える人が多くいます。そうではありません。400 や 401 は、毎回同じように失敗します。それを再試行するループは、時間と枠を無駄にするだけです。通る可能性のある失敗だけを、数回だけ再試行します。

タイムアウトで、待ち続けずにすむ

返答が来ない呼び出しもあります。待つ最長の時間としてタイムアウトを決め、それを超えたら、再試行する価値のある失敗として扱います。

下の補助ファイルは、これらを一つにまとめます。タイムアウト、数回の試行、ジッター付きのバックオフがあり、直すべき失敗ではすぐ止まり、何もうまくいかなければ記録済みのサンプルを表示します。

retry.js その1を書く

コマンド
docker exec -w /home/node/project agent node -e 'require("fs").writeFileSync("retry.js", ["// retry.js その1:どのステータスを再試行するか、どれだけ待つか。", "const lab = require(\"./lab.js\");", "const kind = (status) => {", "  if (status >= 200 && status < 300) return \"ok\";", "  return [408, 429, 500, 502, 503, 504].includes(status) ? \"retry\" : \"fix\";", "};", "const pause = (ms) => new Promise((done) => setTimeout(done, ms));", "", "// バックオフ、Retry-After ヘッダー(あれば)、上限、ジッター。", "function waitFor(attempt, response) {", "  let wait = 150 * 2 ** attempt;", "  if (response) wait = Math.max(wait, Number(response.headers.get(\"retry-after\")) * 1000 || 0);", "  wait = Math.min(wait, 3000);", "  return wait / 2 + Math.random() * wait / 2;", "}"].join("\n") + "\n")'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

再試行のループを足す

コマンド
docker exec -w /home/node/project agent node -e 'require("fs").appendFileSync("retry.js", ["", "// その2:再試行します。ただし、直すべき失敗は再試行しません。", "async function callWithRetry(body, tries) {", "  for (let attempt = 1; attempt <= tries; attempt++) {", "    let response = null;", "    try {", "      response = await lab.send(body, 15000);", "      if (response.ok) return { ok: true, data: await response.json() };", "      console.log(attempt + \" 回目:HTTP \" + response.status + \"、\" + kind(response.status));", "      if (kind(response.status) === \"fix\") return { ok: false, why: \"原因を直す、再試行しない\" };", "    } catch (error) {", "      console.log(attempt + \" 回目:応答なし(\" + error.name + \")\");", "    }", "    if (attempt < tries) await pause(waitFor(attempt, response));", "  }", "  return { ok: false, why: tries + \" 回試して断念\" };", "}", "", "module.exports = { callWithRetry };"].join("\n") + "\n")'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

再試行付きでモデルを呼ぶ

コマンド
docker exec -w /home/node/project agent node -e 'const { callWithRetry } = require("./retry.js"); (async () => { const body = { max_tokens: 20, messages: [{ role: "user", content: "一語であいさつしてください。" }] }; const result = await callWithRetry(body, 3); if (result.ok) console.log("モデルの答え:" + result.data.choices[0].message.content); else console.log("記録済みのサンプル:こんにちは。(" + result.why + ")"); })();'

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

さらに:試行回数を変える

retry.js で、最初の待ち時間を 150 から 50 に変えます。最後の Run で、試行回数を 3 から 5 に変えます。実行して、表示を読んでください。

人が待っているボタンには何を選ぶか、夜のあいだ動く仕事には何を選ぶかを、言ってみましょう。

このラボとソースについて: AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。