🐷

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

2024/05/26に公開

sky.jpg

はじめに

前回は、XML形式のファイルから必要なエレメントの情報のみVectorDBに格納する方法を試してみました。

https://qiita.com/ogi_kimura/items/4bbf2c987856a9435280

大量ファイルをVectorDBに取り込んだ場合、データ量が多すぎて「ぼやけてしまう」せいか、生成AIから返ってくる回答精度が悪くなることもわかってきました。
今回は、前回作成した属性を追加したVectorDBを基に、生成AIから精度の高い回答をもらえるようにしてみます。

VectorDBの内容

今回は、filterを使ってVectorDBのデータを絞り込んだうえで、生成AIに質問を投げる方法を考えたいと思います。それにより「ぼやける」ことが無くなるのではないかと考えました。

前回記事の内容から、Chromaのデータベースembedding_metadataというテーブルに以下の属性が追加されており、その中にデータが入っています。
「DB Browser for SQLite」を用いて実際にデータの中身を見ていきたいと思います。

属性名 内容 備考
name ファイルの名前 拡張子は含まれない
source ファイルパス ファイル名・拡張子が含まれる
tag タグ名 名前空間名を含む、前回の記事で属性追加
title 特許のタイトル 前回の記事で属性追加
entry_name 特許を申請した人・組織の名前 前回の記事で属性追加
patent_citation_text 特許請求番号 前回の記事で属性追加
chroma:document ドキュメント内容 Chromaのデフォルト

tag

以下の様にembedding_metadataのkeyカラムがtagのものについて、名前空間({}でくくられている)とタグ名がセットで入っていることがわかります。
image.png

title

以下の様にembedding_metadataのkeyカラムがtitleのものについて、特許名称が入っていることがわかります。
'title'のstring_value値が設定されているレコード数が少ないため、クエリーを使って表示することにします。

select * from embedding_metadata where key="title" and string_value <> ""

image.png

entry_name

以下の様にembedding_metadataのkeyカラムがentry_nameのものについて、特許を請求した人もしくは組織の名前が設定されています。
image.png

patent_citation_text

以下の様にembedding_metadataのkeyカラムがpatent_citation_textのものについて、特許を請求したときの番号が設定されていることがわかります。

image.png

生成AI対話画面(chainlit)

次にchainlitの画面表示を修正します。
chainlitについては私がほとんど理解しておらず、ネットのサンプルコードを色々調べることにしました。ただ、「特許番号テキストボックス」と「メッセージ欄」の2つを同時に設ける例がどこにもありませんでした。
今回は仕方がないので、「メッセージ欄」1つで2つの機能を持たせることにしました。具体的には、入力欄の10桁目まではファイル名として、それ以降を質問内容にすることとしました。
例としては

生成AIに投げる質問の例
0007350061この特許の概要を説明して。

上記の例では、メッセージ文字列の10桁(0007350061)でXMLファイルを特定し、それ以降(この特許の概要を説明して。)で質問をするという具合になります。

では実際にソースコードを記述していきます。
少し余談なのですが、前回の記事でVectorDBにデータをロードするプログラムを作成しましたが、そこではembeddingモデルをtext-embedding-3-smallにしていました。ただ、回答精度について悪くなることが分ってきました。そのためtext-embedding-ada-002に戻しています。

chroma_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
from langchain.vectorstores import Chroma
from qdrant_client.http.models import Filter, FieldCondition, MatchValue

embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
chat = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)


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

文章: 
{document}

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

database = Chroma(
    persist_directory="C:\\Users\\ogiki\\vectorDB\\local_chroma", 
    embedding_function=embeddings
)

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

@cl.on_message
async def on_message(input_message):
    print("入力されたメッセージ: " + input_message)
    name = input_message[:10]
    documents = database.similarity_search_with_score(input_message[10:], k=3, filter={"name":name})
    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(input_message[10:])
    print(documents_string)
    result = chat([
        HumanMessage(content=prompt.format(document=documents_string,
                                           query=input_message[10:]))
    ])
    await cl.Message(content=result.content).send()

ソースコードでは、メッセージの10桁までをnameに格納します。

name = input_message[:10]

それからメッセージの10桁目をnameとしてfilterをかけるようにしました。

documents = database.similarity_search_with_score(input_message[10:], k=3, filter={"name":name})

とてもブサイクなのですが、chainlitをよく理解していないので、もう少しちゃんとしたプログラムにしていくつもりです。今回は堪忍です!

実行

早速chainlitを起動します。

python -m chainlit run chroma_chainlit.py

画面が出てきたので、質問をしてみます。

「0007350061この特許の概要は?」
image.png

回答が返ってきました。
内容は合っているようです。

では、ログで詳細を確認します。

---------------document.metadata---------------
{'name': '0007350061', 'source': 'C:\\Users\\ogiki\\langchain_book\\JPB_2023185\\DOCUMENT\\P_B1\\0007350001\\0007350061\\0007350061\\0007350061.xml'}
0.35284486413002014
---------------document.metadata---------------
{'name': '0007350061', 'source': 'C:\\Users\\ogiki\\langchain_book\\JPB_2023185\\DOCUMENT\\P_B1\\0007350001\\0007350061\\0007350061\\0007350061.xml'}
0.39237409830093384
---------------document.metadata---------------
{'name': '0007350061', 'source': 'C:\\Users\\ogiki\\langchain_book\\JPB_2023185\\DOCUMENT\\P_B1\\0007350001\\0007350061\\0007350061\\0007350061.xml'}
0.40330567955970764

それぞれのスコアは0.35284486413002014 0.39237409830093384 0.40330567955970764です。前回(約0.3)より少し上がっていることが確認できます。
また、sourceがすべて「0007350061.xml」からということで、より精度が上がっていることもわかりました。

まとめ

上記の様に、VectorDBの属性を増やすことにより、生成AIに精度の高い質問ができる可能性が出てきました。VectorDBにはtag title entry_nameという属性も追加したことから、これらに対して条件を設定することでより精度の高い検索が出来そうです。
ただ、残念なのは私がchainlitを理解していない為、メッセージの記述方法に変な制約を与えることになっていましたが・・・

Discussion