プロローグ:なぜAIには「記憶」が必要なのか?
君が作ったチャットボットを想像してみてほしい。ユーザーと素晴らしい対話をし、信頼関係を築きかけたその瞬間、ブラウザが誤ってリロードされる。次の瞬間、ボットはこう言うんだ。「初めまして、何かお手伝いしましょうか?」
これは、ユーザー体験(UX)における悪夢だ。まるで親友だと思っていた相手に、翌日会ったら顔も名前も忘れられていたような絶望感を与える。どうだろう、こんな経験はないだろうか?
「ステートレス」なLLMの弱点
実務の現場で散見されるのは、この「記憶」の設計がおろそかにされているケースだ。基本的に、ChatGPTのAPIを含む大規模言語モデル(LLM)はステートレス(状態を持たない)システムである。彼らは過去のやり取りを一切覚えていない。毎回のリクエストは、彼らにとって常に「新しい出会い」なのだ。
私たちがChatGPTやClaudeのWeb画面で会話が続いているように感じるのは、裏側でシステムが過去のやり取りを全て束ねて、毎回プロンプトとして送信し直しているからに過ぎない。この仕組みを自前のアプリケーションに適切に組み込まなければ、君のAIは永遠に「健忘症」のままだ。最新のChatGPTの最新モデルシリーズのような高度なモデルであっても、API経由で利用する限りこの原則は変わらない。
ユーザー体験を損なう「記憶喪失」の正体
ビジネスにおいて、文脈を理解しない対話は致命的だ。「さっきの件だけど」が通じないサポートボットに、顧客は苛立ちを感じ、二度と使わなくなるだろう。記憶とは単なるログの保存ではない。ユーザーへの関心と理解の証明なのだ。
本記事で作成する「文脈を理解するボット」のゴール
この記事では、以下の3段階で「記憶喪失」を治療していく。まずは動くプロトタイプを作り、そこから実用的なシステムへと昇華させていこう。
- 短期記憶: プログラム実行中のみ会話を覚える(インメモリ)
- 長期記憶: Redis(またはValkey等の互換ストア)を使って再起動しても会話を忘れないようにする
- 記憶の最適化: 会話が長くなってもトークン制限でパンクしないよう、要約して脳に定着させる
Pythonのコードを書きながら、一緒に進めていこう。これは、おもちゃのボットをビジネスに直結する実用的なアプリケーションへと進化させるための最初の一歩だ。
Step 1:LangChain Memoryの基本概念を理解する
まずはLangChainがどのように「記憶」を扱っているか、そのメカニズムを解剖する。多くの初心者がここで躓くが、最新のアーキテクチャを理解すれば仕組みは非常に合理的だ。
HumanMessageとAIMessageのキャッチボール
LangChainの最新バージョン(v0.1以降のアーキテクチャ)では、機能が langchain-core、langchain-community、langchain の3つのパッケージに再構成された。会話履歴の中核となるのは、langchain-core で定義される以下のメッセージオブジェクトだ。
- HumanMessage: ユーザーの発言
- AIMessage: AIからの応答
かつては role="user" のように文字列で役割を指定するケースもあったが、API呼び出しの簡素化と型安全性の観点から、現在はこれらのクラスオブジェクトを使用する方法が標準化されている。Memoryコンポーネントの役割は、このキャッチボールの履歴をリストとして保持し、invoke メソッドでチェーンが実行される際に、自動的にプロンプトへコンテキストとして注入することだ。
ConversationBufferMemoryの役割と限界
最も基本的なメモリクラスが ConversationBufferMemory だ。これは履歴をそのまま保存する。まずは手を動かして、基本的な挙動を確認してみよう。
from langchain.memory import ConversationBufferMemory
# 最新のベストプラクティスではlangchain_coreからメッセージをインポート
from langchain_core.messages import HumanMessage, AIMessage
# メモリのインスタンス化
memory = ConversationBufferMemory(return_messages=True)
# 会話を保存(コンテキストの保存)
memory.save_context(
{"input": "こんにちは、私の名前はHARITAです。"},
{"output": "こんにちはHARITAさん。AIアーキテクトですね。"}
)
# 保存された内容を確認
print(memory.load_memory_variables({}))
# 出力: {'history': [HumanMessage(content='こんにちは...'), AIMessage(content='こんにちは...')]}
これをLCEL(LangChain Expression Language)で構築したチェーンに組み込むことで、AIは文脈を踏まえた回答が可能になる。
しかし、ここには重大な欠陥がある。
この memory 変数はPythonのプログラムが実行されている間、メモリ上(RAM)にのみ存在する。サーバーを再起動したり、プロセスが落ちたりすれば、全ての記憶はリセットされてしまう。また、セキュリティの観点からも、メモリ内容が不適切にテンプレートへ展開されるリスク(CVE-2025-68664等で指摘されるようなインジェクション脆弱性)を考慮し、常に最新の langchain-core を使用することが推奨される。
これでは、PoC(概念実証)のデモはできても、実際の商用サービスとしては不十分だ。そこで、データの永続性と安全性を担保する外部ストアの活用が必要になる。
Step 2:Redisを使って「長期記憶」を授ける
ここからが本番だ。メモリ上の変数を、外部のデータベースに移し替える。これで君のボットは、サーバーがクラッシュしても(比喩だが)ユーザーとの会話を忘れない強靭さを手に入れる。
なぜファイル保存ではなくRedisなのか
「ログファイルに書き出せばいいのでは?」と思うかもしれない。しかし、チャットボットにはリアルタイム性が求められる。ファイルI/Oは遅く、同時アクセスに弱い。対してRedisのようなインメモリKVS(Key-Value Store)は爆速だ。数ミリ秒で履歴を読み書きできるため、ユーザーを待たせることがない。ビジネスの現場では、このレスポンス速度が顧客満足度に直結する。
ローカル環境でのRedisセットアップ(Docker活用)
開発環境にRedisを導入する最も簡単な方法はDockerを使うことだ。仮説を即座に形にするため、以下の docker-compose.yml を作成し、ターミナルで docker-compose up -d を実行してほしい。
version: '3.8'
services:
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
redis_data:
これで、ローカルの6379番ポートでRedisが待ち受ける状態になった。
RedisChatMessageHistoryの実装コード解説
次に、LangChainを使って会話履歴の保存先をRedisに指定する。必要なライブラリをインストールしておこう。ここではOpenAIとの連携およびRedis機能を含むコミュニティパッケージを使用する。
pip install langchain-openai langchain-community redis
以下が、永続化メモリを実装したチャットボットの最小構成コードだ。モデルには、現在主流となっている ChatGPT を指定している。かつての gpt-3.5-turbo はレガシー化が進んでおり、性能とコストのバランスを考慮すると、最新モデル(ChatGPT や ChatGPT mini)への移行が強く推奨される。
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# 1. モデルの準備
# 最新のChatGPTモデルを使用(旧来のgpt-3.5系はレガシーのため非推奨)
llm = ChatOpenAI(model="ChatGPT", temperature=0)
# 2. プロンプトの定義(履歴を差し込む場所を作る)
prompt = ChatPromptTemplate.from_messages([
("system", "あなたは優秀なAIアシスタントです。"),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
])
chain = prompt | llm
# 3. Redisを使った履歴管理の設定
def get_message_history(session_id: str):
return RedisChatMessageHistory(
url="redis://localhost:6379/0", # Dockerで立てたRedis
session_id=session_id, # ユーザーや会話ごとのID
ttl=3600 # 1時間で履歴を消す(オプション)
)
# 4. 履歴付きChainの構築
chain_with_history = RunnableWithMessageHistory(
chain,
get_message_history,
input_messages_key="question",
history_messages_key="history",
)
# 5. 会話の実行(session_idを変えれば別の会話として扱われる)
response = chain_with_history.invoke(
{"question": "私の好きな食べ物は寿司です。"},
config={"configurable": {"session_id": "user_123"}}
)
print(f"AI: {response.content}")
# --- アプリを再起動したと仮定して、もう一度実行 ---
response2 = chain_with_history.invoke(
{"question": "私が好きな食べ物は何かわかる?"},
config={"configurable": {"session_id": "user_123"}}
)
print(f"AI: {response2.content}")
このコードを実行してみてほしい。一度プログラムを終了させても、2回目の実行で「寿司ですよね」と返してくるはずだ。session_id さえ一致していれば、Redisから過去の会話が復元される。これが「永続化」の威力だ。
Step 3:トークン制限との戦いと「要約」の技術
「これで完璧だ!」と喜ぶのはまだ早い。ここで一つ警告しておきたい。履歴を無限に保存し続けることには、重大なリスクがある。
会話が長くなると発生するコストとエラー
LLMには「コンテキストウィンドウ」という入力サイズの上限がある。また、APIの課金はトークン(文字数)ベースだ。
「こんにちは」
「元気?」
程度の会話なら問題ないが、これが100ターン、1000ターンと続くとどうなるか?
プロンプトが膨大になり、やがて context_length_exceeded エラーでシステムが停止するか、月末のクラウド利用料の請求書を見て経営陣が青ざめることになる。
ConversationSummaryMemoryの導入
そこで登場するのが「要約」技術だ。人間も数年前の会話を一言一句覚えているわけではない。「あの時、旅行の話をして盛り上がったな」というように、要点を圧縮して記憶している。
LangChainの ConversationSummaryMemory は、会話の履歴が一定量を超えると、LLM自身を使って古い会話を要約し、短く圧縮して保存し直す。
過去の会話を圧縮して保持する仕組み
実装は以下のように変更する。Redisと組み合わせる場合は、少し工夫が必要だが、概念的には以下のようになる。
from langchain.memory import ConversationSummaryMemory
# 要約用のLLMを用意(安いモデルでOK)
summary_llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 要約メモリの作成
memory = ConversationSummaryMemory(
llm=summary_llm,
buffer="ユーザーは寿司が好きだと言っていた。" # 初期記憶
)
# 新しい会話を追加
memory.save_context(
{"input": "もっと詳しく言うと、サーモンが好き。"},
{"output": "サーモンですね、脂が乗っていて美味しいですよね。"}
)
# 現在の記憶を確認
print(memory.load_memory_variables({}))
# 出力例: 'ユーザーは寿司、特にサーモンが好きだと述べた。AIはそれに同意した。'
このように、生の会話ログではなく「要約された物語」として保持することで、トークン消費を抑えつつ、長期的な文脈を維持できる。実運用では、直近の数ターンは生のログ(ConversationBufferWindowMemory)を使い、それより前は要約する、というハイブリッド構成をとるのがベストプラクティスだ。
エピローグ:記憶を持ったAIが広げる可能性
おめでとう。これで君のチャットボットは、会話を記憶し、サーバー再起動を乗り越え、さらにトークン制限とも賢く付き合えるようになった。これは単なる機能追加ではない。ユーザーとの「関係性」を構築するための土台が完成したということだ。
パーソナライズされた対話体験へ
記憶を持ったAIは、ユーザーの好みを学習し、先回りして提案できるようになる。「いつもの」が通じるAIエージェント。これこそが、私たちが目指すべきAI駆動開発の未来だ。
次の学習ステップ:VectorStoreとの連携
しかし、Redisに保存した履歴はあくまで「直列の会話」だ。膨大な社内ドキュメントやマニュアルを参照させたい場合は、今回の知識をベースに RAG(検索拡張生成) へと進む必要がある。VectorStoreを使えば、必要な情報だけを瞬時に「思い出す」ことが可能になる。
まとめとサンプルコード
今回紹介した手法は、AIアプリ開発の基本中の基本だが、最も重要な部分でもある。基礎がグラついていては、どんな高度なモデルも宝の持ち腐れだ。
最後に、技術的な実装だけでなく、実際にこれらの技術を使って企業がどのような成果を上げているかを知ることも重要だ。技術は手段であり、目的はビジネス課題の解決にあるからだ。
広く公開されている成功事例などを研究することで、他社が「記憶」をどうビジネス価値に変えているか、大きなヒントが得られるはずだ。ぜひ、自社のプロジェクトに活かしてほしい。
コメント