副次的な作業に自分のウィンドウを与える
サブエージェントは、副次的な作業を一つ受け持ち、新しいウィンドウで作業して、短い答えを返す、二回目のモデル呼び出しです。親は作業を見ず、結果だけを見ます。
ここが肝心です。四十個のファイルを読む検索は、親のウィンドウを埋めてしまいます。サブエージェントで行えば、親には数行の費用で済みます。
このレッスンのゴール
メモ
副次的な作業に自分のウィンドウを与え、短い答えだけを受け取れる。
キーワード
| ことば | いみ |
|---|---|
| サブエージェント | 副次的な作業を一人で行うモデル呼び出し |
| 分離 | 副次的な作業が自分のウィンドウを持つこと |
| 指示書 | サブエージェントを始める短いメッセージ |
| 契約 | 結果が持つべきフィールドと大きさ |
| 結果 | サブエージェントが返すもの |
| 親 | 副次的な作業を頼んだエージェント |
学生が入力するセルは、このプレビューでは空白です。
サブエージェントは引き継ぎではない
引き継ぎは、仕事の全体を別のエージェントに渡し、そちらが続けます。サブエージェントは違います。親が責任を持ち続け、質問を一つだけ頼みます。
サブエージェントは、空の状態で始まります。親の履歴は見えません。見えるのは指示書だけです。
まず指示書、次に契約
良い指示書には、目的、大事な事実、見るファイル、答えの大きさが入っています。サブエージェントに必要なものは、すべてそこにあります。ほかに情報源がないからです。
契約は、返るものを決めます。名前の付いたフィールドと、語数の上限です。親が読む前に、ハーネスが結果を契約に合わせて切ります。長い答えが良い答えとは限りません。
指示書と結果の形
| 部分 | 入れるもの | 理由 |
|---|---|---|
| 目的 | 質問を一つ | 終わりが分かる |
| 事実 | すでに分かっていること | 作業を繰り返さない |
| ファイル | 探す場所 | さまよわない |
| 大きさ | 語数の上限 | 結果が短く済む |
| 契約 | フィールドと上限 | 親は決まった形を読む |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
多くの人は、念のため親の履歴をすべてサブエージェントに渡します。すると何も分離されず、費用が倍になります。副次的な作業に必要なものだけで、指示書を書きます。
モデル用の補助を書く(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\");' '// 1 文字を 1 トークンと数える。レッスン 1 と同じ。' 'function words(text) { return Array.from(text).length; }' 'const parent = \"利用者は失敗するテストを直したがっています。app.py を読み、テストを二回実行し、readme を確認し、しばらくスタイルの話をしました。変更は小さくしてほしいとのことです。\";' 'const work = [' ' \"ls: app.py readme tests notes.txt old.py\",' ' \"grep total: app.py 4 行目 def total items return sum items\",' ' \"grep total: tests 9 行目 assert items 3 件の total は 12\",' ' \"tests を読む: かごのテストコード 40 行\",' ' \"app.py を読む: かごの値段を計算して税を足す 22 行\",' '];' > sub4.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
サブエージェントの実行を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function makeBrief(goal, facts, files, max) {' ' const lines = [\"目的: \" + goal];' ' for (const f of facts) lines.push(\"事実: \" + f);' ' for (const f of files) lines.push(\"ファイル: \" + f);' ' lines.push(\"答えは \" + max + \" 文字以内にしてください。\");' ' return lines.join(\" | \");' '}' 'function cut(text, max) { return Array.from(text).slice(0, max).join(\"\"); }' 'function sum(list) { return list.reduce(function (a, b) { return a + b; }, 0); }' >> sub4.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
サブエージェントの実行を書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const brief = makeBrief(\"かごの合計を計算している場所を探す\", [\"3 件のテストが間違っている\"], [\"app.py\", \"tests\"], 60);' ' const sample = \"合計は app.py の 4 行目で計算しています。税を足さずに合計しています。tests の 9 行目も見てください。\";' ' const own = brief + \" | \" + work.join(\" ; \");' ' const reply = await ask([{ role: \"user\", content: own }], sample, 80);' ' const result = cut(reply.text, 60);' ' console.log(\"親のウィンドウ: \" + words(parent) + \" 文字\");' ' console.log(\"副次的な作業のウィンドウ: \" + words(own) + \" 文字、うちツールの結果は \" + sum(work.map(words)) + \" 文字\");' ' console.log(\"残した結果: \" + result);' ' console.log(\"親が払うのは \" + (words(brief) + words(result)) + \" 文字\");' '}' 'main();' >> sub4.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
サブエージェントを実行する
docker exec -w /home/node/project agent node sub4.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ツールの結果は記録したもの
五つのツールの結果は、本物のサブエージェントが読むものの代わりに、作って記録したものです。短い呼び出しには指示書とこの結果を渡し、Run は答えを六十文字に切ります。
モデルがなければ、記録済みサンプルを表示し、記録した答えを使います。
出力の一例
| 行 | 示すこと |
|---|---|
| 親のウィンドウ: 89 文字 | 親がすでに持っているもの |
| 副次的な作業のウィンドウ: 311 文字、うちツールの結果は 203 文字 | 作業が行われた場所 |
| 残した結果: 合計は app.py の ... | 六十文字まで |
| 親が払うのは 153 文字 | 指示書と結果で、203 文字は払わない |
学生が入力するセルは、このプレビューでは空白です。