Skip to content
Published on

エージェントメモリシステム設計:AIエージェントの記憶のすべて

Authors

なぜメモリがAIエージェントの最も難しい問題なのか

「こんにちは、私は田中です。」 「こんにちは、田中さん!」 [会話終了]

[新しいセッション開始] 「私の名前を知っていますか?」 「申し訳ありませんが、以前の会話の記録がないため...」

LLMは基本的に記憶がありません。すべての会話が初めての会話です。これが現在のAIアシスタントの最も根本的な限界の一つです。

人間関係で当たり前だと思っていること——「前回話したこと」「あなたが好きなもの」「一緒に解決した問題」——AIはこれをデフォルトではできません。

だからこそエージェントメモリシステムが重要です。この記事では、メモリシステムの理論から実装まですべてを扱います。


人間の心理学から借りた4つのメモリタイプ

心理学者が人間の記憶を分類した方法が、AIメモリシステムの設計に驚くほど役立ちます。

1. 感覚記憶 (Sensory Memory) → コンテキストウィンドウ

感覚記憶は、外部から入ってきた情報がごく短時間(0.5秒〜3秒)保持されるものです。LLMではこれが現在の入力、つまりコンテキストウィンドウの内容に該当します。

# これが感覚記憶です:
messages = [
    {"role": "user", "content": "私の名前は田中です"},
    {"role": "assistant", "content": "こんにちは、田中さん!"},
    {"role": "user", "content": "私の名前は何ですか?"},
    # LLMは上の会話内容をそのまま見られるので答えられます
]

特徴:素早くアクセス可能だが、会話が終わると消える。コンテキストウィンドウの長さの制限がある。

2. 短期記憶 (Short-term/Working Memory) → 会話バッファ

短期記憶は、作業中に情報を一時的に保持するものです。会話バッファがこれに相当します。

from langchain.memory import ConversationBufferWindowMemory

# 最近10回の会話のやり取りのみ保持
memory = ConversationBufferWindowMemory(k=10, return_messages=True)

# 会話追加
memory.save_context(
    {"input": "Pythonのリスト内包表記って何?"},
    {"output": "リスト内包表記は、既存のリストをもとに新しいリストを作る簡潔な方法です。"}
)

# メモリ読み込み(LLMに注入)
history = memory.load_memory_variables({})

特徴:現在のセッションコンテキストを維持する。しかしk個を超えると古いものから削除される。長期持続不可。

3. 長期記憶 (Long-term Memory) → ベクターストア + 外部DB

長期記憶は、長期間情報を保存し、必要なときに取り出すものです。ベクターストアがこの役割を担います。

from langchain.memory import VectorStoreRetrieverMemory
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

# 長期メモリストア作成
embedding = OpenAIEmbeddings()
vectorstore = FAISS.from_texts(["placeholder"], embedding)
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

memory = VectorStoreRetrieverMemory(retriever=retriever)

# 記憶の保存
memory.save_context(
    {"input": "私の好きなプログラミング言語はPythonです"},
    {"output": "わかりました!Pythonがお好きなんですね。"}
)

memory.save_context(
    {"input": "私は東京に住んでいてスタートアップで働いています"},
    {"output": "東京のスタートアップにお勤めなんですね!"}
)

# 後で:関連記憶の検索
relevant = memory.load_memory_variables({"prompt": "私に合う技術を教えて"})
# Python、東京スタートアップ関連の記憶を返す

主なメリット:数百万の記憶の中から関連性の高いものだけを選んで取得できる。意味的類似性に基づく検索。

4. エピソード記憶 (Episodic Memory) → 構造化イベントログ

エピソード記憶は、特定の出来事や体験を記憶するものです。時間と文脈が含まれます。

import json
from datetime import datetime

class EpisodicMemory:
    def __init__(self, db_connection):
        self.db = db_connection

    def record_episode(self, user_id: str, episode: dict):
        """特定のイベント/インタラクションを記録"""
        self.db.insert("episodes", {
            "user_id": user_id,
            "timestamp": datetime.now().isoformat(),
            "event_type": episode["type"],  # "purchase", "complaint", "success"
            "summary": episode["summary"],
            "metadata": json.dumps(episode.get("metadata", {}))
        })

    def retrieve_episodes(
        self,
        user_id: str,
        event_type: str = None,
        limit: int = 10
    ):
        """ユーザーの過去のエピソードを検索"""
        if event_type:
            query = """
                SELECT * FROM episodes
                WHERE user_id = ? AND event_type = ?
                ORDER BY timestamp DESC LIMIT ?
            """
            return self.db.query(query, [user_id, event_type, limit])
        else:
            query = """
                SELECT * FROM episodes
                WHERE user_id = ?
                ORDER BY timestamp DESC LIMIT ?
            """
            return self.db.query(query, [user_id, limit])

# 使用例
memory = EpisodicMemory(db)
memory.record_episode("user_123", {
    "type": "purchase",
    "summary": "ユーザーがPython上級コースを購入",
    "metadata": {"course_id": "py-advanced", "price": 8900}
})

実用的なメモリアーキテクチャ

理論はここまでにして、実際にどう組み合わせて使うか見てみましょう。

ユーザーメッセージ
     |
     v
+--------------------------------------------------+
| メモリルーター                                    |
| (関連メモリ収集・優先度決定)                      |
+------------+----------+-----------+--------------+
             |          |           |
             v          v           v           v
          感覚      短期記憶      長期記憶    エピソード
        (context)  (buffer)     (vector)    (event log)
             |          |           |           |
             +----------+-----------+-----------+
                                |
                                v
                 統合されたコンテキスト
                 (最も関連性の高い記憶を選択)
                                |
                                v
                         LLM生成
                                |
                                v
                         メモリ更新
                     (新情報を適切なメモリに保存)

簡単に説明すると:

  1. ユーザーメッセージが来たら、メモリルーターが各メモリタイプから関連内容を取得
  2. 最も関連性の高い記憶を選択してコンテキストとして組み合わせ
  3. LLMがそのコンテキストで応答を生成
  4. 新しい情報が生まれたら適切なメモリタイプに保存

実際のコードで見ると:

from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.memory import (
    ConversationBufferWindowMemory,
    VectorStoreRetrieverMemory,
    CombinedMemory
)
from langchain.prompts import PromptTemplate
from langchain.chains import ConversationChain

# メモリ構成
short_term = ConversationBufferWindowMemory(
    k=5,
    memory_key="chat_history",
    return_messages=True
)

embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_texts(["placeholder"], embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
long_term = VectorStoreRetrieverMemory(
    retriever=retriever,
    memory_key="relevant_history"
)

# 2つのメモリを結合
combined_memory = CombinedMemory(memories=[short_term, long_term])

# プロンプト設定
template = """
あなたはユーザーをよく覚えている個人AIアシスタントです。

過去の関連記憶:
{relevant_history}

最近の会話:
{chat_history}

ユーザー:{input}
アシスタント:"""

prompt = PromptTemplate(
    input_variables=["relevant_history", "chat_history", "input"],
    template=template
)

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

chain = ConversationChain(
    llm=llm,
    memory=combined_memory,
    prompt=prompt,
    verbose=True
)

# 会話
chain.predict(input="こんにちは!私はバックエンド開発者です。")
chain.predict(input="最近Rustを学んでいます。")

# 新しいセッションでも長期記憶を活用
chain.predict(input="私が学んでいる言語でWebサーバーを作る方法を教えて。")
# 「Rustで...」と答えることができる

Mem0:最新のメモリフレームワーク

自分で実装するより便利な方法があります。Mem0はAIアプリケーション用のメモリレイヤーを提供する最新のオープンソースフレームワークです。

from mem0 import Memory

m = Memory()

# メモリ追加(LLMが重要な情報を自動的に抽出して保存)
result = m.add(
    messages=[
        {"role": "user", "content": "私はベジタリアンで、日本人で東京に住んでいます。Pythonが好きです。"},
        {"role": "assistant", "content": "わかりました!覚えておきます。"}
    ],
    user_id="user_123"
)

# 保存されたメモリの確認
print(result)
# [
#   {"memory": "ユーザーはベジタリアン", "id": "..."},
#   {"memory": "ユーザーは日本人で東京在住", "id": "..."},
#   {"memory": "ユーザーはPythonを好む", "id": "..."}
# ]

# メモリ検索(意味的類似性に基づく)
memories = m.search("食べ物を推薦して", user_id="user_123")
# 返る:ベジタリアン、東京在住関連の記憶

# エージェントで活用
context = "\n".join([item["memory"] for item in memories["results"]])
response = llm.invoke(
    f"ユーザー情報:\n{context}\n\nリクエスト:食べ物を推薦して"
)

Mem0の利点:

  • LLMが会話から重要な情報を自動的に抽出して保存
  • 重複・競合メモリの自動処理
  • ユーザー別、エージェント別、セッション別のメモリ分離
  • REST APIとPython SDK両方提供

メモリシステムの難しい問題

実装方法を理解したので、次は本当に難しい部分を見てみましょう。

問題1:メモリの競合

# 3年前: "私は東京に住んでいます"
# 今日: "去年大阪に引っ越しました"

# どう処理する?
# オプションA: 最新情報で上書き
# オプションB: タイムスタンプ付きで両方保持
# オプションC: ユーザーに確認を求める

Mem0はLLMを使って競合を検出し、自動的に更新する方式を使います。しかし100%完璧ではありません。

問題2:プライバシー

どこまで記憶すべきか?これは技術的な問題でもあり、倫理的な問題でもあります。

  • ユーザーが記憶の削除を要求できる必要がある(GDPR)
  • センシティブな情報(医療、金融)は特別な取り扱いが必要
  • 別のユーザーのデータと絶対に混在させてはならない
# GDPR準拠のためのメモリ削除
def delete_user_memory(user_id: str):
    m.delete_all(user_id=user_id)
    print(f"ユーザー {user_id} のすべてのメモリを削除完了")

問題3:適切な忘却 — エビングハウスの忘却曲線

心理学者エビングハウスは、学習後の時間経過による記憶の忘却パターンを研究しました。AIメモリにもこれを適用できます。

import math
from datetime import datetime

def calculate_memory_importance(memory: dict) -> float:
    """
    記憶の現在の重要度を計算します。
    時間が経つほど重要度が下がりますが、
    頻繁に参照される記憶は重要度が維持されます。
    """
    days_since_last_access = (
        datetime.now() - memory["last_accessed"]
    ).days
    access_count = memory["access_count"]

    # エビングハウス忘却曲線に基づく計算
    # 基本的な忘却:時間が経つほど低下
    base_retention = math.exp(-days_since_last_access / 30)

    # アクセス頻度による強化(頻繁に参照されるほど忘れにくい)
    reinforcement = math.log(1 + access_count) * 0.3

    return min(1.0, base_retention + reinforcement)

# 重要度が低いメモリを定期的に整理
def prune_memories(user_id: str, threshold: float = 0.1):
    memories = m.get_all(user_id=user_id)
    for memory in memories:
        if calculate_memory_importance(memory) < threshold:
            m.delete(memory["id"])

問題4:スケーラビリティ

何百万人ものユーザーのメモリをどう管理するか?

  • ユーザー別独立したベクターインデックスvs共有インデックス
  • メモリ圧縮(要約によって情報密度を高める)
  • ホット/コールドストレージの分離(よく使う記憶vs古い記憶)
  • シャーディングと分散処理

この領域ではまだベストプラクティスが確立されていません。実際のプロダクションではRedis、Pinecone、Weaviateなどのインフラと組み合わせて使うのが一般的です。


どのメモリ戦略を選ぶべきか

シンプルな意思決定ガイドをお伝えします。

ユースケース推奨メモリ戦略
シンプルなチャットボットConversationBufferWindowMemory(最近k個)
パーソナライズドアシスタント短期+長期(ベクター)の組み合わせ
カスタマーサービスエピソード記憶+長期記憶
コーディングアシスタントプロジェクト別コードベースコンテキスト+短期記憶
プロダクションAIアプリMem0またはカスタム実装+プライバシーレイヤー

まとめ

メモリはAIエージェントを本当に役立つものにするための核心要素です。「また最初から説明しなければならない?」というフラストレーションをなくすことが、AIアシスタントの次のステップです。

今AIプロジェクトをしているなら、メモリシステムを最初から設計に含めてください。後から追加しようとするとずっと難しくなります。

まずはMem0のようなフレームワークから始めてみましょう。自分で実装する前に、すでに解決されている問題を学ぶ良い方法です。

記憶を持つAIは単なるツールではなく、本当のパートナーになれます。その違いは思ったより大きいです。