クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

ツールを使うアシスタントを作る

このレッスンのあとで: 自分の関数を使う小さなアシスタントを作れる。

75分のレッスンコマンド3件AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

自分の関数を使う小さなアシスタント

ツールを使うアシスタントの部品は、これでそろいました。モデルが名前で呼べるツールと、それを実行するループが必要です。さらに、ステップ上限、結果として返すエラー、危険な呼び出しを承認する人も必要です。

このレッスンでは、それらの部品を1つの小さなプログラムにまとめます。

このレッスンのゴール

メモ

自分の関数を使う小さなアシスタントを作れる。

キーワード

ことばいみ
アシスタントモデルとツールを使って仕事をするプログラム
ループ答えか上限まで繰り返す手順
振り分け役各依頼をその関数へ送るコード
承認書き込みツールの前に出る、人の「はい」
安全装置呼び出しの実行を止める確認

学生が入力するセルは、このプレビューでは空白です。

アシスタントは5つの部品でできている

どの部品も、すでに作ったものです。ツールは、あなたの関数とその説明文です。ループは、聞いて、実行して、また聞きます。振り分け役は、各依頼をその関数へ送ります。

安全装置は、ステップ上限、エラー結果、書き込みの承認です。1つでも外すと、失敗が問題になります。

例で確認:各部品が防ぐもの

部品することないと起きること
ステップ上限3ステップで止める終わらないループ
振り分けの表不明なツールを拒否する依頼が別のものを実行する
エラー結果失敗を返す立て直せずクラッシュする
承認書き込みの前に尋ねるミスでデータが変わる
ログ呼び出しを記録する何が起きたか誰も分からない

学生が入力するセルは、このプレビューでは空白です。

モデルと承認のヘルパーを保存する

コマンド
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)};const tool=(n,d,k)=>({type:'function',function:{name:n,description:d,parameters:{type:'object',properties:{[k]:{type:'string'}}}}});const mk=()=>{const rl=require('readline').createInterface({input:process.stdin});const it=rl[Symbol.asyncIterator]();return{yes:async q=>{process.stdout.write(q+'(y/n) ');const l=await it.next();const ok=l.value?.trim()=='y';console.log(ok?'y':'n');return ok},close:()=>rl.close()}};require('fs').writeFileSync('agent.cjs','exports.ask='+ask+';exports.tool='+tool+';exports.mk='+mk+';');console.log('agent.cjsを保存しました')"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

お店のツールを保存する

コマンド
docker exec -w /home/node/project agent node -e "const make=()=>{const{tool}=require('./agent.cjs');const N=[];const k=(i,n,o)=>({role:'assistant',tool_calls:[{id:i,function:{name:n,arguments:JSON.stringify(o)}}]});return{N,T:[tool('get_price','商品の値段を返す。','item'),tool('add_note','メモを保存する。','text')],F:{get_price:a=>({りんご:120,なし:150})[a.item]||'エラー:見つからない',add_note:a=>N.push(a.text)},FK:[k('c1','get_price',{item:'なし'}),k('c2','add_note',{text:'なし150円'}),{role:'assistant',content:'保存しました。'}]}};require('fs').writeFileSync('tools.cjs','exports.make='+make+';');console.log('tools.cjsを保存しました')"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

呼び出しを決まった順で確かめる

依頼ごとに、同じ質問を同じ順で行います。名前は表にあるか。承認が必要か。人はyと言ったか。実行して、エラーはすべて結果にします。

質問の1つで止まった呼び出しは、次へ進みません。実行されたかどうかにかかわらず、すべての結果が、ツールメッセージとログの1行になります。

アシスタント全体を実行する

コマンド
printf 'y\n' | docker exec -i -w /home/node/project agent node -e "const{ask,mk}=require('./agent.cjs');const{T,F,N,FK}=require('./tools.cjs').make();const{yes,close}=mk();let rec=0;(async()=>{const m=[{role:'user',content:'なしの値段を調べて、メモに保存してください。'}];for(let s=1;s<4;s++){let a;try{if(rec)throw 0;a=await ask(m,T)}catch(e){rec||console.log('モデルなし('+e.message+')。記録済みサンプル:');rec=1;a=FK[s-1]}m.push(a);if(a.tool_calls==null){console.log('答え:'+a.content);break}for(const c of a.tool_calls){const n=c.function.name;let r='エラー:不明なツール';if(Object.hasOwn(F,n))r=n=='add_note'&&(await yes(n+' '+c.function.arguments+'を許可しますか?'))===false?'人が断りました':String(F[n](JSON.parse(c.function.arguments)));console.log('ステップ'+s+':'+n+' -> '+r);m.push({role:'tool',tool_call_id:c.id,content:r})}}console.log('メモ:'+N.join('、'));close()})()"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

よくある間違い

注意

モデルがよければ、アシスタントは安全だと思っている人が多くいます。よいモデルでも間違えます。安全は、あなたのコードにあります。ツールの表、ステップ上限、承認、ログです。先にこれらを作り、モデルを信頼するのはそのあとにしましょう。

さらに:仕事を変えてみる

アシスタントの実行の質問をメモを保存してください:パンを買うに変えます。先に入れる答えを、1回目はy、2回目はnにして実行しましょう(コマンドのprintfの中の文字です)。

2つのメモの行を比べます。プログラムのどの部品が違いを生んだか、書き留めます。

このラボとソースについて: AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。