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

無料レッスン

モデル呼び出しが失敗したときの代替手段

このレッスンのあとで: 私は、失敗の種類ごとに代替手段を選び、モデル呼び出しが失敗しても機能を動かし続けられます。

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

モデルが失敗しても動き続ける

代替手段とは、モデルの呼び出しが失敗したときに機能が行う別の対応で、ユーザーに何かを返すためのものです。呼び出しは何通りもの形で失敗するので、すべてに同じ対応をするのは、たいていの失敗にとって誤りです。

タイムアウトには再試行が向いています。断りには向きません。一日の予算が尽きたときは、キャッシュした答えか、正直なメッセージが要ります。失敗の種類ごとに選びましょう。

このレッスンのゴール

メモ

私は、失敗の種類ごとに代替手段を選び、モデル呼び出しが失敗しても機能を動かし続けられます。

キーワード

ことばいみ
代替手段呼び出しが失敗したときに、代わりに行うこと
再試行待ってから、同じモデルにもう一度尋ねること
二つ目のモデル最初のモデルが失敗したときに尋ねる別のモデル
定型の返答モデルを使わない、固定で正直なメッセージ
縮退何も返さない代わりに、小さな答えを返すこと
失敗の種類呼び出しが失敗した理由

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

代替手段は順番に並べた鎖

選択肢を、費用の小さいものから、縮退の大きいものへ並べます。最初は再試行で、少し待ってから行います。次に二つ目のモデル。次に、古いかもしれないキャッシュの答え。最後に定型の返答で、固定の正しいメッセージを返し、ユーザーがすべきことを伝えます。

どの段階にも時間とお金がかかります。失敗のたびにすべてを試す鎖は、障害のときに請求額を倍にしてしまいます。

例: どの代替手段が合うか

失敗最初に理由
タイムアウト、レート制限一回だけ再試行ひとりでに通ることが多い
サーバーのエラー二つ目のモデル最初のモデルが止まっている
形式の誤り一回だけ再試行二回目で通ることが多い
断り定型の返答もう一度尋ねても変わらない
予算切れキャッシュの答え呼び出し自体が許されない

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

鎖に上限を付け、それを伝える

どの段階にも上限を付けます。再試行は一回、二つ目のモデルも一回です。試行の間は待ち、その待ち時間はユーザーのタイムアウトに含めます。代替手段が失敗を隠してはいけません。どの段階が答えたかを記録します。

キャッシュの答えは古いかもしれないので、重要なら、そう伝えます。定型の返答は、モデルの答えのふりをしてはいけません。

よくある間違い

注意

「再試行がいつも一番安全な代替手段だ」と考える人が多くいます。しかし、断りを再試行するとお金が無駄になり、障害中の再試行は負荷を増やします。ひとりでに通りうる失敗だけを、一回だけ再試行しましょう。

モデル用の助っ人を書く(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"

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

モデル用の助っ人を書く(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 d = await res.json();' '    return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"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\");' '' 'let now = 0; // ミリ秒。自分で進める時計' 'const store = new Map([[\"営業時間\", \"営業は午前九時から午後五時までです。\"]]);' 'const PLAIN = \"ただいま、アシスタントは答えられません。しばらくしてからもう一度お試しください。\";' '// 記録された失敗が、本物の失敗の代わりになる。ここでは本物のエラーは起こさない。' 'function fail(kind) { throw new Error(kind); }' > fallback5.js"

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

代替手段の鎖を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function secondModel() {' '  const messages = [{ role: \"user\", content: \"営業時間は午前九時から午後五時です。一文で伝えてください。\" }];' '  return (await ask(messages, \"営業は午前九時から午後五時までです。\", 40)).text;' '}' >> fallback5.js"

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

代替手段の鎖を書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function answer(question, second) {' '  const steps = [' '    [\"最初のモデル\", function () { fail(\"rate_limit\"); }],' '    [\"再試行\", function () { now = now + 200; fail(\"rate_limit\"); }],' '    [\"二つ目のモデル\", second],' '    [\"キャッシュ\", function () { return store.has(question) ? store.get(question) : fail(\"miss\"); }],' '  ];' >> fallback5.js"

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

代替手段の鎖を書く(4)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  for (const [name, run] of steps) {' '    try {' '      const text = await run();' '      console.log(question + \": \" + name + \" が \" + now + \" ms の時点で答えました\");' '      return text;' '    } catch (error) {' '      console.log(question + \": \" + name + \" は失敗しました (\" + error.message + \")\");' '    }' '  }' '  console.log(question + \": 定型の返答\");' '  return PLAIN;' '}' >> fallback5.js"

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

代替手段の鎖を書く(5)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  const down = function () { fail(\"server_error\"); };' '  await answer(\"営業時間\", secondModel);' '  await answer(\"営業時間\", down);' '  await answer(\"返金\", down);' '}' 'main();' >> fallback5.js"

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

鎖を実行する

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

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

失敗は記録されたものです

この Run は、本物の失敗を起こしません。レート制限とサーバーのエラーは、記録されたデータとしてスクリプトに書かれ、時計はスクリプトが進める数です。本物の呼び出しは二つ目のモデルだけです。

本物のシステムでは、これらのエラーは本物のネットワークから届き、試行の間は本当に待ちます。

出力の例

行示していること
営業時間: 最初のモデル は失敗しました (rate_limit)記録された失敗
営業時間: 再試行 は失敗しました (rate_limit)自分の時計で 200 ms 待ったあとの再試行
営業時間: 二つ目のモデル が 200 ms の時点で答えました唯一の本物の呼び出し
営業時間: キャッシュ が 400 ms の時点で答えました二つ目のモデルが止まっているので、キャッシュが答える
返金: 定型の返答キャッシュに何もなかったので、定型のメッセージを使う

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

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

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