【LangChain】RAGとchain周りを理解する③
メモリーから会話履歴を引き出して会話をする方法です
メモリの登録と呼び出し
例として、ユーザー(人間側)が「ここはどこですか?」と問い、AIが「大阪です」と答えたとしましょう。
input = {"question":"ここはどこですか"}
result = {"answer": "大阪です"}
なお、実際にはanswerはAIが答えることになりますので、answerは以下のように作成しておくと良いでしょう
from langchain_core.messages import AIMessage
result = {"answer": AIMessage(content="大阪です")}
これらを登録できるメモリを以下のように作成します。
output_keyの"answer"とinput_keyの"question"は、上記でそれぞれキーを"answer"および"question"と定めたからです
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(
return_messages=True, output_key="answer", input_key="question"
)
メモリには以下のように会話を登録できます。
(当然、"answer"と"question"のキーがなければエラーとなります)
memory.save_context(input, {"answer": result["answer"].content})
登録した記録は以下のように呼び出せます
memory.load_memory_variables({})
#-> {'history': [HumanMessage(content='ここはどこですか'), AIMessage(content='大阪です')]}
"history"というキーにHumanMessageとAIMessageが入っています。
履歴を呼び出して会話する
まず、ユーザーの質問に対して、会話履歴を参照し、回答を生成する流れをchainで作成してみましょう。
promptには、ユーザーの問いかけ(question)と会話履歴(chat_history)が必要なので、以下のような流れになるはずです。
template = """questionで問いかけますので、chat_historyを参照して回答してください
chat_history:{chat_history}
question:{question}
回答:"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()
chain = (
# ここに会話履歴を呼び出す
| prompt
| model
)
chain.invoke({"question":"何と答えましたか"})
invokeで与えられるのは{"question":"何と答えましたか"}だけなので、"chat_history"は後から付け加えます。
後から付け加えるには、RunnablePassthrough().assign()を使えば良いのでした。
試しに「'大阪です'」と付け加えるようにしてみましょう。
chain = (
RunnablePassthrough().assign(
chat_history = '大阪です'
)
| prompt
| model
)
chain.invoke({"question":"何と答えましたか"})
おっと、エラーが出ました。
Expected a Runnable, callable or dict.Instead got an unsupported type: <class 'str'>
RunnablePassthrough().assign()の引数では、lambdaを与えなければなりません。
chain = (
RunnablePassthrough().assign(
chat_history = lambda x: '大阪です' # 修正
)
| prompt
| model
)
chain.invoke({"question":"何と答えましたか"})
#-> AIMessage(content='大阪です')
これできちんと動きました。
さて、実際は"大阪です"のベタ打ちではなく、メモリから呼び出したデータを使いたいです。
memory.load_memory_variables({})
を使ってみましょう
chain = (
RunnablePassthrough().assign(
chat_history = lambda x: memory.load_memory_variables({}) #修正
)
| prompt
| model
)
chain.invoke({"question":"何と答えましたか"})
#-> AIMessage(content='大阪です')
chain.invoke({"question":"2つ前の質問は何でしたか"})
#-> AIMessage(content='ここはどこですか')
きちんと会話することができました。
公式ドキュメントの書き方に寄せる
一応上記の書き方でも動きますが、もう少し工夫しましょう
まずRunnablePassthrough().assign()
の部分が長くなるので、変数loaded_memoryを用意して外だししてあげましょう
loaded_memory = RunnablePassthrough().assign(
chat_history = lambda x: memory.load_memory_variables({})
)
template = """questionで問いかけますので、chat_historyを参照して回答してください
chat_history:{chat_history}
question:{question}
回答:"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()
chain = loaded_memory | prompt | model
次に、chat_history = lambda x: memory.load_memory_variables({})
の部分、
xは使っていないのに変数として存在するのは気持ち悪いです。
代わりにRunnableLambdaを使って書き直しましょう
loaded_memory = RunnablePassthrough().assign(
chat_history = RunnableLambda(memory.load_memory_variables)
)
今回、memory.load_memory_variablesは
{'history': [HumanMessage(content='ここはどこですか'), AIMessage(content='大阪です')]}
なのですが、pythonは型指定がないのでどういったキーを持つ辞書型が返されるかわかりにくいです。
そこで、'history'の値を取り出して、'chat_history'というキーに入れ直しているんだよ、ということを明示してやるためにitemgetterを使いましょう
loaded_memory = RunnablePassthrough().assign(
chat_history = RunnableLambda(memory.load_memory_variables) | itemgetter("history")
)
これで公式ドキュメントと同様の書き方ができました。
Discussion