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

無料レッスン

利用制限と安全な再試行

このレッスンのあとで: 嵐を起こさずに、呼び出しを再試行することができる。

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

嵐を起こさずに再試行する

レート制限とは、サービスが短い時間に受け付ける呼び出しの数の上限です。再試行とは、断られた呼び出しをもう一度試すことです。考えなしの再試行は、過負荷をもっと悪くします。

コスト、トレース、信頼性のモジュールでは、一度再試行して、代わりの手に移りました。このレッスンは、さらに進みます。待ち時間を増やし、ばらつきを足し、試行の数を限り、何度やっても安全な再試行にします。

このレッスンのゴール

メモ

嵐を起こさずに、呼び出しを再試行することができる。

キーワード

ことばいみれい
レート制限時間の窓に、サービスが受け付ける最大の呼び出し数
バックオフ失敗するたびに、長くなっていく待ち時間400、800、1600
ばらつき呼び出しごとに少しずつ違う待ち時間
嵐多くの呼び出しが、同じ瞬間に再試行すること
冪等二回やっても、一回の効果と同じで安全なこと
模擬の時計待つ代わりに、手で進める数

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

レート制限の数え方

サービスは、時間を 1000 ミリ秒の窓に区切ります。それぞれの窓で、決まった数、ここでは 2 回を受け付けます。残りは、次の窓が始まるまで断ります。

ここでの時間は、手で進める数です。何も待たないので、毎回同じ答えになります。

例: 窓ごとに 2 回

時刻(ms)窓受け付けずみ結果
000受け付ける
001受け付ける
002断る。窓がいっぱい
40002また断る
120010受け付ける。新しい窓

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

よくある間違い

注意

すぐに再試行するのが、答えを得る最短の道だと考える人が多いです。サービスが過負荷なら、すぐの再試行は負荷を足すだけで、みんなにとって悪くなります。まず待ち、そのたびに長く待ちます。

バックオフとばらつき

バックオフは、倍になる待ち時間です。base が 400 なら、待ち時間は 400、800、1600 です。上限をつけ、ここでは 2000 にします。つけないと、待ち時間は際限なく伸びます。

6 つの呼び出しが同時に失敗して、どれも 400 待つと、同時に戻ってきます。ばらつきは、呼び出しごとに少し違う数を足すので、一つずつ戻ります。

例: base 400、上限 2000

失敗した試行バックオフリクエスト 3 のばらつき次の試行の時刻(0 から)
140034434
2800471281(434 から)
31600102891(1281 から)
42000、上限使わないtries が 4 ならあきらめる

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

再試行は、繰り返しても安全にする

呼び出しは成功したのに、答えが途中で消えることがあります。呼び出し側は失敗と思って、もう一度試します。再試行がまた課金したり送ったりすると、二回してしまいます。

すべての操作に、鍵をつけます。サーバーは、見た鍵を覚えます。見た鍵での繰り返しは、新しいことを何もしません。

助っ人 model.js を書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答のトークン数。なければ null。' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"

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

助っ人 model.js を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", {' '      method: \"POST\",' '      headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' '      body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"status \" + res.status);' '    const data = await res.json();' '    const text = data.choices[0].message.content;' '    return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' 'module.exports = { ask };' >> model.js"

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

再試行のスクリプトを書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'function delay(attempt, base, cap) { return Math.min(base * Math.pow(2, attempt - 1), cap); }' 'function spread(id, attempt) { return (id * 7 + attempt * 13) % 50; }' 'function run(count, limit, window, base, cap, tries, spreadOn) {' '  const pending = [];' '  const history = {};' '  const counts = {};' '  for (let id = 1; id <= count; id++) { pending.push([0, id, 1]); history[id] = []; }' > retry.js"

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

再試行のスクリプトを書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  while (pending.length > 0) {' '    pending.sort(function (a, b) { return a[0] - b[0] || a[1] - b[1] || a[2] - b[2]; });' '    const item = pending.shift();' '    const slot = Math.floor(item[0] / window);' '    history[item[1]].push(item[0]);' '    counts[slot] = counts[slot] || 0;' '    if (counts[slot] < limit) { counts[slot] = counts[slot] + 1; continue; }' '    if (item[2] < tries) {' '      const extra = spreadOn ? spread(item[1], item[2]) : 0;' '      pending.push([item[0] + delay(item[2], base, cap) + extra, item[1], item[2] + 1]);' '    }' '  }' '  return history;' '}' >> retry.js"

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

再試行のスクリプトを書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  for (const on of [false, true]) {' '    const h = run(6, 2, 1000, 400, 2000, 4, on);' '    const seen = {};' '    let worst = 0;' '    for (const id in h) h[id].slice(1).forEach(function (t) { seen[t] = (seen[t] || 0) + 1; worst = Math.max(worst, seen[t]); });' '    console.log((on ? \"ばらつきあり\" : \"ばらつきなし\") + \": 同じ 1 ミリ秒での再試行の最多 \" + worst);' '    console.log(\"  リクエスト 3 の試行 \" + h[3].join(\" \") + \"、リクエスト 5 は \" + h[5].join(\" \"));' '  }' '  const reply = await ask([{ role: \"user\", content: \"短く一文で答えてください。再試行の待ち時間に、ばらつきを足すのは、なぜですか。\" }], \"多くの呼び出しが同じ瞬間に再試行して、サービスをまた過負荷にしないためです。\", 80);' '  console.log(\"モデル: \" + reply.text);' '}' 'main();' >> retry.js"

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

再試行を動かす

コマンド
docker exec -w /home/node/project agent node retry.js

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

Run の出力

行意味
ばらつきなし: 同じ 1 ミリ秒での再試行の最多 44 つの呼び出しが、同じ瞬間に戻った
リクエスト 3 の試行 0 400 1200、リクエスト 5 は 0 400 1200 2800ばらつきがないと、時刻が整って同じになる
ばらつきあり: 同じ 1 ミリ秒での再試行の最多 1同じ瞬間に重なる再試行はない
リクエスト 3 の試行 0 434 1281 2891、リクエスト 5 は 0 448 1259 2883呼び出しごとに待ち方が少し違う
モデル: ...ラボからの一文。モデルが答えなければ記録済みサンプル

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

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

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