送る前に個人情報を見つける
個人情報とは、生きている一人の人を特定できる情報のことです。名前、メールアドレス、電話番号が当てはまります。
プロンプトは自分のパソコンの外へ出ると、取り戻せません。モデルにメッセージを送るアプリは、気づかないうちにこれらを送ってしまうことがあります。見つけられなければ守れないので、まず見つけることから始めます。ここでは日本語の文章で、ルールで見つける方法を学びます。
このレッスンのゴール
メモ
ルールで個人情報を見つけ、ルールが見逃すものを説明できる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 個人情報 | 生きている一人の人を指す情報 | |
| ルール | 文字の並びが合うか合わないかを調べるテスト | @ がある |
| 形 | ある種類の値の書き方 | 090-0000-1111 |
| 全角と半角 | 同じ数字でも幅が違う、二通りの書き方 | 090 と 090 |
| 誤検知 | 個人情報ではないのに、ルールが拾ってしまうこと | |
| 見逃し | 個人情報なのに、ルールが拾えなかったこと |
学生が入力するセルは、このプレビューでは空白です。
形のある情報は、ルールで探せる
個人情報の中には、いつも同じ書き方をするものがあります。メールアドレスには @ とドットがあります。携帯電話の番号は、090、080、070 のどれかで始まる 11 桁です。
一つの形を調べるのが、一つのルールです。探す道具は、文章を小さく区切り、区切りごとにすべてのルールで調べます。見つけたものは、ルールの順ではなく、言葉が文章に出てくる順に並べます。例の番号はすべて作り話で、本物にはなりません。
例: 四つのルール
| 種類 | ルール | 作り話の例 |
|---|---|---|
| @ があり、その後ろにドットがある | [email protected] | |
| phone | 070、080、090 で始まる 11 桁。ハイフンは無視 | 090-0000-1111 |
| mynumber | 12 桁の数字 | 123456789012 |
| card | 16 桁の数字 | 4242424242424242 |
学生が入力するセルは、このプレビューでは空白です。
日本語には空白がない
日本語の文章は、言葉の間に空白を入れません。「連絡は[email protected]まで」のように、メールアドレスが文の中に埋まります。空白で区切る方法は使えません。
そこで、半角の英数字と @ . _ + - が続く部分を取り出します。ひらがなや漢字は区切りとして扱います。名前は漢字なので、この方法では見つかりません。
全角と半角をそろえる
数字には全角と半角があります。090は全角、090は半角で、コンピューターには別の文字です。全角のままだと、電話番号の形に合いません。
そこで、調べる前に全角を半角にそろえます。文字の番号を決まった数だけ引くと、そろいます。そろえても文字の数は変わらないので、位置は元の文章にそのまま使えます。
例: そろえる前と後
| 書き方 | 文章 | 調べる文字 | 結果 |
|---|---|---|---|
| 半角 | 090-0001-2222 | 090-0001-2222 | 見つかる |
| 全角 | 090-0001-2222 | 090-0001-2222 | そろえれば見つかる |
| 全角、そろえない | 090-0001-2222 | 変えない | 見つからない |
| 長音の「ー」 | 090ー0001ー2222 | 090ー0001ー2222 | 見つからない(扱わない) |
学生が入力するセルは、このプレビューでは空白です。
ルールは見逃し、誤検知もする
ルールが知っているのは形だけです。名前には決まった形がないので、どのルールも見つけられません。空白を入れて書いた電話番号は三つの数字に分かれ、どれも合いません。
誤検知は、その反対の失敗です。16 桁の注文番号はカードと同じ形なので、ルールが拾ってしまいます。どちらもよくあることです。数えて、ルールをどこまで信じてよいかを知りましょう。
例: 見逃しと誤検知
| 文章の一部 | 人の判断 | ルールの答え | 結果 |
|---|---|---|---|
| 見本花子 | 個人情報 | 見つからない | 見逃し |
| 090 0000 3333 | 個人情報 | 見つからない | 見逃し |
| 1234567890123456(注文番号) | 個人情報ではない | card | 誤検知 |
| [email protected] | 個人情報 | 正しい |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
何も見つからなければ、そのメッセージは安全だ、と考える人が多いです。そうではありません。どのルールにも合わなかった、というだけです。名前や、言葉で書いた住所には形がありません。「個人情報なし」ではなく「知っている形は見つからず」と言いましょう。
助っ人 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 件 | そろえた効果 |
| モデル: ... | 一文。言葉は毎回変わる |
学生が入力するセルは、このプレビューでは空白です。