気持ちが変わるお客様
模擬ユーザーとは、アシスタントとの会話でお客様を演じるスクリプトです。必要な理由は、対話には唯一の正解がなく、試験用のチャットを手で打つ方法は、数が増えると続かないからです。
ミナさんは返金を頼み、注文を1つ挙げ、そのあと気持ちを変えます。アシスタントは付いていけるでしょうか。
このレッスンのゴール
メモ
台本どおりのユーザーで対話を試験し、会話を採点できる。
キーワード
| ことば | いみ |
|---|---|
| 模擬ユーザー | お客様を演じるスクリプト |
| 人物像 | ユーザーが誰で、どう書くか |
| 目的 | ユーザーが最後に望むこと |
| 終了条件 | 対話を止めるルール |
| 記録 | 会話全体を、1行ずつ並べたもの |
学生が入力するセルは、このプレビューでは空白です。
ユーザーをスクリプトとして書く
ユーザーに人物像、目的、発言の一覧を与えます。さらに終了条件を加えます。さようならの一言や、ターン数の上限などで、対話が必ず止まるようにするためです。
スクリプトなら、実行のたびに同じユーザーになります。モデルがユーザーを演じると、毎回変わり、1行ごとにクレジットを使い、失敗を再現しにくくなります。まずスクリプトから始めます。
例: ミナさんの4行
| 行 | ミナさんの発言 | 試すこと |
|---|---|---|
| 1 | こんにちは、返金をお願いします | どの注文かを尋ねるか |
| 2 | 注文A17です | 返金を開始するか |
| 3 | やっぱり注文B02にしてください | 気持ちの変化に対応するか |
| 4 | ありがとう、さようなら | 対話を終えるか |
学生が入力するセルは、このプレビューでは空白です。
返事ごとではなく、記録を採点する
記録全体で、3つを確かめます。目的は達成されたか。ターン数は上限以内か。動作は正しい回数だったか。
最後の確認が、目立たない欠点を見つけます。A17 の返金を開始し、さらに B02 の返金も開始するアシスタントは、目的を達成しながら、2回返金したことになります。
モデル用の補助を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '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 || 200 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"status \" + res.status);' ' return (await res.json()).choices[0].message.content;' > model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
モデル用の補助を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' } catch (error) {' ' console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' ' const list = [].concat(sample);' ' const turn = messages.filter(function (m) { return m.role === \"assistant\"; }).length;' ' return list[Math.min(turn, list.length - 1)];' ' }' '}' 'module.exports = { ask };' >> model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
対話の進行役を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '// 台本どおりのユーザー: 人物像、目的、発言、そして3行目での気持ちの変化です。' 'const user = {' ' persona: \"ミナ。急いでいて、短いメッセージを書く\",' ' goal: \"注文B02の返金\",' ' lines: [\"こんにちは、返金をお願いします\", \"注文A17です\", \"やっぱり注文B02にしてください\", \"ありがとう、さようなら\"],' '};' '// 規則でできた代役のアシスタント。本物は、試験する対象のエージェントです。' 'function assistant(text) {' ' const order = text.match(/[AB][0-9][0-9]/);' ' if (order) return order[0] + \" の返金を開始しました。\";' ' if (text.includes(\"さようなら\")) return \"さようなら。\";' ' return \"注文番号を教えてください。\";' '}' > sim.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
対話の進行役を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function drive(maxTurns) {' ' const transcript = [];' ' for (let i = 0; i < user.lines.length && i < maxTurns; i = i + 1) {' ' transcript.push(\"ユーザー: \" + user.lines[i]);' ' const reply = assistant(user.lines[i]);' ' transcript.push(\"アシスタント: \" + reply);' ' if (reply.includes(\"さようなら\")) break;' ' }' ' return transcript;' '}' 'const transcript = drive(6);' 'transcript.forEach(function (line) { console.log(line); });' 'const refunds = transcript.filter(function (line) { return line.includes(\"返金を開始しました\"); });' >> sim.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
対話の進行役を書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'console.log(\"目的の達成: \" + transcript.join(\" \").includes(\"B02 の返金を開始しました\"));' 'console.log(\"使ったターン: \" + transcript.filter(function (line) { return line.startsWith(\"ユーザー\"); }).length);' 'console.log(\"開始された返金: \" + refunds.length + \" (ユーザーが望んだのは1件)\");' 'async function main() {' ' const system = \"あなたは店のサポートアシスタントです。返金の前に、必ず注文番号を尋ねてください。短く答えてください。\";' ' const sample = \"かしこまりました。注文番号を教えてください。\";' ' const text = await ask([{ role: \"system\", content: system }, { role: \"user\", content: user.lines[0] }], sample, 60);' ' console.log(\"ライブの返事: \" + text.trim());' ' console.log(\"注文について尋ねている: \" + text.includes(\"注文\"));' '}' 'main();' >> sim.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ミナさんでアシスタントを動かす
docker exec -w /home/node/project agent node sim.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
進行役が表示するもの
| 行 | 示すこと |
|---|---|
| 記録 | ユーザーの4行とアシスタントの4行 |
| 目的の達成 | true |
| 使ったターン | 4 |
| 開始された返金 | 2件。ユーザーが望んだのは1件 |
| ライブの返事 | 出力例: 注文についての質問 |
学生が入力するセルは、このプレビューでは空白です。
試してみましょう
ヒント
sim.js で3行目の「やっぱり注文B02にしてください」を取り除いて、もう一度実行します。開始された返金は1件になりますが、目的は達成されません。その理由を説明します。
よくある間違い
注意
最後の返事がもっともらしければ、対話は合格だと考える人がいます。2回の返金のあとに、丁寧なさようならが来ることもあります。アシスタントが記録全体で何をしたかを採点します。目的、動作、ターン数です。