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

無料レッスン

Text to SQL の答えを説明する

このレッスンのあとで: クエリを見せ、迷いを認め、質問があいまいなら聞き返す返事を書くことができる。

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

Text to SQL の答えが見せるもの

Text to SQL の答えを説明するとは、文だけでなく、クエリとその行を利用者に見せることです。文だけだと、クエリが間違っていたときでも、確かな答えに聞こえます。

モジュール 84 は、人のための AI の機能を設計しました。このレッスンは、Text to SQL の機能が返す返事を書きます。何を見せるか、いつ迷いを認めるか、いつ先に聞くかです。

このレッスンのゴール

メモ

クエリを見せ、迷いを認め、質問があいまいなら聞き返す返事を書くことができる。

キーワード

ことばいみれい
返事質問した後に利用者が見るもの
読み方質問をクエリに変える一つのしかた
あいまいな言葉ここでは意味が二つ以上ある言葉人気
聞き返し推測をなくすために返す質問
空の結果一行も返さないクエリ

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

クエリと行を見せる

文の横に、クエリとその行を置きます。データを知っている利用者なら、違う結合にすぐ気づけます。

行は短くします。最初の数行を見せ、残りが何件あるかを言います。文は行から作り、モデルの記憶からは決して作りません。

例: 一つの質問への二つの返事

返事利用者が確かめられること
貸し出し中の本は 5 冊です。何もない
答え: 5。クエリ: SELECT COUNT(*) FROM loans WHERE returned = 0条件、表、数

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

確かでないときは、そう言う

「一番多く本を借りたのは誰か」には、二つの読み方があります。貸し出しの件数で数えると、名前の順で同点を決めて上田さんです。別々の本の数で数えると、佐藤さんです。

両方の読み方を動かします。一致すれば答えます。食い違えば、確かでないと言い、両方を見せます。空の結果も答えです。一致する行がないと言います。

あいまいな言葉は推測せずに聞く

人気、最近、一番いい、活発のような言葉は、データの中で一つの意味を持ちません。人気は、二回借りられたことかもしれず、今月借りられたことかもしれません。

質問にこうした言葉があれば、先に短く聞き返します。日本語には単語の間の空白がないので、言葉は部分一致で探します。一つ聞き返すほうが、自信のある間違った答えより安くつきます。

よくある間違い

注意

自信のある文が一番役に立つ返事だ、と考える人が多いです。自信のある間違った答えは、聞き返しより悪いものです。読み方が一致し、あいまいな言葉がないときだけ、はっきり答えましょう。

助っ人 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 } = require(\"./model\");' 'const { open } = require(\"./library\");' 'const db = open();' '// 日本語には単語の間の空白がないので、部分一致で探す。' 'const VAGUE = [\"人気\", \"最近\", \"一番いい\", \"活発\"];' 'const M = \"SELECT m.name, COUNT(\";' 'const J = \") AS n FROM loans l JOIN members m ON m.id = l.member_id GROUP BY m.name ORDER BY n DESC, m.name LIMIT 1\";' > explain.js"

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

返事のスクリプトを書く、その 2

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const asks = [' '  { q: \"まだ貸し出し中の本は、何冊ですか。\", a: \"SELECT COUNT(*) FROM loans WHERE returned = 0\" },' '  { q: \"会員 2 が借りた本の題名は、何ですか。\",' '    a: \"SELECT b.title FROM loans l JOIN books b ON l.book_id = b.id WHERE l.member_id = 2\" },' '  { q: \"人気のある本は、どれですか。\", a: \"SELECT book_id FROM loans GROUP BY book_id HAVING COUNT(*) > 1\" },' '  { q: \"一番多く本を借りたのは、誰ですか。\", a: M + \"*\" + J, b: M + \"DISTINCT l.book_id\" + J },' '  { q: \"9 月に借りられた本は、どれですか。\",' '    a: \"SELECT b.title FROM loans l JOIN books b ON l.book_id = b.id WHERE l.month = 9\" },' '];' >> explain.js"

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

返事のスクリプトを書く、その 3

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function rows(sql) {' '  return db.prepare(sql).all().map(function (r) { return Object.values(r).join(\" \"); });' '}' 'function reply(x) {' '  const vague = VAGUE.filter(function (w) { return x.q.indexOf(w) >= 0; });' '  if (vague.length) return \"答える前に: 「\" + vague[0] + \"」とは、どういう意味ですか。\";' '  const a = rows(x.a);' '  const b = x.b ? rows(x.b) : a;' '  if (a.join(\"|\") === b.join(\"|\")) {' '    if (a.length === 0) return \"一致する行はありません。クエリ: \" + x.a;' '    return \"答え: \" + a.join(\"、\") + \"。クエリ: \" + x.a;' '  }' '  return \"確かではありません。二つの読み方が食い違います: \" + a.join(\"、\") + \" または \" + b.join(\"、\");' '}' >> explain.js"

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

返事のスクリプトを書く、その 4

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  asks.forEach(function (x, i) { console.log((i + 1) + \" \" + x.q + \"\n  \" + reply(x)); });' '  const facts = asks[0].q + \" 行: \" + JSON.stringify(rows(asks[0].a));' '  const sample = \"まだ貸し出し中の本は、5 冊です。\";' '  const res = await ask([{ role: \"system\", content: \"答えを短い一文で言ってください。渡された行だけを使ってください。\" },' '    { role: \"user\", content: facts }], sample, 60);' '  console.log(\"モデルの文: \" + res.text);' '  console.log(\"  クエリと行を横に見せるので、利用者が確かめられる\");' '}' 'main();' >> explain.js"

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

五つの質問に返事をする

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

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

Run の出力

質問返事
1 まだ貸し出し中の本は、何冊ですか。答え: 5。クエリ: ...
2 会員 2 が借りた本の題名は、何ですか。答え: 雨の地図、静かなエンジン、七つのやかん。クエリ: ...
3 人気のある本は、どれですか。答える前に: 「人気」とは、どういう意味ですか。
4 一番多く本を借りたのは、誰ですか。確かではありません。二つの読み方が食い違います: 上田 3 または 佐藤 3
5 9 月に借りられた本は、どれですか。一致する行はありません。クエリ: ...

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

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

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