アプリを止めた返事
モデルに JSON を頼むとは、ほしい答えの形を、プロンプトの中で正確に伝えることです。プログラムは、読める返事しか使えないからです。
大学の学食アプリが、モデルにおにぎりを1つ考えて値段を付けてもらいます。ある日、返事が「はい、こちらです」で始まり、プログラムが止まります。モデルは答えましたが、読める形ではありませんでした。
このレッスンのゴール
メモ
自分のプログラムが読める形で、答えを頼める。
キーワード
| ことば | いみ | れい |
|---|---|---|
| プロンプト | モデルに送る文章 | |
| JSON | キーと値でできた、データ用の文字列の形式 | |
| キー | JSON のオブジェクトにある、項目の名前 | price |
| コードブロック | 返事の中でコードを囲む、バッククォート3つ | |
| 読み取り | 文字列を、プログラムが使えるデータにすること | |
| トークンの上限 | 返事に使える長さの最大 |
学生が入力するセルは、このプレビューでは空白です。
形をプロンプトで伝える
モデルは、言葉から、あなたの望みを推測します。ですから、形を伝えます。キーの名前、各値の型、入れないものを書きます。
最後に、簡単な決まりを1つ付けます。JSON だけで答える、という決まりです。偶然に任せる部分が多いほど、返事はばらばらになります。
例: おにぎり1つを頼む3つの文
| プロンプトの内容 | 返ってくるかもしれないもの |
|---|---|
| おにぎりと値段を教えて | 普通の文章。リストのこともあり、キーは決まらない |
| おにぎりを JSON で教えて | たぶん JSON。ただしキーは自分で選んでいない |
| キー name (文字列) と price (数値) を持つ JSON オブジェクトだけで答えて | 頼んだ形になる可能性が、より高い |
学生が入力するセルは、このプレビューでは空白です。
答えの例を1つ見せる
例は、決まりを並べた段落より役に立ちます。作り物の値で、答えを1つ丸ごとプロンプトに入れ、本当の答えも同じキーを持つように伝えます。
モデルは、例の並びをまねしやすいものです。本物と間違えない値を使ってください。そうしないと、写した例が本物の答えに見えてしまいます。
モデル用の補助を書く(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 messages = [' ' { role: \"system\", content: \"あなたは JSON だけで答えます。\" },' ' { role: \"user\", content: \"大学の学食で売るおにぎりを1つ考えてください。キー name (文字列) と price (円の整数) を持つ JSON オブジェクトだけで答えてください。\" },' '];' 'const fence = \"\x60\x60\x60\";' '// 記録済みサンプル。モデルが答えないときだけ使う。わざと雑にしてある。' 'const sample = \"はい、こちらです。\n\" + fence + \"json\n\" + JSON.stringify({ name: \"鮭おにぎり\", price: 180 }) + \"\n\" + fence + \"\nどうぞ。\";' > ask1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
最初のスクリプトを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function pullJson(text) {' ' try { return JSON.parse(text); } catch (error) { }' ' 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; }' '}' '' 'async function main() {' ' const reply = await ask(messages, sample, 100);' ' console.log(\"返事そのまま:\");' ' console.log(reply);' ' let whole = true;' ' try { JSON.parse(reply); } catch (error) { whole = false; }' ' console.log(\"返事全体が JSON か:\", whole ? \"はい\" : \"いいえ\");' ' console.log(\"取り出した結果:\", JSON.stringify(pullJson(reply)));' '}' 'main();' >> ask1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
実行して返事を読む
docker exec -w /home/node/project agent node ask1.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
出力の一例
| 行 | 表示されるかもしれないもの |
|---|---|
| 記録済みサンプル: ... | モデルが答えなかったときだけ、1行目に出る |
| 返事そのまま | 1文、そのあとコードブロックの中の JSON、さらに1文 |
| 返事全体が JSON か | いいえ |
| 取り出した結果 | {"name":"鮭おにぎり","price":180} |
学生が入力するセルは、このプレビューでは空白です。
良いプロンプトでも失敗する理由
はっきりしたプロンプトでも、失敗することはあります。モデルが挨拶を足す、JSON をコードブロックで囲む、キーの名前を変える、などです。トークンの上限が小さすぎると途中で止まり、JSON が切れたままになります。
だから、いつも読み取って確かめます。上手に頼むと、悪い返事は減ります。ゼロにはなりません。
よくある間違い
注意
「プロンプトが十分はっきりしていれば、確認は要らない」と考える人が多くいます。プロンプトは、悪い返事の確率を下げるだけです。直近100回が良い返事でも、確認は残してください。