「なぜこのエージェントは昨日の会話を覚えていないんだ」
AIエージェントを本番運用している開発者から、最もよく届くフィードバックです。LLM単体では会話が終わると記憶がリセットされる。それをどう補うかが、エージェント設計の核心です。
実際に10社以上のAIエージェント導入を支援する中で気づいたのは、メモリ設計を後回しにしたプロジェクトはほぼ例外なく「使いにくい」と評価される、ということでした。この記事では、AIエージェントのメモリを3つの層に分けて設計する手法と、LangChain・LangGraphを使ったPythonコードを全公開します。
AIエージェントの基本的な構築パターンについては、AIエージェント構築完全ガイドでも体系的に解説しています。
AIエージェントの3層メモリとは何か
AIエージェントのメモリは「いつまで保持するか」と「何を保持するか」の2軸で整理できます。
| メモリ層 | 保持期間 | 容量 | 用途 | 実装手段 |
|---|---|---|---|---|
| 短期メモリ(Short-Term) | セッション内 | 最大128Kトークン程度 | 現在の会話・タスクの文脈 | コンテキスト窓 / LangGraph checkpointer |
| 長期メモリ(Long-Term) | セッション間(永続) | 無制限 | ユーザー情報・ドメイン知識・過去の成功パターン | ベクトルDB / PostgresStore |
| エピソード記憶(Episodic) | 永続(時系列) | 無制限 | 過去の体験・失敗例・成功パターン | 時系列DB / Few-shotプロンプト |
この3層を適切に組み合わせることで、エージェントは「今の会話」を処理しながら「ユーザーの好み」を学習し、「過去に同じ失敗をしたこと」を活かせるようになります。
短期メモリの実装:LangGraph Checkpointer
短期メモリはセッション内の文脈を保持します。LangGraphでは MemorySaver を使うのが最も手軽です。
以下のコードは、複数ターンの会話でも文脈が保持される基本エージェントを構築します。
# 動作環境: Python 3.11+, langgraph>=0.2.0, langchain-anthropic>=0.3.0
# pip install langgraph langchain-anthropic
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.memory import MemorySaver
from langchain_anthropic import ChatAnthropic
import os
# 注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。
model = ChatAnthropic(
model="claude-sonnet-4-6",
api_key=os.environ["ANTHROPIC_API_KEY"] # ハードコード禁止、必ず環境変数で
)
def call_model(state: MessagesState):
messages = state["messages"]
response = model.invoke(messages)
return {"messages": response}
builder = StateGraph(MessagesState)
builder.add_node("model", call_model)
builder.add_edge(START, "model")
# checkpointerを設定することでセッション内のメモリが保持される
memory = MemorySaver()
graph = builder.compile(checkpointer=memory)
# thread_idを固定することで同一会話スレッドとして扱われる
config = {"configurable": {"thread_id": "user-123-session-1"}}
response1 = graph.invoke(
{"messages": [("user", "私の名前は田中です")]},
config
)
print(response1["messages"][-1].content) # 「田中さん...」と返す
response2 = graph.invoke(
{"messages": [("user", "さっき名乗った名前を教えて")]},
config
)
print(response2["messages"][-1].content) # 「田中さんとおっしゃっていました」と返す
ポイント:
thread_idがセッションの単位。ユーザーIDとセッションIDを組み合わせると管理しやすい- 本番環境では
MemorySaver(オンメモリ)ではなくSqliteSaverやPostgresSaverに切り替える - コンテキスト窓の上限に注意。長大な会話は要約(Summarization)で圧縮する
長期メモリの実装:InMemoryStore と Namespace 設計
長期メモリはセッションをまたいで情報を保持します。LangGraphの InMemoryStore(開発用)と PostgresStore(本番用)で実装できます。
以下は、ユーザーの好みを永続化してエージェントが参照できるようにするコードです。
# 動作環境: Python 3.11+, langgraph>=0.2.30
# pip install langgraph psycopg2-binary # 本番はpsycopg2-binary追加
from langgraph.store.memory import InMemoryStore
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.checkpoint.memory import MemorySaver
import uuid
# 開発用: InMemoryStore(本番は PostgresStore に変更)
store = InMemoryStore()
def save_user_preference(user_id: str, preference: dict):
"""ユーザーの好みを長期メモリに保存"""
namespace = (user_id, "preferences")
memory_id = str(uuid.uuid4())
store.put(namespace, memory_id, preference)
def get_user_preferences(user_id: str) -> list:
"""ユーザーの好みを長期メモリから取得"""
namespace = (user_id, "preferences")
memories = store.search(namespace)
return [m.value for m in memories]
# 使用例
user_id = "user-123"
save_user_preference(user_id, {
"language": "Python",
"framework": "LangChain",
"coding_style": "関数型、型アノテーションを重視"
})
# エージェント呼び出し時に長期メモリを参照してシステムプロンプトに注入
prefs = get_user_preferences(user_id)
system_prompt = f"""あなたはコーディングアシスタントです。
このユーザーの好み: {prefs}
上記を考慮してコードを提案してください。"""
本番切り替え(PostgresStore):
# pip install langgraph-postgres
from langgraph.store.postgres import PostgresStore
store = PostgresStore(
connection_string=os.environ["DATABASE_URL"] # 環境変数で管理
)
store.setup() # テーブルの自動作成
ポイント:
- Namespace は
(user_id, "category")で設計するとスコープが明確になる - 長期メモリへの書き込みは毎ターン行うとコストがかさむ。セッション終了時や重要イベント時に絞る
- ベクトル検索を使う場合は
store.search(namespace, query="...")で意味的に近い記憶を取得できる
エピソード記憶の実装:Few-Shot と時系列ログ
エピソード記憶は「過去の体験」を体系的に保存・参照する仕組みです。実装方法は2つあります。
方法1: 動的Few-Shotプロンプト(推奨)
# 動作環境: Python 3.11+, langchain>=0.3.0, langchain-chroma>=0.1.0
# pip install langchain langchain-chroma langchain-anthropic
from langchain_chroma import Chroma
from langchain_anthropic import AnthropicEmbeddings
import json
# エピソード記憶ストア(Chromaを使用、本番はPineconeやWeaviateも可)
# 注意: 本番環境で使用する前に、必ずテスト環境で動作確認してください。
episode_store = Chroma(
collection_name="agent_episodes",
embedding_function=AnthropicEmbeddings(
model="voyage-3", # Anthropicの埋め込みモデル
api_key=os.environ["ANTHROPIC_API_KEY"]
)
)
def save_episode(task: str, approach: str, result: str, success: bool):
"""タスク実行の体験をエピソード記憶として保存"""
episode = {
"task": task,
"approach": approach,
"result": result,
"success": success,
"timestamp": "2026-04-09"
}
episode_store.add_texts(
texts=[f"タスク: {task}nアプローチ: {approach}n結果: {result}"],
metadatas=[episode]
)
def recall_episodes(current_task: str, k: int = 3) -> list:
"""現在のタスクに類似した過去のエピソードを取得(Few-Shot)"""
similar_docs = episode_store.similarity_search(current_task, k=k)
return [doc.metadata for doc in similar_docs]
# エージェントがタスク実行前に類似体験を参照
episodes = recall_episodes("Pythonコードのバグ修正")
few_shot_examples = "n".join([
f"過去の体験: {e['task']} → {e['approach']} → {'成功' if e['success'] else '失敗'}"
for e in episodes
])
ポイント:
- 失敗エピソードを積極的に保存すると、同じ過ちを繰り返しにくくなる
- 類似タスクのFew-Shotをシステムプロンプトに注入することでエージェントの精度が向上する
- エピソードが増えすぎたら古いもの・失敗率の高いものを定期的に削除するメンテナンス処理も設計する
3層メモリを組み合わせた実装パターン
実際のエージェントでは3層を統合します。以下は統合パターンのコード骨格です。
# 統合メモリ管理クラス(コード骨格)
# 動作環境: Python 3.11+, langgraph>=0.2.30
class AgentMemoryManager:
def __init__(self, user_id: str, store, episode_store):
self.user_id = user_id
self.store = store # 長期メモリ(InMemoryStore or PostgresStore)
self.episode_store = episode_store # エピソード記憶(Chroma等)
def build_context(self, current_task: str) -> str:
"""タスク実行前に3層メモリから文脈を構築"""
# 1. 長期メモリ: ユーザーの好みや知識
prefs = get_user_preferences(self.user_id)
# 2. エピソード記憶: 類似タスクの過去体験
episodes = recall_episodes(current_task, k=3)
# 3. 短期メモリはLangGraphのcheckpointerが自動管理
return f"""
ユーザー情報: {json.dumps(prefs, ensure_ascii=False)}
関連する過去の体験:
{chr(10).join([f"- {e['task']}: {e['approach']}" for e in episodes])}
"""
def after_task(self, task: str, approach: str, result: str, success: bool):
"""タスク完了後のメモリ更新"""
# エピソード記憶に保存
save_episode(task, approach, result, success)
# 重要な発見があれば長期メモリにも保存
if success and "重要" in result:
save_user_preference(self.user_id, {"learned": f"{task}の解法: {approach}"})
【要注意】よくある設計ミスと回避策
失敗1: 全てをコンテキスト窓に詰め込む
❌ 会話履歴を全件コンテキストに渡す
⭕ 直近N件 + 重要発言のみを抽出してコンテキストに渡す
なぜ重要か: コンテキスト窓は有限。128Kトークンを超えると古い情報が切り捨てられ、しかもコストが爆発します。「ConversationSummaryMemory」で要約してから渡すのが定石です。
失敗2: 長期メモリの書き込みタイミングが毎ターン
❌ 全ての会話で長期メモリに書き込む
⭕ セッション終了時・重要イベント検出時のみ書き込む
なぜ重要か: 毎ターン書き込むとDBのコストとレイテンシが増大し、記憶のノイズも増えます。「この情報は長期保存すべきか?」の判断ロジックをエージェント自身に実装するとスマートです。
失敗3: Namespaceを設計せずに全ユーザーでメモリを共有
❌ store.put("memories", id, data) でフラットに保存
⭕ store.put((user_id, "category"), id, data) でスコープを分離
なぜ重要か: ユーザーAの記憶がユーザーBに漏れるという致命的なプライバシー問題が発生します。Namespaceは設計初期に必ず決める。
失敗4: エピソード記憶のメンテナンスを忘れる
❌ エピソードを追加するだけで削除しない
⭕ 古いエピソードや失敗パターンの多いエピソードを定期的にパージ
なぜ重要か: エピソードが増えすぎると類似検索の精度が低下し、古い情報に引きずられた判断をするようになります。
セキュリティと運用ルール
| 項目 | 推奨設定 |
|---|---|
| APIキー管理 | 環境変数または Secret Manager(ハードコード絶対NG) |
| PII(個人情報)のマスキング | 長期メモリ保存前に氏名・メアドをマスク処理 |
| メモリのTTL設定 | エピソード記憶は90日で自動削除するCronを設定 |
| アクセス制御 | user_id でNamespaceを分離。API認証を必ず通す |
| 監査ログ | メモリの読み書きをログに記録(規制業種は必須) |
参考・出典
- Memory – Docs by LangChain — LangChain公式(参照日: 2026-04-09)
- Memory for agents – LangChain Blog — LangChain Blog(参照日: 2026-04-09)
- Agent Memory in LangChain: Short-Term, Long-Term, and Episodic — Propelius Tech(参照日: 2026-04-09)
- AIエージェントのメモリ系プロジェクト比較(2026年1月) — Zenn(参照日: 2026-04-09)
まとめ:今日から始める3つのアクション
- 今日やること: LangGraphの
MemorySaverを使って短期メモリを実装し、同一スレッドで文脈が保持されることを確認する - 今週中:
InMemoryStoreでユーザー情報の長期メモリを追加。Namespaceを(user_id, "category")で設計する - 今月中: エピソード記憶をChromaで実装し、類似タスクのFew-Shot注入が精度に影響するか検証する
あわせて読みたい:
- AIエージェント構築完全ガイド — メモリ設計の前に読む基礎
- Dify × Claude Code連携ガイド — ノーコードでメモリ機能を実装する方法
この記事はAIgent Lab編集部がお届けしました。AIエージェントの導入・構築支援については 株式会社Uravation(お問い合わせフォーム) からご相談ください。