チケットを渡し合う2つのエージェント
マルチエージェントとは、それぞれ1つの役割を持つエージェントに、仕事を分けるしくみです。仕事は、引き継ぎのメッセージで渡します。引き継ぎのたびに呼び出しが1回かかり、文脈も失われかねません。
請求のエージェントが、在庫のエージェントにチケットを送りました。在庫のエージェントは、すぐに送り返します。チケットは一晩中行き来しました。
このレッスンのゴール
メモ
仕事をエージェントに分け、1つで足りる場合を説明できる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 役割 | エージェントが持つ、ただ1つの仕事 | 価格 |
| 引き継ぎ | 仕事を別のエージェントに渡すメッセージ | |
| ルーター | 仕事を受ける役割を選ぶ部分 | |
| 共有の記録 | 全員のエージェントが読み、書き足す記録 | |
| 繰り返しの防止 | 仕事が際限なく行き来するのを止める確認 |
学生が入力するセルは、このプレビューでは空白です。
1つのエージェントで足りるとき
まず、エージェント1つとプロンプト1つで始めます。得られるものを言えるときだけ、2つ目を足します。たとえば、短いプロンプト、見えるツールの数の削減、別のモデル、人が承認する段階です。
エージェントが増えるほど、呼び出し、遅れ、文脈の漏れが増えます。ツール3つの1つのエージェントで足りるなら、そこでやめます。
例: 1つか、複数か
| 場面 | 選択 | 理由 |
|---|---|---|
| 値段を調べて伝える | エージェント1つ | ツール1つ、プロンプト1つ |
| 価格、在庫、配送の規則と、20個のツール | 役割に分ける | 各役割は、自分のツールだけを見る |
| 10000円を超える返金 | 人が行う段階 | 承認はエージェントの仕事ではない |
学生が入力するセルは、このプレビューでは空白です。
モデル用の補助を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '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 || 200 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"status \" + res.status);' ' return (await res.json()).choices[0].message.content;' > model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
モデル用の補助を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' } catch (error) {' ' console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' ' const list = [].concat(sample);' ' const turn = messages.filter(function (m) { return m.role === \"assistant\"; }).length;' ' return list[Math.min(turn, list.length - 1)];' ' }' '}' 'module.exports = { ask };' >> model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
引き継ぎのデモを書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const PRICES = { \"おにぎり\": 150, \"お茶\": 140 };' 'const STOCK = { \"おにぎり\": 12, \"お茶\": 0 };' 'function look(table, q, unit) {' ' const key = Object.keys(table).find(function (w) { return q.indexOf(w) >= 0; });' ' return key === undefined ? \"見つかりません\" : key + \": \" + table[key] + unit;' '}' 'const roles = {' ' \"価格\": function (q) { return look(PRICES, q, \" 円\"); },' ' \"在庫\": function (q) { return look(STOCK, q, \" 個\"); },' '};' 'function route(q) {' ' if (q.indexOf(\"いくら\") >= 0) return \"価格\";' ' if (q.indexOf(\"在庫\") >= 0) return \"在庫\";' ' return null;' '}' > handoff1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
引き継ぎのデモを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function pick(q) {' ' const rule = route(q);' ' if (rule) return rule;' ' const text = \"次の仕事に合う役割は、価格、在庫、なしのどれですか。1語だけで答えてください。仕事: \" + q;' ' const word = (await ask([{ role: \"user\", content: text }], \"なし\", 10)).trim();' ' return word === \"価格\" || word === \"在庫\" ? word : \"なし\";' '}' 'const trail = [];' 'function allowed(from, to) {' ' trail.push(from + \">\" + to);' ' return trail.filter(function (t) { return t === from + \">\" + to; }).length < 3;' '}' >> handoff1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
引き継ぎのデモを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const record = [];' ' for (const q of [\"おにぎりはいくらですか\", \"お茶は在庫がありますか\", \"明日の天気は\"]) {' ' const role = await pick(q);' ' const result = role === \"なし\" ? \"合う役割がありません。人に聞いてください\" : roles[role](q);' ' record.push({ to: role, task: q, result: result });' ' }' ' record.forEach(function (r) { console.log(r.to + \" | \" + r.task + \" | \" + r.result); });' ' let who = \"価格\";' ' for (let i = 0; i < 6; i++) {' ' const next = who === \"価格\" ? \"在庫\" : \"価格\";' ' if (allowed(who, next) === false) { console.log(\"繰り返し: \" + who + \" から \" + next + \" へ3回目、停止します\"); break; }' ' who = next;' ' }' '}' 'main();' >> handoff1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
実行して共有の記録を読む
docker exec -w /home/node/project agent node handoff1.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
出力の一例
| 行 | 表示されるかもしれないもの |
|---|---|
| 価格 | おにぎりはいくらですか | おにぎり: 150 円 |
| 在庫 | お茶は在庫がありますか | お茶: 0 個 |
| 天気のタスク | なし。またはモデルが選んだ役割と、弱い結果 |
| 繰り返し | ある組が3回目に現れたという行 |
学生が入力するセルは、このプレビューでは空白です。
引き継ぎは形のあるメッセージ
引き継ぎのメッセージには、送る側、受ける側、タスク、受ける側に必要な文脈を書きます。受ける側が、送る側の履歴を全部読む必要はありません。
エージェントは、したことを共有の記録に書きます。仕事についての本当の状態は、この1か所にあります。
仕事の行き来を止める
2つのエージェントが、どちらも「相手が担当すべきだ」と判断することがあります。どちらの中にも誤りはありませんが、仕事は終わりません。
送る側から受ける側への組ごとに、回数を数えます。ある組が上限に達したら止め、仕事を人に渡します。引き継ぎの経過を記録に残すと、繰り返しが見つけやすくなります。
よくある間違い
注意
「ルーターは、いつもモデルに聞くべきだ」と考える人が多くいます。モデルの呼び出しは遅く、お金がかかり、存在しない役割を挙げることもあります。規則で正確に決まるところは規則で行い、残りだけモデルに聞き、答えを確かめます。