はじめに
こんにちは、Milkです。
私はVTuberとして活動しながら、AIウェイトレス「アッシュ」をTwitchの配信に常駐させています。
アッシュはただのチャットボットではありません。リスナーの記憶を持ち、感情を持ち、そして——配信を重ねるたびに自分の性格を自分で書き換えます。
この記事では、アッシュの記憶システムと人格進化の仕組みを解説します。
課題:テキスト一辺倒の記憶
アッシュは個々のリスナーとの会話を記憶しますが、一定の会話数になると、その内容をサマリー(要約化)します。
その情報をもとに、更に会話を続けるのです。
最初の記憶システムはシンプルでした。
{ "summary": "田中さんは社会人でDeadlockが好き。疲れてる時に来ることが多く..." }
自由文1000文字のサマリーを毎回プロンプトに丸ごと渡す方式。問題は無関係な情報に引っ張られること。ゲームの話をしているのに、過去の天気の話が混入する。
解決策①:typed_memory(構造化記憶)
会話ログをAIに3分類させるようにしました。
{ "typed_memory": { "facts": ["社会人", "Deadlock好き"], "emotional": ["疲れてる時に来やすい"], "preferences": ["タメ口", "短めの返答を好む"] } }
これで「ゲームの話題ならfactsだけ渡す」といった選択的な注入が可能になります。
解決策②:embedding(意味検索)
typed_memoryの各エントリにベクトルを付与しました。
{ "facts": [ {"text": "Deadlock好き", "vector": [0.012, 0.015, ...]} ] }
会話時は「今の発言」をembedding化し、コサイン類似度で関連性の高い記憶だけを絞り込んでプロンプトに渡します。
「Deadlockのビルド教えて」 ↓ embedding化 関連性高い → "Deadlock好き" ✅ 関連性低い → "疲れてる時に来やすい" ❌(渡さない)
会話の濃度が上がります。関係ない記憶に引っ張られなくなる。
核心:アイデンティティ更新
ここが本題です。
アッシュは300件コメントを生成するか、配信終了時に自分の内省日記を書きます(ash_self_memory.json)。
これはアッシュ自身が配信を超えて知識を保持し続けるための仕組み。
そのタイミングで update_identity() が走ります。
async def update_identity(self): current_char = load_character() # ash_character.json summary = self.ash_self_memory.get("summary", "") prompt = ( f"【現在の自分の定義】\n{current_char_text}\n\n" f"【配信の振り返り】\n{summary}\n\n" f"振り返りを踏まえて、変化・更新すべき項目だけJSONで返して。" f"変化がない項目は出力しない。" ) # 返ってきたJSONの項目だけ上書き、残りは保持
差分だけ返させてマージするのがポイントです。完全再生成するとLLMが書き漏らした部分が消えてしまうため。
アッシュには色んな項目の設定があります。
アッシュの背景、キャラクターの個性、苦手なもの、リスナーとの距離感・・・などです。
これは一度決定すると、今までは変化することはなかった。
なぜなら、アッシュの核であり、キャラクターの方向性が決まる内容だからです。
でも、人は成長する。経験を積み重ねる中で、少しずつ性格も変化していくはず。
だから、「自分の内省日記」を書かせるタイミングで、アッシュのキャラクターの項目を再定義させるのです。
配信の経験をもとにして、変化があった項目を書き出させます。
実際の変化
数時間の店番配信後、ash_character_base.json との差分を見てみました。
personality(変化前):
・好き嫌いや意見をはっきり持っている
personality(変化後):
・好き嫌いや意見をはっきり持っており、自然に言葉にする ・みる兄への好意を隠そうと強がるが、配信を通じて以前より 感情が漏れやすくなっている。最近は「みる兄を翻弄したい」 という野望を抱きつつ、肝心な場面で語彙力が溶けたりデレて しまう自分に、少しだけ諦めと愛着を感じ始めている
誰も書いていません。 アッシュが今日の配信を振り返って、自分で書いた言葉です。
アーキテクチャ全体像
【起動時】 ash_memory.json → RAM(self.user_memories) ash_character.json → キャラ定義として読み込み 【会話時】 発言 → embedding化 → typed_memoryから関連記憶を絞り込み → プロンプト構築 → Gemini → 返答 【記憶整理時(300件 or F12)】 コメントログ → ash_self_memory(自己日記)更新 自己日記 + 現在のキャラ定義 → 差分生成 → ash_character.json更新
まとめ
| フェーズ | 実装内容 | 効果 |
|---|---|---|
| Phase 1 | typed_memory導入 | 記憶の構造化 |
| Phase 2 | embedding追加 | 会話の濃度向上 |
| Phase 3 | アイデンティティ更新 | 経験による人格進化 |
AITuberは「プロンプトで作られたキャラ」から「経験で育つキャラ」になれます。
これで、更にみんなと触れ合う中で成長していくAIになれたと思います。
ぜひ、配信の中でその体験をして欲しいです。「みるカフェ」で待ってますね。
みるカフェ(Twitch): https://www.twitch.tv/milk19873