ANTIGRAVITY LABEN
記事一覧/Agents & Manager
Agents & Manager/2026-04-19上級

AIエージェントのメモリ設計:会話を超えた文脈管理の4つのパターン

会話が終わると消える揮発性のやり取りを超えて、AIエージェントが「覚えている」ようにする設計パターンを4つ整理。実装コードと運用上の落とし穴を合わせて解説します。

AIエージェント36メモリ設計RAG5ベクトルDB実装パターン

エージェントを作り始めてすぐにぶつかる壁は、「昨日話したことを今日また説明しないといけない」という問題です。APIコールのたびにコンテキストがリセットされるのは仕様どおりですが、実用的なエージェントを作るには、何らかの形で「記憶」を設計する必要があります。

ここでは、実際に自分が実装してきたメモリアーキテクチャを4つのパターンに整理してお伝えします。どれが正解という話ではなく、ユースケースに応じて選択するものです。

パターン1: セッション内圧縮(短期メモリ)

最もシンプルなアプローチで、会話が長くなってきたときにコンテキストウィンドウの使用量を抑える手法です。

考え方はシンプルです。会話が一定のターン数(または一定のトークン数)を超えたとき、古いやり取りを要約して1つのブロックに圧縮し、詳細な履歴の代わりに要約を先頭に置きます。

def compress_history(messages: list[dict], keep_recent: int = 6) -> list[dict]:
    if len(messages) <= keep_recent:
        return messages
    
    old_messages = messages[:-keep_recent]
    recent_messages = messages[-keep_recent:]
    
    # 古いメッセージを要約
    summary_prompt = [
        {"role": "user", "content": f"以下の会話を3〜5文で要約してください:\n{format_messages(old_messages)}"}
    ]
    summary = call_llm(summary_prompt)
    
    # 要約を先頭に挿入して返す
    compressed = [
        {"role": "system", "content": f"これまでの会話の要約:{summary}"}
    ] + recent_messages
    
    return compressed

このパターンの限界は、セッションをまたいだ記憶の保持ができない点です。「昨日の続き」には対応できません。ただし、外部ストレージが不要で実装が軽いため、プロトタイプや単一セッション完結のユースケースには十分機能します。

パターン2: キー・バリューストアによる事実の永続化

ユーザーが教えてくれた情報(名前、好み、設定値、プロジェクトの詳細)を永続的に保存するパターンです。

重要なのは、何を保存するかをエージェント自身に判断させることです。すべての発言を保存するのではなく、後で参照価値がある「事実」だけを抽出します。

def extract_and_store_facts(user_message: str, agent_response: str, user_id: str):
    extraction_prompt = f"""
以下の会話から、後で参照すべき永続的な事実を抽出してください。
一時的な内容(「今日は疲れた」など)は除外し、
ユーザーについての固定情報や設定のみを抽出してください。
 
ユーザー: {user_message}
アシスタント: {agent_response}
 
抽出すべき事実があれば JSON 形式で返してください。なければ空配列を返してください。
形式: [{"key": "事実の種類", "value": "内容"}]
"""
    facts = call_llm_json(extraction_prompt)
    
    for fact in facts:
        key = f"user:{user_id}:{fact['key']}"
        redis_client.set(key, fact['value'], ex=86400*365)  # 1年

このパターンで注意が必要なのは「古くなった事実」の処理です。「東京に住んでいる」という事実が3ヶ月前に記録されていても、今は違うかもしれません。TTLを設定するか、事実の更新日を記録して、古すぎる情報は再確認するロジックを組み込むことをお勧めします。

パターン3: ベクトル検索による意味的な記憶想起

最もよく耳にするパターンで、過去の会話や文書をベクトルDBに保存し、現在の質問と意味的に近い情報を検索して文脈に挿入する手法です。

実装自体はシンプルです。問題は「何をインデックスするか」と「どのタイミングで検索するか」の設計です。

from openai import OpenAI
import chromadb
 
client = OpenAI()
chroma = chromadb.Client()
collection = chroma.get_or_create_collection("agent_memory")
 
def store_exchange(user_msg: str, agent_msg: str, session_id: str):
    # 会話ペアをチャンクとして保存
    text = f"ユーザー: {user_msg}\nアシスタント: {agent_msg}"
    embedding = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    ).data[0].embedding
    
    collection.add(
        documents=[text],
        embeddings=[embedding],
        ids=[f"{session_id}_{int(time.time())}"],
        metadatas=[{"session_id": session_id, "timestamp": time.time()}]
    )
 
def recall_relevant_memory(query: str, n_results: int = 3) -> str:
    query_embedding = client.embeddings.create(
        model="text-embedding-3-small",
        input=query
    ).data[0].embedding
    
    results = collection.query(
        query_embeddings=[query_embedding],
        n_results=n_results
    )
    
    if not results["documents"][0]:
        return ""
    
    return "関連する過去の会話:\n" + "\n---\n".join(results["documents"][0])

実際に運用してみて気づいた落とし穴は2つです。

落とし穴1: 関係のない情報が召喚される

類似度スコアに閾値を設けないと、「なんとなく近い」過去の会話が毎回挿入されて、むしろ回答の精度が下がります。collection.query の結果に distances が含まれるので、しきい値(例:コサイン類似度 0.75以上)を下回るものは除外する処理が必須です。

落とし穴2: 矛盾する情報が複数ヒットする

同じトピックについて時系列で異なる情報が記録されていると、古い情報と新しい情報が同時に検索にヒットします。タイムスタンプで降順ソートして、最新の情報を優先する設計が必要です。

パターン4: エピソード記憶としての構造化ログ

最も設計コストが高いですが、「何をいつ誰と話したか」という構造を持ったメモリです。単なるテキスト検索ではなく、「先週のプロジェクトXに関する議論を要約して」のような時間軸や文脈軸での検索を可能にします。

from dataclasses import dataclass
from datetime import datetime
 
@dataclass
class Episode:
    session_id: str
    timestamp: datetime
    topic: str          # LLMで自動抽出
    participants: list  # ユーザーID等
    summary: str        # LLMで自動生成
    key_decisions: list # 決定事項の抽出
    follow_ups: list    # 次回に持ち越す事項
 
def create_episode(messages: list[dict], session_id: str) -> Episode:
    # セッション終了時に自動生成
    analysis_prompt = f"""
以下の会話を分析し、JSON形式で構造化してください:
- topic: 主なトピック(1文)
- summary: 会話全体の要約(3〜5文)
- key_decisions: 決定したこと(リスト)
- follow_ups: 次回に確認・継続すべきこと(リスト)
 
会話:
{format_messages(messages)}
"""
    structured = call_llm_json(analysis_prompt)
    
    return Episode(
        session_id=session_id,
        timestamp=datetime.now(),
        topic=structured["topic"],
        participants=["user"],
        summary=structured["summary"],
        key_decisions=structured.get("key_decisions", []),
        follow_ups=structured.get("follow_ups", [])
    )

このパターンは、定期的に長期的な関係を持つユーザーへの対応や、プロジェクト管理系エージェントに特に有効です。「先月決めたアーキテクチャの方針は何でしたっけ」という問いに、ベクトル検索だけでは正確に答えられない場合でも、エピソード構造があれば検索精度が上がります。

4パターンの使い分けガイド

実際には、これらを組み合わせて使うことが多いです。私が個人開発のエージェントで使っているのは「パターン2(事実の永続化)+パターン3(ベクトル検索)」の組み合わせです。ユーザーの基本情報はKey-Valueに、会話の流れはベクトルDBに保存する構成です。

パターンセッション持続長期記憶構造的な検索実装コスト
1. セッション内圧縮
2. KVストア事実保存低〜中
3. ベクトル検索
4. エピソード記憶

判断の出発点として: 「ユーザーが明示的に教えた情報を覚えたい」ならパターン2から始めてください。「過去の会話の流れを参照したい」ならパターン3を追加する、という順序が最もスムーズに進みます。パターン4は「このエージェントを1年以上継続的に使う予定がある」場合に初めて検討するくらいの設計コストです。

メモリ設計で最初に決めること

どのパターンを選ぶにしても、最初に決めておくべきことが1つあります。それは「何を忘れていいか」です。

すべてを記憶しようとすると、関係のない情報がノイズになります。「このエージェントが覚えているべきことは何か」を先に定義し、それ以外は積極的に捨てる設計にすることで、メモリの精度と維持コストの両方が改善されます。

次のセッションでは、ユーザーが「覚えていてほしい」と感じる情報の1つを選んで、パターン2の実装から試してみてください。動くものができてから、必要に応じてパターン3を重ねるのがスムーズな進め方だと思います。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Agents & Manager2026-06-30
Android CLI が3倍速・トークン7割減になったとき、増やすべきは並列数ではなく1件あたりの検証だった
Android CLI のエージェントが3倍速・トークン約7割減になった、というニュースを見て最初に考えたのは「では何本同時に走らせようか」でした。けれど速くなって本当に変わるのは生産量ではなく、詰まる場所です。レビューと検証ゲートに移ったボトルネックを Little's Law で見積もり、WIP キャップで並列数を抑え、浮いた予算を1件あたりの検証に回す設計を、動く Python 実装と実測値でまとめました。
Agents & Manager2026-06-30
1,400箇所の置換を1コミットで投げてきたエージェントに、バッチで返してもらう設計
Antigravity に大規模な機械的置換(codemod)を任せると、レビュー不能な巨大1コミットが返ってくることがあります。ast-grep のルールと検証付きバッチドライバで、置換を「機械の仕事」と「人の確認」に分け、安全に通す設計を実装つきでまとめました。
Agents & Manager2026-06-24
外部ページに紛れ込んだ指示で無人エージェントが動いてしまう前に — 入力の汚染を追跡して権限を落とす設計
無人実行のエージェントが取得した外部ページやPDFに指示が紛れていても乗っ取られないよう、入力の汚染を追跡して副作用ツールの権限を落とす設計を、動くPythonコードと運用の実測値つきで解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →