LangChain RetrievalQAの使い方: ChatGPTを活用した専門情報の検索

# 0. はじめに
ChatGPTを利用する際、モデルに特定の情報を問い合わせたいときにどうすればよいか。この記事では、RetrievalQAという便利なツールを使って、ChatGPTを活用した情報検索方法を紹介します。

# 1. RetrievalQAとは？
LangChainに実装されているRetrievalQAは、大量の（ベクトル化された）テキストデータの中からユーザーの質問に合致する情報を迅速に検索し、LLMを使用してその情報を基に回答を生成するためのツールです。特に、特定の知識ベースやドキュメントセット（VectorStore）をベースにして回答をしたい場合に有効です。

![RetrievalQA (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3565029/15d51662-879d-739e-26d5-bd489b71350f.jpeg)

### ChatGPTの弱点とRetrievalQAの強み
ChatGPTや一般的なLLMモデルの弱点は、専門的な知識や情報に対する反応が限られていることです。一方、RetrievalQAは、特定の知識ベースやドキュメントを参照し、その情報を基に回答を生成するため、この専門性のギャップを埋める役割を果たします。

逆に、RetrievalQAだけで質問に回答することは可能ではありますが、多くの場合、専門的な質問だけでなく、一般的な質問にも答えられるようにする必要があるため、Agentと組み合わせて使うことが多いです。今回の記事でもLangChainのAgentについて簡単に触れますが、詳細はまた違う記事で紹介したいと思います。

# 2. 実装のステップ

### 2.1 OpenAIのAPIキーの設定
OpenAIのAPIキーを設定します。
```py
openai.api_key = os.environ['OPENAI_API_KEY']
```
### 2.2 データベースのロード
Chromaというデータベース(VectorStore)をロードします。このデータベースには、LLMが質問に答えるための情報が保存されています。

```py
embeddings = OpenAIEmbeddings()
loaded_db = Chroma(
    persist_directory='./dragonball_db',
    embedding_function=embeddings
)
```
### 2.3 ChatOpenAIモデルの設定
LLMのモデルを設定します。

```py
LLM = ChatOpenAI(
    model_name='gpt-3.5-turbo',
    temperature=0
)
```
### 2.4 PromptTemplateの作成
RetrievalQAのLLMに指示を与えるためのテンプレートを作成します。このテンプレートに従い、LLMはユーザーの質問に答えるように動作します。経験的に日本語で回答を得たい場合、プロンプト自体を日本語にしてしまう方がよいです。

`context`には、このすぐ後で設定する`chain_type`のstuffingでVectorStoreからもってきた関連チャンク情報をそのまま突っ込みます。

```py
prompt_template_qa = """あなたは親切で優しいアシスタントです。丁寧に、日本語でお答えください！
もし以下の情報が探している情報に関連していない場合は、そのトピックに関する自身の知識を用いて質問
に答えてください。

{context}

質問: {question}
回答（日本語）:"""

prompt_qa = PromptTemplate(
        template=prompt_template_qa, 
        input_variables=["context", "question"]
)
chain_type_kwargs = {"prompt": prompt_qa}
```

### 2.5 Retrieverオブジェクトの作成と設定
ここが本記事のメインの部分です。RetrievalQAを使用して、質問に答えるための情報を検索 (similarity search) し、データベースから見つけてきた関連情報（チャンク情報）をLLMに渡します。

```py
retriever = loaded_db.as_retriever()
qa = RetrievalQA.from_chain_type(
    llm=LLM,
    chain_type='stuff',
    retriever=retriever,
    chain_type_kwargs=chain_type_kwargs
)
```

### 2.6 Agentの設定
次にAgentを設定し、実際の質問と回答のやり取りを行います。Agentは一連のツールの中から次にとるべきアクションを判断し、実行します。ここではツールにRetrievalQAしか入れていないので、LLM自身の知識に基づいて回答するか、RetrievalQAを使うかのどちらか、ということになります。RetrievalQAだけだと専門知識にひっぱられ、一般的な質問に対しても小難しいことを回答するなどといったことがあるので、ツールがRetrievalQAだけだとしてもAgentに入れた方がおそらくベターです。

まずはツールの設定です。
```py
tools = [
    Tool(
        name="DragonballDB",
        func=qa.run,
        description="ドラゴンボールに関する質問に回答する際に使用する"
    ),
]
```

そしてAgentを定義します。
```py
chat_agent = initialize_agent(
        tools,
        llm=LLM,
        agent = "zero-shot-react-description",
        verbose=True,
        system_message="あなたは親切なアシスタントです。日本語で回答してください!",
)
```

LangChainのAgentには標準実装されているものだけでもいくつもの種類がありますが、今回使用している`zero-shot-react-description`はReActベースのLangChain Agentです。`zero-shot`なので会話履歴をメモリに保持したりはしません。また、`zero-shot-react-description`は、ツールのdescriptionを参考にどのアクションをとるのかを決定します。

次に、ReActについてですが、ReActは、「次に何をするべきか」の判断とアクションの実行を連携させるためのフレームワークです。詳しい情報は以下の論文を参照して下さい。

https://arxiv.org/abs/2210.03629

# 出力の確認
今回はドラゴンボールを例に具体的な出力を一緒にみていきたいと思います。

```py
question = 'ベジータについておしえてください'
result = chat_agent.run(question)
print(result)
```

さて、Agentの思考プロセスを一緒にみてみましょう。まず、今回の質問に回答するためにDragonballDBを見に行きます。今は`tools`に１つしか`Tool`が定義されていないので、基本的にはDragonballDBを調べに行く以外のことはできません。このデータベース内でベジータについて調べ、調査結果を確認し、最終回答をつくっているのがわかります。
Agentは基本的に、`Question`, `Thought`, `Action`, `Action Input`, `Observation`, `Thought`, `Final Answer`というプロセスを辿って最終回答を出します。場合によっては`Final Answer`にいくまでに、`Action`/`Action Input`/`Observation`のプロセスを繰り返します。
```
> Entering new AgentExecutor chain...
I should use DragonballDB to find information about Vegeta.
Action: DragonballDB
Action Input: Vegeta
Observation: ベジータは、ドラゴンボールシリーズに登場するキャラクターです。彼はサイヤ人の王子であり、最初
は悟空の敵でしたが、後に仲間となります。ベジータは非常に強力な戦士であり、自身の力を追求するために常にトレー
ニングを積んでいます。彼はサイヤ人編から登場し、ドラゴンボール超でも重要な役割を果たしています。ベジータは悟
空との競争心やライバル意識を持ちながらも、時には仲間として協力し合う関係を築いています。彼のキャラクターは、
プライドが高く厳しい一面もありますが、時折見せる優しさや家族への愛情もあります。ベジータはドラゴンボールシリ
ーズの中でも人気のあるキャラクターであり、多くのファンに愛されています。
Thought:I now know the final answer.
Final Answer: ベジータは、ドラゴンボールシリーズに登場するサイヤ人の王子であり、最初は悟空の敵でしたが、後
に仲間となります。彼は非常に強力な戦士であり、常にトレーニングを積んで自身の力を追求しています。彼は悟空との
競争心やライバル意識を持ちながらも、時には仲間として協力し合う関係を築いています。ベジータのキャラクターは、
プライドが高く厳しい一面もありますが、時折優しさや家族への愛情も見せます。彼はドラゴンボールシリーズの中でも
人気のあるキャラクターです。

> Finished chain.
ベジータは、ドラゴンボールシリーズに登場するサイヤ人の王子であり、最初は悟空の敵でしたが、後に仲間となりま
す。彼は非常に強力な戦士であり、常にトレーニングを積んで自身の力を追求しています。彼は悟空との競争心やライバ
ル意識を持ちながらも、時には仲間として協力し合う関係を築いています。ベジータのキャラクターは、プライドが高く
厳しい一面もありますが、時折優しさや家族への愛情も見せます。彼はドラゴンボールシリーズの中でも人気のあるキャ
ラクターです。
```

ちなみに、RetrievalQAの引数で、`return_source_documents=True`をセットするとRetrievalQAがどのチャンクデータを取ってきたのかを確認することができます。

```py
qa = RetrievalQA.from_chain_type(
    llm=LLM,
    chain_type='stuff',
    retriever=retriever,
    chain_type_kwargs=chain_type_kwargs,
    return_source_documents=True
)
result = qa('ベジータについておしえてください')
print(result)
```
`source_documents`として以下4つのチャンクデータが抽出されたことがわかります。`stuff`のデフォルトでは4つチャンクを持ってきます。
```
'result': 
'ベジータはサイヤ人の生き残りであり、超エリートに属する惑星ベジータの王子です。彼は初期の頃は残忍で冷酷な性
格でしたが、悟空やブルマたちに感化され、次第に残忍さは薄れていきました。彼は悟空をライバル視しており、フリー
ザ編以降も悟空との戦いを求めています。また、彼はブルマと結ばれ、トランクスとブラの父親となりました。ベジータ
は成長していく過程で多くの修行と強敵との戦いを経験し、最終的には格闘戦士として成長していきました。'

'source_documents': 
[Document(page_content='Unknown Title\n# 孫悟空\n本作品の主人公。純粋で心優しい地球育ちのサイヤ人。サイ
ヤ人名は「カカロット」。様々な師の下での修行と強敵やライバルとの死闘を経て、最強を求める格闘戦士へと成長して
いく。多くの仲間にも恵まれ、のち家庭を持つ。\n\n# ベジータ\nサイヤ人の生き残りであり、超エリートに属する惑
星ベジータの王子。ナッパとともに地球に襲来し悟空たちと死闘を繰り広げる。初期の頃は残忍で冷酷な性格だったが、
悟空やブルマたちに感化され、次第に残忍さは薄れていく。ブルマと結ばれトランクスとブラの父親となる。フリーザ編
以降悟空をライバル視している。', metadata={'source': 'input\\dragonball_members.txt'}), 

Document(page_content='Unknown Title\n# ブルマ\n悟空が出会った最初の仲間。全ての物語の引き金となったカプ
セルコーポレーションの令嬢。ヤムチャと交際するが、持ち前の性格ゆえに喧嘩が絶えず破局する。その後、ベジータと
結ばれ、トランクスとブラを授かる。\n自他ともに認める天才であり、様々なメカの発明・改造によって仲間たちをサポ
ートする。お嬢様気質で立場が弱くなると逃げようとしたり文句が多い。わがままな性格だったが物語が進むにつれて仲
間意識、絆も深まり変わっていく。', metadata={'source': 'input\\dragonball_members.txt'}), 

Document(page_content='###ドラゴンボール超\n当初は劣勢だったベジータはダメージを受けるほど進化を続ける我
儘の極意により何度倒れてもその都度強くなって立ち上がり、次第にガスは押されるもののとうとうダメージが限界を超
えてベジータは倒れる。しかしその様子から悟空は、感情を保ったまま自身が一番力を発揮できる身勝手の極意を見出
し、ガスを追い詰める。すると今度はエレクがガスに「命を使い切る覚悟で戦え」と発破を掛け、精神的に追い詰めるこ
とでまたしても力を開放させるが、ガスの姿は老け込み、別人のように変貌していた。再び追い詰められる悟空だが、夢
の中でバーダックの声を聴いていたグラノラは自身がするべきことに気付き、協力を申し出る。悟空とベジータが足止め
している間にグラノラは力を溜め、その膨大なエネルギーをオートミルの照準によってガスに命中させた。とうとうガス
を倒し、能力を開花させたモナイトによって体力を回復してもらった悟空たちだったが、突然モナイトが光線に貫かれ
る。ガスは願いの代償…宛ら「宇宙一の呪い」でゾンビのような姿になってもまだ戦おうとしており、悟空たちの攻撃を
受けて損傷しても構わず向かってきた。マキの静止も聞かずエレクはガスを戦わせようとするが、そこでガスは自身の寿
命が尽きかけていると知って動揺する。しかしそこに突然フリーザが現れ、ガスを一撃で殺害。エレクの野望も40年前か
ら知っていたことを告げ、そのままエレクも瞬殺する。フリーザは侵攻した惑星で「精神と時の部屋」のような場所を見
つけ、10年分のトレーニングを積んでいたのだ。グラノラやガスが宇宙一の強さを願った時も異次元にいて対象外になっ
ていたという。そして新たな形態ブラックフリーザへと変身し、悟空とベジータを圧倒する。しかし今回の目的は悟空た
ちではないとしてとどめは刺さず去り、残されたマキとオイルはフリーザ軍の厨房係とウェイターとして雇われることに
なった。その後、ウイスによってモナイトは回復。グラノラは悟空にビルス星へ誘われるも、シリアル星をドラゴンボー
ルで元に戻すために断る。モナイトもそれを最後にドラゴンボールの封印を決意し、悟空にバーダックのスカウターを渡
した。', metadata={'source': 'input\\dragonball_super_story.txt'}), 

Document(page_content='###ドラゴンボール超\nグラノラは手始めに悟空と戦い、ベジータは荒廃した町を調べる内
に、かつて優れた赤い目を持つ種族が存在し、グラノラこそがかつてのサイヤ人によって滅ぼされたシリアル人で、マキ
たちに騙されたと突き止める。悟空は身勝手の極意でグラノラを圧倒したが、悟空が戦っていたのは分身であり、本物の
グラノラが悟空の急所を突いてダウンさせる。そこにベジータが現れ、次はベジータと戦う。ベジータはグラノラとの戦
いで成長していき、「我儘の極意」という形態に目覚める。ダメージを受けるごとに闘争本能で強くなっていったがグラ
ノラを倒すに至らず、グラノラも左目も赤くするという潜在能力を引き出す。命懸けでベジータに止めを刺そうとしたグ
ラノラだったが、グラノラの相棒、オートミル、モナイトに止められる。', 
metadata={'source': 'input\\dragonball_super_story.txt'})]
```
# まとめ
RetrievalQAを使えば、特定の情報源をベースにした質問回答が簡単に実現できます。これにより、GPT-3.5をさらに強力な情報検索ツールとして活用することができます。是非試してみてください！

最近X(Twitter)もはじめたのでよろしくお願いします！

https://x.com/keisuke_yoshiha

最後にRetrievalQAの実装に必要なライブラリをリストしておきます。OpenAI APIに必要なライブラリも含まれています。

```py
import os
import openai
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain import PromptTemplate
from langchain.chains import RetrievalQA
from langchain.agents import initialize_agent, Tool
```

# 関連記事
- 似たようなRetrieval機能をもったモジュールにConversationalRetrievalChainがあります。ConversationalRetrievalChainに関してはこちらの記事を参照してください。

https://qiita.com/mashmoeiar11/items/5aa2c1f3381e5184c919

- VectorStoreの作成についてはこちらの記事で解説しております。

https://qiita.com/mashmoeiar11/items/cf4d6573e3aa50a725c0

# 参考文献

https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%A9%E3%82%B4%E3%83%B3%E3%83%9C%E3%83%BC%E3%83%AB

https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%A9%E3%82%B4%E3%83%B3%E3%83%9C%E3%83%BC%E3%83%AB%E8%B6%85

＊本記事はcode snippetをもとにChatGPTに作らせています。