はじめに
こんにちは、LLMチャットアプリの完成を目指している新卒エンジニアです。
前回はLangChainの基礎機能を使ってシンプルなLLMチャットデモを作成しました。今回はLangChainのmemory機能というものを使って会話履歴を保持するようにしたいと思います。今回の記事ではその途中でなんだかうまくいかなかったポイントがあったので解説しています。今回はLangGraphに触れていますが、LangGraphの本質的な理論などは手に余るのでこの記事では紹介しません。これからよく使いそうな技術なので、気が向いたら勉強して記事を書いてみようかなと思います。
前回の記事
LangChainのmemory機能とは
LangChain の memory 機能とは、会話の履歴や文脈情報を記憶し、複数回の対話をサポートする仕組みです。
従来の LangChain では、ConversationBufferMemory、ConversationSummaryMemory、ConversationBufferWindowMemory といったメモリクラスを使って、会話履歴を管理していました。
これらは以下のような役割を果たしていました:
ConversationBufferMemory:すべての会話履歴を保持(メモリが増え続ける)
ConversationBufferWindowMemory:直近 k ターンのみ保持(古い履歴は破棄)
ConversationSummaryMemory:会話を要約して圧縮
しかし、LangChain v1 の設計理念の変化に伴い、これらのメモリクラスの使用は推奨されなくなりました。
従来のmemory機能だと上手くいかない
LangChain v1 以前のコード例で ConversationBufferWindowMemory を使うと、以下のようなエラーに遭遇します:
ImportError: cannot import name 'ConversationBufferWindowMemory' from 'langchain_community.memory'
このエラーが発生する理由は、LangChain v1 でこれらのメモリクラスが langchain_community パッケージから削除されたためです。
どうやら、私が使おうとしていたmemoryの機能は公式ドキュメントから削除されているようです。
ここで最新情報を追うのにかなり時間が掛かりました。公式ドキュメントを見ている途中でLangChainのAIがあったので公式ドキュメントについて聞いてみましたが、これがかなり便利でした。
v1への移行で非推奨になったもの
LangChain v1 への移行に伴い、以下の機能や概念は公式ドキュメントから削除または大幅に変更:
-
ConversationBufferWindowMemory などのメモリクラス全般
- 従来の langchain_community.memory モジュール自体が廃止
-
langchain.chains による LCEL チェーン外でのメモリ管理
- 従来:メモリを明示的にチェーンに接続
- v1:状態管理を LangGraph に統一
-
設定パターンの変更
- 従来:config["configurable"] で設定
- v1:{"configurable": {...}} で統一、より明示的に
LangChain の公式ドキュメントでは、これらの従来のメモリパターンをサポートしなくなった代わりに、LangGraph を使った memory 管理を推奨しています。
履歴管理はLangGraphを使う
LangChain v1 では、会話履歴の管理に LangGraph のPersistence機能を使うことが標準となりました。
LangGraph は以下の 2 つのメモリタイプをサポートしています:
- Short-term memory(短期記憶)
スレッド内でのみ有効な会話履歴
チェックポインター(InMemorySaver など)を使ってスレッド単位で保存
マルチターン会話の実現に必須
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver() # メモリ上に履歴を保持
Short-term memory の仕組みは以下の通りです:
グラフの状態(MessagesState)にメッセージを蓄積
各呼び出しで thread_id を指定すると、LangGraph が自動的にその thread_id の履歴を読み込む
ノードの処理後、新しい状態をチェックポインターに保存
次回の呼び出し時に、保存された状態から復帰
- Long-term memory(長期記憶)
複数のスレッド間で共有される情報
ユーザー情報や環境設定など、セッション跨ぎで必要な情報を保存
本記事では触れませんが、store API を使って実装可能
全体のコード
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, MessagesState, START
from langchain_ollama import ChatOllama
from langchain_core.messages import SystemMessage
# モデル初期化
model = ChatOllama(model="llama3:8b", temperature=0)
# チェックポインター(履歴保持用)
checkpointer = InMemorySaver()
# ノード定義
def call_model(state: MessagesState):
# システムメッセージをモデル呼び出しの時だけ使用
system_msg = SystemMessage(content="日本語で、履歴を考慮して簡潔に答えてください。")
messages = [system_msg] + state["messages"]
response = model.invoke(messages)
# 状態に返すのはAIメッセージだけ
return {"messages": [response]}
# グラフ構築
builder = StateGraph(MessagesState)
builder.add_node("call_model", call_model)
builder.add_edge(START, "call_model")
app = builder.compile(checkpointer=checkpointer)
# 1回目の呼び出し
result = app.invoke(
{"messages": [{"role": "user", "content": "わたしの名前は新卒太郎です。"}]},
{"configurable": {"thread_id": "1"}}
)
print([(msg.type, msg.content) for msg in result["messages"]])
# 2回目の呼び出し
result = app.invoke(
{"messages": [{"role": "user", "content": "わたしの名前は何ですか?"}]},
{"configurable": {"thread_id": "1"}}
)
print([(msg.type, msg.content) for msg in result["messages"]])
実際に実行してみます。
$ python langchain_sample02.py
[('human', 'わたしの名前は新卒太郎です。'), ('ai', "Nice to meet you, Shinso Tarō! 😊 What would you like to talk about? I'll do my best to provide a concise and considerate response. Please feel free to ask me anything! 🤔")]
[('human', 'わたしの名前は新卒太郎です。'), ('ai', "Nice to meet you, Shinso Tarō! 😊 What would you like to talk about? I'll do my best to provide a concise and considerate response. Please feel free to ask me anything! 🤔"), ('human', 'わたしの名前は何ですか?'), ('ai', '新卒太郎ですね!😊')]
上手く動作しましたね。履歴もちゃんと保持されているようです。今回使っているモデルが日本語はまだ弱そうなので若干言語が混ざっていますが履歴管理はできているので良しとしましょう。
コードの解説
それでは、使用したコードの主要部分を詳しく説明します。
SystemMessage の使い方
SystemMessage を明示的に作成して、モデル呼び出しの時だけ使用しています。
from langchain_core.messages import SystemMessage
system_msg = SystemMessage(content="日本語で、履歴を考慮して簡潔に答えてください。")
messages = [system_msg] + state["messages"]
SystemMessage の利点:
柔軟性:ノード内で動的にシステムメッセージを変更可能
状態の効率化:会話履歴だけが保存され、メモリ使用量が少ない
StateGraph と MessagesState
StateGraph はワークフローの状態を管理するグラフを構築します。MessagesState はメッセージのリストを状態として保持する組み込みクラスです。
from langgraph.graph import StateGraph, MessagesState, START
builder = StateGraph(MessagesState) # メッセージを状態として管理
MessagesState は以下の特徴があります:
messages というリストを自動的に管理
add_messages という関数で新しいメッセージを追加する際に重複を排除
LLMとのやり取りに最適化されている
InMemorySaver チェックポインター
InMemorySaver は会話履歴をメモリ上に保持するコンポーネントです。
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
app = builder.compile(checkpointer=checkpointer)
thread_id を指定することで、特定のスレッド(会話)の履歴を取得・保存できます。
result = app.invoke(
{"messages": [...]},
{"configurable": {"thread_id": "1"}} # スレッドIDで会話を特定
)
さいごに
今回は、LangChainのmemory機能について解説しました。
学んだポイント
-
LangChain v1 での memory の位置付け
- 従来のメモリクラスは廃止され、LangGraph での設計に統一
-
StateGraph+MessagesStateで会話履歴を管理 -
InMemorySaverチェックポインターで自動的に履歴を保存・復帰
-
thread_id によるマルチ会話管理
-
thread_idを指定することで、複数の独立した会話を同時に管理可能 - 同じ
thread_idでカウントすれば、会話が自動的に続く
-
今後の展望
本記事では LLM とのシンプルな対話を実装しましたが、次回はAgent 機能の実装をやっていきたいと思います。
次回以降の記事では、以下のトピックを予定しています:
-
LangChain v1 の
create_agentAPI- 推奨される Agent 構築方法
- Middleware の活用パターン
-
Tool(ツール)の実装
- カスタムツールの定義方法
- 複数ツールの組み合わせ
-
外部API との連携
- Web 検索 API の統合
- データベースクエリツールの実装
参考