🦜️🔗LangChain : ユースケース : 質問応答 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 09/04/2023
* 本ページは、LangChain の以下のドキュメントを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- 人工知能研究開発支援
- 人工知能研修サービス(経営者層向けオンサイト研修)
- テクニカルコンサルティングサービス
- 実証実験(プロトタイプ構築)
- アプリケーションへの実装
- 人工知能研修サービス
- PoC(概念実証)を失敗させないための支援
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
- 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
- sales-info@classcat.com ; Web: www.classcat.com ; ClassCatJP
🦜️🔗 LangChain : ユースケース : 質問応答
ユースケース
貴方が幾つかのテキスト文書 (PDF, ブログ, Notion pages 等) を持ちそれらの文書に関連する質問をしたいと仮定します。LLM は、それらのテキスト理解の習熟 (proficiency) を考えると、このための素晴らしいツールです。
このウォークスルーでは、LLM を使用した、文書に対する質問応答アプリケーションを構築する方法を調べます。以下の非常に関連性の高い 2 つのユースケースは他の場所でカバーします :
- QA over 構造化データ (e.g., SQL)
- QA over コード (e.g., Python)
概要
raw 非構造化データを QA チェインに変換するためのパイプラインはこのようなものです :
- Loading (ロード) : 最初にデータをロードする必要があります。ローダーの完全なセットをブラウズするには LangChain 統合ハブ を使用してください。各ローダーは LangChain ドキュメント としてデータを返します。
- Splitting (分割) : テキスト分割は文書を特定のサイズの分割に分解します。
- Storage (ストレージ) : ストレージ (e.g., 多くの場合 ベクトルストア) は分割を保管して分割を 埋め込む場合も多いです。
- Retrieval (検索) : アプリケーションはストレージから分割を検索取得します (e.g., 多くの場合、入力質問に類似した埋め込みによります)。
- Generation (生成) : LLM は質問と検索取得したデータを含むプロンプトを使用して答えを生成します。
- Conversation (Extension) (会話 (拡張)): QA チェインにメモリを追加して複数ターンの会話を保持します。
クイックスタート
スニーク・プレビューをしますと、上記のパイプラインは単一オブジェクト: VectorstoreIndexCreator にすべてラップすることができます。この ブログ記事 に対する QA アプリケーションを望むとします。これを数行のコードで作成できます。最初に環境変数を設定してパッケージをインストールします :
pip install openai chromadb
# Set env var OPENAI_API_KEY or load from a .env file
# import dotenv
# dotenv.load_dotenv()
from langchain.document_loaders import WebBaseLoader
from langchain.indexes import VectorstoreIndexCreator
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
index = VectorstoreIndexCreator().from_loaders([loader])
API リファレンス :
index.query("What is Task Decomposition?")
' Task decomposition is a technique used to break down complex tasks into smaller and simpler steps. It can be done using LLM with simple prompting, task-specific instructions, or with human inputs. Tree of Thoughts (Yao et al. 2023) is an extension of Chain of Thought (Wei et al. 2022) which explores multiple reasoning possibilities at each step.'
Ok, しかし内部的に何が起きているのでしょう、そして特定のユースケースのためにこれをどのようにカスタマイズできるのでしょう?そのため、このパイプラインをどのように構築できるかを一つ一つ見てみましょう。
ステップ 1. ロード
非構造化データを Documents としてロードするために DocumentLoader を指定します。Document はテキストのピース (page_content) と関連するメタデータです。
from langchain.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
data = loader.load()
API リファレンス :
更に詳しく
ステップ 2. 分割
ドキュメントを埋め込みとベクトルストレージ用のチャンクに分割します。
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size = 500, chunk_overlap = 0)
all_splits = text_splitter.split_documents(data)
API リファレンス :
更に詳しく
- DocumentSplitters はより汎用的な DocumentTransformers の単なる一つの種類で、この前処理ステップすべてで役立ちます。
- 変換についての詳細なドキュメントは こちら をご覧ください。
- コンテキスト-aware なスプリッターは元の Document の各分割の位置 (“context”) を保持しています :
- Markdown ファイル
- コード (py or js) (訳注: リンク切れ)
- ドキュメント
ステップ 3. ストア
ドキュメント分割を検索できるように、最初にそれらをストアする必要があります、私たちはそこでそれらを検索できます。これを行なう最も一般的な方法は、各ドキュメントのコンテンツを埋め込み、そしてその埋め込みとドキュメントをベクトルストアにストアすることで、埋め込みはドキュメントをインデックスするために使用されます。
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
vectorstore = Chroma.from_documents(documents=all_splits, embedding=OpenAIEmbeddings())
API リファレンス :
更に詳しく
- 40 を超えるベクトルストア統合を ここ でブラウズできます。
- ベクトルストアの更なるドキュメントを ここ でご覧ください。
- 30 を超えるテキスト埋め込み統合を ここ でブラウズできます。
- 埋め込みモデルの更なるドキュメントを ここ でご覧ください。
Here are Steps 1-3:
ステップ 4. 検索
類似検索 を使用して質問に関連する分割を検索取得します。
question = "What are the approaches to Task Decomposition?"
docs = vectorstore.similarity_search(question)
len(docs)
4
更に詳しく
検索のためにベクトルストアが一般に使用されますが、それらが唯一の選択肢ではありません。例えば、SVM (ここ のスレッド参照) もまた使用できます。
LangChain はベクトルストアを含む、しかしそれには制限されない 多くの retriever を持ちます。すべての retriever は共通メソッド get_relevant_documents() (そしてその非同期バリアント aget_relevant_documents()) を実装します。
from langchain.retrievers import SVMRetriever
svm_retriever = SVMRetriever.from_documents(all_splits,OpenAIEmbeddings())
docs_svm=svm_retriever.get_relevant_documents(question)
len(docs_svm)
API リファレンス :
4
ベクトル類似検索を改良する幾つかの一般的な方法は以下を含みます :
- MultiQueryRetriever は検索を改善する 入力質問のバリアントを生成します。
- Max marginal relevance (MMR) は取得されたドキュメント内で 関連性と多様性 のために選択します。
- ドキュメントは メタデータフィルタ を使用して検索の間にフィルタリングできます。
import logging
from langchain.chat_models import ChatOpenAI
from langchain.retrievers.multi_query import MultiQueryRetriever
logging.basicConfig()
logging.getLogger('langchain.retrievers.multi_query').setLevel(logging.INFO)
retriever_from_llm = MultiQueryRetriever.from_llm(retriever=vectorstore.as_retriever(),
llm=ChatOpenAI(temperature=0))
unique_docs = retriever_from_llm.get_relevant_documents(query=question)
len(unique_docs)
API リファレンス :
INFO:langchain.retrievers.multi_query:Generated queries: ['1. How can Task Decomposition be approached?', '2. What are the different methods for Task Decomposition?', '3. What are the various approaches to decomposing tasks?'] 4
ステップ 5. 生成
RetrievalQA チェインにより LLM/チャットモデル (e.g., gpt-3.5-turbo) を使用して、取得したドキュメントを回答に蒸留します。
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
qa_chain = RetrievalQA.from_chain_type(llm,retriever=vectorstore.as_retriever())
qa_chain({"query": question})
API リファレンス :
{'query': 'What are the approaches to Task Decomposition?', 'result': 'There are three approaches to task decomposition:\n\n1. Using Language Model with simple prompting: This approach involves using a Language Model (LLM) with simple prompts like "Steps for XYZ" or "What are the subgoals for achieving XYZ?" to guide the task decomposition process.\n\n2. Using task-specific instructions: In this approach, task-specific instructions are provided to guide the task decomposition. For example, for the task of writing a novel, an instruction like "Write a story outline" can be given to help decompose the task into smaller subtasks.\n\n3. Human inputs: Task decomposition can also be done with the help of human inputs. This involves getting input and guidance from humans to break down a complex task into smaller, more manageable subtasks.'}
Note, you can pass in an LLM or a ChatModel (like we did here) to the RetrievalQA chain.
更に詳しく
LLM の選択
- ここ で 55 を超える LLM とチャットモデルの統合をブラウズできます。
- ここ で LLM とチャットモデルのドキュメントを更にご覧ください。
- ローカル LLM の使用: PrivateGPT と GPT4All の人気は LLM をローカルで実行することの重要性を明確に示しています。GPT4All の使用は バイナリをダウンロード (訳注: リンク切れ) してから以下を行なうほどに単純です :
from langchain.llms import GPT4All
from langchain.chains import RetrievalQA
llm = GPT4All(model="/Users/rlm/Desktop/Code/gpt4all/models/nous-hermes-13b.ggmlv3.q4_0.bin",max_tokens=2048)
qa_chain = RetrievalQA.from_chain_type(llm, retriever=vectorstore.as_retriever())
API リファレンス :
Found model file at /Users/rlm/Desktop/Code/gpt4all/models/nous-hermes-13b.ggmlv3.q4_0.bin objc[61331]: Class GGMLMetalClass is implemented in both /Users/rlm/miniforge3/envs/llama/lib/python3.9/site-packages/gpt4all/llmodel_DO_NOT_MODIFY/build/libreplit-mainline-metal.dylib (0x2e3384208) and /Users/rlm/miniforge3/envs/llama/lib/python3.9/site-packages/gpt4all/llmodel_DO_NOT_MODIFY/build/libllamamodel-mainline-metal.dylib (0x2e37b0208). One of the two will be used. Which one is undefined. llama.cpp: using Metal llama.cpp: loading model from /Users/rlm/Desktop/Code/gpt4all/models/nous-hermes-13b.ggmlv3.q4_0.bin llama_model_load_internal: format = ggjt v3 (latest) llama_model_load_internal: n_vocab = 32001 llama_model_load_internal: n_ctx = 2048 llama_model_load_internal: n_embd = 5120 llama_model_load_internal: n_mult = 256 llama_model_load_internal: n_head = 40 llama_model_load_internal: n_layer = 40 llama_model_load_internal: n_rot = 128 llama_model_load_internal: ftype = 2 (mostly Q4_0) llama_model_load_internal: n_ff = 13824 llama_model_load_internal: n_parts = 1 llama_model_load_internal: model size = 13B llama_model_load_internal: ggml ctx size = 0.09 MB llama_model_load_internal: mem required = 9031.71 MB (+ 1608.00 MB per state) llama_new_context_with_model: kv self size = 1600.00 MB ggml_metal_init: allocating ggml_metal_init: using MPS ggml_metal_init: loading '/Users/rlm/miniforge3/envs/llama/lib/python3.9/site-packages/gpt4all/llmodel_DO_NOT_MODIFY/build/ggml-metal.metal' ggml_metal_init: loaded kernel_add 0x2bbbbc2f0 ggml_metal_init: loaded kernel_mul 0x2bbbba840 ggml_metal_init: loaded kernel_mul_row 0x2bb917dd0 ggml_metal_init: loaded kernel_scale 0x2bb918150 ggml_metal_init: loaded kernel_silu 0x2bb9184d0 ggml_metal_init: loaded kernel_relu 0x2bb918850 ggml_metal_init: loaded kernel_gelu 0x2bbbc3f10 ggml_metal_init: loaded kernel_soft_max 0x2bbbc5840 ggml_metal_init: loaded kernel_diag_mask_inf 0x2bbbc4c70 ggml_metal_init: loaded kernel_get_rows_f16 0x2bbbc5fc0 ggml_metal_init: loaded kernel_get_rows_q4_0 0x2bbbc6720 ggml_metal_init: loaded kernel_get_rows_q4_1 0x2bb918c10 ggml_metal_init: loaded kernel_get_rows_q2_k 0x2bbbc51b0 ggml_metal_init: loaded kernel_get_rows_q3_k 0x2bbbc7630 ggml_metal_init: loaded kernel_get_rows_q4_k 0x2d4394e30 ggml_metal_init: loaded kernel_get_rows_q5_k 0x2bbbc7890 ggml_metal_init: loaded kernel_get_rows_q6_k 0x2d4395210 ggml_metal_init: loaded kernel_rms_norm 0x2bbbc8740 ggml_metal_init: loaded kernel_norm 0x2bbbc8b30 ggml_metal_init: loaded kernel_mul_mat_f16_f32 0x2d4395470 ggml_metal_init: loaded kernel_mul_mat_q4_0_f32 0x2d4395a70 ggml_metal_init: loaded kernel_mul_mat_q4_1_f32 0x1242b1a00 ggml_metal_init: loaded kernel_mul_mat_q2_k_f32 0x29f17d1c0 ggml_metal_init: loaded kernel_mul_mat_q3_k_f32 0x2d4396050 ggml_metal_init: loaded kernel_mul_mat_q4_k_f32 0x2bbbc98a0 ggml_metal_init: loaded kernel_mul_mat_q5_k_f32 0x2bbbca4a0 ggml_metal_init: loaded kernel_mul_mat_q6_k_f32 0x2bbbcae90 ggml_metal_init: loaded kernel_rope 0x2bbbca700 ggml_metal_init: loaded kernel_alibi_f32 0x2bbbcc6e0 ggml_metal_init: loaded kernel_cpy_f32_f16 0x2bbbccf90 ggml_metal_init: loaded kernel_cpy_f32_f32 0x2bbbcd900 ggml_metal_init: loaded kernel_cpy_f16_f16 0x2bbbce1f0 ggml_metal_init: recommendedMaxWorkingSetSize = 21845.34 MB ggml_metal_init: hasUnifiedMemory = true ggml_metal_init: maxTransferRate = built-in GPU ggml_metal_add_buffer: allocated 'data ' buffer, size = 6984.06 MB, ( 6984.45 / 21845.34) ggml_metal_add_buffer: allocated 'eval ' buffer, size = 1024.00 MB, ( 8008.45 / 21845.34) ggml_metal_add_buffer: allocated 'kv ' buffer, size = 1602.00 MB, ( 9610.45 / 21845.34) ggml_metal_add_buffer: allocated 'scr0 ' buffer, size = 512.00 MB, (10122.45 / 21845.34) ggml_metal_add_buffer: allocated 'scr1 ' buffer, size = 512.00 MB, (10634.45 / 21845.34)
プロンプトのカスタマイズ
RetrievalQA チェインのプロンプトは簡単にカスタマイズできます。
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
template = """Use the following pieces of context to answer the question at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
Use three sentences maximum and keep the answer as concise as possible.
Always say "thanks for asking!" at the end of the answer.
{context}
Question: {question}
Helpful Answer:"""
QA_CHAIN_PROMPT = PromptTemplate.from_template(template)
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm,
retriever=vectorstore.as_retriever(),
chain_type_kwargs={"prompt": QA_CHAIN_PROMPT}
)
result = qa_chain({"query": question})
result["result"]
API リファレンス :
ggml_metal_free: deallocating 'The approaches to task decomposition include using LLM with simple prompting, task-specific instructions, or human inputs. Thanks for asking!'
ソースドキュメントを返す
回答の蒸留のために使用された検索取得ドキュメントの完全なセットは return_source_documents=True を使用して返すことができます。
from langchain.chains import RetrievalQA
qa_chain = RetrievalQA.from_chain_type(llm,retriever=vectorstore.as_retriever(),
return_source_documents=True)
result = qa_chain({"query": question})
print(len(result['source_documents']))
result['source_documents'][0]
API リファレンス :
4 Document(page_content='Task decomposition can be done (1) by LLM with simple prompting like "Steps for XYZ.\\n1.", "What are the subgoals for achieving XYZ?", (2) by using task-specific instructions; e.g. "Write a story outline." for writing a novel, or (3) with human inputs.', metadata={'description': 'Building agents with LLM (large language model) as its core controller is a cool concept. Several proof-of-concepts demos, such as AutoGPT, GPT-Engineer and BabyAGI, serve as inspiring examples. The potentiality of LLM extends beyond generating well-written copies, stories, essays and programs; it can be framed as a powerful general problem solver.\nAgent System Overview In a LLM-powered autonomous agent system, LLM functions as the agent’s brain, complemented by several key components:', 'language': 'en', 'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/', 'title': "LLM Powered Autonomous Agents | Lil'Log"})
引用 (citations) を返す
回答の引用は RetrievalQAWithSourcesChain を使用して返すことができます。
from langchain.chains import RetrievalQAWithSourcesChain
qa_chain = RetrievalQAWithSourcesChain.from_chain_type(llm,retriever=vectorstore.as_retriever())
result = qa_chain({"question": question})
result
API リファレンス :
{'question': 'What are the approaches to Task Decomposition?', 'answer': 'The approaches to Task Decomposition include:\n1. Using LLM with simple prompting, such as providing steps or subgoals for achieving a task.\n2. Using task-specific instructions, such as providing a specific instruction like "Write a story outline" for writing a novel.\n3. Using human inputs to decompose the task.\nAnother approach is the Tree of Thoughts, which extends the Chain of Thought (CoT) technique by exploring multiple reasoning possibilities at each step and generating multiple thoughts per step, creating a tree structure. The search process can be BFS or DFS, and each state can be evaluated by a classifier or majority vote.\nSources: https://lilianweng.github.io/posts/2023-06-23-agent/', 'sources': ''}
検索取得されたドキュメント処理のカスタマイズ
取得ドキュメントは幾つかの異なる方法で回答を蒸留するために LLM に供給できます。
LLM プロンプトへのドキュメントを渡すための stuff, refine, map-reduce と map-rerank チェインは ここ で上手くまとめられています。
stuff は一般的に使用されます、それはすべての取得ドキュメントをプロンプトに単純に「詰め込む (stuffs)」からです。
load_qa_chain はこれらの様々なアプローチ (e.g., chain_type 参照) を使用してドキュメントを LLM に渡す簡単な方法です。
from langchain.chains.question_answering import load_qa_chain
chain = load_qa_chain(llm, chain_type="stuff")
chain({"input_documents": unique_docs, "question": question},return_only_outputs=True)
API リファレンス :
{'output_text': 'The approaches to task decomposition mentioned in the provided context are:\n\n1. Chain of thought (CoT): This approach involves instructing the language model to "think step by step" and decompose complex tasks into smaller and simpler steps. It enhances model performance on complex tasks by utilizing more test-time computation.\n\n2. Tree of Thoughts: This approach extends CoT by exploring multiple reasoning possibilities at each step. It decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS or DFS, and each state is evaluated by a classifier or majority vote.\n\n3. LLM with simple prompting: This approach involves using a language model with simple prompts like "Steps for XYZ" or "What are the subgoals for achieving XYZ?" to perform task decomposition.\n\n4. Task-specific instructions: This approach involves providing task-specific instructions to guide the language model in decomposing the task. For example, providing the instruction "Write a story outline" for the task of writing a novel.\n\n5. Human inputs: Task decomposition can also be done with human inputs, where humans provide guidance and input to break down the task into smaller subtasks.'}
We can also pass the chain_type to RetrievalQA.
qa_chain = RetrievalQA.from_chain_type(llm,retriever=vectorstore.as_retriever(),
chain_type="stuff")
result = qa_chain({"query": question})
In summary, the user can choose the desired level of abstraction for QA:
ステップ 6. チャット
See our use-case on chat for detail on this!
以上