はじめに
普段、RAGを使ったシステムをよく作っているのですがLangChainでやったことがなかったので何番煎じかわかりませんがやってみた記録として残します。
この記事はLCELの何となくの雰囲気を知りたい人、ちょこっとRAGを作ってみたい人向けです。
手順
手順は、LangChainのドキュメントを参考に進めます。
以下の組織が提供するモデルを使えますが、今回はOpenAIのものを使います。また、APIキーは取得済みの前提で進めます。
- OpenAI
- Anthropic
- Cohere
- FireworksAI
- MistralAI
- TogetherAI
インストール
pip install --upgrade --quiet langchain-core langchain-community langchain-openai
とりあえず動かす
RAG云々の前にLCELの記法で書かれたプログラムを動かしてみます。
サンプルコードが載っていたのでそのまま動かしてみました。
import getpass
import os
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
os.environ["OPENAI_API_KEY"] = getpass.getpass()
prompt = ChatPromptTemplate.from_template("何かジョークを言って。お題: {topic}")
model = ChatOpenAI(model="gpt-3.5-turbo")
output_parser = StrOutputParser()
chain = prompt | model | output_parser
result = chain.invoke({"topic": "アイスクリーム"})
print(result)
アイスクリームが好きな理由は、何度食べても「冷たい!」と言っても怒られないからです!
無事ジョークを言ってくれました。
ポイントはchain = prompt | model | output_parser
の部分ですね。
|
で繋ぐことで左から右に処理が進んでいきます。
prompt
、 model
、output_parser
でそれぞれ何が起こっているか見た方が理解が進みそうなので、|
で一括で処理するのではなく要素ごとに出力するようにしてみました。
import getpass
import os
import json
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
os.environ["OPENAI_API_KEY"] = getpass.getpass()
# Prompt
prompt = ChatPromptTemplate.from_template("何かジョークを言って。お題: {topic}")
prompt_value = prompt.invoke({"topic": "アイスクリーム"})
print("Prompt:")
print(prompt_value)
# Model
model = ChatOpenAI(model="gpt-3.5-turbo")
message = model.invoke(prompt_value)
print("Model:")
print("- Message content")
print(message.content)
print("- Message metadata")
print(json.dumps(message.response_metadata, indent=4))
# Output parser
output_parser = StrOutputParser()
result = output_parser.invoke(message)
print("Output parser:")
print(result)
Prompt:
messages=[HumanMessage(content='何かジョークを言って。お題: アイスクリーム')]
Model:
- Message content
アイスクリームって、氷のように冷たいけど、人気は熱いよね!
- Message metadata
{
"token_usage": {
"completion_tokens": 33,
"prompt_tokens": 28,
"total_tokens": 61
},
"model_name": "gpt-3.5-turbo",
"system_fingerprint": null,
"finish_reason": "stop",
"logprobs": null
}
- Entire message
content='アイスクリームって、氷のように冷たいけど、人気は熱いよね!' response_metadata={'token_usage': {'completion_tokens': 33, 'prompt_tokens': 28, 'total_tokens': 61}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None} id='run-32b5af3f-2481-447e-a356-765305a40ac6-0'
Output parser:
アイスクリームって、氷のように冷たいけど、人気は熱いよね!
想像通りというか特に違和感のない結果かと思います。
- プロンプトのテンプレートの変数を辞書型で渡してプロンプトを作る
- そのプロンプトをモデルに渡すとメッセージをが返る
- パースして文字列部分を返す
というだけですね。
RAGしてみる
とりあえず、動いてざっくりわかったところでRAGも試してみます。
まずは必要なライブラリを入れるところからです。
pip install langchain docarray
docarray
でVectorStoreをメモリに簡易的に用意して、RAGしてみます。
サンプルコードと実行結果です。その後に要素ごとに説明を書いてみます。
from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
from langchain_openai import ChatOpenAI
vectorstore = DocArrayInMemorySearch.from_texts(
# Vector Storeに保持する文章群
[
"Vantiqにはクラウドバージョンとオンプレバージョンがあります",
"Vantiqのクラウドバージョンはパブリッククラウドとプライベートクラウドがあります"
],
embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever()
template = """次の参考情報のみに基づいて質問に答えてください
参考情報: {reference_info}
質問: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
output_parser = StrOutputParser()
setup_and_retrieval = RunnableParallel(
{"reference_info": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser
user_question = "Vantiqのバージョンにはどんなものがありますか。"
result = chain.invoke(user_question)
print("結果:")
print(result)
結果:
Vantiqのバージョンにはクラウドバージョンとオンプレバージョンがあります。クラウドバージョンにはパブリッククラウドとプライベートクラウドがあります。
無事、回答できています。
まずVectorStoreに登録している内容です。Vantiqにはクラウドバージョンとオンプレバージョンがあります
とVantiqのクラウドバージョンはパブリッククラウドとプライベートクラウドがあります
という2つの情報を今回は登録しています。
vectorstore = DocArrayInMemorySearch.from_texts(
# VectorStoreに保持する文章群
[
"Vantiqにはクラウドバージョンとオンプレバージョンがあります",
"Vantiqのクラウドバージョンはパブリッククラウドとプライベートクラウドがあります"
],
embedding=OpenAIEmbeddings()
)
次に、プロンプトについてです。{reference_info}
にはVectorStoreに登録された内容うち、質問された内容と類似度が高いものが入ります。{question}
にはユーザーからの質問が入ります。
template = """次の参考情報のみに基づいて質問に答えてください
参考情報: {reference_info}
質問: {question}
"""
モデルは最初とほぼ同じですがtemperature
の設定を変更したかったのでその設定を行なっています。
model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
最後に
今回実施した内容はかなり基礎的な内容で、実際には類似性検索周りのチューニングやリランキング、そもそも入力内容に対してRAGの実行が必要かどうかの判断などなどやることは多いかと思います。
その辺りも触っていこうと思います。