Pythonで大規模言語モデル(LLM: Large Language Model)を活用する際に使用できるLangChainでMemory(メモリ)機能を使用する方法を解説します。
Contents
LangChainのMemory機能
LangChainは、大規模言語モデル(LLM: Large Language Model)を活用するためのオープンソースPythonライブラリです。LangChain自体の概要については「LangChainで大規模言語モデル(LLM)を活用する」で説明していますので参考にしてください。
LangChainは、LLMの複雑なタスクを実行するためのライブラリですが、構成要素としてMemory(メモリ)という機能があります。Memoryは、LangChainが過去の対話やデータを保存しておくための機能となっており、モデルが以前の情報を参照して一貫性のある応答や新たな問い合わせに対する文脈を理解できます。
この記事では、LangChainでMemory(メモリ)機能を使用する方法について紹介します。
この記事で紹介しているプログラムは以下のバージョンで動作確認しています。生成AIの分野は進歩が速いため、API等の変更により最新バージョンではプログラムがそのままでは動作しない可能性もありますので注意してください。
- Python: 3.11.5
- langchain: 0.2.11
- langchain-core: 0.2.26
- langchain-community: 0.2.10
- langchain-openai: 0.1.16
Memory(メモリ)機能
LangChainのMemory(メモリ)では、複数種類が提供されています。以下の表は、公式ページのこちらから参照できるメモリ機能の種類です。(最新では種類が追加されている可能性があります。)
Memory(メモリ)の種類 | 概要 |
---|---|
Conversation Buffer | 最もシンプルなMemoryで、過去の対話を記憶し、継続的な対話の管理ができます。 クラス: ConversationBufferMemory |
Conversation Buffer Window | 特定回数(ウィンドウサイズ)の最新の対話のみを保持します。 クラス: ConversationBufferWindowMemory |
Conversation Token Buffer | 指定トークン数を超えた場合には、過去のメッセージを削除します。 クラス: ConversationTokenBufferMemory |
Conversation Summary | 対話を要約し、簡潔な形で保持します。 クラス: ConversationSummaryMemory |
Conversation Summary Buffer | 指定トークン数を超えた場合に要約が行われます。 クラス: ConversationSummaryBufferMemory |
Entity | 対話中に言及されたエンティティ(人物、場所、オブジェクトなど)の情報を記憶します。 クラス: ConversationEntityMemory |
Conversation Knowledge Graph | 対話中のエンティティ間の関係をグラフとして構築し、複雑なクエリや推論を支援します。 クラス: ConversationKGMemory |
Backed by a Vector Store | ベクトルデータベースに対話内容を保存し、関連情報を効率的に検索します。 クラス: VectorStoreRetrieverMemory |
今回は、代表的なConversation Buffer、Conversation Buffer Window、Conversation Token Buffer、Conversation Summary、Conversation Summary Bufferの5つを取り上げて、以降で簡単な例を紹介しようと思います。
Conversation Buffer
Conversation Bufferは、最もシンプルで基本的なMemoryで過去の対話を記憶し、継続的な対話の管理するのに役立ちます。クラスは、ConversationBufferMemory
として実装されています。
この機能により、対話履歴をメモリ内に保持し、必要に応じて参照することが可能になります。
一般的な使用方法
以下は、Conversation Bufferを使ってメモリにメッセージを記録する例です。
from langchain.memory import ConversationBufferMemory from langchain_core.messages import AIMessage, HumanMessage, SystemMessage # メモリを用意する memory = ConversationBufferMemory() # メモリにユーザーメッセージを記録する memory.chat_memory.add_user_message("ユーザーのメッセージ1") # メモリーにAIのメッセージを記録する memory.chat_memory.add_ai_message("AIのメッセージ1") # メモリーに汎用的にメッセージを記録する memory.chat_memory.add_message(HumanMessage(content="ユーザーのメッセージ2")) memory.chat_memory.add_message(AIMessage(content="AIのメッセージ2")) memory.chat_memory.add_message(SystemMessage(content="システムのメッセージ1")) # メモリの情報を取得する(辞書) print(type(memory.load_memory_variables({}))) print(memory.load_memory_variables({})) print("-----") # 文字列で取得する print(type(memory.load_memory_variables({})["history"])) print(memory.load_memory_variables({})["history"]) print("-----") # メモリを削除する memory.clear() print(memory.load_memory_variables({}))
【実行結果】 <class 'dict'> {'history': 'Human: ユーザーのメッセージ1\nAI: AIのメッセージ1\nHuman: ユーザーのメッセージ2\nAI: AIのメッセージ2\nSystem: システムのメッセージ1'} ----- <class 'str'> Human: ユーザーのメッセージ1 AI: AIのメッセージ1 Human: ユーザーのメッセージ2 AI: AIのメッセージ2 System: システムのメッセージ1 ----- {'history': ''}
Conversation Bufferを使うには、ConversationBufferMemory
クラスをインポートして使用します。
メモリにメッセージを追加する場合には、ユーザーのメッセージ(質問)を記録するadd_user_message
やAIのメッセージ(回答)を記録するadd_ai_message
を使用します。また、汎用的なメソッドとしてadd_message
というメソッドを使用することも可能です。
なお、add_message
を使用する場合は「HumanMessage
」「AIMessage
」「SystemMessage
」といったクラスにcontent
引数でメッセージを渡します。これらのクラスは、langchain_core.messages
からインポートして使用します。
記録したメッセージを取り出す場合には、load_memory_variables
メソッドを使用します。history
というキーを持った辞書が返却され、値の中に過去のメッセージの文字列が格納されています。文字列を取り出したい場合は「memory.load_memory_variables({})['history']
」のようにすることで文字列の取り出しが可能です。
リストで結果を取得する
リストで過去のやり取りを取得する場合には、以下のようにConversationBufferMemory
を生成する際にreturn_messages=True
を引数として設定します。
※変更箇所のみ抜粋
# メモリを用意する memory = ConversationBufferMemory(return_messages=True)
【実行結果】 <class 'dict'> {'history': [HumanMessage(content='ユーザーのメッセージ1'), AIMessage(content='AIのメッセージ1'), HumanMessage(content='ユーザーのメッセージ2'), AIMessage(content='AIのメッセージ2'), SystemMessage(content='システムのメッセージ1')]} ----- <class 'list'> [HumanMessage(content='ユーザーのメッセージ1'), AIMessage(content='AIのメッセージ1'), HumanMessage(content='ユーザーのメッセージ2'), AIMessage(content='AIのメッセージ2'), SystemMessage(content='システムのメッセージ1')] ----- {'history': []}
上記結果から、リスト形式で過去のメッセージ情報を取得できていることが分かります。以降の他クラスの紹介でも、return_messages=True
を指定して紹介していきます。
1回のやり取り(質問と回答)をまとめて記録する
質問と回答のやり取りをまとめて1回で記録することも可能です。まとめて記録を行う場合は、以下のようにsave_content
メソッドを使用します。
from langchain.memory import ConversationBufferMemory # メモリを用意する memory = ConversationBufferMemory(return_messages=True) # 入力と回答をまとめて記録する memory.save_context( {"input": "ユーザーのメッセージ1"}, {"output": "AIのメッセージ1"}, ) # メモリの情報をで取得する print(type(memory.load_memory_variables({})["history"])) print(memory.load_memory_variables({})["history"]) print("-----") # メモリを削除する memory.clear() print(memory.load_memory_variables({}))
【実行結果】 <class 'list'> [HumanMessage(content='ユーザーのメッセージ1'), AIMessage(content='AIのメッセージ1')] ----- {'history': []}
上記例のように、ユーザーのメッセージ(質問)を"input"
として、AIのメッセージ(回答)を"output"
としてsave_content
に渡します。
今回はadd_message
といったメソッドは呼び出していませんが、結果を見てみるとsave_content
によりユーザーのメッセージ(質問)はHumanMessage
、AIのメッセージ(回答)はAIMessage
として記録されていることが分かるかと思います。
Conversation Buffer Window
Conversation Buffer Windowは、特定の回数(ウィンドウサイズ)で最新の対話のみを保持します。クラスはConversationBufferWindowMemory
として実装されています。
例えば、メモリ容量を節約したい場合や、直近2回の質問・回答の情報を考慮に入れて後続のやり取りをしたい場合などに便利です。
from langchain.memory import ConversationBufferWindowMemory from langchain_core.messages import AIMessage, HumanMessage, SystemMessage # メモリを用意する memory = ConversationBufferWindowMemory(return_messages=True, k=2) # メッセージを記録する memory.chat_memory.add_message(HumanMessage(content="ユーザーのメッセージ1")) memory.chat_memory.add_message(AIMessage(content="AIの回答1")) memory.chat_memory.add_message(HumanMessage(content="ユーザーのメッセージ2")) memory.chat_memory.add_message(AIMessage(content="AIの回答2")) memory.chat_memory.add_message(HumanMessage(content="ユーザーのメッセージ3")) memory.chat_memory.add_message(AIMessage(content="AIの回答3")) # メモリの情報を取得する print(memory.load_memory_variables({})["history"]) # メモリを削除する memory.clear()
【実行結果】 [HumanMessage(content='ユーザーのメッセージ2'), AIMessage(content='AIの回答2'), HumanMessage(content='ユーザーのメッセージ3'), AIMessage(content='AIの回答3')]
使用するクラスは「ConversationBufferWindowMemory
」ですので、上記例に従ってインポートして使用してください。
上記の実行結果を見ると直近2回の質問・回答となる2と3のメッセージのみ結果が保持されており1のメッセージは保存されていないことが分かるかと思います。
このようにすることでメモリの使用を制限することができます。また、直近の情報をベースにして次の質問に渡すことで、過去の情報を考慮したAIの回答をさせるといったことも可能になります。
Conversation Token Buffer
Conversation Token Bufferは、指定トークン数を超えた場合には、過去のメッセージを削除します。クラスはConversationTokenBufferMemory
として実装されています。
このメモリでは指定したトークン数までしか過去のメッセージを保持しませんので、メモリの節約に役立ちます。
以降で紹介するプログラムでは、有償のOpenAIのAPIを使用しますのでご注意ください。APIの使い方は「OpenAI APIの使い方の基本」でまとめていますので参考にしてください。
キー情報は以下のようなコンフィグファイルから読み込むことにします。以下のようなconfig.ini
ファイルを作成し、キーを設定しておいてください。xxxx
の部分にはご自身のキー情報が入ります。
【config.ini
】キー情報の設定ファイル
[OPENAI] key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Conversation Token Bufferは以下のように使用します。
import configparser from langchain.memory import ConversationTokenBufferMemory from langchain_openai import ChatOpenAI # コンフィグファイルからキーを取得する config = configparser.ConfigParser() config.read("config.ini") # モデルを初期化する model = ChatOpenAI( openai_api_key=config["OPENAI"]["key"], model="gpt-3.5-turbo", ) # メモリを用意する memory = ConversationTokenBufferMemory( llm=model, max_token_limit=50, return_messages=True ) memory.save_context( {"input": "Pythonの概要を教えてください。"}, { "output": "Pythonは、シンプルでわかりやすい文法を持つ高水準のプログラミング言語です。" }, ) print(memory.load_memory_variables({})["history"]) # メモリを削除する memory.clear()
【実行結果】 [AIMessage(content='Pythonは、シンプルでわかりやすい文法を持つ高水準のプログラミング言語です。')]
使用するクラスは「ConversationTokenBufferMemory
」ですので、上記例に従ってインポートして使用してください。また、OpenAIのChatOpenAI
をインポートしておきます。
コンフィグファイルからはconfigparser
を使ってキーを読み込み、ChatOpenAI
に指定してモデルを生成します。ConversationTokenBufferMemory
をインスタンス化する際には、トークン数を計算するためのベースとなるモデルをllm
引数で指定します。メモリへの記録はsave_content
を使って入力しています。
今回は、max_token_limit=50
として指定しましたが、直近の回答のみしかメモリに保存されませんでした。この数値を大きくすると質問も含めてメモリに保持されます。このようにConversationTokenBufferMemory
を使用することで、記録する過去の対話の量を制限しながらメモリーに情報を保持することができます。
configparserについては「configparserによる設定ファイル管理」にまとめてありますので、使い方をご存じでない方は参考にしてください。
Conversation Summary
Conversation Summaryは、対話を要約し、簡潔な形で保持することができます。クラスはConversationSummaryMemory
として実装されています。
Conversation Summaryの機能を使用する場合には、以下のように実行します。
import configparser from langchain.memory import ConversationSummaryMemory from langchain_openai import ChatOpenAI # コンフィグファイルからキーを取得する config = configparser.ConfigParser() config.read("config.ini") # モデルを初期化する model = ChatOpenAI( openai_api_key=config["OPENAI"]["key"], model="gpt-3.5-turbo", ) # メモリを初期化し、モデルを設定する memory = ConversationSummaryMemory(llm=model, return_messages=True) memory.save_context( {"input": "Pythonの概要を教えてください。"}, { "output": "Pythonは、シンプルでわかりやすい文法を持つ高水準のプログラミング言語です。" }, ) print(memory.load_memory_variables({})["history"]) # メモリを削除する memory.clear()
【実行結果】 [SystemMessage(content='The human asks the AI to explain an overview of Python. The AI responds that Python is a high-level programming language with simple and easy-to-understand syntax.')]
使用するクラスは「ConversationSummaryMemory
」ですので、上記例に従ってインポートして使用してください。
ConversationSummaryMemory
をインスタンス化する際には、要約のために使うモデルとして上記で準備したモデルを渡します。メモリへの記録はsave_content
を使って入力しています。
実行結果を見ていただくと分かりますが、質問と回答がそのまま記録されているわけではなく、そのやり取りを要約した文字列が記録されていることが分かります。このようにConversationSummaryMemory
を使用することで、過去の対話を要約して簡潔な形で保持することができます。
Conversation Summary Buffer
Conversation Summary Bufferは、指定トークン数を超えた場合に要約が行われます。クラスはConversationSummaryBufferMemory
として実装されています。
使用方法は、ConversationSummaryMemory
とほとんど同じですが、以下のようにmax_token_limit
引数を指定します。
import configparser from langchain.memory import ConversationSummaryBufferMemory from langchain_openai import ChatOpenAI # コンフィグファイルからキーを取得する config = configparser.ConfigParser() config.read("config.ini") # モデルを初期化する model = ChatOpenAI( openai_api_key=config["OPENAI"]["key"], model="gpt-3.5-turbo", ) # メモリを初期化し、モデルを設定する # ※max_token_limitを変更して試してください。 memory = ConversationSummaryBufferMemory( llm=model, max_token_limit=100, return_messages=True ) memory.save_context( {"input": "Pythonの概要を教えてください。"}, { "output": "Pythonは、シンプルでわかりやすい文法を持つ高水準のプログラミング言語です。" }, ) print(memory.load_memory_variables({})["history"]) # メモリを削除する memory.clear()
【実行結果】 [HumanMessage(content='Pythonの概要を教えてください。'), AIMessage(content='Pythonは、シンプルでわかりやすい文法を持つ高水準のプログラミング言語です。')]
上記は、まずmax_token_limit
を100にしました。この場合、このトークン内に質問と回答が収まるので質問と回答はそのままの形式で保存されています。
ここで、上記のmax_token_limit
を以下のように30といった小さな値に変えて実行してみます。
memory = ConversationSummaryBufferMemory( llm=model, max_token_limit=30, return_messages=True )
【実行結果】 [SystemMessage(content='The human asks for an overview of Python. The AI explains that Python is a high-level programming language with simple and easy-to-understand syntax.')]
結果は、上記のようにmax_token_limit
で指定した最大トークン数を超えた場合に要約が行われました。このようにトークン数に応じて要約を行うかを制御する際に便利です。
まとめ
Pythonで大規模言語モデル(LLM: Large Language Model)を活用する際に使用できるLangChainでMemory(メモリ)機能を使用する方法を解説しました。
Memoryは、LangChainが過去の対話やデータを保存しておくための便利な機能となっており、モデルが以前の情報を参照して一貫性のある応答や新たな問い合わせに対する文脈を理解できます。
メモリ機能には、いくつも種類が用意されていますが、この記事では基本的なConversation Buffer、Conversation Buffer Window、Conversation Token Buffer、Conversation Summary、Conversation Summary Bufferといった5つのメモリ機能を簡単な例を使って紹介しました。
実際にはLangChainには、他にも様々な機能があります。皆さんもLangChainの使い方をぜひ覚えていってもらえるといいと思います。