LangChainを用いて大量ファイルをロードするVectorDBを作ってみた(5)

![sky.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/9e8de5a3-0e4b-5eb5-7d42-308fd760c05e.jpeg)
# はじめに
　前回の投稿では、`Chroma`、`Qdrant`、`FAISS`の３つでローカルのVectorDBを作成プログラムを作成しました。

https://qiita.com/ogi_kimura/items/551c93cd94404c9381c7
 
 　今回は、それらのプログラムを実行して、本当にVectorDBができているかを確認します。
  それから`chainlit`を適用して、生成AIに特許情報のことを確認してみたいと思います。
  ３つの中の精度の違いなどを比較検証したいと思います。

# プログラム実行とVectorDBの確認
　３つのそれぞれ実行してみました。
 
 ### 1．Chroma
　`Chroma`では、`sqlite`のデータベース(`chroma.sqlite3`)が生成されました。
 では、SQLiteの「DB Browser」を利用して、内容を確認してみます。

![chroma_db.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/dee96c85-adb6-241c-a187-60c0f8993a03.jpeg)

いろいろテーブルが入っていることがわかります。
特に`embedding_metadata`がちゃんと格納されているのかを確認するのにわかりやすいので、一度見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/6393eece-1fe4-d9bc-a19e-4cf67a75f594.png)


ちゃんとテーブルにデータが格納されているようです。
また、レコード数は、13万程度ですので大丈夫かなぁと思います:sweat_smile:。

 ### 2．Qdrant
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/4cd99df5-134a-5c6e-c0d7-200147bc0103.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/d2b3244b-46b2-dd40-62f3-de5e84b522bd.png)

`index.faiss'と`index.pkl`の2ファイルがありました。
容量はそれぞれ261MBと167MBです。
中身を見ようとしましたが、文字化けしていたので解析はあきらめました。
多分大丈夫かと思います:rolling_eyes:。

 ### 3．FAISS
　FAISSの場合は、Chromaと同様に`sqlite`のデータベースです。
  テーブル構成はシンプルで`points`というテーブルのみです。
  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/ea55be93-c6cd-5bfe-7715-fe00db3612fb.png)

　`points`のテーブルの中身は以下のようになっていました。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/fe175bda-d06c-a77e-d635-49bce3bc8411.png)

　43,524レコードということで、たぶん大丈夫かと・・・

全体的な話になりますが、プログラム実行をしてからローカルデータベースに書き込み終えるまで、それぞれ1時間程度かかりました。

# chainlitで対話準備

　では、`chainlit`を適用して、生成AIと対話ができるようにします。
  以下のソースコードを作成しました。
  ユーザが質問をすると、Chroma、Qdrant、FAISSのそれぞれが同時に回答するようにしました。
  そうすれば、どのVectorDBの精度が高いか解ると思いました。

  ```python:all_chainlit.py
import chainlit as cl
from langchain.chat_models import ChatOpenAI
from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import PromptTemplate
from langchain.schema import HumanMessage
import qdrant_client
from langchain.chains import RetrievalQA
from langchain.vectorstores import Chroma
from langchain.vectorstores import Qdrant
from langchain.vectorstores import FAISS

embeddings = OpenAIEmbeddings(
    model="text-embedding-ada-002"
)

chat = ChatOpenAI(model="gpt-3.5-turbo")

prompt = PromptTemplate(template="""文章を元に質問に答えてください。 

文章: 
{document}

質問: {query}
""", input_variables=["document", "query"])

# ========== VectorDB ===========
# ----- Chroma -----
db_chroma = Chroma(
    persist_directory="C:\\Users\\***\\langchain_book\\local_chroma", 
    embedding_function=embeddings
)
rtr_chroma = db_chroma.as_retriever()
# ----- Qdrant -----
client = qdrant_client.QdrantClient(
    path="C:\\Users\\***\\langchain_book\\local_qdrant",
    prefer_grpc=True
)
db_qdrant = Qdrant(
    client=client, collection_name="manga_data", 
    embeddings=embeddings
)
rtr_qdrant = db_qdrant.as_retriever()
# ----- FAISS -----
db_faiss = FAISS.load_local(
    "C:\\Users\\***\\langchain_book\\local_faiss",
    embeddings,
    allow_dangerous_deserialization=True
)
rtr_faiss = db_faiss.as_retriever()


chat = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)



# ========== ON_CHAT_START ===========
@cl.on_chat_start
async def on_chat_start():
    await cl.Message(content="準備ができました！メッセージを入力してください！").send()


# ========== ON_MESSAGE ===========
@cl.on_message
async def on_message(input_message):
    print("入力されたメッセージ: " + input_message)
    qa_chain_chroma = RetrievalQA.from_chain_type(llm=chat, chain_type="stuff", retriever=rtr_chroma)
    qa_chain_qdrant = RetrievalQA.from_chain_type(llm=chat, chain_type="stuff", retriever=rtr_qdrant)
    qa_chain_faiss = RetrievalQA.from_chain_type(llm=chat, chain_type="stuff", retriever=rtr_faiss)
    result_chroma = qa_chain_chroma.run(input_message)
    result_qdrant = qa_chain_qdrant.run(input_message)
    result_faiss = qa_chain_faiss.run(input_message)
    await cl.Message(content=result_chroma).send()
    await cl.Message(content=result_qdrant).send()
    await cl.Message(content=result_faiss).send()
  ```

楽をするために、それぞれの`retriever`インスタンスを作成し、`RetrievalQA`を利用しようと思いました。
ただ、これだとスコアがわかりませんし、引っかかったファイル名などがわからないため、解析ができません。今回はそこも見たいので、以下のプログラムに変更しました。

```python: all_chainlit_detail.py
import chainlit as cl
from langchain.chat_models import ChatOpenAI
from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import PromptTemplate
from langchain.schema import HumanMessage
import qdrant_client
from langchain.chains import RetrievalQA
from langchain.vectorstores import Chroma
from langchain.vectorstores import Qdrant
from langchain.vectorstores import FAISS

embeddings = OpenAIEmbeddings(
    model="text-embedding-ada-002"
)

chat = ChatOpenAI(model="gpt-3.5-turbo")

prompt = PromptTemplate(template="""文章を元に質問に答えてください。 

文章: 
{document}

質問: {query}
""", input_variables=["document", "query"])

# ========== VectorDB ===========
# ----- Chroma -----
db_chroma = Chroma(
    persist_directory="C:\\Users\\ogiki\\langchain_book\\local_chroma", 
    embedding_function=embeddings
)
rtr_chroma = db_chroma.as_retriever()
# ----- Qdrant -----
client = qdrant_client.QdrantClient(
    path="C:\\Users\\ogiki\\langchain_book\\local_qdrant",
    prefer_grpc=True
)
db_qdrant = Qdrant(
    client=client, collection_name="manga_data", 
    embeddings=embeddings
)
rtr_qdrant = db_qdrant.as_retriever()
# ----- FAISS -----
db_faiss = FAISS.load_local(
    "C:\\Users\\ogiki\\langchain_book\\local_faiss",
    embeddings,
    allow_dangerous_deserialization=True
)
rtr_faiss = db_faiss.as_retriever()


chat = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)


# ========== 文字列作成 ===========
def make_documents_string(documents):
    documents_string = ""
    for document in documents:
        print("---------------document.metadata---------------")
        print(document[0].metadata)
        print(document[1])
        documents_string += f"""
            ---------------------------
            {document[0].page_content}
            """
    print("---------------documents_string---------------")
    print(documents_string)
    return documents_string


# ========== ON_CHAT_START ===========
@cl.on_chat_start
async def on_chat_start():
    await cl.Message(content="準備ができました！メッセージを入力してください！").send()


# ========== ON_MESSAGE ===========
@cl.on_message
async def on_message(input_message):

    print("入力されたメッセージ: " + input_message)

    documents = db_chroma.similarity_search_with_score(input_message, k=3)
    documents_string = make_documents_string(documents)
    result = chat([HumanMessage(content=prompt.format(document=documents_string, query=input_message))])
    await cl.Message(content=result.content).send()
    
    documents = db_qdrant.similarity_search_with_score(input_message, k=3)
    documents_string = make_documents_string(documents)
    result = chat([HumanMessage(content=prompt.format(document=documents_string, query=input_message))])
    await cl.Message(content=result.content).send()
    
    documents = db_faiss.similarity_search_with_score(input_message, k=3)
    documents_string = make_documents_string(documents)
    result = chat([HumanMessage(content=prompt.format(document=documents_string, query=input_message))])
    await cl.Message(content=result.content).send()

```
`similarity_search_with_score`を適用したことでスコアがわかるようになりました。またスコアだけではなく、検索適用したファイル名やその文章も標準出力することができます。
今回は`k=3`としました。`k`の値が大きければ本当に必要な文章に引っかかる確率は高まるのですが、その副作用として不要な文章も引っかかってしまい、生成AIが雑味のある回答をしてしまいます。
今回はこれで行ってみたいと思います。

# chainlitの実行

では、chainlitを実行してみましょう。

```bash:
python -m chainlit run all_chainlit_detail.py
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/c7cc099c-6172-a38f-0031-85bef37efb2c.png)

こんな感じで出てきました。
緊張の一瞬です！
ではさっそく前回のXMLファイルの内容を確認してみましょう。
今回は「背面に水蒸気バリア性を有する感熱記録体に関する特許を提出した会社は？」で質問してみました。期待する回答は「日本製紙株式会社」です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/2cbde761-c991-fbef-bc25-b15a1b7c4875.png)

お！まずはすべてのvectorDBが正解しました。
次は「サミー株式会社の特許の概要を教えて」で確認しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/2977e5e3-0be3-dfeb-8098-c527bbc9bb98.png)

おー、Chroma以外は正解ですね。

次は「特開２００９－０９６１７５の特許のタイトルは？」で確認してみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3784222/001d6256-4c91-8f98-d9a4-0eb43ce866b6.png)

正解は`Qdrant`と`FAISS`の回答です。

# おわりに
　今回の結果から、`Qdrant`と`FAISS`の精度が高いように見えました。
  次回は、出力されたスコアなどを確認して、詳細を見ていきたいと思います。