チームは1つのエージェントと違う壊れ方をする
マルチエージェントの失敗とは、エージェントのチームが、単独のエージェントなら起こさない形で道を外れることです。欠陥はエージェント同士の会話にあるので、1つずつ見ると、どれも正常に見えます。
見つけるには、やり取りの記録を読みます。送られたメッセージの一覧です。
このレッスンのゴール
メモ
やり取りの中の失敗を見つけ、その種類を言える。
キーワード
| ことば | いみ |
|---|---|
| やり取りの記録 | エージェントが送ったメッセージを、順に並べたもの |
| 繰り返し | 同じメッセージが何度も送られること |
| にらみ合い | 2つのエージェントが、互いに相手を待つこと |
| 暴走するコスト | 予算を超えて増え続けるトークンの使用 |
| 推測が事実に | 推測が、あとで事実として繰り返されること |
| 防止策 | 失敗を早く止める、コードの中の規則 |
学生が入力するセルは、このプレビューでは空白です。
チームが道を外れる4つの形
繰り返しは、同じメッセージをくり返します。にらみ合いは、2つのエージェントが、相手が先に動くのを待ちます。暴走するコストは、毎ラウンドがそれまでの全履歴を読むので増え続けます。
4つ目は、気づきにくい形です。ワーカーが推測し、次のエージェントが推測を繰り返し、3つ目では事実として書かれます。
例: 兆候と防止策
| 失敗 | 記録に出る兆候 | 防止策 |
|---|---|---|
| 繰り返し (loop) | 同じ送り手、受け手、文が3回 | 3回目の繰り返しで止める |
| にらみ合い (deadlock) | 「待機中」で始まる2つのメッセージが逆向き | 時間の上限と、先に動く側の決め方 |
| 推測が事実に (passed-on) | 推測のあと、同じ文が事実として出る | 状態を主張と一緒に運ぶ |
| 暴走 (runaway) | ラウンドごとのトークンが増え続ける | 実行全体の予算 |
学生が入力するセルは、このプレビューでは空白です。
はっきりした兆候から調べる
実際のやり取りには、兆候が複数出ることがあります。順番を決めて、それを守ります。にらみ合いも繰り返しも仕事を止めるので、コストより先に名づけます。
エージェントを作るモジュールでは、繰り返しの防止策を扱いました。このレッスンは、4つとも見分けることが目的です。どの防止策を足すかを決めるためです。
よくある間違い
注意
チームが止まると、リトライを足す人が多くいます。リトライは繰り返しを長くしますが、にらみ合いは短くしません。先に失敗に名前を付け、それに合う防止策を選んでください。
モデル用の補助を書く(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\");' 'function m(from, to, text, tokens, status) {' ' return { from: from, to: to, text: text, tokens: tokens, status: status };' '}' 'const T = {};' 'T.loop = [m(\"書き手\", \"確認役\", \"下書きを確認して\", 300, \"none\"), m(\"確認役\", \"書き手\", \"出典を足して\", 200, \"none\"),' ' m(\"書き手\", \"確認役\", \"下書きを確認して\", 300, \"none\"), m(\"確認役\", \"書き手\", \"出典を足して\", 200, \"none\"),' ' m(\"書き手\", \"確認役\", \"下書きを確認して\", 300, \"none\")];' 'T.deadlock = [m(\"計画役\", \"査読役\", \"待機中 査読を待ってから計画します\", 150, \"none\"),' ' m(\"査読役\", \"計画役\", \"待機中 計画を待ってから査読します\", 150, \"none\")];' > fail.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
失敗の検出を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'T.passed = [m(\"読み手\", \"計画役\", \"土曜の来館が4割増えた\", 200, \"guess\"),' ' m(\"計画役\", \"書き手\", \"土曜の来館が4割増えた\", 200, \"fact\")];' 'T.grows = [m(\"a\", \"b\", \"第1ラウンド\", 1000, \"none\"), m(\"b\", \"a\", \"第2ラウンド\", 2000, \"none\"), m(\"a\", \"b\", \"第3ラウンド\", 4000, \"none\"),' ' m(\"b\", \"a\", \"第4ラウンド\", 8000, \"none\"), m(\"a\", \"b\", \"第5ラウンド\", 16000, \"none\")];' 'T.fine = [m(\"計画役\", \"読み手\", \"日曜の需要を調べて\", 200, \"none\"), m(\"読み手\", \"計画役\", \"400人中212人\", 150, \"fact\")];' >> fail.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
失敗の検出を書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function some2(ms, test) {' ' return ms.some(function (x, i) { return ms.some(function (y, j) { return i < j && test(x, y); }); });' '}' 'function deadlock(ms) {' ' return some2(ms, function (x, y) {' ' return x.text.startsWith(\"待機中\") && y.text.startsWith(\"待機中\") && x.from === y.to && x.to === y.from;' ' });' '}' 'function loop(ms) {' ' return ms.some(function (x) {' ' return ms.filter(function (y) { return y.from === x.from && y.to === x.to && y.text === x.text; }).length >= 3;' ' });' '}' >> fail.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
失敗の検出を書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function passedOn(ms) {' ' return some2(ms, function (x, y) { return x.status === \"guess\" && y.status === \"fact\" && x.text === y.text; });' '}' 'function total(ms) { return ms.reduce(function (a, x) { return a + x.tokens; }, 0); }' 'function find(ms, budget) {' ' if (deadlock(ms)) return \"deadlock\";' ' if (loop(ms)) return \"loop\";' ' if (passedOn(ms)) return \"passed-on\";' ' return total(ms) > budget ? \"runaway\" : \"none\";' '}' >> fail.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
失敗の検出を書く(5)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' for (const name of Object.keys(T)) {' ' console.log(name + \": \" + T[name].length + \"件、\" + total(T[name]) + \"トークン、判定 \" + find(T[name], 20000));' ' }' ' const q = [{ role: \"user\", content: \"2つのエージェントが同じメッセージを永遠に送り合うのを防ぐ、簡単な規則を1文で答えてください。\" }];' ' const r = await ask(q, \"メッセージごとに数え、3回目の繰り返しで止めます。\", 40);' ' console.log(\"モデル: \" + r.text.trim());' '}' 'main();' >> fail.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
失敗の検出を実行する
docker exec -w /home/node/project agent node fail.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
記録済みのやり取りです
本物の実行は、壊れたチーム1つでラボのクレジットを使い切ります。そのため5つのやり取りは、短く作った記録済みのデータです。検出関数は本物のコードです。
ライブ呼び出しは、規則を1つ頼むだけです。言葉は実行のたびに変わります。
出力の一例
| 行 | 何を示すか |
|---|---|
| loop: 5件、1300トークン、判定 loop | 同じメッセージが3回 |
| deadlock: 2件、300トークン、判定 deadlock | 互いに相手を待つ |
| passed: 2件、400トークン、判定 passed-on | 推測が事実になった |
| grows: 5件、31000トークン、判定 runaway | 予算20000を超えた |
| fine: 2件、350トークン、判定 none | 失敗の兆候なし |
学生が入力するセルは、このプレビューでは空白です。