re:Invent 2025 から帰ってきました。ハロー
アプリケーションサービス部の千葉です。
冷蔵庫を開けて「今日の晩ごはん、何にしよう...」と悩んだこと、ありませんか?
そんな日常の困りごとを解決してくれる"料理アシスタント AI"を作りながら、Amazon Bedrock AgentCore Memory の新機能「Episodic Memory(エピソード記憶)」を検証してみました。
今回の検証は Python インタプリタから直接 MemoryClient を操作して検証してみました。
AgentCore Memory の動作を理解するには、直接 API を叩いてみるのが近道です。
AgentCore Memory とは? 〜 4つの記憶戦略
AgentCore Memory は、AI エージェントに「記憶」を持たせるためのマネージドサービスです。
2025年12月現在、以下の4つの記憶戦略が提供されています。
「料理アシスタント」を例に、それぞれの戦略が何を覚えるか整理してみましょう。
| 戦略 | 何を覚えるか | 料理アシスタントでの例 |
|---|---|---|
| Summary | 会話のあらすじ | 「昨日は豚肉とキャベツで野菜炒めを提案した」 |
| Semantic | 事実・知識 | 「ユーザーの家族は4人構成」「冷蔵庫によく豚肉がある」 |
| User Preference | 好み・傾向 | 「辛い料理が苦手」「和食が好き」 |
| Episodic | 体験の記憶 | 「回鍋肉を提案 → "辛いのは苦手"と言われた → 野菜炒めで成功」という一連の体験 |
Summary / Semantic / User Preference の限界
3つの戦略はそれぞれ便利ですが、以下のような限界があります。
- Summary: 「何を話したか」は覚えているが、「なぜそうなったか」の文脈が薄い
- Semantic: 事実は記録されるが、体験としての学習には向かない
- User Preference: 明示的な好みは覚えるが、失敗から学ぶ仕組みがない
ここで Episodic Memory の出番です。
Episodic Memory が他と違う点
「経験から学ぶ」エージェントへ
Episodic Memory は、会話のやり取りを「エピソード(体験)」として抽出・保存します。
従来の記憶戦略と比較してみましょう。
従来の記憶戦略: ユーザー: 「辛いのは苦手です」 → User Preference に保存 Episodic Memory: [状況] 豚肉とキャベツの料理を聞かれた [意図] おいしい料理を提案したい [結果] 回鍋肉を提案 → 「辛いのは苦手」と断られた [学び] 辛い料理は避けるべき
単に「好み」を記録するのではなく、「どういう状況で、何を提案して、どうなったか」という体験全体を記録します。これにより、エージェントは文脈を踏まえた判断ができるようになります。
リフレクション機能
Episodic Memory には「リフレクション(振り返り)」機能があります。複数のエピソードを横断して「インサイト」を自動生成してくれます。
例えば、複数セッションで以下のようなエピソードが蓄積された場合を考えてみましょう。
セッション1: 回鍋肉を提案 → 「辛いのは苦手」と断られた セッション2: 野菜炒めを提案 → 喜ばれた セッション3: キムチ鍋を提案 → 「辛いのは...」と断られた → リフレクション: 「このユーザーには辛くないレシピを優先すべき」
個別のエピソードからパターンを見出し、より高度な学習を実現します。
実装してみた 〜 冷蔵庫アシスタント
では、実際に Episodic Memory を使った料理アシスタントを作ってみましょう。
事前準備
まず、bedrock-agentcore パッケージをインストールします。
pip install bedrock-agentcore
メモリの作成
from bedrock_agentcore.memory import MemoryClient client = MemoryClient(region_name="us-east-1") memory = client.create_memory_and_wait( name="CookingAssistantMemory", description="冷蔵庫の余り食材から料理を提案するアシスタント用メモリ", strategies=[ { "episodicMemoryStrategy": { "name": "cookingEpisodeStrategy", "namespaces": ["/strategy/{memoryStrategyId}/actor/{actorId}/session/{sessionId}"], "reflectionConfiguration": { "namespaces": ["/strategy/{memoryStrategyId}/actor/{actorId}"] } } } ] ) memory_id = memory.get('id') strategy_id = memory['strategies'][0]['strategyId']
ポイントは以下の2つです。
namespaces: エピソードの保存階層を指定。セッション単位で保存する設定にしていますreflectionConfiguration: リフレクション(振り返り)の生成先を指定。アクター(ユーザー)単位で集約する設定にしています
会話イベントの登録
メモリにイベントを登録するには create_event を使います。messages 引数には (テキスト, ロール) のタプルのリストを渡します。
from datetime import datetime session_id = f"cooking_session_{datetime.now().strftime('%Y%m%d_%H%M%S')}" actor_id = "example_user" # 1往復目:ユーザーの発言とアシスタントの応答をまとめて登録 client.create_event( memory_id=memory_id, actor_id=actor_id, session_id=session_id, messages=[ ("冷蔵庫にキャベツと豚肉があります。何か料理を提案してください", "USER"), ("キャベツと豚肉があれば、回鍋肉、野菜炒め、お好み焼き、蒸し豚などが作れますよ!", "ASSISTANT"), ] )
このように、ユーザーとアシスタントの会話を登録していきます。
残りの会話イベントの登録
同様に、残りの会話も登録していきます。
# 2往復目 client.create_event( memory_id=memory_id, actor_id=actor_id, session_id=session_id, messages=[ ("辛い料理は苦手です。他に何かありますか?", "USER"), ("辛いものが苦手でしたら、塩バター炒め、コンソメスープ、クリーム煮などがおすすめです。", "ASSISTANT"), ] ) # 3往復目 client.create_event( memory_id=memory_id, actor_id=actor_id, session_id=session_id, messages=[ ("野菜炒めを作ってみます。調味料は何を使えばいいですか?", "USER"), ("醤油大さじ1、みりん小さじ1、塩コショウ少々がおすすめです。", "ASSISTANT"), ] ) # 4往復目 client.create_event( memory_id=memory_id, actor_id=actor_id, session_id=session_id, messages=[ ("美味しくできました!ありがとうございます", "USER"), ("よかったです!辛くない優しい味付けがお好みなんですね。また何かあればお気軽にどうぞ。", "ASSISTANT"), ] ) # 5往復目(終了シグナル) client.create_event( memory_id=memory_id, actor_id=actor_id, session_id=session_id, messages=[ ("今日の料理相談はこれで終わりです", "USER"), ("ありがとうございました。またいつでもご相談ください!", "ASSISTANT"), ] )
最後のメッセージ「今日の料理相談はこれで終わりです」がポイントです。この終了シグナルについては次のセクションで説明します。
注意点:エピソード生成には時間がかかる
Episodic Memory を使う上で、最も重要な注意点があります。
エピソードの生成には時間がかかります。
他の戦略(Summary 等)は会話中にリアルタイムで記録されますが、Episodic Memory は会話の「終了」を検知してからエピソードを生成します。
# 会話直後に検索しても... episodes = client.retrieve_memories( memory_id=memory_id, namespace=f"/strategy/{strategy_id}/actor/{actor_id}/session/{session_id}", query="料理" ) print(f"エピソード数: {len(episodes)}") # → 0件
会話直後は 0 件です。今回の検証では約10分でエピソードが生成されました。(公式ドキュメントでは最大1時間程度かかる場合があると記載されています)
なぜ時間がかかるのか?
Episodic Memory は以下のステップで処理されます。
- 抽出(Extraction): 進行中のエピソードを分析して完了を判定
- 統合(Consolidation): エピソード完了時に抽出結果を単一レコードに統合
- リフレクション(Reflection): エピソード間でインサイトを生成
この処理には時間がかかるため、リアルタイム性が求められる用途には向きません。
会話の終了を明確にする
「会話の終了」を明確にするため、最後のメッセージで終了シグナルを送ることを推奨します。
良い例:「今日の相談はこれで終わりです」 曖昧な例:「ありがとう」
エピソードの確認
約10分待ってから再度検索すると、エピソードが生成されていることを確認できます。
# 約10分後に再度検索 episodes = client.retrieve_memories( memory_id=memory_id, namespace=f"/strategy/{strategy_id}/actor/{actor_id}/session/{session_id}", query="料理" ) print(f"エピソード数: {len(episodes)}") # → 1件
生成されたエピソードには、会話全体から抽出された「学び」が含まれています。
import json for episode in episodes: content = json.loads(episode['content']['text']) print(f"状況: {content['situation']}") print(f"意図: {content['intent']}") print(f"振り返り: {content['reflection']}")
実際に生成されたエピソードの一部を見てみましょう。
※: 実際の出力は英語です。あくまで一例として
状況: ユーザーは冷蔵庫にある食材(キャベツと豚肉)を使った料理の提案を求めていた
意図: 美味しい料理を提案したい
振り返り: 最初に幅広い選択肢を提示し、ユーザーの好み(辛いものが苦手)が
分かった時点で具体的なレシピに絞り込む流れが効果的だった。
調味料の分量を具体的に示したことで、ユーザーは成功体験を得られた。
このように、単なる会話ログではなく「どういう状況で、何をして、どうなったか」という体験が構造化されて保存されます。
なお、今回の検証は「エピソードの保存と検索」までを対象としています。実際にエージェントがこのエピソードを参照して応答を生成するには、retrieve_memories で取得したエピソードを LLM のプロンプトに含める実装が別途必要です。
リフレクションの確認
リフレクション(振り返り)は、複数のエピソードを横断してインサイトを生成する機能です。メモリ作成時に reflectionConfiguration で指定した namespace に保存されます。
# REFLECTION を取得 reflections = client.retrieve_memories( memory_id=memory_id, namespace=f"/strategy/{strategy_id}/actor/{actor_id}", query="reflection" ) print(f"リフレクション数: {len(reflections)}")
リフレクションが生成されている場合、以下のように内容を確認できます。
for r in reflections: content = json.loads(r['content']['text']) record_type = r.get('metadata',{}).get('x-amz-agentcore-memory-recordType', {}).get('stringValue') if record_type == 'REFLECTION': # 正しいキー名を使用 print(f"タイトル: {content.get('title')}") print(f"ユースケース: {content.get('use_cases')}") print(f"ヒント: {content.get('hints')}") print(f"確信度: {content.get('confidence')}")
リフレクションは複数のエピソードが蓄積された後に生成されるため、単一セッションの検証では確認が難しい場合があります。
複数セッションで異なる体験を登録し、時間を置いてから検索することで、パターンを見出したインサイトが生成されることを確認できます。
いつ Episodic を使うべきか? 〜 戦略選択フローチャート
4つの戦略をどう使い分ければよいでしょうか?以下のフローチャートを参考にしてください。
「好み」を覚えたい? └─ YES → User Preference 「事実・知識」を蓄積したい? └─ YES → Semantic 「会話の要約」が欲しい? └─ YES → Summary 「体験から学習」させたい? └─ YES → Episodic 迷ったら? └─ 複数の戦略を組み合わせる
Episodic が向いているユースケース
| ユースケース | 理由 |
|---|---|
| カスタマーサポート | 過去の問い合わせ対応の成功/失敗を学習 |
| コードアシスタント | 「このエラーは前回こう解決した」を記憶 |
| トラブルシューティング | 診断フローの最適化 |
| 料理アシスタント | 提案が受け入れられたかを学習 |
共通点は「成功/失敗のパターンから学習させたい」という点です。
Episodic が向いていないユースケース
一方、以下のケースでは他の戦略の方が適しています。
- 即時性が求められる: リアルタイムで記憶を参照したい → Summary / Semantic
- 単純な好みの記録: 「辛いのは苦手」だけ覚えればよい → User Preference
- 事実の蓄積: 「家族は4人」「予算は1000円」→ Semantic
まとめ
| 項目 | 内容 |
|---|---|
| Episodic Memory とは | 会話を「エピソード(体験)」として保存し、エージェントに"経験から学ぶ"能力を与える |
| 他の戦略との違い | Summary/Semantic/User Preference は「何を覚えるか」、Episodic は「体験として学ぶ」 |
| リフレクション機能 | 複数エピソードからパターンを見出し、インサイトを自動生成 |
| 注意点 | エピソード生成に時間がかかる(今回は約10分、最大1時間程度) |
| 向いているケース | 成功/失敗のパターンから学習させたいエージェント |
「冷蔵庫の余りもので何作ろう?」という日常の悩みを解決しながら、AgentCore Memory の Episodic 戦略を検証しました。
エージェントに「経験」を持たせることで、単なる Q&A ボットから「学習するアシスタント」へ進化させられます。
皆さんもぜひ試してみてください。
後片付け:メモリの削除
検証が終わったら、作成したメモリを削除しておきましょう。
# メモリを削除
client.delete_memory(memory_id=memory_id)
削除には少し時間がかかります。ステータスが DELETING から消えれば完了です。