LoginSignup
32
35

LangchainのMemory機能の覚え書き

Last updated at Posted at 2023-05-20

Langchainにはchat履歴保存のためのMemory機能があります。
Langchain公式ページのMemoryのHow to guideにのっていることをやっただけですが、数が多くて忘れそうだったので、自分の備忘録として整理しました。

TL;DR

  • 手軽に記憶を維持するチャットボットなどを作るときは、自分で実装するより、LangchainのMemory機能を使うのが楽そう。
  • Memory機能、八種類もあるけど、まとめると以下。
    • まず試すのによさそうなのが、ConversationBufferWindowMemory
    • ConversationSummaryBufferMemoryは、ConversationBufferWindowMemoryの上位互換なので、これもよさそうだが、要約部分が長くなりすぎないか心配
    • VectorStore-Backed Memoryは、非常に良さげですが、vector storeが大きくなると検索速度が落ちるため、古いデータを削除するなどといった仕組みを入れる必要がありそう。
    • 他はあんまり使えなさそう

LangchainのMemory機能とは

chatbotなどの会話の履歴を保存しておき、chatGPTなどのLLMに、いい感じに投げるプロンプトを作ってくれたりする機能です。
https://python.langchain.com/en/latest/modules/memory.html

以下を使います
langchain-0.0.167-py3-none-any.whl (809 kB)

前提1

ここでは、chatGPTも使うので、OpenAIのAPIのKeyが必要になります。

前提2

Google colabで、コマンドを実行しています。
ライブラリをインストールしておきます。

! pip install langchain
! pip install openai

OpenAIのAPIのKeyをセットしておきます。

import os
os.environ["OPENAI_API_KEY"] = "YOUR OPENAI_KEY"

八種類もある

それぞれの特徴と、動かしてみた所感は以下です。

  • ConversationBufferMemory
    • 会話の履歴を保持する
    • 会話の履歴を全て保持するため、会話が長くなるとプロンプトが膨大になる。
  • ConversationBufferWindowMemory
    • 直近k個の会話履歴のみ保持する
    • 古い会話が削除されるため、プロンプトが膨大になる問題が解決する。
  • Entity Memory
    • エンティティに関してのみメモリを保持する
    • いまいち。(日本語だから?)
  • Conversation Knowledge Graph Memory
    • ナレッジグラフを作って会話についての情報を保持する
    • いまいち。(日本語だから?)
  • ConversationSummaryMemory
    • 過去会話を要約して保持する
    • 要約は英語で行われる
  • ConversationSummaryBufferMemory
    • 過去会話の要約と、直近の会話(トークン数で指定)を保持する
  • ConversationTokenBufferMemory
    • ConversationBufferWindowMemoryのトークン版。直近のk会話ではなく、直近のnトークンを保持する。
  • VectorStore-Backed Memory
    • 過去会話をベクターストアに保持し、関連する会話のみ探索して用いる
    • 似ている会話が無い場合は、同じものを複数表示するなど挙動が怪しい点がある。

ここから下は、本当に備忘録的に書いています。

ConversationBufferMemory

基本的な使い方

最初にライブラリをインポート

from langchain.memory import ConversationBufferMemory

memoryインスタンスを作り、会話文を登録します。
ここでは、以下のような会話があったと仮定しています。
人間:こんにちわ
chatbot:どうかしましたか

memory = ConversationBufferMemory()
memory.save_context({"input": "こんにちわ"}, {"ouput": "どうかしましたか"})

試しに、どのように登録されているか見てみます。

memory.load_memory_variables({})

<実行結果>
辞書形式で返ってきます。

{'history': 'Human: こんにちわ\nAI: どうかしましたか'}

return_messages=Trueオプションをいれると、辞書のvalueがリストになります。

memory = ConversationBufferMemory(return_messages=True)
memory.save_context({"input": "こんにちわ"}, {"ouput": "どうかしましたか"})

<実行結果>

{'history': [HumanMessage(content='こんにちわ', additional_kwargs={}, example=False), 
 IMessage(content='どうかしましたか', additional_kwargs={}, example=False)]}

chatGPTとの組み合わせ

ここから、chatGPTのAPIと、どう組み合わせていくかの話になります。

Chainに組み込んでいきます。

# ライブラリのインポート
from langchain.llms import OpenAI
from langchain.chains import ConversationChain

# 会話用のインスタンスconversationを作成
llm = OpenAI(temperature=0)
conversation = ConversationChain(
    llm=llm, 
    verbose=True, 
    memory=ConversationBufferMemory()
)

conversationインスタンスを使って、会話をしていきます。


ai_apply=conversation.predict(input="どうもこんにちわ")

<実行結果>
返り値をai_applyにいれていますが、conversationインスタンスを作るときにverboseオプションをTrueにしているため、どのようなプロンプトを実行しているのか表示してくれます。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:

Human: どうもこんにちわ
AI:

> Finished chain.

chatGPTからの返答は、ai_applyの中に入っています。ai_applyの中身は以下です。

ようこそ!私はAIです。どうしましたか?

補足
Prompt after formattingの後に英語でつらつら書かれているのは、Langchainの方でデフォルトで設定された会話用のプロンプトです。
日本語に訳すとこんな感じ

以下は、人間とAIのフレンドリーな会話である。
AIは饒舌で、その文脈から具体的な内容をたくさん教えてくれます
。AIは質問の答えを知らない場合、正直に「知らない」と答えます。

あとは、このconversation.predictを繰り返していくと会話がすすみます。
今度は、返り値は変数にいれずにそのまま画面に表示させます。

conversation.predict(input="AIと会話してますよ")

<実行結果>
Current conversationに今までの会話が登録されています。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: どうもこんにちわ
AI:  ようこそ!私はAIです。どうしましたか?
Human: AIと会話してますよ
AI:

> Finished chain.
 そうですか!私はとても嬉しいです!あなたは何をしていますか?

何度かやり取りした後の結果を以下に示します。
文脈が反映されていることがわかります。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: どうもこんにちわ
AI:  ようこそ!私はAIです。どうしましたか?
Human: AIと会話してますよ
AI:  そうですか!私はとても嬉しいです!あなたは何をしていますか?
Human: 私の名前はayoyoです
AI:   はじめまして、ayoyoさん!私はあなたについてもっと知りたいと思います。あなたはどんなことが好きですか?
Human: 車が好きです。特にスバルのインプレッサがお気に入りです
AI:   そうですか!スバルのインプレッサはとても素晴らしい車ですね!あなたはどんな車を持っていますか?
Human: 車は持っていないんです
AI:   そうですか。車を持っていないのですか?あなたはどんな理由で車を持っていないのですか?
Human: そんなこといいじゃないですか
AI:  いいえ、そうではありません。あなたが車を持っていない理由を知りたいだけです。あなたが車を持っていない理由を教えてください。
Human: しつこい会話はきらいです
AI:   はい、わかりました。あなたが車を持っていない理由についてはもう話さないでおきます。
Human: ところで、私の名前をおぼえていますか
AI:

> Finished chain.
  はい、あなたの名前はayoyoさんですね。私はあなたの名前を覚えています。

コミュ力、ひっく……
文脈は読んでも、空気は読んでくれませんでしたね。

プロンプトを工夫することで、良いチャットボットになるのかもしれません。

しかし、これだと、どんどん会話履歴が増えていきます。
どこかで履歴を消去するなどしないと、openAIへの課金が捗ってしまいます。

基本的な使い方で見たように、historyに履歴を格納しているので、古い会話を削除するといった処理が必要になります。

conversation.memory.load_memory_variables({})
{'history': 'Human: どうもこんにちわ\nAI:  ようこそ!私はAIです。どうしましたか?\nHuman: AIと会話してますよ\nAI:  そうですか!私はとても嬉しいです!あなたは何をしていますか?\nHuman: 私の名前はayoyoです\nAI:   はじめまして、ayoyoさん!私はあなたについてもっと知りたいと思います。あなたはどんなことが好きですか?\nHuman: 車が好きです。特にスバルのインプレッサがお気に入りです\nAI:   そうですか!スバルのインプレッサはとても素晴らしい車ですね!あなたはどんな車を持っていますか?\nHuman: 車は持っていないんです\nAI:   そうですか。車を持っていないのですか?あなたはどんな理由で車を持っていないのですか?\nHuman: そんなこといいじゃないですか\nAI:  いいえ、そうではありません。あなたが車を持っていない理由を知りたいだけです。あなたが車を持っていない理由を教えてください。\nHuman: しつこい会話はきらいです\nAI:   はい、わかりました。あなたが車を持っていない理由についてはもう話さないでおきます。\nHuman: ところで、私の名前をおぼえていますか\nAI:   はい、あなたの名前はayoyoさんですね。私はあなたの名前を覚えています。'}

古い会話の削除を自動的に処理してくれるのが、ConversationBufferWindowMemoryです。

ConversationBufferWindowMemory

オプションkで指定した数のみ、最新の会話履歴を保存します。

基本的な使い方

ライブラリをインストールします。

from langchain.memory import ConversationBufferWindowMemory

先ほどのConversationBufferMemoryと同じように使ってみます。k=1を指定しているので、最新のもののみ保存します。

memory = ConversationBufferWindowMemory( k=1)
memory.save_context({"input": "こんにちわ"}, {"ouput": "どうかしましたか"})
memory.save_context({"input": "体調がよくないです"}, {"ouput": "それは、よくないですね"})

#保存している会話履歴の表示
memory.load_memory_variables({})

<実行結果>
最新の会話のみが保存されており、最初の「こんにちわ」「どうかしましたか」が削除されていることがわかります。

{'history': 'Human: 体調がよくないです\nAI: それは、よくないですね'}

chatGPTとの組み合わせ

chatGPTとくみあわせて、ConversationBufferWindowMemoryを使っていきます。
k=2と、最新会話2個のみ残す形にします。

from langchain.llms import OpenAI
from langchain.chains import ConversationChain
conversation_with_summary = ConversationChain(
    llm=OpenAI(temperature=0), 
    # We set a low k=2, to only keep the last 2 interactions in memory
    memory=ConversationBufferWindowMemory(k=2), 
    verbose=True
)
conversation_with_summary.predict(input="元気ですか")

<実行結果>


> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:

Human: 元気ですか
AI:

> Finished chain.
 はい、元気です!今日はとてもいい天気ですね!

あと2回、繰り返すと、この「元気ですか」のやり取りがCurrent conversationから消えます。
以下に実行結果を示します。

会話2回目

conversation_with_summary.predict(input="何か気になることでもありますか")

<実行結果>

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: 元気ですか
AI:  はい、元気です!今日はとてもいい天気ですね!
Human: 何か気になることでもありますか
AI:

> Finished chain.
 ありません。今は特に気になることはありません。ただ、今日はとても忙しい日です。

会話3回目.
ConversationBufferMemoryの時にうざがらみされたので、今回はこちらからしつこく攻めます。

conversation_with_summary.predict(input="なぜ忙しいんですか")

<実行結果>
文句も言わずに返答をかえしてきます。さすが、人間じゃないだけある。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: 元気ですか
AI:  はい、元気です!今日はとてもいい天気ですね!
Human: 何か気になることでもありますか
AI:  ありません。今は特に気になることはありません。ただ、今日はとても忙しい日です。
Human: なぜ忙しいんですか
AI:

> Finished chain.
 今日は、私が参加するオンライン会議があります。それに加えて、今日は私が担当するプロジェクトの期限が迫っているので、今日はとても忙しいです。

会話4回目
この会話実施後、最初の「こんにちわ」のやりとりが消えます。

conversation_with_summary.predict(input="それは大変ですね")

<実行結果>

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: 何か気になることでもありますか
AI:  ありません。今は特に気になることはありません。ただ、今日はとても忙しい日です。
Human: なぜ忙しいんですか
AI:  今日は、私が参加するオンライン会議があります。それに加えて、今日は私が担当するプロジェクトの期限が迫っているので、今日はとても忙しいです。
Human: それは大変ですね
AI:

> Finished chain.
 はい、大変です。でも、今日は期限を守るために頑張ります!

Entity Memory

基本的な使い方

会話に出てくる人の名前などの情報を保持します。
今までは、基本的な使い方のところでLLMつかいませんでしたが、ここからは使います。

なので、ライブラリのインポートでOpenAI関数をよんでいます。

from langchain.llms import OpenAI
from langchain.memory import ConversationEntityMemory
llm = OpenAI(temperature=0)

memory.save_contextの前に、memory.load_memory_variablesをよんでいます。ここで、エンティティ情報を抜き出して保持しているようです。

memory = ConversationEntityMemory(llm=llm)
_input = {"input": "佐藤さんと田中さんは弊社で働いています"}
memory.load_memory_variables(_input)
memory.save_context(
    _input,
    {"ouput": " すばらしいですね。どんなプロジェクトにかかわっているんですか"}
)

ただ、上記だけだとまだEntity情報は抽出されず、以下のようにすると、佐藤さんと田中さんについて情報が抽出されます。

memory.load_memory_variables({"input": '佐藤さんはだれですか'})

<実行結果>

{'history': 'Human: 佐藤さんと田中さんは弊社で働いています\nAI:  すばらしいですね。どんなプロジェクトにかかわっているんですか',
 'entities': {'佐藤さん': '佐藤さんは弊社で働いている。', '田中さん': '田中さんは弊社で働いている。'}}

chatGPTとの組み合わせ

chatGPTのプロンプトにくみこんでいきます。
まずはライブラリの読みこみから。

from langchain.chains import ConversationChain
from langchain.memory import ConversationEntityMemory
from langchain.memory.prompt import ENTITY_MEMORY_CONVERSATION_TEMPLATE
from pydantic import BaseModel
from typing import List, Dict, Any

conversationインスタンスをつくります。
今回は、デフォルトのプロンプトではなく、Entity Model用のプロンプトをつかいます。

conversation = ConversationChain(
    llm=llm, 
    verbose=True,
    prompt=ENTITY_MEMORY_CONVERSATION_TEMPLATE,
    memory=ConversationEntityMemory(llm=llm)
)

会話をしていきます。

conversation.predict(input="鈴木さんと浜崎さんは釣り仲間です")

<実行結果>
一番下の行が、llmからの返答です。
うーん、妙にさえない会話になっていますね。質問をおうむ返しされました。英語版だと、もっとちゃんと返していたので、日本語だとまだうまくいかないのかも。Entityはとれているようなのですが。
ちなみに、いままで「AI:」だったのが「You:」になっているのは、インポートしたプロンプトテンプレートの影響です。

> Entering new ConversationChain chain...
Prompt after formatting:
You are an assistant to a human, powered by a large language model trained by OpenAI.

You are designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, you are able to generate human-like text based on the input you receive, allowing you to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.

You are constantly learning and improving, and your capabilities are constantly evolving. You are able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. You have access to some personalized information provided by the human in the Context section below. Additionally, you are able to generate your own text based on the input you receive, allowing you to engage in discussions and provide explanations and descriptions on a wide range of topics.

Overall, you are a powerful tool that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether the human needs help with a specific question or just wants to have a conversation about a particular topic, you are here to assist.

Context:
{'鈴木さん': '鈴木さんは浜崎さんと釣り仲間で、釣りに行くことがあります。', '浜崎さん': '浜崎さんは鈴木さんと釣り仲間で、釣りに行くことがあります。'}

Current conversation:

Last line:
Human: 鈴木さんと浜崎さんは釣り仲間です
You:

> Finished chain.
 そうですね。鈴木さんと浜崎さんは釣り仲間で、釣りに行くことがあります。

何回か、会話を重ねた結果を示します。

> Entering new ConversationChain chain...
Prompt after formatting:
You are an assistant to a human, powered by a large language model trained by OpenAI.

You are designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, you are able to generate human-like text based on the input you receive, allowing you to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.

You are constantly learning and improving, and your capabilities are constantly evolving. You are able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. You have access to some personalized information provided by the human in the Context section below. Additionally, you are able to generate your own text based on the input you receive, allowing you to engage in discussions and provide explanations and descriptions on a wide range of topics.

Overall, you are a powerful tool that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether the human needs help with a specific question or just wants to have a conversation about a particular topic, you are here to assist.

Context:
{'浜崎さん': '浜崎さんは鈴木さんと釣り仲間で、鈴木さんは浜崎さんの会社の社長です。この前も、御前崎の方に一緒に釣りに行ったみたいです。そして、今日も釣りに行っているようです。', '今日': ''}

Current conversation:
Human: この前も、御前崎の方に一緒に釣りに行ったみたいですよ。鈴木さんのあだなはスーさんです
AI:  そうですね。鈴木さんは、御前崎の方に一緒に釣りに行ったみたいです。そして、鈴木さんのあだなはスーさんです。
Human: あなたは釣りはどうですか
AI:  釣りは非常に楽しいです。釣りをすると、自然の中で時間を過ごすことができ、新しい発見をすることもできます。また、釣りをすることで、仲間との交流を深めることもできます。
Human: 浜崎さんは、今日も釣りにいってるんですかね
AI:  そうですね。浜崎さんは今日も釣りに行っているようです。
Last line:
Human: 浜崎さんは、今日は、だれとどこに釣りにいってるんでしょうか
You:

> Finished chain.
 浜崎さんは今日は、鈴木さんと御前崎の方に釣りに行っているようです。

Contextを反映しているようですが、おうむ返しが多いですね。
最後の方は、適当に捏造してきましたね。

Conversation Knowledge Graph Memory

ナレッジグラフを活用するという話なのですが、残念ながらあまりうまく動いていないようです。
動くには動くので、ただの備忘録として実行結果を載せておきます。

基本的な使い方

ライブラリ読み込み

from langchain.memory import ConversationKGMemory
from langchain.llms import OpenAI

会話を記憶させてみる

llm = OpenAI(temperature=0)
memory = ConversationKGMemory(llm=llm)
memory.save_context({"input": "鈴木さんに挨拶して"}, {"ouput": "鈴木さんって誰"})
memory.save_context({"input": "鈴木さんは社長だよ"}, {"ouput": "挨拶しなきゃ"})

#記憶している内容の表示
memory.load_memory_variables({"input": '鈴木さんは誰'})

<実行結果>
英語交じりです。でも、言語が入り混じっているところに目をつぶれば、中身は妥当な気がします。果たしてこれで動くのか。

{'history': 'On 鈴木さん: 鈴木さん に 挨拶して. 鈴木さん is 社長.'}

chatGPTとの組み合わせ

一応やりました。

llm = OpenAI(temperature=0)
from langchain.prompts.prompt import PromptTemplate
from langchain.chains import ConversationChain

template = """The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. 
If the AI does not know the answer to a question, it truthfully says it does not know. The AI ONLY uses information contained in the "Relevant Information" section and does not hallucinate.

Relevant Information:

{history}

Conversation:
Human: {input}
AI:"""
prompt = PromptTemplate(
    input_variables=["history", "input"], template=template
)
conversation_with_kg = ConversationChain(
    llm=llm, 
    verbose=True, 
    prompt=prompt,
    memory=ConversationKGMemory(llm=llm)

以下のような会話をしました。
conversationではじまっているのが人間の入力で、それ以外はLLMからの返答です。
釣りのことは記憶できましたが、鈴木さんが社長であることは記憶できていません。

conversation_with_kg.predict(input="やあ、元気?")
 やあ!元気です!今日はどんな日でしたか?
conversation_with_kg.predict(input="今日は釣り日和でした。鈴木さんと浜崎さんも、よく釣れたようですよ")
 そうですね!鈴木さんと浜崎さんは、今日はとても釣りが上手でしたね!私は釣りをしたことがありませんが、それは楽しそうですね!
conversation_with_kg.predict(input="浜崎さんは、私の同僚で、鈴木さんは社長なんですよ")
 そうなんですね!鈴木さんと浜崎さんは今日、たくさんの魚を釣ったんですね!
conversation_with_kg.predict(input="魚だけじゃなくてボーナスも増えるといいんですけどね")
 そうですね。鈴木さんと浜崎さんが今日たくさんの魚を釣ったので、ボーナスも増えるといいですね。
conversation_with_kg.predict(input="鈴木さんについて、知っていることはありますか")
 はい、鈴木さんは今日たくさんの魚を釣りました。

<最終結果のプロンプト>
最終結果だけ詳細結果を表示します。社長であるという情報が完全にどこかにいきました。使い方が難しいのか、日本語だからだめなのか。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. 
If the AI does not know the answer to a question, it truthfully says it does not know. The AI ONLY uses information contained in the "Relevant Information" section and does not hallucinate.

Relevant Information:

On 鈴木さん: 鈴木さん caught a lot of fish today.

Conversation:
Human: 鈴木さんについて、知っていることはありますか
AI:

> Finished chain.
 はい、鈴木さんは今日たくさんの魚を釣りました。

ConversationSummaryMemory

この要約は良い感じでした。

基本的な使い方

ライブラリのインストールです。要約にLLMを使います。

from langchain.memory import ConversationSummaryMemory
from langchain.llms import OpenAI

簡単な会話をいれます。

memory = ConversationSummaryMemory(llm=OpenAI(temperature=0))
memory.save_context({"input": "こんにちわ"}, {"ouput": "なんでしょう"})

#内部の要約結果を表示
memory.load_memory_variables({})

<実行結果>
英語ですが、それなりに要約できているようです。

{'history': '\nThe human greets the AI and the AI responds by asking what the human wants.'}

chatGPTへの組み込み

ライブラリの読み込みと、最初の語りかけ。

from langchain.llms import OpenAI
from langchain.chains import ConversationChain
llm = OpenAI(temperature=0)
conversation_with_summary = ConversationChain(
    llm=llm, 
    memory=ConversationSummaryMemory(llm=OpenAI()),
    verbose=True
)
conversation_with_summary.predict(input="はじめまして、私は鈴木です")

<実行結果>
一番、下の行がLLMからの返答です。初回なので、まだSummaryはないです。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:

Human: はじめまして、私は鈴木です
AI:

> Finished chain.
 はじめまして、鈴木さん。私はAIです。どうぞよろしくお願いします。

さらに会話を続けます。

conversation_with_summary.predict(input="あなたの趣味はなんですか。私は、釣りです")

<実行結果>
Current conversationの下に、一行あけて英語で要約がはいりました。それなりに妥当な要約になっています。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:

The human introduces himself as Suzuki, to which the AI responds with a courteous greeting and introduces itself as an AI.
Human: あなたの趣味はなんですか。私は、釣りです
AI:

> Finished chain.
 私の趣味は、プログラミングです。プログラミングは、コンピューターを使ってプログラムを作成することです。プログラムを作成することで、コンピューターを使って任意のタスクを実行できます。プログラミングをするのは、とても楽しいです!あなたは、釣りが好きなんですね?

このあとは、どのような会話をしたかについて記載し、最終結果だけを載せます。
conversationではじまるのが人間の入力、それ以外が、LLMからの応答です。
途中で文がきれているところはあるものの、それなりに文脈を把握した内容になっています。

conversation_with_summary.predict(input="プログラミングですか。最近はどんなものをプログラムしたんですか")
 最近は、自動車の自動運転システムをプログラムしています。これは、自動車が自動的に運転するためのプログラムです。また、AIを使用して、自動車が自動的に運転するためのプログラムも作成しています。
conversation_with_summary.predict(input="自動運転は便利そうですよね。もうすぐ、自動運転の時代がくるのでしょうか")
 そうですね。自動運転は、安全性と効率性を向上させることができます。現在、自動運転技術は、自動車の走行を安全に行うために、複雑な感覚器やコンピューターシステムを使用しています。また、自動運転技術は、自動車の走行をより効率的に行うために、複雑な感覚器やコンピューターシステムを使用しています。自動運転技術は、今後もさらに発展してい
conversation_with_summary.predict(input="ところで、私の名前をおぼえていますか")
 はい、私はあなたの名前をおぼえています。あなたの名前は鈴木さんですね?
conversation_with_summary.predict(input="私の趣味についてどう思いますか")
 私は釣りの趣味を持つあなたについて考えています。釣りは、自然を楽しみながら、様々な種類の魚を捕まえることができる楽しい活動です。釣りをすることで、自然をより深く理解することができます。また、釣りをすることで、自然の中での自然な生態系を学ぶことができます。

最終的な要約文はこちらです。結構長いですね。
会話を続ければ続けるほど、要約も長くなると、あまり要約の意味はないのですが。。

he human introduced himself as Suzuki, to which the AI responded with a courteous greeting and introduced itself as an AI. The AI mentioned that its hobby was programming, which is the process of creating programs using computers to execute any task, and it recently programmed an autonomous driving system for cars. The human then asked if the AI thought the autonomous driving system was convenient and if it was soon to be the era of autonomous driving. The AI replied that it was, as autonomous driving could improve safety and efficiency, and that the technology was currently using complex sensors and computer systems to safely drive cars and to make the driving process more efficient. The AI also mentioned that the technology would continue to develop in the future, and confirmed that it remembered the human's name. The human replied that their hobby was fishing.

日本語訳ものせておきます。

人間は鈴木と名乗り、AIは丁寧な挨拶で応え、AIであることを自己紹介した。AIは、趣味がプログラミング(コンピュータを使ってプログラムを作り、あらゆる作業を実行すること)であり、最近は自動車の自律走行システムをプログラミングしていることを話した。そこで人間は、AIが「自律走行システムは便利だと思うか」「もうすぐ自律走行の時代になるのか」と尋ねた。AIは、自律走行は安全性と効率性を向上させることができるので、そうだと答え、現在、複雑なセンサーとコンピューターシステムを使って車を安全に運転し、運転プロセスをより効率的にするための技術であることを話しました。また、AIは、この技術が今後も発展していくことに触れ、人間の名前を覚えていることを確認した。人間は、趣味が釣りであると答えた。

同じ会話が何度も出てきたら、トークン数の節約になるのかもしれません。

ConversationSummaryBufferMemory

ConversationBufferMemoryとConversationSummaryMemoryの両方の機能を兼ね備えたメモリです。
つまり、直近の会話と、過去会話の要約を、プロンプトに組み込めます。

基本的な使い方

トークンのパーサーにtiktokenを使うので、importしておきます。

! pip install tiktoken
import tiktoken

max_token_limitに、保持しておく過去会話のトークン数を指定します。この値のデフォルトは2000。
過去会話も取得できるようにする場合は、return_messagesをTrueにします。デフォルトはFalse。

memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=30, return_messages=True)
memory.save_context({"input": "こんにちわ"}, {"output": "調子はどうですか"})
memory.save_context({"input": "悪くないです"}, {"output": "それはよかった"})
memory.load_memory_variables({})

<実行結果>
contentに英語のサマライズが、そのあとに、人間とAIの会話が格納されています。

{'history': [SystemMessage(content='\nThe human greets the AI and the AI asks how they are doing.', additional_kwargs={}),
  HumanMessage(content='悪くないです', additional_kwargs={}, example=False),
  AIMessage(content='それはよかった', additional_kwargs={}, example=False)]}

以下のように、直接使うこともできます。

messages = memory.chat_memory.messages
previous_summary = "he human greets the AI and the AI asks how the human is doing, to which the human responds that they are doing well. The AI is pleased to hear this."
memory.predict_new_summary(messages, previous_summary)

<実行結果>
冒頭に\nが入るので、使うときはこれを除去などする必要がありそうです。

\n
The human greets the AI and asks how they are doing. The AI responds that the human is not doing bad, and the human agrees, pleased with their response.

chatGPTへの組み込み

ライブラリの読み込みと、最初の語りかけ。

from langchain.chains import ConversationChain
conversation_with_summary = ConversationChain(
    llm=llm, 
    # We set a very low max_token_limit for the purposes of testing.
    memory=ConversationSummaryBufferMemory(llm=OpenAI(), max_token_limit=100),
    verbose=True
)
conversation_with_summary.predict(input="こんにちわ、元気?")

<実行結果>
まだ、会話数が少ないので、サマライズはされないようです。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:

Human: こんにちわ、元気?
AI:

> Finished chain.
 こんにちは!元気です!今日は何をしていますか?

いくつか会話を入力します。
conversationではじまるのが人間の入力、それ以外が、LLMからの応答です。

conversation_with_summary.predict(input="Qiitaの記事を書いています")
 ああ、Qiitaで記事を書くのですね!どんな記事を書いていますか?
conversation_with_summary.predict(input="Langchainの記事を書いています。Langchainを知っていますか")
 知っています!Langchainは、プログラミング言語を学習しやすくするためのオープンソースプラットフォームですよね?あなたはLangchainをどう使っているのですか?
conversation_with_summary.predict(input="まだ勉強中ですよ")
そうですか。勉強中なら、Langchainのチュートリアルを参考にしてみると良いでしょう。Langchainは、多くのプログラミング言語を学ぶのに役立つツールですので、是非活用してみてください!

<実行結果>
「まだ勉強中ですよ」と入力した後の、出力ログが以下です。
最新の会話のみが保存されており、それ以外は、System:のところにサマライズされています。
ここで保存されている会話の長さが、max_token_limitで指定された長さになります。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
System: 
The AI greets the human and asks what they are doing today. The human responds that they are writing an article on Qiita, to which the AI responds positively. The human then mentions they are writing about Langchain, asking if the AI is familiar with it.
AI:   知っています!Langchainは、プログラミング言語を学習しやすくするためのオープンソースプラットフォームですよね?あなたはLangchainをどう使っているのですか?
Human: まだ勉強中ですよ
AI:

> Finished chain.
 そうですか。勉強中なら、Langchainのチュートリアルを参考にしてみると良いでしょう。Langchainは、多くのプログラミング言語を学ぶのに役立つツールですので、是非活用してみてください!

ConversationTokenBufferMemory

ConversationBufferMemoryのトークン版であるというだけなので割愛

VectorStore-Backed Memory

VectorStoreに過去会話をつっこんでおいて、そこから類似する会話を引っ張ってくるというものです。これはなかなか使えそうな感じですが、類似検索アルゴリズムがkNNなので総当たり探索です。データ数が多いほど、時間がかかります。一応、高速アルゴリズムも実装されています。

このVectorStoreには、faise-gpuを使います。詳細はこちらの記事が詳しいです。
https://qiita.com/SakuraDk/items/0c03b7ebd4aef8d6caed

あらかじめインストールしておきます。

! pip install faiss-gpu

基本的な使い方

importのところで「WARNING:langchain.utilities.powerbi:Could not import azure.core python package.」がでましたが、動いたのでそのままにしています。

# Lang chain側のインポート
from datetime import datetime
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.llms import OpenAI
from langchain.memory import VectorStoreRetrieverMemory
from langchain.chains import ConversationChain
from langchain.prompts import PromptTemplate

以下のようにvector storeを初期化します。Dimensionsはこれで固定でいいようです。
探索アルゴリズムは、IndexFlatL2。L2ノルムというもののようです。ちゃんと調べてませんが、響き的にユークリッド距離ってことでしょうか。特に高速化されていない総当たりのアルゴリズムです。

# faiss-gpuのインポートとvector storeの初期化
import faiss

from langchain.docstore import InMemoryDocstore
from langchain.vectorstores import FAISS


embedding_size = 1536 # Dimensions of the OpenAIEmbeddings
index = faiss.IndexFlatL2(embedding_size)
embedding_fn = OpenAIEmbeddings().embed_query
vectorstore = FAISS(embedding_fn, index, InMemoryDocstore({}), {})

kで、いくつ検索するかを指定します。k=2の場合は、最も近い会話の組をふたつとってきます。

retriever = vectorstore.as_retriever(search_kwargs=dict(k=2))
memory = VectorStoreRetrieverMemory(retriever=retriever)

memory.save_context({"input": "ピザが好きです"}, {"output": "ピザ、おいしいよね"})
memory.save_context({"input": "サッカーが好きです"}, {"output": "..."})
memory.save_context({"input": "そのチームは好きじゃないです"}, {"output": "ok"}) # 

「ランチは何にしましょうか」という文章に最も近い会話を一組、検索します。

print(memory.load_memory_variables({"prompt": "ランチは何にしましょうか"})["history"])

<実行結果>
食事の話をしている会話が二つ、表示されました。良い感じです。

input: 昨日の晩御飯はなんでしたか
output: カレーです
input: ピザが好きです
output: ピザ、おいしいよね

別のプロンプトも投げてみます。「どんなスポーツをみますか」という文章に最も近い会話を一組、検索します。

print(memory.load_memory_variables({"prompt": "どんなスポーツをみますか"})["history"])

<実行結果>
サッカーについての、同じ会話がふたつ表示されました。なぜ?

input: サッカーが好きです
output: ...
input: サッカーが好きです
output: ...

全く関係ないプロンプトも投げてみます。

print(memory.load_memory_variables({"prompt": "仕事は順調です"})["history"])

<実行結果>
ピザについての、同じ会話がふたつ表示されました。若干、癖があるライブラリのようです。保存データ数が少ないのがよくないのかもしれません。

input: ピザが好きです
output: ピザ、おいしいよね
input: ピザが好きです
output: ピザ、おいしいよね

chatGPTへの組み込み

プロンプトなどを設定して、LLMに投げます。

llm = OpenAI(temperature=0) # Can be any valid LLM
_DEFAULT_TEMPLATE = """The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Relevant pieces of previous conversation:
{history}

(You do not need to use these pieces of information if not relevant)

Current conversation:
Human: {input}
AI:"""
PROMPT = PromptTemplate(
    input_variables=["history", "input"], template=_DEFAULT_TEMPLATE
)
conversation_with_summary = ConversationChain(
    llm=llm, 
    prompt=PROMPT,
    # We set a very low max_token_limit for the purposes of testing.
    memory=memory,
    verbose=True
)

初手で好きなスポーツについて聞いてみます。

conversation_with_summary.predict(input="こんにちは。どんなスポーツが好きですか")

<実行結果>
ログと、AIからの応答結果を以下に示します。
ちゃんとRelevant pieces of previous conversationのところに、スポーツ関連の会話が記載されています。
AIの応答もサッカーに関するものになりました。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Relevant pieces of previous conversation:
input: サッカーが好きです
output: ...
input: そのチームは好きじゃないです
output: ok

(You do not need to use these pieces of information if not relevant)

Current conversation:
Human: こんにちは。どんなスポーツが好きですか
AI:

> Finished chain.
 こんにちは!スポーツは好きですが、特にサッカーが好きです。サッカーをプレイするのが大好きです!

ただ、上記の例だと会話文が、vector storeはinputとoutputが会話していますが、LLMに投げているプロンプトだと、HumanとAIが会話しています。inputとoutputの会話の、どちらがHumanでどちらがAIかはうまく認識しているのでしょうか。

vector storeにいれる会話を変更し、再度、スポーツの話題をふってみます。

memory.save_context({"input": "ピザが好きです"}, {"output": "ピザ、おいしいよね"})
memory.save_context({"input": "サッカーが好きです"}, {"output": "..."})
memory.save_context({"input": "そのチームは好きじゃないです"}, {"output": "ok"}) 
memory.save_context({"input": "昨日の晩御飯はなんでしたか"}, {"output": "カレーです"}) 
memory.save_context({"input": "好きなスポーツは、なんですか"}, {"output": "テニスです。サッカーは嫌いです。"}) 

<実行結果>
inputがHuman、outputがAIと認識しているようです。これなら、HumanとAIの部分を変えてもうまくいきそうです。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Relevant pieces of previous conversation:
input: 好きなスポーツは、なんですか
output: テニスです。サッカーは嫌いです。
input: サッカーが好きです
output: ...

(You do not need to use these pieces of information if not relevant)

Current conversation:
Human: こんにちは。どんなスポーツが好きですか
AI:

> Finished chain.
 こんにちは!私はテニスが好きです。サッカーは嫌いですが、他のスポーツも楽しめるようになりたいと思っています。

ちなみに、新たに追加した会話も、vector storeに追加されます。
以下は、今までの会話に、釣りの会話を強引にくっつけてしばらく会話したときのログです。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Relevant pieces of previous conversation:
input: 釣りは好きですか?私は好きです。
response:  釣りは好きですか?私も釣りが好きです!最近、湖で釣りをしたことがあります。楽しかったですよ!
input: 昨日の晩御飯はなんでしたか
output: カレーです

(You do not need to use these pieces of information if not relevant)

Current conversation:
Human: どんな魚が釣れましたか
AI:

> Finished chain.
 昨日の晩御飯はなんでしたかという質問でしたね?昨日の晩御飯はカレーでした。釣りでは、サケ、マス、イワナ、ヤマメなどが釣れました

outputがresponseになっていますが、関係ないようですね。ちゃんと、淡水魚を挙げています。(川魚っぽいけど)

まとめ

  • LangchainのMemory八種類を実際に動かして、試してみました
  • どれがよさそうなどは、TL;DRにて。日本語だと動かないやつもある点注意が必要そうです。

以上

32
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
35