一つのエージェントに、三つの信頼の段階
権限モードは、エージェントが確認なしでしてよいことを決めます。plan モードでは、読むだけです。ask モードでは、何かを変える前に確認します。auto モードでは、限られた範囲で一人で動きます。
同じエージェントでも、あるモードなら放っておけて、別のモードでは危険です。だからモードは、実行ごとに選びます。
このレッスンのゴール
メモ
plan、ask、auto の各モードで、エージェントが確認なしでしてよい操作を言える。
キーワード
| ことば | いみ |
|---|---|
| モード | 何を確認なしで実行するかを決める設定 |
| plan モード | 読むだけのモード |
| リスク | 操作がどれだけ害を与えうるか |
| 許可リスト | 前もって承認したコマンド |
| 保護されたパス | 常に承認が必要なファイル |
| 承認 | 一つの操作に対する、利用者の「はい」か「いいえ」 |
学生が入力するセルは、このプレビューでは空白です。
モードは設定であり、プロンプトではない
プロンプトに「ファイルを変えるな」と書くこともできます。それでもモデルが変えるかもしれません。プロンプトはお願いだからです。
モードは、ハーネスの中のコードです。すべての操作は、実行の前に一つの関数を通ります。plan モードでは、モデルが何を言っても、その関数が書き込みを拒否します。モデルには、迂回する道が見えません。
規則は順番に調べる
各操作にリスクを付けます。read、write、run、destructive のどれかです。そのうえでモードごとに判断し、規則は決まった順番に並べます。plan モードが最初です。ほかのどのモードより多く拒否するからです。
次に、保護されたパスと destructive の操作は、常に確認します。そのあとで初めて、モードが何かを緩めます。許可リストがあると、auto モードは名前のあるコマンドを確認なしで実行できます。
各モードがリスクごとにすること
| リスク | plan | ask | auto |
|---|---|---|---|
| read | 許可 | 許可 | 許可 |
| write | 拒否 | 確認 | 許可 |
| run | 拒否 | 確認 | リストにあれば許可 |
| destructive | 拒否 | 確認 | 確認 |
| 保護されたパス | 拒否 | 確認 | 確認 |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
多くの人は、すべての操作で承認を求めます。すると人は、読まずに「はい」を押します。確認するのは、大事な場面だけにします。保護されたパス、destructive の操作、リストにないコマンドです。
モデル用の補助を書く(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 ALLOWED = [\"pytest\"];' 'const actions = [' ' { tool: \"read_file\", risk: \"read\", target: \"src/app.py\" },' ' { tool: \"write_file\", risk: \"write\", target: \"src/app.py\" },' ' { tool: \"run\", risk: \"run\", target: \"pytest\" },' ' { tool: \"run\", risk: \"run\", target: \"curl site\" },' ' { tool: \"delete_file\", risk: \"destructive\", target: \"src/old.py\" },' ' { tool: \"read_file\", risk: \"read\", target: \".env\" },' '];' > modes3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
モードの判定を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function protectedPath(t) {' ' return t === \".env\" || t.startsWith(\".env.\") || t.startsWith(\".git/\") || t.toLowerCase().includes(\"secret\");' '}' 'function decide(mode, a) {' ' if (mode === \"plan\") return a.risk === \"read\" && protectedPath(a.target) === false ? \"allow\" : \"deny\";' ' if (protectedPath(a.target) || a.risk === \"destructive\") return \"ask\";' ' if (mode === \"ask\") return a.risk === \"read\" ? \"allow\" : \"ask\";' ' if (a.risk === \"run\") return ALLOWED.includes(a.target) ? \"allow\" : \"ask\";' ' return \"allow\";' '}' >> modes3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
モードの判定を書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' for (const a of actions) {' ' const row = [\"ask\", \"plan\", \"auto\"].map(function (m) { return m + \":\" + decide(m, a); });' ' console.log(a.tool + \" \" + a.target + \" -> \" + row.join(\" \"));' ' }' ' const q = \"エージェントが src/old.py を削除しようとしています。利用者に承認を求める短い一文を書いてください。\";' ' const reply = await ask([{ role: \"user\", content: q }], \"エージェントが src/old.py を削除しようとしています。許可しますか。\", 60);' ' console.log(\"表示する質問: \" + reply.text);' '}' 'main();' >> modes3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
三つのモードで操作を実行する
docker exec -w /home/node/project agent node modes3.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
判断にモデルは要らない
六行は判断の関数だけから出るので、だれが実行しても同じです。そのあと Run は短い呼び出しを一回行い、削除の前に利用者が目にする質問を文章にします。
モデルがなければ、その一行だけ記録済みサンプルを表示します。
出力の一例
| 行 | 示すこと |
|---|---|
| read_file src/app.py -> ask:allow plan:allow auto:allow | 単純な読み取りはどこでも通る |
| write_file src/app.py -> ask:ask plan:deny auto:allow | 書き込みを通すのは auto だけ |
| run pytest -> ask:ask plan:deny auto:allow | 許可リストにある |
| run curl site -> ask:ask plan:deny auto:ask | リストにないので、auto も確認する |
| delete_file src/old.py -> ask:ask plan:deny auto:ask | 削除は常に確認する |
| read_file .env -> ask:ask plan:deny auto:ask | 保護されたパスは常に確認する |
学生が入力するセルは、このプレビューでは空白です。