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

無料レッスン

わざと忘れるエージェントの記憶

このレッスンのあとで: エージェントに、わざと忘れる記憶を持たせることができる。

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

忘れる記憶

エージェントの記憶は、あとで引ける小さなメモの置き場です。よい記憶は、わざと忘れます。古いメモは期限が切れ、決して保存しないものもあります。

何でも覚える記憶は、パスワードも覚えますし、もう本当でなくなった事実も覚えます。「エージェントを作る」のモジュールでは、記憶を作りました。このレッスンは、忘れることを足します。

このレッスンのゴール

メモ

エージェントに、わざと忘れる記憶を持たせることができる。

キーワード

ことばいみ
記憶の置き場エージェントが引けるメモのリスト
キーワードメモを探すのに使う言葉
保存期限メモを何日間残すか
期限切れ古すぎて、忘れられること
保存禁止ルール入口でメモを断る確認

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

メモは年とともに期限が切れる

どのメモも、書いた日を持ちます。年齢は、今日から、その日を引いた数です。年齢が保存期限より大きいと、メモは期限切れで、使われません。

調べるときは、問い合わせの言葉が各メモに何個あるかを数えます。その数の多い順、同じなら日の新しい順に並べます。メモは、言葉を半角スペースで区切って書きます。

例: 40 日目、保存期限 30

メモ日年齢使う?
デプロイ ステージング 金曜535いいえ: 期限切れ
ステージング サーバー 名前 box72020はい、ヒット 1
デプロイ 承認 二人 必要337はい、ヒット 1
デプロイ ステージング 修正 マージ済み391はい、ヒット 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日前: デプロイ ステージング 修正 マージ済み一番よく合うメモが先頭
モデル: ...一文。言葉は毎回変わる

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

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

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