AIに「記憶」を持たせる!LangChain Memory完全実装マニュアル
はじめに
「さっきの話だけど...」とAIに話しかけても、「何の話ですか?」と返されてガッカリしたことはありませんか? LLMは基本的に一問一答(ステートレス)であり、過去のやり取りを覚えていません。
ユーザーと文脈を共有した自然な会話を実現するためには、アプリケーション側で「記憶(Memory)」を管理し、毎回プロンプトに過去ログを含めて送信する必要があります。LangChainは、この面倒な処理を劇的に簡単にする仕組みを提供しています。
LLMは基本ステートレス
Memoryの仕組みは実は単純です。毎回「過去の会話 + 新しい質問」をセットにしてLLMに送っているだけです。
しかし、会話が長くなるとトークン制限(入力文字数の上限)に引っかかったり、APIコストが嵩んだりします。そのため、「最新の10件だけ残す」「要約して保存する」といった戦略が必要になります。
ChatMessageHistoryの基本
まずは手動で履歴を管理する方法を見てみましょう。
from langchain_core.messages import HumanMessage, AIMessage
from langchain_community.chat_message_histories import ChatMessageHistory
history = ChatMessageHistory()
# 会話の追加
history.add_user_message("こんにちは!")
history.add_ai_message("こんにちは、何かお手伝いしましょうか?")
# 履歴の確認
print(history.messages)
これを llm.invoke(history.messages) のように渡せば、文脈を維持した回答が得られます。
RunnableWithMessageHistoryによる自動化
LangChain v0.2以降のモダンな書き方(LCEL)では、RunnableWithMessageHistory
を使ってチェーン自体をラップし、セッションIDごとに履歴を自動管理させるのが一般的です。
from langchain_core.runnables.history import RunnableWithMessageHistory
# 履歴を取得する関数を定義(ここではインメモリだが、実際はDBから取得)
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
)
# セッションIDを指定して実行
chain_with_history.invoke(
{"input": "私の好きな色は赤です"},
config={"configurable": {"session_id": "user_123"}}
)
次に同じセッションIDで「私の好きな色は?」と聞けば、「赤ですね」と答えてくれます。
履歴の保存(Redis/SQL)
メモリ上の履歴はプログラムを再起動すると消えます。永続化するには、RedisChatMessageHistory や
PostgresChatMessageHistory を使います。
LangChain Communityパッケージには多くのDB用クラスが用意されており、わずか数行の変更で会話ログをデータベースに保存できるようになります。
まとめ
Memoryの実装は、チャットボット開発の肝です。まずは簡単なインメモリ管理から始めて、必要に応じてRedisなどの外部ストアへの移行を検討しましょう。
古い記事でよく見る `ConversationChain` は現在非推奨(Legacy)扱いになりつつあります。LCELベースの書き方(今回紹介した方法)に慣れておくことを強くお勧めします。
次回は、これまで断片的に登場してきた謎のパイプ記法 | 、すなわち「LCEL (LangChain Expression Language)」について徹底解説します。
コメント
コメントを投稿