クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

検索を答えと別に採点する

このレッスンのあとで: 検索の点数を、答えとは別に付けられる。

70分のレッスンコマンド9件AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

検索の指標: 再現率と逆順位

多くのAI機能は、文書の集まりを検索し、見つけたものから答えます。答えが間違っていたとき、失敗したのは検索と答えのどちらでしょうか。

検索だけに点を付けることができます。ここでの検索は、コードで書いたキーワードの数え役で、埋め込みは使いません。

このレッスンのゴール

メモ

検索の点数を、答えとは別に付けられる。

キーワード

ことばいみれい
検索役質問に合う文書を見つける部分
順位つきのリスト文書を、良い順に並べたもの
正しい文書本当に答えを持っている文書
上位 k 件の再現率上位 k 件に入った、正しい文書の割合0.5
逆順位最初の正しい文書の順位の逆数0.5

学生が入力するセルは、このプレビューでは空白です。

キーワード検索は、共通する文字の組を数える

日本語には言葉の間の空白がないので、文字を 2 文字ずつ組にして比べます。質問の組のうち、何個を文書が持っているかが、点数です。点数の高い順に並べます。

単純なので、限界があります。開き と 開く は別の組で、延長 と 更新 が同じ意味だとは知りません。

決めた四つのこと

手順すること例
1 そろえる全角を半角に、大文字を小文字に1ページ から 1ページ
2 区切る助詞と、です、ます、から、まで、ください、ですか、ますか で切る図書館は何時に から 図書館|何時|
3 組にする区切りの中を 2 文字ずつ重ねる。一文字の区切りは捨てる図書館 から 図書、書館
4 数える同じ組は一度だけ数える。語幹は処理しない開き と 開く は別

学生が入力するセルは、このプレビューでは空白です。

例: 一つの質問と三つの文書

質問文書共通する組の数順位
遅れて返すと、いくらかかりますか返すのが遅れた本は、1日20円かかります41
遅れて返すと、いくらかかりますか会員は6冊を3週間まで借りられます0-
遅れて返すと、いくらかかりますかカフェではスープとお茶を売っています0-

学生が入力するセルは、このプレビューでは空白です。

再現率と順位が、リストを採点する

上位 k 件の再現率は、正しい文書のうち、上位 k 件に入ったものの割合です。正しい文書が二つあり、上位 2 件に一つなら、0.5 です。

逆順位は、最初の正しい文書だけを見ます。1 位なら 1、2 位なら 0.5、4 位なら 0.25 です。多くの質問での平均が、平均の逆順位です。

例: 五つの質問

質問正しい文書の順位逆順位1 位までの再現率
図書館は何時に開くか111
図書館はいつ閉まるか(正しい文書が二つ)1 と 210.5
借りた本は、いつまでに返せばよいか20.50
貸出の延長見つからない00
印刷はいくらか111

学生が入力するセルは、このプレビューでは空白です。

よくある間違い

注意

答えが間違っていたら、モデルが悪いと考える人が多いです。正しい文書が見つからなかったなら、完璧なモデルにも答えられません。先に検索に点を付け、次に答えに点を付けましょう。

助っ人 model.js を書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答のトークン数。なければ null。' 'function count(data, name) {' '  return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

助っ人 model.js を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' '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 || 100 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"status \" + res.status);' '    const data = await res.json();' '    const text = data.choices[0].message.content;' '    return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"completion_tokens\"), recorded: false };' '  } catch (error) {' '    console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' '    return { text: sample, inTokens: null, outTokens: null, recorded: true };' '  }' '}' 'module.exports = { ask };' >> model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

キーワード検索を書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const docs = {' '  d1: \"図書館は9時に開き、17時に閉まります\",' '  d2: \"会員は6冊を3週間まで借りられます\",' '  d3: \"返すのが遅れた本は、1日20円かかります\",' '  d4: \"カフェではスープとお茶を売っています\",' '  d5: \"パソコンは1時間まで無料で使えます\",' '  d6: \"本の更新は、カウンターかネットでできます\",' '  d7: \"図書館は日曜日は休みです\",' '  d8: \"印刷は1ページ10円です\",' '};' > search.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

キーワード検索を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const stop = [\"ですか\", \"ますか\", \"ください\", \"です\", \"ます\", \"から\", \"まで\", \"は\", \"が\", \"を\", \"に\", \"で\", \"の\", \"と\", \"も\", \"へ\", \"や\"];' 'function toHalf(t) {' '  return t.split(\"\").map(function (c) {' '    const n = c.charCodeAt(0);' '    if (n >= 0xFF01 && n <= 0xFF5E) return String.fromCharCode(n - 0xFEE0);' '    return n === 0x3000 ? \" \" : c;' '  }).join(\"\");' '}' >> search.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

キーワード検索を書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function tokens(text) {' '  let t = toHalf(text).toLowerCase();' '  stop.forEach(function (s) { t = t.split(s).join(\"|\"); });' '  const out = [];' '  t.split(/[|、。 ?,.]+/).forEach(function (seg) {' '    for (let i = 0; i + 1 < seg.length; i++) {' '      const pair = seg.slice(i, i + 2);' '      if (out.indexOf(pair) < 0) out.push(pair);' '    }' '  });' '  return out;' '}' >> search.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

キーワード検索を書く(4)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function search(query) {' '  const q = tokens(query);' '  const scored = Object.keys(docs).map(function (id) {' '    const have = tokens(docs[id]);' '    return { id: id, score: q.filter(function (w) { return have.indexOf(w) >= 0; }).length };' '  }).filter(function (s) { return s.score > 0; });' '  scored.sort(function (a, b) { return b.score - a.score || (a.id < b.id ? -1 : 1); });' '  return scored.map(function (s) { return s.id; });' '}' 'const queries = [' '  [\"図書館は何時に開きますか\", [\"d1\"]],' '  [\"図書館が閉まるのはいつですか\", [\"d1\", \"d7\"]],' '  [\"借りた本を遅れずに返すには、いつまでに返せばよいですか\", [\"d2\"]],' '  [\"貸出の延長はできますか\", [\"d6\"]],' '  [\"印刷は1ページいくらですか\", [\"d8\"]],' '];' >> search.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

キーワード検索を書く(5)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'let sumRr = 0;' 'let sumRecall = 0;' 'queries.forEach(function (item) {' '  const ranked = search(item[0]);' '  const at = ranked.findIndex(function (id) { return item[1].indexOf(id) >= 0; });' '  const rr = at < 0 ? 0 : 1 / (at + 1);' '  const hit = ranked.slice(0, 1).filter(function (id) { return item[1].indexOf(id) >= 0; }).length;' '  sumRr += rr;' '  sumRecall += hit / item[1].length;' '  console.log(item[0] + \" -> \" + (ranked.join(\",\") || \"なし\") + \"  逆順位 \" + rr.toFixed(2));' '});' >> search.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

キーワード検索を書く(6)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  console.log(\"平均の逆順位: \" + (sumRr / queries.length).toFixed(2));' '  console.log(\"1 位までの平均の再現率: \" + (sumRecall / queries.length).toFixed(2));' '  const q = \"次の文だけを使って、一文で答えてください。文: \" + docs.d3 + \"。質問: 借りた本は、いつまでに返せばよいですか。\";' '  const sample = \"この文には書かれていません。遅れた本は1日20円かかる、とだけあります。\";' '  const reply = await ask([{ role: \"user\", content: q }], sample, 80);' '  console.log(\"一番上の文書からの答え: \" + reply.text);' '}' 'main();' >> search.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

検索を動かして採点する

コマンド
docker exec -w /home/node/project agent node search.js

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

Run の出力

行意味
図書館は何時に開きますか -> d1,d7 逆順位 1.00順位つきのリストと、その順位の点
借りた本を遅れずに返すには、... -> d3,d2 逆順位 0.50正しい文書は 2 番目
貸出の延長はできますか -> なし 逆順位 0.00数え役には見えない言い換え
平均の逆順位: 0.70五つの質問の平均
一番上の文書からの答え: ...ラボからの一文。答えがなければ記録済みサンプル

学生が入力するセルは、このプレビューでは空白です。

このラボとソースについて: AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。