ベクトルデータベース「Weaviate」を試す 1:Quickstart
ハイブリッド検索ができるということで、以前少し触ってみてそれからあまり触ってなかったWeaviateについて改めてやり直してみる。
やり直すきっかけになったのはこれ。
上記の記事にもあるように、Weaviateはベクトル検索・キーワード検索のハイブリッド検索ができる。ただし、日本語のキーワード検索の場合にはトークナイザーがそれに対応している必要があるのだけど、ここが以前は対応していなかった。(Weaviateで日本語ハイブリッド検索、みたいな記事がQiitaあたりにすでにあるけど、多分正しくトークン化されていないのでは?と思っている。)
で、たまたまGitHubのレポジトリを見ていたら、どうやら以下のPRで日本語トークナイザーに対応したらしい。
また、これ以外にもWeaviateにはいろいろ便利な機能があって、かなり柔軟に使えそうな気がしているので(ただその分、他のベクトルDBに比べると最初の敷居はちょっと高いかもしれない)、そのあたりも含めて一通り見ていきたいと思う。
全10回にわけてまとめた。
Quickstart Tutorial
前提として以下で進める。
- Weaviateのクラウドサービス「Weaviate Cloud Services(WCS)」にベクトルデータベースを作成して、Google Colaboratoryからアクセスする。
- Weaviateでは、無料で使えるsandboxクラスタが提供される。ただし、sandboxクラスタは14日後に削除される。
- 登録するデータは以下のFAQデータセットを使う。
まずWeaviate Cloud Services(WCS)でクラスタを作成する。クラスタ作成したら、クラスタURLとAPIキーが後ほど必要になるのでメモするなりしておく。
以降はQuickstart Tutorialに従ってColaboratory上で進める。
クライアントライブラリのインストール。Python版はv4とv3の2系統があるが、今後はv4になっていくようなので、v4を使う。
!pip install -U weaviate-client
今回はv4.5.1がインストールされた
Successfully installed (snip) weaviate-client-4.5.1
以下をColaboratoryのSecretに登録しておく。
-
OPENAI_API_KEY
: OpenAIのAPIキー -
WEAVIATE_CLUSTER_URL
: 上で作成したWCSのクラスタURL -
WEAVIATE_API_KEY
: 上で作成したWCSのクラスタのAPIキー
クライアントから接続。
import weaviate
import weaviate.classes as wvc
import os
import requests
import json
from google.colab import userdata
client = weaviate.connect_to_wcs(
cluster_url=userdata.get('WEAVIATE_CLUSTER_URL'),
auth_credentials=weaviate.auth.AuthApiKey(userdata.get('WEAVIATE_API_KEY')),
headers={
"X-OpenAI-Api-Key": userdata.get('OPENAI_API_KEY')
}
)
data collectionの定義。data collectionは、オブジェクトを保存するための「箱」だと思えば良さそう。
faq = client.collections.create(
name="FAQ",
vectorizer_config=wvc.config.Configure.Vectorizer.text2vec_openai(), # If set to "none" you must always provide vectors yourself. Could be any other "text2vec-*" also.
generative_config=wvc.config.Configure.Generative.openai() # Ensure the `generative-openai` module is used for generative queries
)
今回は"FAQ"という名前でcollectionを作成した。vectorizer_config
とgenerative_config
は、Weaviate型のベクトルデータベースと異なる特徴の1つである「モジュール」に関連するもの。ここはあとで細かく見る。
ではFAQデータセットを登録する。まずデータセットをpandasのデータフレームに読み込んで整形する。
!wget https://d.line-scdn.net/stf/linecorp/ja/csr/dataset_.zip
!unzip dataset_.zip
import pandas as pd
df = pd.read_excel("dataset_.xlsx")
df.drop(columns=["ID","サンプルID", "カテゴリ2","出典","<参考>UMカテゴリタグ","<参考>UMサービスメニュー\n(標準的な行政サービス名称)"], inplace=True)
df.rename(columns={
'サンプル 問い合わせ文': 'question',
'サンプル 応答文': 'answer',
'カテゴリ1': 'category',
}, inplace=True)
df
データフレームを、辞書のリストに変換する。
faq_objs = df.to_dict(orient='records')
print(len(faq_objs))
print(faq_objs[0])
662
{'question': '母子手帳を受け取りたいのですが、手続きを教えてください。', 'answer': '窓口で妊娠届をご記入いただき、母子手帳をお渡しします。\n住民票の世帯が別の方が代理で窓口に来られる場合は、委任状が必要になります。\n\n▼詳しくはこちら\n(自治体HP内関連ページのURL)', 'category': '妊娠・出産'}
これをWeaviateのdata collectionに追加する。
faq = client.collections.get("FAQ")
faq.data.insert_many(faq_objs)
登録された。結果は少し整形しているけど、エラーが出てなければOK。
BatchObjectReturn(
all_responses=[UUID('a46ed57a-0361-431d-a076-f77b28fd504f'), ...snip..., UUID('d0cb5142-08d4-4b15-b351-b9cf8a25fd86')],
elapsed_seconds=5.962344408035278,
errors={},
uuids={0: UUID('f30c1069-6632-45f6-b949-7655f86a45b1'), 1: UUID('b173426e-edb2-4bf6-ae07-33a1ea312a48'), ...snip..., 661: UUID('d0cb5142-08d4-4b15-b351-b9cf8a25fd86')},
has_errors=False
)
WCS側でもオブジェクトが登録されているのが確認できる。
では検索してみる。いわゆるベクトル類似検索にはcollection.query.near_text
を使う。
response = faq.query.near_text(
query="妊娠したのですが、どういう手続が必要ですか?",
limit=5,
)
for r in response.objects:
print(r.properties)
{'answer': '妊娠したら妊娠届を○○課窓口(または支所・出張所窓口)に提出し、母子手帳を受け取ってください。\n\n▼詳しくはこちら\n(自治体HP内関連ページのURL)', 'question': '妊娠したので、必要な手続きを教えてください。', 'category': '妊娠・出産'}
{'answer': '産前は母子手帳以外の手続きは特にありません。\n産後に、出生の届出や出生通知書の提出、(自治体が行う出産助成等)の申請をお願いします。', 'question': '母子手帳の他に産前に市役所でやるべき手続きはありますか?', 'category': '妊娠・出産'}
{'answer': '母子手帳の申請には診断書はいりませんが、妊娠届に診断を受けた病院名・医師名を記入していただきます。', 'question': '母子手帳の申請には医師の診断書が必要ですか?', 'category': '妊娠・出産'}
{'answer': '出産後に必要な手続きは出生届・出生通知票の提出、児童手当、子ども医療費助成の申請等があります。\n\n▼詳しくはこちら\n(自治体HP内関連ページのURL)', 'question': '子どもが生まれたら、どんな手続きが必要ですか。', 'category': '妊娠・出産'}
{'answer': '窓口で妊娠届をご記入いただき、母子手帳をお渡しします。\n住民票の世帯が別の方が代理で窓口に来られる場合は、委任状が必要になります。\n\n▼詳しくはこちら\n(自治体HP内関連ページのURL)', 'question': '母子手帳を受け取りたいのですが、手続きを教えてください。', 'category': '妊娠・出産'}
それっぽいものが返ってきている。
フィルタを掛けることもできる。例えば、フィルタを掛けない場合。
response = faq.query.near_text(
query="子どもが生まれたら、どのような手当がありますか。",
limit=5,
)
for r in response.objects:
print(r.properties["category"], " -- ", r.properties["question"])
以下のように、「妊娠・出産」「子どもの手当・助成」と複数のカテゴリが検索されている。
妊娠・出産 -- 子どもが生まれたら、どのような手当がありますか。
子どもの手当・助成 -- 子どもの手当にはどんなものがありますか?
子どもの手当・助成 -- 児童育成手当について教えてください。
妊娠・出産 -- 子どもが生まれたら、どんな手続きが必要ですか。
子どもの手当・助成 -- 児童手当の支給額を教えてください。
「妊娠・出産」カテゴリに絞ってみる。
response = faq.query.near_text(
query="子どもが生まれたら、どのような手当がありますか。",
limit=5,
filters=wvc.query.Filter.by_property("category").equal("妊娠・出産")
)
for r in response.objects:
print(r.properties["category"], " -- ", r.properties["question"])
指定のカテゴリに合致したものから検索されている。
妊娠・出産 -- 子どもが生まれたら、どのような手当がありますか。
妊娠・出産 -- 子どもが生まれたら、どんな手続きが必要ですか。
妊娠・出産 -- 出産育児一時金について教えてください。
妊娠・出産 -- 母子手帳の他に産前に市役所でやるべき手続きはありますか?
妊娠・出産 -- 出生届の届出に必要なものは何ですか?
さらに「Generative Search」というのをやってみる。「Generative Search」はRAGそのもので、検索結果を踏まえて回答生成まで行うというもの。collection.generate.near_text
を使って、single_prompt
にプロンプトを設定する。
query = "妊娠したのですが、どういう手続が必要ですか?"
response = faq.generate.near_text(
query=query,
limit=5,
single_prompt="{answer}を踏まえて次の質問に回答して下さい: " + query,
)
for r in response.objects:
print("コンテキスト: ", r.properties['answer'].replace("\n",""))
print("回答: ", r.generated)
print()
検索結果ごとにsingle_prompt
で指定した指示に従ってテキストが生成されている。
コンテキスト: 妊娠したら妊娠届を○○課窓口(または支所・出張所窓口)に提出し、母子手帳を受け取ってください。▼詳しくはこちら(自治体HP内関連ページのURL)
回答: 妊娠した場合、妊娠届を提出し、母子手帳を受け取る手続きが必要です。妊娠届は、○○課窓口(または支所・出張所窓口)に提出する必要があります。母子手帳は、妊娠届を提出した後に受け取ることができます。詳細な手続きや必要書類については、自治体HP内関連ページのURLをご確認ください。
コンテキスト: 産前は母子手帳以外の手続きは特にありません。産後に、出生の届出や出生通知書の提出、(自治体が行う出産助成等)の申請をお願いします。
回答: 妊娠した場合、まずは妊娠を医療機関で確認し、妊娠届を提出する必要があります。その後、母子手帳を取得するために妊婦健診を受けることが必要です。また、出産に備えて出産費用や出産手続きについての情報を収集し、準備をしておくことも重要です。出産予定日が近づいたら、出産施設の選定や入院手続きなども行う必要があります。その他、妊娠中の健康管理や栄養管理にも注意を払い、定期的な健診や検査を受けることが大切です。
コンテキスト: 母子手帳の申請には診断書はいりませんが、妊娠届に診断を受けた病院名・医師名を記入していただきます。
回答: 妊娠した場合、まずは妊娠届を提出する必要があります。妊娠届には、診断を受けた病院名や医師名を記入する必要があります。その後、母子手帳の申請を行うことが推奨されます。母子手帳は妊娠中から出産後までの健康管理や子育てのサポートに役立つ重要な書類です。手続きに関する詳細は、地域の保健所や役所に問い合わせることをお勧めします。
コンテキスト: 出産後に必要な手続きは出生届・出生通知票の提出、児童手当、子ども医療費助成の申請等があります。▼詳しくはこちら(自治体HP内関連ページのURL)
回答: 妊娠した場合、出産後に必要な手続きとしては、出生届・出生通知票の提出が主なものです。また、出産後には児童手当や子ども医療費助成の申請も必要になります。自治体によって手続きの方法や必要書類が異なる場合があるので、自治体のHP内関連ページを確認して詳細な情報を入手することが重要です。
コンテキスト: 窓口で妊娠届をご記入いただき、母子手帳をお渡しします。住民票の世帯が別の方が代理で窓口に来られる場合は、委任状が必要になります。▼詳しくはこちら(自治体HP内関連ページのURL)
回答: 妊娠した場合、住民票の所在地に妊娠届を提出する必要があります。妊娠届を提出することで、母子手帳を受け取ることができます。また、住民票の世帯が別の方が代理で窓口に来る場合は、委任状が必要になります。詳細な手続きや必要書類については、自治体のHP内関連ページをご確認ください。
検索結果をすべて踏まえたうえで1つの回答を生成させる場合はgrouped_task
を使う
query = "妊娠したのですが、どういう手続が必要ですか?"
response = faq.generate.near_text(
query=query,
limit=5,
grouped_task="提供されたコンテキストを踏まえて回答してください。回答は可能な限り丁寧に詳しいものである必要があります。",
)
for r in response.objects:
print("コンテキスト: ", r.properties['answer'].replace("\n",""))
print()
print("回答: ", response.generated)
コンテキスト: 妊娠したら妊娠届を○○課窓口(または支所・出張所窓口)に提出し、母子手帳を受け取ってください。▼詳しくはこちら(自治体HP内関連ページのURL)
コンテキスト: 産前は母子手帳以外の手続きは特にありません。産後に、出生の届出や出生通知書の提出、(自治体が行う出産助成等)の申請をお願いします。
コンテキスト: 母子手帳の申請には診断書はいりませんが、妊娠届に診断を受けた病院名・医師名を記入していただきます。
コンテキスト: 出産後に必要な手続きは出生届・出生通知票の提出、児童手当、子ども医療費助成の申請等があります。▼詳しくはこちら(自治体HP内関連ページのURL)
コンテキスト: 窓口で妊娠届をご記入いただき、母子手帳をお渡しします。住民票の世帯が別の方が代理で窓口に来られる場合は、委任状が必要になります。▼詳しくはこちら(自治体HP内関連ページのURL)
回答: 妊娠した場合、まずは妊娠届を○○課窓口(または支所・出張所窓口)に提出し、母子手帳を受け取る必要があります。母子手帳の申請には診断書は必要ありませんが、妊娠届に診断を受けた病院名・医師名を記入する必要があります。産前には母子手帳以外の特別な手続きは必要ありませんが、産後には出生の届出や出生通知書の提出、出産助成等の申請が必要です。子どもが生まれた後には、出生届・出生通知書の提出や児童手当、子ども医療費助成の申請などが必要です。窓口で妊娠届を記入し、母子手帳を受け取る際には、住民票の世帯が別の方が代理で窓口に来る場合は委任状が必要になります。詳細な手続きや必要書類については、自治体HP内の関連ページをご確認ください。
Weaviateのモジュール
Quickstartで分かる通り、WeaviateはRAGの一般的な実装とはかなり違うのがわかる。これを実現するのが「モジュール」となっている。
一般的なRAGはこう
EmbeddingsおよびCompletionの各APIとのやりとりを全部自分で実装する形になる。
Weaviateのモジュールを使うと、各APIとのやりとりをWeaviate側に任せることができる。例えばVectorizerモジュールを有効化するとこうなる。
Embeddingsの生成処理を気にすることなく、単にドキュメントなりクエリを渡してやるだけで、ベクトルインデックス化およびベクトル検索ができるようになる。
さらにGenerativeモジュールも有効にするとこうなる。
もはやRAGそのものであり、単にリクエストとレスポンスを処理するだけのものとなる。
モジュールは他にもあって、例えばRerankerモジュールを加えると、試してないけど多分こんな感じになると思う。
モジュールを組み合わせることでかなりコードを簡略化できる可能性もあるし、面倒なEmbeddings生成と検索はモジュールにお任せ・検索結果の処理やCompletion部分は自分で細かく制御する、なんてのもできると思うので、非常に柔軟に使えるのではないかと思う。
モジュールの設定はcollection作成時に行う。Quickstartのコードだとこの部分。以下はOpenAI用のVectorizerとGenerativeモジュールを有効にしている。
faq = client.collections.create(
name="FAQ",
vectorizer_config=wvc.config.Configure.Vectorizer.text2vec_openai(),
generative_config=wvc.config.Configure.Generative.openai(),
)
当然OpenAIへのアクセスが必要になるので、クライアント初期化時にAPIキーを渡す必要がある。
client = weaviate.connect_to_wcs(
cluster_url=userdata.get('WEAVIATE_CLUSTER_URL'),
auth_credentials=weaviate.auth.AuthApiKey(userdata.get('WEAVIATE_API_KEY')),
headers={
"X-OpenAI-Api-Key": userdata.get('OPENAI_API_KEY')
}
)
次の記事