正しい答えでも、道のりが悪い
軌跡(トラジェクトリ)とは、エージェントが答えにたどり着くまでに踏んだステップの一覧です。これを採点する理由は、エージェントが無駄な呼び出しのあとや偶然で正解することがあり、ステップごとに費用がかかるからです。
ある店には、注文を返金するサポートエージェントがいます。答えは良さそうですが、道のりはどうでしょうか。
このレッスンのゴール
メモ
エージェントが踏んだステップを、参照パスと比べて採点できる。
キーワード
| ことば | いみ |
|---|---|
| 軌跡 | エージェントが踏んだステップを順に並べたもの |
| 参照パス | 良い実行が踏むと期待されるステップ |
| 無駄な呼び出し | 前のステップを繰り返すステップ |
| ステップ予算 | 1回の実行で許すステップ数の上限 |
| パス得点 | 答えにたどり着いた道のりへの点数 |
学生が入力するセルは、このプレビューでは空白です。
答えだけでなく、道のりを採点する
最終回答のチェックは、「答えは正しいか」と1つだけ尋ねます。パスのチェックは、どうやってそこに着いたかを尋ねます。軌跡を参照パスと比べます。
パスには4つの欠点が現れることがあります。ステップが足りない。無駄な呼び出しが繰り返す。順序が違う。ステップ予算を超える。
例: 記録済みの4回の実行
| 実行 | 踏んだステップ | 答え | パスの欠点 |
|---|---|---|---|
| A | 検索、返金、送信 | 正しい | なし |
| B | 検索、検索、ヘルプ、返金、送信 | 正しい | 繰り返しと予算超過 |
| C | 返金、検索、送信 | 正しい | 検索の前に返金 |
| D | 検索、送信 | 誤り | 返金が不足で、順序も違う |
学生が入力するセルは、このプレビューでは空白です。
欠点を1つの得点にする
欠点ごとに決まった点数を決め、100から引きます。ここでは、ステップ不足が25、無駄な呼び出しが10、予算超過が20、順序違いが15です。
点数は、実行を見る前に決めます。参照は唯一の良い道ではないので、順序を採点するのは、順序が大切なところだけにします。
モデル用の補助を書く(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"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
記録済みの実行を書く
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const reference = [\"lookup_order:A17\", \"refund:A17\", \"send_message:A17\"];' '// 記録済みのサポートエージェントの実行。各ステップは、ツールとその引数です。' 'const runs = [' ' { name: \"実行A\", answer: \"ok\", steps: [\"lookup_order:A17\", \"refund:A17\", \"send_message:A17\"] },' ' { name: \"実行B\", answer: \"ok\", steps: [\"lookup_order:A17\", \"lookup_order:A17\", \"search_help:refund\", \"refund:A17\", \"send_message:A17\"] },' ' { name: \"実行C\", answer: \"ok\", steps: [\"refund:A17\", \"lookup_order:A17\", \"send_message:A17\"] },' ' { name: \"実行D\", answer: \"誤り\", steps: [\"lookup_order:A17\", \"send_message:A17\"] },' '];' 'module.exports = { reference, runs };' > paths.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
パスの採点を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { reference, runs } = require(\"./paths\");' 'const { ask } = require(\"./model\");' 'const budget = 4;' 'function wasted(steps) {' ' const seen = [];' ' let count = 0;' ' steps.forEach(function (step) {' ' if (seen.includes(step)) count = count + 1;' ' else seen.push(step);' ' });' ' return count;' '}' 'function missing(steps) {' ' return reference.filter(function (step) { return steps.includes(step) === false; });' '}' > score.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
パスの採点を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function inOrder(steps) {' ' let at = 0;' ' steps.forEach(function (step) {' ' if (at < reference.length && step === reference[at]) at = at + 1;' ' });' ' return at === reference.length;' '}' 'function score(steps) {' ' let points = 100 - 25 * missing(steps).length - 10 * wasted(steps);' ' if (steps.length > budget) points = points - 20;' ' if (inOrder(steps) === false) points = points - 15;' ' return Math.max(points, 0);' '}' >> score.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
パスの採点を書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const weak = [];' 'runs.forEach(function (run) {' ' const points = score(run.steps);' ' console.log(run.name + \": \" + points + \"点、ステップ \" + run.steps.length + \" / \" + budget + \"、不足 \" + missing(run.steps).length + \"、無駄 \" + wasted(run.steps) + \"、順序どおり \" + inOrder(run.steps) + \"、答え \" + run.answer);' ' if (run.answer === \"ok\" && points < 100) weak.push(run.name);' '});' 'console.log(\"答えは正しいがパスが弱い実行: \" + weak.join(\"、\"));' >> score.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
パスの採点を書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const prompt = \"お客様が注文A17の返金を求めています。ツールは lookup_order、refund、send_message、search_help です。最初に呼ぶツールの名前だけを答えてください。\";' ' const text = await ask([{ role: \"user\", content: prompt }], \"lookup_order\", 20);' ' console.log(\"ライブの最初のステップ: \" + text.trim());' ' console.log(\"参照と一致: \" + text.includes(\"lookup_order\"));' '}' 'main();' >> score.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
4回の実行を採点する
docker exec -w /home/node/project agent node score.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
採点が表示するもの
| 行 | 示すこと |
|---|---|
| 実行A | 100点、欠点なし |
| 実行B | 70点: 無駄な呼び出し1つと、ステップ5 / 4 |
| 実行C | 85点: 順序どおりでない |
| 実行D | 60点: 返金が不足 (25)、順序も違う (15)、答えは誤り |
| パスが弱い実行 | 実行Bと実行C |
| ライブの最初のステップ | 出力例: lookup_order |
学生が入力するセルは、このプレビューでは空白です。
試してみましょう
ヒント
score.js で予算を 4 から 5 に変えて、もう一度採点します。実行Bは20点増えるはずです。それで良いパスになるかを考えます。
よくある間違い
注意
参照パスだけが正しい道だと考え、ほかの順序をすべて欠点にする人がいます。エージェントには、良い道が2つあることも多いです。順序を採点するのは、あるステップが別のステップの結果を必要とするときだけにし、2通りとも使えるなら2つ目の参照を認めます。