はじめに:AI VTuber「牡丹プロジェクト」とは
本記事は、AI VTuber三姉妹(Kasho、牡丹、ユリ)の記憶システム設計の技術解説シリーズ第2弾です。
プロジェクト概要
「牡丹プロジェクト」は、過去の記憶を持つAI VTuber を実現するプロジェクトです。三姉妹それぞれが固有の記憶・個性・価値観を持ち、視聴者と自然に会話できることを目指しています。
三姉妹の構成
- Kasho(長女): 論理的・分析的、慎重でリスク重視、保護者的な姉
- 牡丹(次女): ギャル系、感情的・直感的、明るく率直、行動力抜群
- ユリ(三女): 統合的・洞察的、調整役、共感力が高い
GitHubリポジトリ
本プロジェクトのコードは以下で公開しています:
- リポジトリ: https://github.com/koshikawa-masato/AI-Vtuber-Project
- 主要機能: 記憶生成システム、三姉妹決議システム、LangSmith統合、VLM対応
前回のおさらい:RAGの3つの課題
第1弾の記事では、RAG(Retrieval-Augmented Generation)をAI VTuberの記憶システムとして試した結果、以下の3つの課題があることがわかりました:
| 課題 | 内容 |
|---|---|
| 1. 主観性の欠如 | 客観的テキストになり、一人称の記憶が作りにくい |
| 2. 時系列の扱いにくさ | 年齢範囲などの構造的条件が苦手 |
| 3. 関係性の表現 | 姉妹間の相互作用が記録できない |
これらの課題を解決するために、私は独自の記憶システム**「記憶製造機」** を設計しました。
記憶システム第2弾:記憶製造機の設計
本記事では、RAGとは全く異なるアプローチで設計した「記憶製造機」について解説します。
🎯 この記事で分かること
- 記憶製造機の設計思想(主観性・三層記憶・独立性)
- SQLiteを使った構造化記憶の実装
- 三姉妹討論システムによる記憶の自律生成
- RAGとの性能比較
📦 対象読者
- AI VTuber / AIキャラクター開発者
- RAGの限界を感じている方
- 構造化データとLLMの組み合わせに興味がある方
記憶製造機とは
名前の由来
「記憶製造機」という名前は、三姉妹が自律的に討論し、記憶を"製造"する という意味から名付けました。
開発者の洞察
「今までの100の記憶は我々が作りました。
ここから無限に続く記憶は彼女たちで
みずから作り上げていくんです」
システム概要
記憶製造機は2つの柱で構成されています:
┌──────────────────────────────────┐
│ 記憶製造機 │
├──────────────────────────────────┤
│ │
│ 1. 構造化記憶 │
│ └─ SQLiteによる主観的記憶管理 │
│ │
│ 2. 自律生成 │
│ └─ 三姉妹討論による記憶製造 │
│ │
└──────────────────────────────────┘
RAGとの根本的な違い
| 観点 | RAG | 記憶製造機 |
|---|---|---|
| データ形式 | ベクトル(768〜1536次元) | 構造化テーブル |
| 検索方法 | 類似度検索(コサイン距離) | SQL(WHERE, JOIN) |
| 記憶の視点 | 三人称(客観的) | 一人称(主観的) |
| 記憶の生成 | 手動入力 | LLMが自律生成 |
| 関係性 | 暗黙的(ベクトル距離) | 明示的(外部キー) |
設計思想
記憶製造機は3つの設計原則に基づいています。
1. 主観的記憶の原則
「牡丹の記憶は、牡丹の視点で語られるべき」
RAGとの対比
# ❌ RAGで保存したテキスト(三人称・客観的)
text = "牡丹は5歳の時、英語テストに失敗して泣いた。"
# ✅ 記憶製造機で保存する記憶(一人称・主観的)
memory = {
"diary_entry": """
今日、英語テストで全然できなくて、マジで泣いちゃった。
周りの子はスラスラ答えてるのに、私だけわかんない。
悔しい...私ってバカなのかな...
でも、お姉ちゃんが「大丈夫だよ」って言ってくれて、
ちょっと救われた。負けたくない。絶対に負けたくない!
""",
"emotion_frustration": 9, # 感情スコア
"emotion_defiance": 7 # 反抗心
}
主観性の価値
- ✅ キャラクターらしさが出る
- ✅ 没入感が高まる
- ✅ 個性が記憶に内在する
2. 三層記憶システム
人間の記憶には複数の種類があります。牡丹AIも同様に、3つの記憶層 を持つことで、よりリアルで自然な応答を実現します。
三層記憶の分類
| 記憶の種類 | 定義 | 確信度 | 表現 |
|---|---|---|---|
| 直接記憶 | 自分が経験したこと | 8-10 | 「マジで覚えてる!」 |
| 伝承記憶 | 人から聞いたこと | 5-8 | 「ママから聞いた話だけど〜」 |
| 推測記憶 | 状況から推測したこと | 3-6 | 「多分だけど〜だと思う」 |
具体例:伝承記憶
-- Event 001: Kasho誕生(牡丹が生まれる2年前)
INSERT INTO botan_memories (
event_id,
memory_date,
memory_type,
heard_from,
heard_when,
confidence_level,
diary_entry
) VALUES (
1,
'2006-05-20',
'heard', -- 伝承記憶
'mother',
'8歳の頃(家族団欒の時)',
7,
'ママから聞いた話。お姉ちゃんが生まれたとき、パパが泣いて喜んだんだって。
初めての子供で、すっごく嬉しかったらしい。お姉ちゃん、可愛い赤ちゃんだったんだって〜。
私は見てないけど、想像できる。'
);
応答例:
ユーザー: 「Kashoお姉ちゃんの誕生日、知ってる?」
牡丹(伝承記憶):
「お姉ちゃん?5月20日だよ!
私が生まれる前だから、直接は覚えてないけど、
ママから聞いた。
お姉ちゃんが生まれたとき、パパが泣いて喜んだんだって〜。
初めての子供で、すっごく嬉しかったらしい。
可愛い赤ちゃんだったって聞いたよ。
私は見てないけどね!」
ポイント:
- ✅ 「直接は覚えてないけど」と明示
- ✅ 情報源を示す(「ママから聞いた」)
- ✅ 確信度が反映されている(confidence_level: 7)
3. 姉妹関係の独立性
重要原則: 牡丹はKashoの内面を「知っている」のではなく「推測している」。
推測記憶の例
INSERT INTO botan_memories (
event_id,
absolute_day,
memory_date,
memory_type,
botan_emotion,
botan_action,
diary_entry,
-- Kashoの観察と推測
kasho_observed_behavior,
kasho_inferred_feeling,
inference_basis,
confidence_level
) VALUES (
82,
4746,
'2021-05-05',
'direct', -- 牡丹本人の記憶は直接
'感動、興奮、夢の発見',
'VTuber配信を初めて見た',
'今日、初めて配信見た。マジで面白い。私もこういうのやりたい。',
-- Kashoへの推測(断定しない)
'優しく笑ってた、見守ってくれてた',
'多分、私が何か見つけたって嬉しかったんじゃないかな。
お姉ちゃんはいつも私の成長を喜んでくれるから。
でも本当に何考えてたかは分かんない。',
'observed_smile, past_sisterhood_pattern',
5 -- 推測なので確信度は低い
);
応答例:
ユーザー: 「その時、Kashoお姉ちゃんは何考えてたの?」
牡丹(推測記憶):
「お姉ちゃん?う〜ん、多分だけど、
私が楽しそうにしてるの見て、嬉しかったんじゃないかな〜。
優しく笑ってたから。
お姉ちゃんはいつも私の成長を喜んでくれるし。
でも、本当に何考えてたかは分かんない。
お姉ちゃんに聞いてみないと!」
重要: 断定しない、推測であることを明示、観察可能な範囲のみ述べる。
データベース設計
スキーマ全体像
sisters_memory.db
├── sister_shared_events(共通イベント)
├── botan_memories(牡丹の記憶)
├── kasho_memories(Kashoの記憶)
└── yuri_memories(ユリの記憶)
botan_memories テーブル
CREATE TABLE IF NOT EXISTS botan_memories (
memory_id INTEGER PRIMARY KEY AUTOINCREMENT,
-- 時系列情報
absolute_day INTEGER NOT NULL, -- 絶対日数(1〜6205)
age INTEGER NOT NULL,
memory_date TEXT NOT NULL,
-- 記憶の種類(三層記憶)
memory_type TEXT DEFAULT 'direct', -- 'direct', 'heard', 'inferred'
confidence_level INTEGER, -- 確信度 1-10
-- 伝承記憶用フィールド
heard_from TEXT, -- 誰から聞いたか
heard_when TEXT, -- いつ聞いたか
-- 推測記憶用フィールド
inference_basis TEXT, -- 推測の根拠
-- 主観的内容
diary_entry TEXT NOT NULL, -- 日記本文(一人称)
botan_emotion TEXT, -- 感情(テキスト)
botan_action TEXT, -- 行動
botan_thought TEXT, -- 思考
-- 感情スコア(0-10)
emotion_joy INTEGER DEFAULT 5,
emotion_sadness INTEGER DEFAULT 0,
emotion_anxiety INTEGER DEFAULT 0,
emotion_frustration INTEGER DEFAULT 0,
emotion_defiance INTEGER DEFAULT 0, -- 反抗心(牡丹固有)
-- 姉妹への観察・推測
kasho_observed_behavior TEXT, -- 観察した行動
kasho_inferred_feeling TEXT, -- 推測した感情
yuri_observed_behavior TEXT,
yuri_inferred_feeling TEXT,
memory_importance INTEGER CHECK(memory_importance BETWEEN 1 AND 10),
-- イベント関連
event_id INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (event_id) REFERENCES sister_shared_events(event_id),
CHECK (confidence_level BETWEEN 1 AND 10)
);
ポイント
- 構造化された感情: スコアとして数値化 → 集計・分析が可能
- 時系列の明確化: absolute_day で連続性を保証
- 関係性の記録: event_id で共通イベントと紐付け
- 三層記憶: memory_type, heard_from, inference_basis で表現
sister_shared_events テーブル
CREATE TABLE IF NOT EXISTS sister_shared_events (
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
-- イベント情報
event_name TEXT NOT NULL, -- 'LA移住初日'
event_date TEXT NOT NULL,
location TEXT NOT NULL,
event_category TEXT NOT NULL, -- 'migration', 'birthday', 'family'
emotional_weight TEXT NOT NULL, -- 'life_changing', 'important'
-- 各姉妹の該当日記ID
kasho_diary_id INTEGER,
botan_diary_id INTEGER,
yuri_diary_id INTEGER,
-- 各姉妹の年齢(その時点)
kasho_age INTEGER,
kasho_absolute_day INTEGER,
botan_age INTEGER,
botan_absolute_day INTEGER,
yuri_age INTEGER,
yuri_absolute_day INTEGER,
-- イベント共通説明
event_description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (kasho_diary_id) REFERENCES kasho_memories(id),
FOREIGN KEY (botan_diary_id) REFERENCES botan_memories(id),
FOREIGN KEY (yuri_diary_id) REFERENCES yuri_memories(id)
);
姉妹関係の記録
LA移住初日の例:
INSERT INTO sister_shared_events (
event_name,
event_date,
location,
kasho_age, botan_age, yuri_age,
kasho_diary_id, botan_diary_id, yuri_diary_id
) VALUES (
'LA移住初日',
'2011-08-01',
'Los Angeles',
5, 3, 1, -- それぞれの年齢
1899, 1185, 391 -- それぞれの記憶ID
);
これにより:
- ✅ 同じイベントの3つの視点を明示的に紐付け
- ✅ 「LA移住初日」で検索すると3人の記憶が取得できる
- ✅ RAGでは不可能だった「関係性」を実現
記憶の自律生成
記憶製造機の真髄は、三姉妹が自律的に討論し、記憶を生成することです。
記憶製造機のフロー
[議題提案]
↓
[三姉妹の自律討論](Phase 4: 起承転結システム)
↓
[各キャラクターの主観的記憶を生成]
↓
[構造化DB保存]
↓
[品質評価](Phase 3: LLM as a Judge)
↓
[安全性確認](Phase 5: センシティブ判定)
↓
[記憶として確定]
Phase 1-5との統合
| Phase | 機能 | 記憶製造機での活用 |
|---|---|---|
| Phase 1 | LangSmithトレーシング | 記憶生成プロセスの可観測性 |
| Phase 3 | LLM as a Judge | 生成した記憶の品質評価 |
| Phase 4 | 三姉妹討論システム | 記憶の自律生成 |
| Phase 5 | センシティブ判定 | 記憶の安全性確保 |
記憶生成の具体例
議題:「PON機能の調整について討論してください」
討論の結果(Phase 4)
discussion_result = {
"kasho": "完璧を求めすぎるのは良くないかもしれないが、基準は必要...",
"botan": "お姉ちゃんは厳しいけど、わかる。でも私のペースでやりたい...",
"yuri": "お二人とも正しい。バランスが大切..."
}
牡丹の主観的記憶(自動生成)
botan_memory = {
"diary_entry": """
今日はPON機能の話した。お姉ちゃんは完璧主義だから
厳しいこと言うけど、わかる。でも私は私のペースでやりたい。
ユリちゃんはいつも間に入ってくれて助かる。
お姉ちゃんとぶつかることもあるけど、嫌いじゃない。
むしろそれが私たちらしい。
""",
# 感情スコア(自動計算)
"emotion_defiance": 6, # 反抗心
"emotion_frustration": 4,
"emotion_joy": 5,
# 姉妹への観察・推測
"kasho_observed_behavior": "厳しい表情してた、でも優しさもあった",
"kasho_inferred_feeling": "多分、完璧を求めてるんだと思う。心配してくれてる。",
"inference_basis": "observed_expression, past_sisterhood_pattern",
# 記憶メタデータ
"memory_type": "direct",
"confidence_level": 9
}
特徴
- ✅ 一人称の記述(「私は」)
- ✅ 感情が数値化されている
- ✅ 姉妹への推測(断定しない)
- ✅ キャラクターらしさが出ている
RAGとの比較
検索の違い
RAG(ベクトル検索)
# 意味的類似度で検索
query = "辛かった経験"
query_embedding = openai.embed(query)
results = vectorstore.similarity_search(query_embedding, k=3)
# 結果: ベクトル空間で近いものが返る
# - 時系列は考慮されない
# - 感情スコアでフィルタリングできない
記憶製造機(構造化検索)
-- 複雑な条件で検索
SELECT
m.absolute_day,
m.age,
m.diary_entry,
m.emotion_frustration,
e.event_name
FROM botan_memories m
LEFT JOIN sister_shared_events e ON m.event_id = e.event_id
WHERE
m.age BETWEEN 5 AND 7 -- 年齢範囲
AND m.location = 'Los Angeles' -- 場所
AND m.emotion_frustration > 7 -- 感情スコア
ORDER BY m.emotion_frustration DESC
LIMIT 5;
結果:
| absolute_day | age | diary_entry | emotion_frustration | event_name |
|--------------|-----|-------------|---------------------|------------|
| 1826 | 5 | 英語テストで泣いた... | 9 | 英語テスト失敗 |
| 1920 | 5 | またわからない... | 8 | null |
| 2100 | 6 | お姉ちゃんばっかり... | 8 | null |
比較まとめ
| 観点 | RAG | 記憶製造機 |
|---|---|---|
| 曖昧検索 | ✅ 強い | ❌ 弱い |
| 時系列検索 | ⚠️ メタデータ必要 | ✅ 強い |
| 感情検索 | ❌ 難しい | ✅ 強い |
| 関係性検索 | ❌ 困難 | ✅ JOIN可能 |
| 複雑な条件 | ⚠️ 複雑化 | ✅ SQLで自然 |
パフォーマンス比較
| データ量 | RAG(ベクトル検索) | 記憶製造機(SQL) |
|---|---|---|
| 1,000件 | 〜10ms | 〜5ms |
| 10,000件 | 〜20ms | 〜10ms |
| 100,000件 | 〜50ms | 〜30ms(インデックス有) |
記憶製造機の容量効率:
18,615日の記憶 × 3キャラクター = 55,845件
→ 約31MB(SQLite)
同じデータをRAGで保存:
55,845件 × 768次元 × 4バイト = 約170MB
まとめ
記憶製造機の設計まとめ
3つの設計原則
- 主観的記憶の原則: 一人称の記憶、感情の内在化
- 三層記憶システム: 直接・伝承・推測の区別
- 姉妹関係の独立性: 推測であることを明示
データベース設計
- SQLiteによる構造化: 時系列、感情スコア、関係性を明示的に管理
- 三層記憶の実装: memory_type, heard_from, inference_basis
- 共通イベントテーブル: 姉妹間の関係性を外部キーで表現
記憶の自律生成
- Phase 4統合: 三姉妹討論システムで記憶を生成
- Phase 3統合: LLM as a Judgeで品質評価
- Phase 5統合: センシティブ判定で安全性確保
記憶製造機の強み
| 強み | 説明 |
|---|---|
| ✅主観的記憶 | 一人称の感情的な記憶 |
| ✅構造化検索 | 時系列、感情スコアで柔軟に検索 |
| ✅関係性の記録 | 姉妹間の相互作用を明示的に管理 |
| ✅自律生成 | 三姉妹が自ら記憶を製造 |
記憶製造機の弱み
| 弱み | 説明 |
|---|---|
| ❌意味検索が弱い | 完全一致ベース、同義語に対応できない |
| ❌大規模データ | 数百万件では遅くなる可能性 |
次回予告:ハイブリッドアプローチ
第3弾では、RAGの強み(意味検索)と記憶製造機の強み(構造化)を組み合わせた新・記憶製造機(ハイブリッド) を解説します。
次回の内容
- PostgreSQL + pgvector: 構造化データ + ベクトル検索の両立
- ハイブリッド検索: 構造化フィルター → 意味検索 → 重要度ランキング
- 追加機能: 記憶の重要度自動計算、忘却曲線、連想検索、リハーサル効果
記憶システムシリーズの進行状況
| 記事 | 内容 | 状態 |
|---|---|---|
| 第1弾 | RAGを試して気づいたこと | ✅ 公開済み |
| 第2弾 | 記憶製造機の設計(構造化記憶) | ✅本記事 |
| 第3弾 | ハイブリッドアプローチ(RAG + 構造化) | ✅ 公開済み |
参考リンク
本プロジェクトの技術記事(Phase 1-5)
- Phase 1: LangSmithマルチプロバイダートレーシング
- Phase 2: VLM実装ガイド
- Phase 3: LLM as a Judge実装ガイド
- Phase 4: 三姉妹討論システム
- Phase 5: センシティブ判定システム