はいを待つべき呼び出しがある
値段を調べても、何も変わりません。保存、送信、削除をするツールは、何かを変えてしまいます。モデルが間違ってそれを頼むと、被害は本物です。
だから、危険な呼び出しを実行するかどうか決めるのは、モデルではなくプログラムです。危険な呼び出しでは、まず人に尋ねます。
このレッスンのゴール
メモ
危険なツールを、人の承認が出るまで待たせることができる。
キーワード
| ことば | いみ |
|---|---|
| 読み取りツール | データを見るだけのツール |
| 書き込みツール | 何かを変えるツール |
| 承認 | 呼び出しの実行前に出る、人の「はい」 |
| 原則拒否 | はっきりした「はい」がなければ断ること |
| ログ | 何を尋ね、何を実行したかの記録 |
学生が入力するセルは、このプレビューでは空白です。
変えられるものでツールを分ける
ツールごとにリスクを決めます。読み取りツールは見るだけなので、すぐ実行できます。書き込みツールは何かを変えるので、「はい」が必要です。
リスクは、自分のテーブルに持ちます。呼び出しの危険度をモデルが決めることはなく、間違えたモデルがテーブルをすり抜けることもできません。
例で確認:3つのツールとリスク
| ツール | すること | リスク | 実行 |
|---|---|---|---|
| get_price | 値段を調べる | read | すぐ |
| add_note | メモのリストに足す | write | 「はい」のあと |
| delete_note | メモを消す | write | 「はい」のあと |
| それ以外の名前 | 表にない | 不明 | 実行しない |
学生が入力するセルは、このプレビューでは空白です。
モデルを呼ぶヘルパーを保存する
docker exec -w /home/node/project agent node -e "const ask=async(m,t,c)=>{const r=await fetch((process.env.AI_URL||'http://ai:4000')+'/v1/chat/completions',{method:'POST',headers:{'content-type':'application/json',authorization:'Bearer lab'},body:JSON.stringify({model:'lab',messages:m,tools:t,tool_choice:c,max_tokens:120})});const j=await r.json();if(r.ok)return j.choices[0].message;throw Error(r.status+' '+j.error.message)};require('fs').writeFileSync('ai.cjs','exports.ask='+ask+';');console.log('ai.cjsを保存しました:ask(messages, tools)はモデルのメッセージを返します。')"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
人に尋ね、原則は断る
ツールが何をするかを表示して、答えを1つ読みます。「はい」はyだけです。空行や、答えがないことも含め、それ以外はすべて「いいえ」です。
これが原則拒否です。はっきりした「はい」があるときだけ、呼び出しは実行されます。次の実行では答えを先に入れておくので、全員が同じ行を見ます。手で入力すれば、あなたの答えを待ちます。
3つの呼び出しを決まった答えで通す
printf 'y\nn\n' | docker exec -i -w /home/node/project agent node -e "const rl=require('readline').createInterface({input:process.stdin});const it=rl[Symbol.asyncIterator]();const R={get_price:'read',add_note:'write',delete_note:'write'};const N=['牛乳'];const F={get_price:a=>150,add_note:a=>N.push(a.text),delete_note:a=>N.splice(a.index,1)};(async()=>{const log=[];for(const c of [{n:'get_price',a:{item:'なし'}},{n:'add_note',a:{text:'卵'}},{n:'delete_note',a:{index:0}}]){let ok=R[c.n]=='read';if(ok===false){process.stdout.write(c.n+' '+JSON.stringify(c.a)+'を許可しますか?(y/n) ');const l=await it.next();ok=l.value?.trim()=='y';console.log(ok?'y':'n')}if(ok)F[c.n](c.a);log.push(c.n+':'+(ok?'実行':'拒否'))}console.log(log.join('、'));console.log('メモ:'+N.join('、'));rl.close()})()"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
モデルに頼ませて、断る
printf 'n\n' | docker exec -i -w /home/node/project agent node -e "const{ask}=require('./ai.cjs');const rl=require('readline').createInterface({input:process.stdin});const it=rl[Symbol.asyncIterator]();const T=[{type:'function',function:{name:'add_note',description:'メモを保存する。',parameters:{type:'object',properties:{text:{type:'string'}}}}}];(async()=>{let c;try{c=(await ask([{role:'user',content:'メモを保存してください:牛乳を買う'}],T,'required')).tool_calls[0].function}catch(e){console.log('モデルなし('+e.message+')。記録済みサンプル:');c={name:'add_note',arguments:JSON.stringify({text:'牛乳を買う'})}}process.stdout.write('モデルは'+c.name+' '+c.arguments+'を求めています。許可しますか?(y/n) ');const l=await it.next();const ok=l.value?.trim()=='y';console.log(ok?'y':'n');console.log(ok?'add_noteを実行しました。':'拒否しました。何も実行されていません。');rl.close()})()"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
呼び出しをすべてログに残す
呼び出しごとに、名前、人に尋ねたか、何と答えたか、実行したかを記録します。ログを見れば、アシスタントが何をしたか、何を止められたかが分かります。
断った呼び出しも記録しましょう。誰にも見えない断りは、あとで謎になります。
よくある間違い
注意
危険な呼び出しが安全かどうかは、モデルが決めると思っている人が多くいます。決めません。モデルは間違えることも、読んだ文章にだまされることもあります。決めるのはあなたのプログラムで、書き込みの前には人に尋ねます。
さらに:答えを変えてみる
最初の実行で、先に入れる答えをyとnからnとnに変えます。もう一度実行して、メモの行を読みましょう。
その後、Rのget_priceをwriteに変えます。プログラムが何を尋ねるようになったか書きます。