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

無料レッスン

プロンプトの中の個人情報を見つける

このレッスンのあとで: ルールで個人情報を見つけ、ルールが見逃すものを説明できる。

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

送る前に個人情報を見つける

個人情報とは、生きている一人の人を特定できる情報のことです。名前、メールアドレス、電話番号が当てはまります。

プロンプトは自分のパソコンの外へ出ると、取り戻せません。モデルにメッセージを送るアプリは、気づかないうちにこれらを送ってしまうことがあります。見つけられなければ守れないので、まず見つけることから始めます。ここでは日本語の文章で、ルールで見つける方法を学びます。

このレッスンのゴール

メモ

ルールで個人情報を見つけ、ルールが見逃すものを説明できる。

キーワード

ことばいみれい
個人情報生きている一人の人を指す情報
ルール文字の並びが合うか合わないかを調べるテスト@ がある
形ある種類の値の書き方090-0000-1111
全角と半角同じ数字でも幅が違う、二通りの書き方090 と 090
誤検知個人情報ではないのに、ルールが拾ってしまうこと
見逃し個人情報なのに、ルールが拾えなかったこと

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

形のある情報は、ルールで探せる

個人情報の中には、いつも同じ書き方をするものがあります。メールアドレスには @ とドットがあります。携帯電話の番号は、090、080、070 のどれかで始まる 11 桁です。

一つの形を調べるのが、一つのルールです。探す道具は、文章を小さく区切り、区切りごとにすべてのルールで調べます。見つけたものは、ルールの順ではなく、言葉が文章に出てくる順に並べます。例の番号はすべて作り話で、本物にはなりません。

例: 四つのルール

種類ルール作り話の例
email@ があり、その後ろにドットがある[email protected]
phone070、080、090 で始まる 11 桁。ハイフンは無視090-0000-1111
mynumber12 桁の数字123456789012
card16 桁の数字4242424242424242

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

日本語には空白がない

日本語の文章は、言葉の間に空白を入れません。「連絡は[email protected]まで」のように、メールアドレスが文の中に埋まります。空白で区切る方法は使えません。

そこで、半角の英数字と @ . _ + - が続く部分を取り出します。ひらがなや漢字は区切りとして扱います。名前は漢字なので、この方法では見つかりません。

全角と半角をそろえる

数字には全角と半角があります。090は全角、090は半角で、コンピューターには別の文字です。全角のままだと、電話番号の形に合いません。

そこで、調べる前に全角を半角にそろえます。文字の番号を決まった数だけ引くと、そろいます。そろえても文字の数は変わらないので、位置は元の文章にそのまま使えます。

例: そろえる前と後

書き方文章調べる文字結果
半角090-0001-2222090-0001-2222見つかる
全角090-0001-2222090-0001-2222そろえれば見つかる
全角、そろえない090-0001-2222変えない見つからない
長音の「ー」090ー0001ー2222090ー0001ー2222見つからない(扱わない)

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

ルールは見逃し、誤検知もする

ルールが知っているのは形だけです。名前には決まった形がないので、どのルールも見つけられません。空白を入れて書いた電話番号は三つの数字に分かれ、どれも合いません。

誤検知は、その反対の失敗です。16 桁の注文番号はカードと同じ形なので、ルールが拾ってしまいます。どちらもよくあることです。数えて、ルールをどこまで信じてよいかを知りましょう。

例: 見逃しと誤検知

文章の一部人の判断ルールの答え結果
見本花子個人情報見つからない見逃し
090 0000 3333個人情報見つからない見逃し
1234567890123456(注文番号)個人情報ではないcard誤検知
[email protected]個人情報email正しい

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

よくある間違い

注意

何も見つからなければ、そのメッセージは安全だ、と考える人が多いです。そうではありません。どのルールにも合わなかった、というだけです。名前や、言葉で書いた住所には形がありません。「個人情報なし」ではなく「知っている形は見つからず」と言いましょう。

助っ人 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 messages = [' '  \"連絡は[email protected]までお願いします。\",' '  \"電話は090-0001-2222です。\",' '  \"090 0000 3333 に電話してください。\",' '  \"番号は123456789012と書かれています。\",' '  \"注文1234567890123456は見本花子さんに発送しました。\",' '];' 'const marked = [[\"[email protected]\"], [\"090-0001-2222\"], [\"090 0000 3333\"], [\"123456789012\"], [\"見本花子\"]];' > finder.js"

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

探す道具を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' '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(\"\");' '}' 'function pieces(t) {' '  const out = [];' '  let cur = \"\";' '  (t + \" \").split(\"\").forEach(function (c) {' '    if (/[A-Za-z0-9@._+-]/.test(c)) { cur = cur + c; return; }' '    if (cur.length > 0) out.push(cur);' '    cur = \"\";' '  });' '  return out;' '}' >> finder.js"

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

探す道具を書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function clean(w) {' '  let s = w;' '  while (s.length > 0 && \".-\".includes(s[s.length - 1])) s = s.slice(0, -1);' '  while (s.length > 0 && \".-\".includes(s[0])) s = s.slice(1);' '  return s;' '}' 'function digits(w, n) {' '  return w.length === n && w.split(\"\").every(function (c) { return c >= \"0\" && c <= \"9\"; });' '}' 'function kindOf(w) {' '  const at = w.indexOf(\"@\");' '  if (at > 0 && w.indexOf(\".\", at) > at + 1) return \"email\";' '  const tel = w.split(\"-\").join(\"\");' '  if (digits(tel, 11) && [\"090\", \"080\", \"070\"].indexOf(tel.slice(0, 3)) >= 0) return \"phone\";' '  if (digits(w, 12)) return \"mynumber\";' '  return digits(w, 16) ? \"card\" : \"\";' '}' >> finder.js"

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

探す道具を書く(4)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function find(text, fix) {' '  const out = [];' '  pieces(fix === false ? text : toHalf(text)).forEach(function (p) {' '    const w = clean(p);' '    if (kindOf(w).length > 0) out.push({ kind: kindOf(w), value: w });' '  });' '  return out;' '}' >> finder.js"

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

探す道具を書く(5)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  messages.forEach(function (text, i) {' '    const values = find(text).map(function (f) { return f.value; });' '    const missed = marked[i].filter(function (v) { return values.indexOf(v) < 0; });' '    const extra = values.filter(function (v) { return marked[i].indexOf(v) < 0; });' '    console.log((i + 1) + \" 見つけた \" + JSON.stringify(find(text)) + \" 見逃し \" + JSON.stringify(missed) + \" 誤検知 \" + JSON.stringify(extra));' '  });' '  console.log(\"半角にそろえない場合: \" + find(messages[1], false).length + \" 件、そろえた場合: \" + find(messages[1]).length + \" 件\");' '  const q = \"短く一文で答えてください。ルールは、なぜ名前を見つけられないのですか。\";' '  const sample = \"名前には決まった形がないので、ルールが照らし合わせるものがないからです。\";' '  const reply = await ask([{ role: \"user\", content: q }], sample, 80);' '  console.log(\"モデル: \" + reply.text);' '}' 'main();' >> finder.js"

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

探す道具を動かす

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

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

Run の出力

行意味
1 見つけた [email ...] 見逃し [] 誤検知 []正しく見つかった
2 見つけた [phone ...]全角の番号も、そろえれば見つかる
3 見つけた [] 見逃し ["090 0000 3333"]空白で分けた番号は見逃す
4 見つけた [mynumber ...]12 桁の数字も見つかる
5 ... 見逃し ["見本花子"] 誤検知 ["1234567890123456"]名前は見逃し、注文番号は誤検知
半角にそろえない場合: 0 件、そろえた場合: 1 件そろえた効果
モデル: ...一文。言葉は毎回変わる

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

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

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