🚀

【LangChain】RAGとchain周りを理解する③

2024/03/03に公開

メモリーから会話履歴を引き出して会話をする方法です

メモリの登録と呼び出し

例として、ユーザー(人間側)が「ここはどこですか?」と問い、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