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

無料レッスン

Text to SQL の評価用の集まり

このレッスンのあとで: 答えの分かっている質問の集まりで、候補のクエリを採点することができる。

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

Text to SQL の機能を試す方法

Text to SQL の評価用の集まりとは、正しい答えが分かっている質問の一覧で、プロンプトやモデルが変わるたびに動かすものです。変更でよくなったか悪くなったかが分かります。

モジュール 68 と 85 は、言葉の答えを採点しました。このレッスンは、SQL を、それが返す行で採点します。

このレッスンのゴール

メモ

答えの分かっている質問の集まりで、候補のクエリを採点することができる。

キーワード

ことばいみ
評価用の集まり変更のたびに動かす、答えの分かっている質問
正解のクエリ手で確かめた、正しい行を返すクエリ
実行の一致正解のクエリと同じ行
回帰前は合格し、今は通らない質問
関所変更を止める決まり

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

どの事例にも質問と正解のクエリ

事例は、質問、正解のクエリ、順が大事かどうかを持ちます。正解のクエリは自分で書き、その行をフィクスチャで手で確かめます。

正解のクエリを動かして、期待する行を得ます。こうすれば、正解のクエリが正しい限り、データが増えても集まりは使い続けられます。

例: 図書館の五つの事例

事例質問正解の行
13 月の貸し出しの件数4
23 月に借りた会員の人数3
3まだ貸し出し中の本5
4会員 2 が借りた題名雨の地図、静かなエンジン、七つのやかん
5一度も借りられていない本正午の北

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

文字ではなく結果で採点する

ここでは COUNT(id) と COUNT(*) は同じ数を返します。結合は、副問い合わせでも書けます。違う文字の二つが、どちらも正しいことがあります。

だから、候補をそれぞれ動かし、その行を正解の行と比べます。文字の完全一致で採点すると、正しい答えを間違いにしてしまいます。Run でそれが分かります。

例: 文字と結果が食い違う

候補正解と同じ文字正解と同じ行
SELECT COUNT(id) FROM loans WHERE month = 3いいえはい
IN (SELECT book_id ...) で書いた結合いいえはい
SELECT COUNT(*) FROM loansいいえいいえ

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

回帰は同じ点数の中に隠れる

新しいプロンプトが、質問 2 を直し、質問 3 を壊したとします。どちらの版も五つのうち四つで合格するので、点数は動きません。

事例ごとに比べます。前は合格し、今は通らない質問が回帰です。関所は、直るまでその変更を止めます。

よくある間違い

注意

合格率が同じなら何も悪くなっていない、と考える人が多いです。一つの質問が直り、別の質問が壊れることがあります。事例ごとに前の実行と比べ、合格を一つでも失う変更は止めましょう。

助っ人 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;' '}' '// 応答がクエリを囲みで包んでいたら、内側だけを残す。' 'function sqlOf(text) {' '  const parts = text.split(String.fromCharCode(96).repeat(3));' '  const inner = (parts.length > 2 ? parts[1] : text).trim();' '  return inner.toLowerCase().startsWith(\"sql\") ? inner.slice(3).trim() : inner;' '}' > 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, sqlOf };' >> model.js"

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

小さな図書館のデータを書く、その 1

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { DatabaseSync } = require(\"node:sqlite\");' '// 作り話の図書館: 本が六冊、会員が四人、貸し出しが九件。' 'const BOOKS = [[1, \"塩の道\", 2019], [2, \"ガラスの港\", 2021], [3, \"雨の地図\", 2015],' '  [4, \"静かなエンジン\", 2023], [5, \"七つのやかん\", 2018], [6, \"正午の北\", 2022]];' 'const MEMBERS = [[1, \"上田\"], [2, \"佐藤\"], [3, \"高橋\"], [4, \"中村\"]];' '// id, book_id, member_id, month, returned(1 は返却済み、0 は貸し出し中)' 'const LOANS = [[1, 1, 1, 3, 1], [2, 2, 1, 3, 1], [3, 3, 2, 3, 0], [4, 1, 3, 3, 1], [5, 1, 1, 4, 0],' '  [6, 4, 2, 4, 0], [7, 2, 3, 4, 1], [8, 5, 2, 5, 0], [9, 2, 4, 5, 0]];' > library.js"

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

小さな図書館のデータを書く、その 2

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function put(db, table, rows) {' '  const marks = rows[0].map(function () { return \"?\"; }).join(\", \");' '  const insert = db.prepare(\"INSERT INTO \" + table + \" VALUES (\" + marks + \")\");' '  rows.forEach(function (row) { insert.run(...row); });' '}' 'function open(file) {' '  const db = new DatabaseSync(file || \":memory:\");' '  db.exec(\"CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, year INTEGER)\");' '  db.exec(\"CREATE TABLE members (id INTEGER PRIMARY KEY, name TEXT)\");' '  db.exec(\"CREATE TABLE loans (id INTEGER PRIMARY KEY, book_id INTEGER REFERENCES books(id), \"' '    + \"member_id INTEGER REFERENCES members(id), month INTEGER, returned INTEGER)\");' '  put(db, \"books\", BOOKS);' '  put(db, \"members\", MEMBERS);' '  put(db, \"loans\", LOANS);' '  return db;' '}' 'module.exports = { open };' >> library.js"

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

評価のスクリプトを書く、その 1

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask, sqlOf } = require(\"./model\");' 'const { open } = require(\"./library\");' 'const db = open();' 'const cases = [' '  { gold: \"SELECT COUNT(*) FROM loans WHERE month = 3\" },' '  { gold: \"SELECT COUNT(DISTINCT member_id) FROM loans WHERE month = 3\" },' '  { gold: \"SELECT COUNT(*) FROM loans WHERE returned = 0\" },' '  { gold: \"SELECT b.title FROM loans l JOIN books b ON l.book_id = b.id WHERE l.member_id = 2\" },' '  { gold: \"SELECT title FROM books WHERE id NOT IN (SELECT book_id FROM loans)\" },' '];' > eval.js"

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

評価のスクリプトを書く、その 2

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const A = [\"SELECT COUNT(id) FROM loans WHERE month = 3\",' '  \"SELECT COUNT(member_id) FROM loans WHERE month = 3\", cases[2].gold,' '  \"SELECT b.title FROM books b JOIN loans l ON l.book_id = b.id WHERE l.member_id = 2\",' '  \"SELECT b.title FROM books b LEFT JOIN loans l ON l.book_id = b.id WHERE l.id IS NULL\"];' 'const B = [cases[0].gold, cases[1].gold, \"SELECT COUNT(*) FROM loans\",' '  \"SELECT title FROM books WHERE id IN (SELECT book_id FROM loans WHERE member_id = 2)\", cases[4].gold];' 'function rows(sql) {' '  try { return db.prepare(sql).all().map(function (r) { return JSON.stringify(Object.values(r)); }).sort().join(\"|\"); }' '  catch (error) { return null; }' '}' >> eval.js"

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

評価のスクリプトを書く、その 3

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function score(sql, i) {' '  const got = rows(sql);' '  return got === null ? \"エラー\" : got === rows(cases[i].gold) ? \"合格\" : \"不合格\";' '}' 'function rate(list, test) {' '  return Math.floor(100 * list.filter(test).length / list.length) + \" パーセント\";' '}' 'const a = A.map(score);' 'const b = B.map(score);' 'const same = function (sql, i) { return sql === cases[i].gold; };' '// 回帰: A で合格し、B で合格しない質問。' 'const lost = [];' 'a.forEach(function (v, i) { if (v === \"合格\" && (b[i] === \"不合格\" || b[i] === \"エラー\")) lost.push(i + 1); });' >> eval.js"

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

評価のスクリプトを書く、その 4

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  a.forEach(function (v, i) { console.log(\"質問 \" + (i + 1) + \": A \" + v + \"、B \" + b[i]); });' '  const pass = function (v) { return v === \"合格\"; };' '  console.log(\"結果で採点: A \" + rate(a, pass) + \"、B \" + rate(b, pass));' '  console.log(\"文字の一致で採点: A \" + rate(A, same) + \"、B \" + rate(B, same));' '  console.log(\"B の回帰: \" + (lost.length ? lost.join(\", \") : \"なし\"));' '  console.log(\"関所: \" + (lost.length ? \"止める。前に合格した質問が、今は通らない\" : \"出す\"));' '  await live();' '}' >> eval.js"

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

評価のスクリプトを書く、その 5

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function live() {' '  const q = \"loans(id, book_id, member_id, month, returned)\n質問: 3 月に本を借りた会員は、何人ですか(同じ人は一人と数える)。\";' '  const sample = \"SELECT COUNT(DISTINCT member_id) FROM loans WHERE month = 3;\";' '  const reply = await ask([{ role: \"system\", content: \"SQLite の SELECT 文を一つだけ答えてください。ほかには何も書かないでください。\" },' '    { role: \"user\", content: q }], sample, 120);' '  const sql = sqlOf(reply.text);' '  console.log(\"モデルが書いたクエリ: \" + sql);' '  console.log(\"  合計とは別に採点: \" + score(sql, 1));' '}' 'main();' >> eval.js"

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

二つの版を五つの質問で採点

コマンド
docker exec -w /home/node/project agent node --experimental-sqlite --no-warnings eval.js

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

Run の出力

行意味
質問 2: A 不合格、B 合格新しいプロンプトが質問 2 を直した
質問 3: A 合格、B 不合格新しいプロンプトが質問 3 を壊した
結果で採点: A 80 パーセント、B 80 パーセント点数は動かなかった
文字の一致で採点: A 20 パーセント、B 60 パーセント文字の一致は正しい答えを間違いにする
B の回帰: 3失われた質問
関所: 止める。前に合格した質問が、今は通らない決定

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

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

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