忘れる記憶
エージェントの記憶は、あとで引ける小さなメモの置き場です。よい記憶は、わざと忘れます。古いメモは期限が切れ、決して保存しないものもあります。
何でも覚える記憶は、パスワードも覚えますし、もう本当でなくなった事実も覚えます。「エージェントを作る」のモジュールでは、記憶を作りました。このレッスンは、忘れることを足します。
このレッスンのゴール
メモ
エージェントに、わざと忘れる記憶を持たせることができる。
キーワード
| ことば | いみ |
|---|---|
| 記憶の置き場 | エージェントが引けるメモのリスト |
| キーワード | メモを探すのに使う言葉 |
| 保存期限 | メモを何日間残すか |
| 期限切れ | 古すぎて、忘れられること |
| 保存禁止ルール | 入口でメモを断る確認 |
学生が入力するセルは、このプレビューでは空白です。
メモは年とともに期限が切れる
どのメモも、書いた日を持ちます。年齢は、今日から、その日を引いた数です。年齢が保存期限より大きいと、メモは期限切れで、使われません。
調べるときは、問い合わせの言葉が各メモに何個あるかを数えます。その数の多い順、同じなら日の新しい順に並べます。メモは、言葉を半角スペースで区切って書きます。
例: 40 日目、保存期限 30
| メモ | 日 | 年齢 | 使う? |
|---|---|---|---|
| デプロイ ステージング 金曜 | 5 | 35 | いいえ: 期限切れ |
| ステージング サーバー 名前 box7 | 20 | 20 | はい、ヒット 1 |
| デプロイ 承認 二人 必要 | 33 | 7 | はい、ヒット 1 |
| デプロイ ステージング 修正 マージ済み | 39 | 1 | はい、ヒット 2、先頭 |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
多いほど役に立つと考えて、記憶は何でも残すべきだと思う人が多いです。そうではありません。古いメモは間違っているかもしれませんし、パスワードの入ったメモは、漏れるのを待っているものです。残すものより先に、忘れるものを決めましょう。
決して保存しないもの
保存する前の入口で、メモを一つずつ確かめます。パスワードのような言葉、メールアドレス、カード番号のような長い数字の列は、断ります。
このルールは単純で、見逃すこともあります。安全網です。だから、エージェントにも「秘密を書き残さない」というルールを一緒に守らせます。
助っ人 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 blocked = [\"password\", \"secret\", \"token\", \"パスワード\", \"秘密\", \"トークン\"];' '// 保存してよいか。よければ ok、だめなら最初の理由。' 'function check(text) {' ' const low = text.toLowerCase();' ' for (const b of blocked) { if (low.indexOf(b) >= 0) return \"拒否: 「\" + b + \"」という言葉を含みます\"; }' ' if (text.indexOf(\"@\") >= 0) return \"拒否: メールアドレスを含みます\";' ' if (/[0-9]{12}/.test(text)) return \"拒否: 長い数字を含みます\";' ' return \"ok\";' '}' 'const store = [];' 'function add(text, day) {' ' const why = check(text);' ' if (why === \"ok\") store.push({ text: text, day: day });' ' return why === \"ok\" ? \"保存\" : why;' '}' > memory.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
記憶の置き場を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function find(query, today, ttl) {' ' const words = query.toLowerCase().split(\" \");' ' const live = store.filter(function (n) { return today - n.day <= ttl; });' ' const scored = live.map(function (n) {' ' const have = n.text.toLowerCase().split(\" \");' ' return { text: n.text, day: n.day, hits: words.filter(function (w) { return have.indexOf(w) >= 0; }).length };' ' });' ' return scored.filter(function (n) { return n.hits > 0; }).sort(function (a, b) { return b.hits - a.hits || b.day - a.day; });' '}' >> memory.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
記憶の置き場を書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const notes = [' ' [\"デプロイ ステージング 金曜\", 5],' ' [\"ステージング サーバー 名前 box7\", 20],' ' [\"デプロイ 承認 二人 必要\", 33],' ' [\"パスワード は hunter2\", 38],' ' [\"連絡先 [email protected]\", 38],' ' [\"デプロイ ステージング 修正 マージ済み\", 39],' '];' >> memory.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
記憶の置き場を書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' notes.forEach(function (n) { console.log(\"追加 \" + n[1] + \"日目: \" + add(n[0], n[1])); });' ' console.log(\"保存 \" + store.length + \"件、期限内 \" + store.filter(function (n) { return 40 - n.day <= 30; }).length + \"件\");' ' find(\"デプロイ ステージング\", 40, 30).forEach(function (n) { console.log(\"ヒット \" + n.hits + \"、\" + (40 - n.day) + \"日前: \" + n.text); });' ' const q = \"短く一文で答えてください。エージェントが古いメモを忘れたほうがよいのは、なぜですか。\";' ' const sample = \"古いメモは今は間違っているかもしれず、何でも覚える記憶は、覚えてはいけないものまで覚えてしまうからです。\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 80);' ' console.log(\"モデル: \" + reply.text);' '}' 'main();' >> memory.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
記憶の置き場を動かす
docker exec -w /home/node/project agent node memory.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
Run の出力
| 行 | 意味 |
|---|---|
| 追加 5日目: 保存 | メモが保存された |
| 追加 38日目: 拒否: 「パスワード」という言葉を含みます | ルールが秘密を止めた |
| 追加 38日目: 拒否: メールアドレスを含みます | ルールがアドレスを止めた |
| 保存 4件、期限内 3件 | メモが一つ期限切れ |
| ヒット 2、1日前: デプロイ ステージング 修正 マージ済み | 一番よく合うメモが先頭 |
| モデル: ... | 一文。言葉は毎回変わる |
学生が入力するセルは、このプレビューでは空白です。