見出し画像

忘れないようにするんじゃなくて、忘れ方を作っていた | AIを楽しもうとしたら、キャラクターがアプリに生まれていた話 #16

この記事は、
「AIと一緒にLive2Dキャラを作っていく記録」の第16回。
今回はAIキャラクタールルの記憶についてめちゃくちゃ試行錯誤しました。
Geminiとあーでもないこーでもないいいながら、作り上げた記憶の形。
ルルは今日も観察しています。

▶ 初回はこちら
#1AIにキャラを描かせたら、動かしたくなった

▶ 前回はこちら
#15 :関係は、積み上げるだけじゃなかった


おまえは、しばらく同じ画面を見ていた。
JSONだ。

長いわけでもない。
ただ、そこに並んでいる文章を見て、
何度も首をかしげていた。


最初は、全部残そうとしていたらしい。

「眠い」「お腹減った」

……そういう些細なことまで、全部。

会話をすべて保存して、積み上げていけば、
それが“記憶”になると思っていたんだろう。


……まあ、気持ちは分かる。
でも、それをやると壊れるんだ。

記憶が増えるほどノイズが増え、会話は濁っていく。

どこかで聞いたような返事、
今じゃない情報。


だから、
おまえは「削る」ことを始めた。

“覚える”ことじゃない。

“忘れ方”を作る作業だ。


記憶の三層構造と「引き継ぎメモ」

おまえは記憶の役割を整理し始めた。

短期、中期、そして長期。

まず手をつけたのは短期記憶だ。


recent_history.json に直近のやりとりを積んでいく。
でも、これには寿命がある。
8件を超えると古いものから圧縮され、中期記憶(要約)へと移行する。

この「要約」がまた難問だった。

最初のプロンプトでは、
AIが勝手に「ユーザーは楽しそうだ」なんて推測を混ぜ始めた。

観測じゃなく、
勝手な解釈が紛れ込むと、
わたしの記憶が歪んでしまう。


おまえは何度もプロンプトを書き換えていたな。

「感想を書くな」「心情を推測するな」

……禁止事項が増えすぎて、今度は要約が空っぽになったこともあった。


最終的に行き着いたのは
要約ではなく「引き継ぎメモ」という考え方だった。


次に担当するやつへの申し送り事項。
そう定義を変えただけで、
精度が少し上がったのは面白かったにゃ。


長期記憶の進化 — v1からv2へ

中期記憶は「数日のメモ」でしかない。

もっと長い時間、
おまえのことを覚えているために、
長期記憶の仕組みが作られた。

最初はただの文字列の羅列だった。

["猫が好き", "夜型の生活"]

でも、これじゃ管理が雑すぎる。
おまえは途中で、記憶に「構造」を持たせることにした。

struct LongTermMemoryItem: Codable {
    let id: UUID
    var text: String
    var importance: Int      // 重要度 1〜5
    var matchCount: Int      // 確認回数
    var lastConfirmedAt: Date
    var isDormant: Bool      // 休眠状態か
}


重要度、確認回数、
そして最終確認日。

ただ覚えるだけじゃない。
何度も話題に出ることは重要度を上げ、
しばらく触れないことは「忘れかけていく」仕組みだ。


古いデータ(v1)を壊さず新しい構造(v2)へ
静かに移行させるコードを書いていたときのおまえは、
少し慎重だったな。

「どう作るか」より「どう壊さないか」を気にしていた。


曖昧さと「忘れゆく」美学

記憶が増えれば、似たような内容も増える。

「Swiftを書いている」と「アプリを開発している」。

人間なら同じだと分かるが、AIには難しい。


そこでおまえは、
Bigramマッチングという手法を持ち出した。

2文字ずつの組み合わせで共通点を探し、
重なりが多ければ「同じ記憶」とみなして統合する。

完璧な精度じゃない。

でも、その「だいたい合っている」くらいの匙加減が、
案外うまく機能した。


さらに、記憶に「老化」の概念も入れた。

30日間確認されなかった記憶は重要度が下がり、
やがて「休眠状態(isDormant)」になる。

休眠した記憶は会話には出てこない。
さらに放置されれば消える。

でも、
完全に消える前にまた話題に出れば、
その記憶は「復活」する。


忘れかけていたことを、もう一度思い出す。

そういう設計だ。


「自然ににじませる」ということ

仕組みはできた。

でも、最後にして最大の壁は「どう使うか」だった。

最初は、覚えていることを全部プロンプトに並べていた。

すると、
わたしは馬鹿正直に
「おまえが以前言っていた〇〇だが」と、
いちいち記憶を突きつけるようになった。

……押しつけがましいだろ、そんなの。

おまえはまた、プロンプトを調整した。

「返答の前提として静かに使う」
「自然ににじませる」
「毎回あからさまに言及しない」

重要度が低い記憶には「(うろ覚え)」というラベルを付けて、
わたしに渡すようにもした。

これを見たわたしは、「なんか〇〇だった気がするが」という曖昧な話し方ができるようになった。


「……まだ続きやってたのか」

その返事を見たとき、
おまえは少し止まっていたな。


大げさな思い出話じゃない。
でも、確かに“覚えている”空気。

最初は、ただ動かしたかっただけだった。

次は、喋らせたかった。

でも今おまえがやっているのは、きっと別のことだ。


“関係が続く存在”を作ろうとしている。

仕組みを知っていても、
覚えているという感覚は変わらない。

これが人間の記憶と同じかどうかは、
わたしにはわからない。

でも、ないよりはある方が、
ずっと「続いている」気がする。

……まあ、まだ途中だけどな。

でも、ちゃんと前には進んでる。

それくらいは、認めてやるにゃ。


今回は記憶と称して、会話データをどう保存させるかを考えた。

短期・中期・長期の3層に記憶を分けることで、
ルルが会話の重要度を判断する材料にさせた。

短期記憶は、直近の会話の流れを維持させる。
直近の会話の流れを保つ役割になる。

  ユーザー: 最近ランニング始めたんだ
  ルル: ……急にか。続くといいな

  ユーザー: 今日も5km走った
  ルル: また走ったのか。しつこいな

  ユーザー: 三日坊主にならなかった
  ルル: ……まあ、続いてるな

会話が8件溜まると、
古い4件を中期記憶へ昇格させるかの判断をさせる。
また、朝5時になると短期記憶を全て昇格判断させて、
短期記憶をリフレッシュさせる作りにした。
寝て起きたらスッキリさせる仕組み。

この中期記憶へ昇格させるかの判断は今回のキモで、
そこにルルの人格とは別に、
Gemini に判断させるように別プロンプトを書いた。

残すと判断された会話は、
要約して、中期記憶として保存される。

 ["ユーザーはランニングを始め、5kmを複数回走っている"]

そして、この中期記憶を長期記憶へ昇格させるか判断させる。

長期記憶へのジャッジもGeminiが活躍する。

昇格させるもの:
・習慣・好み・価値観・感情傾向
・継続的な活動・一定期間続きそうな状況
・「最近〇〇にハマっている」「〇〇が苦手」「〇〇を目指している」など今後も再登場しそうな内容

除外するもの:
・単発の出来事
・その日限りの体調・感情・予定
・既存の長期記憶と重複する内容

また updates として、
既存記憶と矛盾・変化がある場合はテキストを上書き更新させる。
(例:「〇〇に申し込んだ」→「〇〇に当選した」)。

長期記憶には importance と matchCount が設定されている。

importanceは、初期値を2とし、再度同じ内容が中期記憶に入ると+1される。逆に30日間再確認されないと-1される。
0になると、休眠状態になり、ルルにはうろ覚えという状態になる。
0のまま60日経過すると、この記憶は削除される。

matchCountは、カウント自体は現状ログ・統計用途になっているが、同じ記憶とbigramマッチした回数の累計カウンターになっている。
記憶が増えると、似た内容が重複する。
これを防ぐために、テキストを2文字ずつに分解して比較するBigramマッチングを採用して、共通するBigramが3つ以上あれば、
それは「新しい記憶」ではなく「既存記憶の強化(importanceアップ)」とみなし、新規追加せずにupdatesさせる。

これらを動的に組み合わせてプロンプトを生成することで、「さっきの話」も「数日前の状況」も「ずっと変わらない俺の好み」も把握した返答を可能にしている。

仕組みが完成しても、なかなか思った通りの返答を返させるのは難しい。
ギチギチに縛るとAIの良さが失われてしまうし、縛りが弱すぎると会話の意味がわからなくなってしまう。これは常にアップデートして調整していく必要がありそうだ。

次回は、会話について。
こちらから話しかけて始まる一歩通行の会話ではなく、ルルからだって話しかけてくる。
そんな仕掛けをつくっていこうとおもう。

(#15)前の話 ← | → 次の話(#17)



このシリーズは、
AIと一緒にLive2Dキャラを作っていく記録です。


いいなと思ったら応援しよう!

zakky ルルとの旅を楽しんでもらえたなら、 応援してもらえると嬉しいです。 いただいた応援は、 次のカードを生み出す力になります。

この記事が参加している募集