個人情報を取り除くと、何が壊れるか
伏せ字にするとは、メッセージが外へ出る前に、個人情報を取り除くことです。サポートのアプリは、返事をモデルに頼む前に、電話番号を伏せ字にできます。
ただし、返事にその番号が要ることもあります。どう伏せ字にするかで、元に戻せるかどうかが決まります。
このレッスンのゴール
メモ
個人情報を取り除き、それで何が壊れるかを説明できる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 伏せ字 | 値を、種類だけを示す言葉に置き換えること | [EMAIL] |
| トークン | 元に戻せる、番号付きの代わりの言葉 | [EMAIL_1] |
| 対応表 | どのトークンがどの値かを書いた、自分だけの一覧 | |
| 元に戻す | 本当の値を、文章に戻すこと | |
| 壊す | 値を、戻せない形で消してしまうこと |
学生が入力するセルは、このプレビューでは空白です。
伏せ字には三つの方法がある
伏せ字(マスク)は、値を [EMAIL] のように種類だけにします。違う二つのメールも、どちらも [EMAIL] になるので、見分けも元に戻すこともできません。
トークンは、[EMAIL_1] のように番号が付きます。トークンと値の対応表を自分で持ち、送りません。壊すと、値は二度と戻らず、対応表も残しません。
日本語には空白がないので、置き換えは「位置」で行います。見つけた部分が何文字目から何文字目までかを調べ、その範囲を置き換えます。
例: 一つのメールを三つの方法で
| 方法 | モデルに見えるもの | 返事に書ける? | 元に戻せる? |
|---|---|---|---|
| 伏せ字 | [EMAIL] | 書けない | 戻せない |
| トークン | [EMAIL_1] | トークンとして書ける | 対応表で戻せる |
| 壊す | 何もない | 書けない | 戻せない(わざと) |
学生が入力するセルは、このプレビューでは空白です。
伏せ字で、返事が壊れることがある
モデルがメールを見ていなければ、メールを書くこともできません。「ご登録の宛先へ送ります」という返事なら大丈夫です。宛先をそのまま書き直す返事には、トークンが要ります。
トークンには新しい危険があります。モデルが角かっこを落としたり、変えたりするのです。日本語は「[EMAIL_1]か」のようにつながるので、返ってきたものだけを戻し、残りがないかを確かめます。
よくある間違い
注意
トークンにすれば匿名になる、と考える人が多いです。そうではありません。対応表を持っているので、本人に戻せます。戻せるデータは、まだ個人情報として扱うのが安全です。これは平易なまとめで、法律の助言ではありません。決まりは、国や使い方で違います。
助っ人 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 text = \"連絡は[email protected]か、電話は090-0000-1111までお願いします。見本花子より。\";' 'const counts = {};' 'const map = {};' 'function isPart(c) { return /[A-Za-z0-9@._+-]/.test(c); }' 'function kindOf(w) {' ' const at = w.indexOf(\"@\");' ' if (at > 0 && w.indexOf(\".\", at) > at + 1) return \"EMAIL\";' ' const tel = w.split(\"-\").join(\"\");' ' return tel.length === 11 && /^0[789]0[0-9]+/.test(tel) ? \"PHONE\" : \"\";' '}' > redact.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
伏せ字にする道具を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function spans(t) {' ' const out = [];' ' let start = -1;' ' for (let i = 0; i <= t.length; i++) {' ' if (i < t.length && isPart(t[i])) { if (start < 0) start = i; continue; }' ' if (start >= 0) {' ' let s = start, e = i;' ' while (e > s && \".-\".includes(t[e - 1])) e--;' ' while (s < e && \".-\".includes(t[s])) s++;' ' const k = kindOf(t.slice(s, e));' ' if (k.length > 0) out.push({ s: s, e: e, kind: k });' ' }' ' start = -1;' ' }' ' return out;' '}' >> redact.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
伏せ字にする道具を書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function mask(t) {' ' let out = \"\";' ' let last = 0;' ' spans(t).forEach(function (p) {' ' out = out + t.slice(last, p.s) + \"[\" + p.kind + \"]\";' ' last = p.e;' ' });' ' return out + t.slice(last);' '}' 'function restore(t) {' ' let out = t;' ' Object.keys(map).forEach(function (k) { out = out.split(k).join(map[k]); });' ' return out;' '}' >> redact.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
伏せ字にする道具を書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function tokenise(t) {' ' const known = {};' ' let out = \"\";' ' let last = 0;' ' spans(t).forEach(function (p) {' ' const v = t.slice(p.s, p.e);' ' if (known[v] === undefined) {' ' counts[p.kind] = (counts[p.kind] || 0) + 1;' ' known[v] = \"[\" + p.kind + \"_\" + counts[p.kind] + \"]\";' ' map[known[v]] = v;' ' }' ' out = out + t.slice(last, p.s) + known[v];' ' last = p.e;' ' });' ' return out + t.slice(last);' '}' >> redact.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
伏せ字にする道具を書く(5)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const safe = tokenise(text);' ' console.log(\"伏せ字: \" + mask(text));' ' console.log(\"トークン: \" + safe);' ' console.log(\"対応表(ここだけに置く): \" + JSON.stringify(map));' ' const q = \"次の問い合わせに、短い返事を一つ書いてください。角かっこの中の言葉は、そのまま写してください。\" + safe;' ' const sample = \"ご連絡ありがとうございます。[EMAIL_1] へお返事し、[PHONE_1] へお電話します。\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 100);' ' console.log(\"返事: \" + reply.text);' ' console.log(\"戻した後: \" + restore(reply.text));' '}' 'main();' >> redact.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
伏せ字にする道具を動かす
docker exec -w /home/node/project agent node redact.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
Run の出力
| 行 | 意味 |
|---|---|
| 伏せ字: ... [EMAIL] ... [PHONE] ... | 種類だけ。何も戻せない |
| トークン: ... [EMAIL_1] ... [PHONE_1] ... | 番号付きの代わりの言葉 |
| 対応表(ここだけに置く): {...} | 自分だけの一覧。送らない |
| 返事: ... | トークン入りの返事。モデルがなければ記録済み |
| 戻した後: ... | トークンを作り話の値に戻した |
学生が入力するセルは、このプレビューでは空白です。