何が悪かったかを伝える
修復ループとは、悪い答えのあとにもう一度聞き、違反を送り返して、モデルに直してもらうことです。問題を知っている2回目は、何も知らない再試行が失敗するところで、成功することがあるからです。
フォームの入力ツールに、スキーマが許さない言葉の返事が来ます。捨てれば、作業が無駄になります。違反をメッセージに入れてもう一度聞けば、たいてい救えます。
このレッスンのゴール
メモ
悪い答えを、エラーを知らせとして付け、数回だけ再試行できる。
キーワード
| ことば | いみ |
|---|---|
| 修復ループ | 悪い答えのあとに、知らせを付けてもう一度聞くこと |
| 知らせ | 新しいメッセージで送り返す、違反 |
| 再試行の上限 | 許す、追加の回数の最大 |
| 代わりの値 | 上限に達したときに使う、安全な値 |
| メッセージのリスト | 呼び出しのたびに送る、会話 |
| あきらめる | 止めて、代わりの値を使うこと |
学生が入力するセルは、このプレビューでは空白です。
違反を送り返す
答えを直すには、それまでの会話をすべて付けて、もう一度聞きます。悪い答えを、アシスタントのメッセージとして足します。次に、違反を並べ、決まりを繰り返すユーザーのメッセージを足します。JSON だけで答える、という決まりです。
メッセージのリストは、毎回2つずつ増えます。モデルは、自分が何を言い、何が悪かったかを見ます。
例: 3回の挑戦
| 回 | 返事 | 見つけた違反 | 次に送るもの |
|---|---|---|---|
| 1 | たぶん、含まれます。 | JSON のオブジェクトではない | 違反と、JSON だけで答える決まり |
| 2 | {"answer": "どちらとも言えない"} | answer は はい か いいえ | 違反と、決まりをもう一度 |
| 3 | {"answer": "はい"} | なし | 何も送らない。受け入れる |
学生が入力するセルは、このプレビューでは空白です。
上限と、代わりの値
再試行には、そのたびに時間とクレジットがかかります。ですから再試行の上限を決めます。最初の1回と、あと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\");' '' 'const allowed = [\"はい\", \"いいえ\"];' 'const MAX_TRIES = 3; // 最初の1回と、あと2回まで' '' 'function pullJson(text) {' ' const start = text.indexOf(\"{\");' ' const end = text.lastIndexOf(\"}\");' ' if (start === -1 || end < start) return null;' ' try { return JSON.parse(text.slice(start, end + 1)); } catch (error) { return null; }' '}' > repair3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
修復ループを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function problemsOf(text) {' ' const reply = pullJson(text);' ' if (reply === null) return [\"JSON のオブジェクトではない\"];' ' if (Object.prototype.hasOwnProperty.call(reply, \"answer\") === false) return [\"answer がない\"];' ' if (allowed.includes(reply.answer) === false) return [\"answer は はい か いいえ\"];' ' return [];' '}' >> repair3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
修復ループを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function repairLoop(question, samples) {' ' const messages = [{ role: \"user\", content: question }];' ' for (let go = 1; go <= MAX_TRIES; go = go + 1) {' ' const reply = await ask(messages, samples, 60);' ' const found = problemsOf(reply);' ' console.log(go + \" 回目: \" + (found.length === 0 ? \"ok\" : found.join(\"、\")));' ' if (found.length === 0) return { ok: true, reply: pullJson(reply), tries: go };' ' messages.push({ role: \"assistant\", content: reply });' ' messages.push({ role: \"user\", content: \"次の問題を直し、JSON だけで答えてください。\" + found.join(\"、\") });' ' }' ' return { ok: false, reply: null, tries: MAX_TRIES };' '}' >> repair3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
修復ループを書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const question = \"小麦粉にはグルテンが含まれますか。キー answer を持つ JSON オブジェクトだけで答えてください。answer の値は はい か いいえ です。\";' ' // 記録済みサンプル。モデルが答えないときだけ使う。悪い返事が2つ、そのあとに良い返事が1つ。' ' const samples = [\"たぶん、含まれます。\", JSON.stringify({ answer: \"どちらとも言えない\" }), JSON.stringify({ answer: \"はい\" })];' ' const result = await repairLoop(question, samples);' ' const fallback = { answer: \"不明\" };' ' console.log(\"使った回数:\", result.tries);' ' console.log(\"結果:\", JSON.stringify(result.ok ? { answer: result.reply.answer } : fallback));' '}' 'main();' >> repair3.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ループを実行する
docker exec -w /home/node/project agent node repair3.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
出力の一例
| 行 | 表示されるかもしれないもの |
|---|---|
| 1 回目 | ok。モデルが最初から正しい JSON を返したとき |
| 2 回目、3 回目 | 1回目が失敗したときだけ。違反、または ok |
| 記録済みサンプル: ... | モデルが答えなかったときだけ、各回の前に1行ずつ出る |
| 使った回数 | 1から3までの数 |
| 結果 | 返事、または代わりの値 {"answer":"不明"} |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
「うまくいくまで再試行すれば安全」と考える人が多くいます。モデルがいつまでも正しい形を出さなければ、上限のないループは終わらず、毎回、時間とクレジットがかかります。必ず上限を決め、代わりの値を先に決めてください。