LangChain

【Python】LangChain: Memoryで情報を保存・管理する

【Python】LangChain Memoryで情報を保存・管理する

Pythonで大規模言語モデル(LLM: Large Language Model)を活用する際に使用できるLangChainでMemory(メモリ)機能を使用する方法を解説します。

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 BufferConversation Buffer WindowConversation Token BufferConversation SummaryConversation 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 BufferConversation Buffer WindowConversation Token BufferConversation SummaryConversation Summary Bufferといった5つのメモリ機能を簡単な例を使って紹介しました。

実際にはLangChainには、他にも様々な機能があります。皆さんもLangChainの使い方をぜひ覚えていってもらえるといいと思います。