出典付きの答え
検索拡張生成 (RAG) は、まず文章を見つけ、次に、言語モデルに、その文章だけを使って答えるよう頼みます。記憶で答えるモデルは、自信ありげに間違えることがありますが、出典を示すモデルは確かめられるので、これが大切です。
利用者が、土曜日は何時に開くかを尋ねます。サイトは2つの文章を見つけ、モデルに尋ね、答えを、もとの文章と一緒に表示します。
このレッスンのゴール
メモ
見つけた文章からプロンプトを作り、その文章だけで答えさせられる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| コンテキスト | プロンプトに入れる文章 | |
| プロンプト | モデルに送る文章 | |
| 引用 | 文章を指す印 | [1] |
| 根拠がある | 与えた文章だけに基づいていること | |
| 断り | 分からない、と伝える答え |
学生が入力するセルは、このプレビューでは空白です。
文章をプロンプトに入れる
モデルは、あなたの文書を読んでいません。ですから、プロンプトが文書を運びます。モデルが1つを指せるように、各文章に番号を付けます。そのあと、質問と決まりを足します。
文章は、少なく、短くします。言葉が1つ増えるごとに費用がかかり、長いプロンプトは、答えを確かめにくくします。
例: プロンプトの部分
| 部分 | 例 | 役目 |
|---|---|---|
| 決まり | 次の文章だけを使って答える | 答えに根拠を持たせる |
| 引用の決まり | 使った文章を [1] のように示す | 答えを確かめられるようにする |
| 逃げ道 | なければ「わかりません」と返す | 作り話の答えを防ぐ |
| 文章 | [1] 土曜日は午前10時に開きます | 使ってよい事実だけ |
| 質問 | 土曜日は何時に開きますか | 答える内容 |
学生が入力するセルは、このプレビューでは空白です。
返ってきたものを確かめる
プロンプトは頼みます。強制はしません。モデルは、存在しない文章を引用したり、与えていない事実を足したり、引用を忘れたりします。
ですから、プログラムが確かめます。引用の印を見つけ、1つずつ照らし合わせます。引用のない答えは、出典付きとして示しません。
例: 答えごとの扱い
| 答え | プログラムのすること |
|---|---|
| [1] を引用し、文章1がある | 文章1の出典と一緒に表示する |
| [7] を引用し、文章は2つだけ | 信用しない |
| 何も引用しない | 出典付きとして示さない |
| わかりません。 | そのまま表示し、出典は付けない |
学生が入力するセルは、このプレビューでは空白です。
1回だけ尋ねて、答えを確かめる
次のスクリプトは answer.js です。先に、補助の model.js を保存します。プロンプトをラボのモデルへ送る補助で、モデルが答えないときは「記録済みサンプル」と表示し、スクリプトに書いた答えを使います。モデルを呼ぶのは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\");' '// 検索が見つけた文章。Run の呼び出しを短い1回にするため、ここに書いてあります。' 'const passages = [' ' { source: \"hours-weekend\", text: \"土曜日は午前10時に開き、午後2時に閉まります。日曜日は休館です。\" },' ' { source: \"hours-weekday\", text: \"つばめ図書館は平日、午前9時に開き、午後6時に閉まります。\" },' '];' > answer.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
答えのスクリプトを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const question = \"土曜日は何時に開きますか。\";' 'function buildPrompt(question, passages) {' ' const lines = [\"次の文章だけを使って答えてください。使った文章は [1] のように番号で示してください。文章に答えがないときは、次の一文だけを返してください: わかりません。\", \"\"];' ' passages.forEach(function (one, i) { lines.push(\"[\" + (i + 1) + \"] \" + one.text); });' ' lines.push(\"\", \"質問: \" + question);' ' return lines.join(\"\n\");' '}' >> answer.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
答えのスクリプトを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// 記録済みサンプル。モデルが答えないときだけ使います。' 'const sample = \"土曜日は午前10時に開きます [1]。\";' 'function citedSources(reply) {' ' const found = [];' ' (reply.match(/\[(\d+)\]/g) || []).forEach(function (mark) {' ' const one = passages[Number(mark.slice(1, -1)) - 1];' ' if (one && found.indexOf(one.source) < 0) found.push(one.source);' ' });' ' return found;' '}' >> answer.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
答えのスクリプトを書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const prompt = buildPrompt(question, passages);' ' console.log(prompt);' ' const reply = await ask([{ role: \"user\", content: prompt }], sample, 120);' ' console.log(\"答え:\", reply);' ' const cited = citedSources(reply);' ' console.log(\"出典:\", cited.length > 0 ? cited.join(\", \") : \"示されていません\");' ' console.log(\"わかりませんと言ったか:\", /わかりません/.test(reply) ? \"はい\" : \"いいえ\");' '}' 'main();' >> answer.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
実行して答えを読む
docker exec -w /home/node/project agent node answer.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
出力の一例
| 行 | 表示されるかもしれない内容 |
|---|---|
| プロンプト | 決まり、番号付きの2つの文章、質問 |
| 答え | 土曜日は午前10時に開きます [1]。 |
| 出典 | hours-weekend |
| わかりませんと言ったか | いいえ |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
文章だけを使うよう頼めば、モデルは必ずそうする、と考える人が多くいます。決まりは、作り話の可能性を下げますが、なくしません。確認を残し、質問を、文章にない内容に変えて、逃げ道が働くのを見ます。