AIコーディング 2026.05.02

AIエージェントが会話を忘れる原因と解決方法【RAG・メモリ設計3つの実装パターン】

タグ:AI / エージェント / RAG / メモリ / 開発

何が起こっているか:AIエージェントの「記憶喪失」

ChatGPT APIや生成AIを使ったエージェント(自動で判断して動作するプログラム)を運用していると、こんな経験はないでしょうか。

  • 10分前の会話の内容をエージェントが忘れている
  • ユーザーが「さっき言ったあの件」と言っても、過去のやり取りが反映されない
  • チャットボットが同じ質問を何度もしてきた

これは「セッション間記憶喪失」と呼ばれる問題です。LLMは本質的にステートレス(状態を持たない)であり、エージェントが新しいセッションを開始すると、前のセッションの文脈が完全に失われてしまいます。

過去の会話を参照させるには、毎回すべてのメッセージ履歴をAPIに送信する必要があり、これがトークン使用量(=コスト)とレスポンス時間に直結します。適切なメモリアーキテクチャを設計することで、以下の改善が期待できます。

  • トークン使用量を30〜50%削減:冗長なメモリ管理を排除
  • レスポンス時間を短縮:不要な履歴データの通信を削減
  • スケーラビリティの向上:数百〜数千ユーザーの同時会話に対応

従来、この問題を解決するために多くの開発者が採用していたのが RAG(検索補強生成) でした。過去のやり取りをデータベースに保存しておいて、新しい質問が来たときに関連する過去の情報を検索して、生成AIに渡すという方法です。

しかし実運用では、RAGにも大きな弱点があることがわかってきました。

RAGは「検索」でしかない

開発者の間で最近よく指摘されているのが、「RAGは検索であって、記憶ではない」 という認識です。RAGは基本的に「キーワードやベクトル(数値化された意味)の類似性で過去の情報を探す」という仕組みに過ぎません。

たとえば、ユーザーが「あのプロジェクトの進捗は?」と聞いたとき、RAGは「プロジェクト」というキーワードで検索します。でも、前のセッションでそのプロジェクトの具体的な名前が出ていなかったら、検索に引っかかりません。つまり、RAGは「キーワードに引っかかる情報」しか持ってこられないという限界を持っています。

さらに、大量のドキュメントの中からエージェントが必要な情報を見つけ出すことは、単なる「検索精度」の問題ではなく、「構造と文脈」の理解にかかっているケースが多いです。つまり、単に似ている情報を持ってくるだけでは不十分なのです。

戦略1:グラフ構造を使った関連情報の追跡

最近注目されているアプローチが、グラフ走査(グラフ traversal) を使った方法です。これは、情報をノード(点)とエッジ(つながり)で構造化し、関連情報を「つながりに沿って」探していくという手法です。

RAGのように「似ているものを検索する」のではなく、「ユーザーが言及した実体(人物、プロジェクト、商品など)から出発して、その周辺のつながった情報を次々と辿っていく」というイメージです。

たとえば:

  • ユーザーが「太郎のプロジェクト」と言及
  • グラフから「太郎」というノードを見つける
  • そのノードから「プロジェクトに参加」というエッジをたどる
  • 関連するプロジェクトのノードに到達
  • そのプロジェクトから「予算」「期限」「チームメンバー」といった属性情報を取得

この方式は、RAGのように「キーワード検索で引っかかるかどうか」に依存しません。明示的な関連性をたどるので、より正確に必要な文脈を取得できるわけです。

戦略2:メモリアーキテクチャの3つのパターン

会話メモリの管理には3つの主流パターンがあります。スキーマ設計(エージェントが生成する出力の形式を統一すること)と組み合わせることで、信頼性をさらに向上させられます。

パターン1:全履歴保持型(Stateful Memory)

すべてのメッセージ履歴をメモリに保持し、毎回APIに送信します。実装が最もシンプルで文脈を完全に保持できる一方、トークン使用量が増加し続けるため、短期会話(5〜10往復程度)に向いています。

from langchain.memory import ConversationBufferMemory
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationChain

memory = ConversationBufferMemory()
llm = ChatOpenAI(model_name="gpt-4", temperature=0.7)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)

response1 = conversation.run(input="こんにちは")
response2 = conversation.run(input="さっきの話の続きは?")

パターン2:要約型(Summarization Memory)

古いメッセージを要約し、トークン数を制限しながら文脈を保持します。トークン使用量を50%程度削減できますが、古い詳細情報が失われる可能性があります。中期会話(10〜50往復)に向いています。

from langchain.memory import ConversationSummaryMemory

memory = ConversationSummaryMemory(llm=llm)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)

パターン3:検索型(Retrieval-Augmented Memory)

会話履歴をベクトルデータベースに保存し、関連性の高いメッセージだけを検索して参照します。スケーラビリティが高く長期会話(50往復以上)に対応できますが、実装が複雑で外部データベースが必要です。

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.memory import VectorStoreRetrieverMemory

embeddings = OpenAIEmbeddings()
vectorstore = Chroma(embedding_function=embeddings, persist_directory="./chroma_db")
memory = VectorStoreRetrieverMemory(
    retriever=vectorstore.as_retriever(),
    llm=ChatOpenAI(model_name="gpt-4")
)

どのパターンを選ぶか

パターン向いているシナリオトークン効率実装難度
全履歴保持型短期会話(5〜10往復)低い簡単
要約型中期会話(10〜50往復)中程度中程度
検索型長期会話(50往復以上)高い難しい

プロトタイプ・MVPフェーズは全履歴保持型でシンプルに始め、本番運用・スケール段階で検索型に移行するのが現実的なアプローチです。

戦略3:エージェントのライフサイクル全体で情報を保持する

一般的に「エージェントの実行が終わった = タスク完了」と考えられています。しかし実は、エージェントの実行(ラン)が完了した後にやることがあります。

エージェントが「生成AIがもう返答を止めた」という状態に到達しても、実は重要な情報整理が済んでいないケースが多いです。たとえば:

  • セッション中に出た決定事項を整理して、構造化形式で保存する
  • 新しく得られた情報を既存のグラフに追加する
  • 矛盾や未解決の課題をマークしておく

このような「実行終了後の処理」を組み込むことで、次のセッションがより確実な情報を参照できるようになります。また、メモリをアプリケーション再起動後も失わないよう永続化することも重要です。JSONファイルへの保存(小規模・シングルユーザー向け)、SQLiteデータベース(複数ユーザー向け)、ベクトルデータベース(検索型メモリの永続化)の3つの方法が代表的です。

実装のポイント

これら3つの戦略を組み合わせるなら:

  1. 前のセッションの情報をグラフ構造で保持する:単なるテキスト保存ではなく、実体と関連性を明示的に記録
  2. 新しいセッションの入力をグラフの既存ノードに接続する:「太郎」と言われたら、過去に出たノードの「太郎」かどうかを確認
  3. エージェントの出力をスキーマに従わせる:決まった形式で、構造化されたデータとして保存
  4. セッション終了時に情報を整理・記録する:ラン完了後も、得られた情報をグラフに統合する処理を走らせる

会話の長さに応じてメモリパターンを動的に切り替える仕組みも有効です。

def choose_memory(conversation_length):
    if conversation_length < 10:
        return ConversationBufferMemory()
    elif conversation_length < 50:
        return ConversationSummaryMemory(llm=llm)
    else:
        return VectorStoreRetrieverMemory(retriever=vectorstore.as_retriever(), llm=llm)

注意点と現実的な課題

ただし、これらの戦略にも実装上の課題があります。

グラフ構造は設計が複雑になりやすく、小規模なプロジェクトでは過剰設計になる可能性があります。また、スキーマ設計を厳密にしすぎると、予期しない状況に対応しにくくなるというトレードオフもあります。

要約型メモリは、要約処理のために追加のAPI呼び出しが発生し、かえってコストが増加するケースもあります。多数のユーザーから同時に会話を送信するとAPIレート制限に引っかかることもあるため、指数バックオフなどの対策が必要です。

実務では、「完璧な構造化」を目指すのではなく、「よく出現するパターンは構造化し、珍しいケースは柔軟に対応する」という段階的なアプローチが現実的と思われます。

まとめ

AIエージェントの記憶喪失問題は、単に「過去の情報を検索できればいい」という次元ではなく、「情報をどう構造化し、どう関連付け、どう保持するか」という設計問題です。RAGだけでは不足しており、グラフ構造、適切なメモリパターンの選択、ライフサイクル管理を組み合わせることで、より堅牢なエージェントが実現できるようになります。


あわせて読みたい

参考ソース