サーキットブレーカーのしくみ
サーキットブレーカーは、失敗が続くサービスへの呼び出しを止める、小さな決まりです。開いているあいだは、失敗する呼び出しを待たずに、代わりの手を使います。
コスト、トレース、信頼性のモジュールでは、代わりの手の一覧を学びます。このレッスンは、さらに進みます。いつ呼ぶのをやめるか、いつ試すか、人が押せる緊急停止です。
このレッスンのゴール
メモ
モデルが失敗しているあいだも、機能を役に立てることができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| サーキットブレーカー | 失敗するサービスへの呼び出しを止める決まり | |
| 閉(closed) | ふだんの状態。呼び出しが通る | |
| 開(open) | 止まった状態。呼ばず、代わりの手が答える | |
| 半開(half_open) | サービスが戻ったか見る、試しの呼び出し一回 | |
| 冷却 | 試す前に待つ歩数 | 4 |
| 緊急停止 | 機能をすぐに止める操作 |
学生が入力するセルは、このプレビューでは空白です。
三つの状態
すべてがうまくいくあいだ、ブレーカーは閉じていて、呼び出しは通ります。続けた失敗を数え、しきい値、ここでは 3 に達すると、開きます。
開いているあいだは、呼び出しません。冷却の 4 歩が過ぎたら、半開になり、一回だけ通します。成功すれば閉じ、失敗すればまた開きます。
例: 3 歩目から 9 歩目
| 歩 | 呼べば | 見つけた状態 | 答え方 | 理由 |
|---|---|---|---|---|
| 3 | 失敗 | closed | failed | 続けた失敗の 1 回目。数は 1 |
| 4 | 失敗 | closed | failed | 数は 2 |
| 5 | 失敗 | closed | failed | 数が 3 になり、開く |
| 6 | 成功 | open | skipped | 呼ばない。開いてから 1 歩 |
| 7 | 成功 | open | skipped | 開いてから 2 歩 |
| 8 | 成功 | open | skipped | 開いてから 3 歩 |
| 9 | 成功 | half_open | model | 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 step(b, outcome, i, threshold, cooldown, kill) {' ' if (kill) return [b.state, \"skipped\"];' ' if (b.state === \"open\" && i - b.openedAt >= cooldown) b.state = \"half_open\";' ' const found = b.state;' ' if (found === \"open\") return [found, \"skipped\"];' ' if (outcome === 1) { b.state = \"closed\"; b.fails = 0; return [found, \"model\"]; }' ' b.fails = b.fails + 1;' ' if (found === \"half_open\" || b.fails >= threshold) { b.state = \"open\"; b.openedAt = i; b.fails = 0; }' ' return [found, \"failed\"];' '}' > breaker.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ブレーカーのスクリプトを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const outcomes = [1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1];' 'function drive(kills) {' ' const b = { state: \"closed\", fails: 0, openedAt: 0 };' ' return outcomes.map(function (o, k) {' ' const r = step(b, o, k + 1, 3, 4, kills.includes(k + 1));' ' return r[0] + \":\" + r[1];' ' });' '}' >> breaker.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ブレーカーのスクリプトを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const trace = drive([]);' ' console.log(trace.slice(0, 10).join(\" \"));' ' console.log(trace.slice(10).join(\" \"));' ' const made = trace.filter(function (t) { return t.endsWith(\"skipped\") === false; }).length;' ' console.log(\"呼び出した回数 \" + made + \" / \" + trace.length);' ' console.log(\"停止 9 と 10: \" + drive([9, 10]).slice(8, 11).join(\" \"));' ' const q = \"短く一文で答えてください。モデルが止まっているとき、機能は何と伝えるべきですか。\";' ' const reply = await ask([{ role: \"user\", content: q }], \"アシスタントは混み合っていると正直に伝え、保存した答えや別の方法を案内します。\", 80);' ' console.log(\"モデル: \" + reply.text);' '}' 'main();' >> breaker.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ブレーカーを動かす
docker exec -w /home/node/project agent node breaker.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
Run の出力
| 行 | 意味 |
|---|---|
| closed:model closed:model closed:failed ... | 1 歩から 10 歩。見つけた状態と答え方 |
| open:skipped open:skipped open:skipped half_open:model | 開き、待ち、試しが成功する |
| 呼び出した回数 14 / 20 | 6 回の呼び出しを節約した |
| 停止 9 と 10: open:skipped open:skipped half_open:model | 9 歩と 10 歩は停止で飛ばされ、11 歩は冷却が終わっているので試す |
| モデル: ... | ラボからの一文。モデルが答えなければ記録済みサンプル |
学生が入力するセルは、このプレビューでは空白です。