😊

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

2024/03/01に公開

LangChain公式ドキュメントのRAGの説明が雑なので、丁寧に勉強しました
https://python.langchain.com/docs/expression_language/cookbook/retrieval

chain

会話として成立させるにはchainにpromptとmodelを渡す必要がありますが、実行だけならpromptだけでもできます。

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template("私のメッセージは「{my_message}」です")

chain = prompt

templateにmy_messageというパラメーターをつけているので、chain.invoke()の引数にはmy_messageに文言を渡す必要があります

chain.invoke({"my_message":"こんにちは"})

#->ChatPromptValue(messages=[HumanMessage(content='私のメッセージは「こんにちは」です')])

my_messageをつけずに直接文言を入れるとエラーとなります

# エラーとなる
chain.invoke("こんにちは")

#->  Expected mapping type as input to ChatPromptTemplate. Received <class 'str'>.

これを回避するにはRunnablePassthrough()を使います

from langchain_core.runnables import RunnablePassthrough

chain = {
    "my_message":RunnablePassthrough(),
} | prompt

chain.invoke("こんにちは")

promptの前に{"my_message":RunnablePassthrough()}を入れたことにより、invokeで渡された引数がmy_messageのキーをつけられてpromptに渡されます。

複数パラメーターを渡す

このように複数パラメーターを渡すこともできます。

template="""
私のメッセージは「{my_message}」、
あなたのメッセージは「{your_message}」
"""

prompt = ChatPromptTemplate.from_template(template)

chain = prompt

chain.invoke({"my_message":"こんにちは。今日は寒いですね","your_message":"そうですね"})

itemgetterを使ってパラメーターの値を取得することができます。
(itemgetterはLangChainのライブラリではなく、python標準ライブラリです)
以下の例では、my_messageをhuman_messageに、your_messageをai_messageに置き換えています

from operator import itemgetter

template="""
人間のメッセージは「{human_message}」、
AIのメッセージは「{ai_message}」
"""

prompt = ChatPromptTemplate.from_template(template)

chain = {
    "human_message":itemgetter("my_message"),
    "ai_message":itemgetter("your_message")
} | prompt

chain.invoke({
    "my_message":"こんにちは。今日は寒いですね",
    "your_message":"そうですね"
  })

パラメーターを加工する

my_messageのうち、あいさつの部分だけを切り出してpromptに渡します。

# あいさつ部分だけを切り取る関数
def only_greeting(message):
  return message.split('。')[0]

only_greeting('こんにちは。今日は寒いですね')
#-> こんにちは

chainの中で、itemgetter(my_message)で「こんにちは。今日は寒いですね」を取り出し → only_greetingであいさつ部分だけを取り出しという処理をしたいです。

chainでは|で区切ることによって、後工程に渡せるので、以下のように書くことができます

chain = {
    "human_message":itemgetter("my_message") | only_greeting,
    "ai_message":itemgetter("your_message")
} | prompt

ただし、上記の書き方はエラーとなります。
忘れがちですが、chainの中にはRunnnableしか入れることができません。
なので関数であるonly_greetingを入れるにはRunnableLambdaでRunnableに変換してやる必要があります

chain = {
    "human_message":itemgetter("my_message") | RunnableLambda(only_greeting) ,
    "ai_message":itemgetter("your_message")
} | prompt

lambdaで編集して渡す

chainのRunnableにlambdaを設定することにより、パラメーターを加工してpromptに渡すことができます

prompt = ChatPromptTemplate.from_template("会話履歴:{chat_history}")

chain = {
    "chat_history": lambda x: "人間:" + x["my_message"] +"、" + "AI:"+ x["your_message"]
} | prompt

chain.invoke({"my_message": "こんにちは。今日は寒いですね", "your_message": "そうですね"})

#-> ChatPromptValue(messages=[HumanMessage(content='会話履歴:人間:こんにちは。今日は寒いですね、AI:そうですね')])

ただ、上記の方法ではmy_messageとyour_messageをがっちゃんこしてchat_historyにしてしまったので、もとのmy_messageとyour_messageはpromptに渡せなくなってしまってます。

RunnablePassthrough().assign()を使うことで、my_messageとyour_messageを保持したまま、新たにchat_historyを作成し、後工程に渡すことができます。
(繰り返しになりますが、chainに入れられる要素はRunnableなので、ただのassignではなくRunnableのassignでなければなりません)

template="""
私のメッセージは「{my_message}」、
あなたのメッセージは「{your_message}」

会話履歴:{chat_history}」
"""

prompt = ChatPromptTemplate.from_template(template)

chain = (
    RunnablePassthrough().assign(chat_history = lambda x: "人間:" + x["my_message"] +"、" + "AI:"+ x["your_message"] ) 
    | prompt
)

chain.invoke({"my_message": "こんにちは。今日は寒いですね", "your_message": "そうですね"})

#-> ChatPromptValue(messages=[HumanMessage(content='\n私のメッセージは「こんにちは。今日は寒いですね」、\nあなたのメッセージは「そうですね」\n\n会話履歴:人間:こんにちは。今日は寒いですね、AI:そうですね」\n')])

ここまで紐解けば、公式ドキュメントの例が何やっているのか見えてきました。

_inputs = RunnableParallel(
    standalone_question=RunnablePassthrough.assign(
        chat_history=lambda x: get_buffer_string(x["chat_history"])
    )
    | CONDENSE_QUESTION_PROMPT
    | ChatOpenAI(temperature=0)
    | StrOutputParser(),
)
_context = {
    "context": itemgetter("standalone_question") | retriever | _combine_documents,
    "question": lambda x: x["standalone_question"],
}
conversational_qa_chain = _inputs | _context | ANSWER_PROMPT | ChatOpenAI()

次はメモリに会話履歴を保存するところをやっていきます。

Discussion