ワーカーは依頼しか知らない
オーケストレーターとワーカーの型は、1つのエージェントが仕事を依頼に分け、ワーカーに渡し、結果をまとめるプログラムです。ワーカーは、依頼に書かれたこと以外を何も知りません。
あいまいな依頼からは、間違った質問への自信たっぷりの答えが返ります。それだけで通じる依頼からは、確かめられる答えが返ります。
このレッスンのゴール
メモ
仕事を、それだけで通じる依頼に分け、結合する前に各結果を確かめられる。
キーワード
| ことば | いみ |
|---|---|
| 依頼 | ワーカーが受け取るすべて。質問、材料、答えの形 |
| 結果の形 | ワーカーが返さなければならない項目 |
| 材料 | ワーカーが答えに使ってよい文 |
| 検証 | 結果を使う前に確かめること |
| 結合 | 受理した結果をまとめること |
| 知らない出典 | ワーカーが挙げたが、渡されていない出典 |
学生が入力するセルは、このプレビューでは空白です。
依頼に入れるもの
ワーカーには、仕事全体も、他の依頼も、会話も見えません。依頼にないものは、存在しないのと同じです。
ですから依頼には、質問、使ってよい材料、答えの形、大きさの上限を入れます。材料は文そのものです。ワーカーが探しに行ってくれる、という約束ではありません。
例: 1つの依頼
| 項目 | 例 | 入れる理由 |
|---|---|---|
| question | 日曜1日の人件費はいくらですか。 | 仕事全体ではなく1つの質問 |
| material | 人件費の表を、文として | ワーカーはそれ以外を使えない |
| sources | s2 | 答えが出どころを言えるように |
| reply | answer と sources を持つオブジェクト | オーケストレーターが確かめられるように |
学生が入力するセルは、このプレビューでは空白です。
結合する前に結果を検証する
結果は、あなたが制御できない部分から来たデータです。まず形を確かめます。答えはあるか、出典のリストはあるか。次に、各出典が依頼で渡したものかを確かめます。
通ったものだけを結合します。エージェントを作るモジュールでは、エージェント間でメッセージを渡しました。ここでは、戻ってきたものを信じてよいかを決めます。
よくある間違い
注意
全部の結果を結合してから、あとで直す人が多くいます。すると悪い結果が、最終的な答えの中で良い結果と同じ重さを持ちます。入り口で却下して、理由を残してください。
モデル用の補助を書く(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\");' 'const SOURCES = {' ' s1: \"アンケート: 400人のうち212人が日曜開館を希望しました。\",' ' s2: \"人件費: 時給1800円で6時間働くと、日曜1日で10800円かかります。\",' ' s3: \"来館数: 先月、土曜は310人、平日は120人が来館しました。\",' '};' '// 依頼には、ワーカーが使えるものがすべて入っています。質問と、文です。' 'function brief(id, question, ids) {' ' const material = ids.map(function (i) { return SOURCES[i]; });' ' return { id: id, question: question, sources: ids, material: material, reply: \"answer と sources を持つオブジェクト\" };' '}' > team.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
オーケストレーターを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// 台本どおりのワーカー。各自、自分の依頼だけを見ます。' 'const WORKERS = {' ' b1: function (b) { return { answer: \"400人中212人が希望しています\", sources: [\"s1\"] }; },' ' b2: function (b) { return { answer: \"日曜1日で10800円です\", sources: [\"s2\"] }; },' ' b3: function (b) { return { answer: \"週末は混みます\", sources: [\"s9\"] }; },' '};' 'function problems(b, r) {' ' const out = [];' ' const full = typeof r.answer === \"string\" && r.answer.trim().length > 0;' ' if (full === false) out.push(\"答えがありません\");' ' if (Array.isArray(r.sources) === false || r.sources.length === 0) out.push(\"出典がありません\");' ' else r.sources.forEach(function (s) { if (b.sources.includes(s) === false) out.push(\"知らない出典 \" + s); });' ' return out;' '}' >> team.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
オーケストレーターを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const own = \"週末の1日に、図書館はどれくらい混みますか。\";' ' const q = [{ role: \"user\", content: \"週末の図書館の混み具合について、短い調査の質問を1つ書いてください。\" }];' ' const text = (await ask(q, own, 30)).text.trim();' ' const briefs = [' ' brief(\"b1\", \"日曜開館を希望する来館者は何人ですか。\", [\"s1\"]),' ' brief(\"b2\", \"日曜1日の人件費はいくらですか。\", [\"s2\"]),' ' brief(\"b3\", text.length >= 5 && text.length <= 60 ? text : own, [\"s3\"]),' ' ];' ' for (const b of briefs) {' ' const r = WORKERS[b.id](b);' ' const p = problems(b, r);' ' console.log(b.id + \" [出典\" + b.material.length + \"件] \" + b.question);' ' console.log(\" \" + (p.length ? \"却下: \" + p.join(\"、\") : \"受理: \" + r.answer));' ' }' '}' 'main();' >> team.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
オーケストレーターを実行する
docker exec -w /home/node/project agent node team.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ワーカーは台本どおり
ラボにあるモデルは1つだけです。本物のチームは、1つのモデルが全部の役を演じることになります。ここの3人のワーカーは、記録済みの答えを返す普通の関数です。ライブ呼び出しは、調査の質問文を作らせるだけです。
本物のシステムは、各依頼をモデルに送ります。確かめる部分は同じです。
出力の一例
| 行 | 何を示すか |
|---|---|
| b1 [出典1件] 日曜開館を希望する来館者は何人ですか。 | 材料つきの依頼 |
| 受理: 400人中212人が希望しています | 形も出典も通った |
| b2 [出典1件] 日曜1日の人件費はいくらですか。 | 2つ目の依頼 |
| 受理: 日曜1日で10800円です | 2人目のワーカーも通った |
| b3 [出典1件] 週末の1日に、図書館はどれくらい混みますか。 | 3つ目の質問文はモデルが作るので、実行のたびに変わる |
| 却下: 知らない出典 s9 | 3人目は、渡されていない出典を挙げた |
学生が入力するセルは、このプレビューでは空白です。