これは、なにをしたくて書いたもの?
Qdrantのチュートリアルから、「LlamaIndexを使ったマルチテナント(Multitenancy with LlamaIndex)」を試してみたいと思います。
Multitenancy with LlamaIndex - Qdrant
今回のチュートリアルの狙い
今回扱うチュートリアルはこちらの「LlamaIndexを使ったマルチテナント(Multitenancy with LlamaIndex)」です。
Multitenancy with LlamaIndex - Qdrant
マルチテナントが主題のように見えますが、ここで示したいのはQdrantとLlamaIndexとの統合ですね。
このチュートリアルの内容をざっくり言うと、LlamaIndexの機能でQdrantと統合し、ドキュメント登録とフィルタリング付きの検索を
行う、というのがよいんでしょうね。
Qdrantは様々なフレームワークと統合することができ、そのうちのひとつにLlamaIndexがあります。
LlamaIndexは、LLMと外部のデータを接続するためのインターフェースを提供するフレームワークだそうです。RAGのための
フレームワーク、と言ってもよさそうです。
LlamaIndex, Data Framework for LLM Applications
LlamaIndex自体には今回は踏み込みません。
Qdrantは他にも様々なフレームワークとの統合が可能なようです。
そして今回は、QdrantをLlamaIndexのベクトルデータベースとして組み合わせて使うチュートリアルとなっています。
Multitenancy with LlamaIndex - Qdrant
…なのですが、このドキュメント内で使われているLlamaIndexのバージョンが古いようなので、現在のLlamaIndex(0.10.12)に
合わせて読み替えていきます。
環境
今回の環境はこちら。Qdrantは172.17.0.2で動作しているものとします。
$ ./qdrant --version qdrant 1.7.4
QdrantのWeb UIは0.1.21を使っています。
Qdrant ClientおよびLlamaIndexを使うPython環境。
$ python3 --version Python 3.10.12 $ pip3 --version pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)
Qdrantのチュートリアル「LlamaIndexを使ったマルチテナント(Multitenancy with LlamaIndex)」を試す
それでは、Qdrantのチュートリアル「LlamaIndexを使ったマルチテナント(Multitenancy with LlamaIndex)」をドキュメントに沿って
進めていきます。
Multitenancy with LlamaIndex - Qdrant
必要なライブラリーのインストール。
$ pip3 install qdrant-client llama-index-vector-stores-qdrant llama-index-embeddings-huggingface
インストールしたライブラリーの一覧。
$ pip3 list Package Version ---------------------------------- ---------- aiohttp 3.9.3 aiosignal 1.3.1 annotated-types 0.6.0 anyio 4.3.0 async-timeout 4.0.3 attrs 23.2.0 certifi 2024.2.2 charset-normalizer 3.3.2 click 8.1.7 dataclasses-json 0.6.4 Deprecated 1.2.14 dirtyjson 1.0.8 distro 1.9.0 exceptiongroup 1.2.0 filelock 3.13.1 frozenlist 1.4.1 fsspec 2024.2.0 greenlet 3.0.3 grpcio 1.62.0 grpcio-tools 1.62.0 h11 0.14.0 h2 4.1.0 hpack 4.0.0 httpcore 1.0.4 httpx 0.27.0 huggingface-hub 0.20.3 hyperframe 6.0.1 idna 3.6 Jinja2 3.1.3 joblib 1.3.2 llama-index-core 0.10.12 llama-index-embeddings-huggingface 0.1.3 llama-index-vector-stores-qdrant 0.1.3 llamaindex-py-client 0.1.13 MarkupSafe 2.1.5 marshmallow 3.20.2 mpmath 1.3.0 multidict 6.0.5 mypy-extensions 1.0.0 nest-asyncio 1.6.0 networkx 3.2.1 nltk 3.8.1 numpy 1.26.4 nvidia-cublas-cu12 12.1.3.1 nvidia-cuda-cupti-cu12 12.1.105 nvidia-cuda-nvrtc-cu12 12.1.105 nvidia-cuda-runtime-cu12 12.1.105 nvidia-cudnn-cu12 8.9.2.26 nvidia-cufft-cu12 11.0.2.54 nvidia-curand-cu12 10.3.2.106 nvidia-cusolver-cu12 11.4.5.107 nvidia-cusparse-cu12 12.1.0.106 nvidia-nccl-cu12 2.19.3 nvidia-nvjitlink-cu12 12.3.101 nvidia-nvtx-cu12 12.1.105 openai 1.12.0 packaging 23.2 pandas 2.2.0 pillow 10.2.0 pip 22.0.2 portalocker 2.8.2 protobuf 4.25.3 pydantic 2.6.1 pydantic_core 2.16.2 python-dateutil 2.8.2 pytz 2024.1 PyYAML 6.0.1 qdrant-client 1.7.3 regex 2023.12.25 requests 2.31.0 safetensors 0.4.2 setuptools 59.6.0 six 1.16.0 sniffio 1.3.0 SQLAlchemy 2.0.27 sympy 1.12 tenacity 8.2.3 tiktoken 0.6.0 tokenizers 0.15.2 torch 2.2.1 tqdm 4.66.2 transformers 4.38.1 triton 2.2.0 typing_extensions 4.9.0 typing-inspect 0.9.0 tzdata 2024.1 urllib3 2.2.1 wrapt 1.16.0 yarl 1.9.4
いきなりドキュメントと違うモジュール(llama-index-vector-stores-qdrant、llama-index-embeddings-huggingface)を
インストールしていますが、これはLlmaIndex側のドキュメントを見るとモジュールが変わっていたからですね。
Configuring Settings - LlamaIndex 🦙 v0.10.12
Qdrant Vector Store - LlamaIndex 🦙 v0.10.12
Embeddings - LlamaIndex 🦙 v0.10.12
Defining and Customizing Documents - LlamaIndex 🦙 v0.10.12
また、llama-index-vector-stores-qdrantをインストールするとqdrant-clientも同時にインストールされますが、今回は明示的に
指定することにします。
この結果、作成するプログラムのimport
等も元のドキュメントから変更が入ります。
最初は、Qdrantにコレクションを作成してポイントを登録するプログラムを作成します。
add_documents.py
from qdrant_client import QdrantClient from qdrant_client.models import HnswConfigDiff, PayloadSchemaType from llama_index.core import Settings, StorageContext, VectorStoreIndex from llama_index.core.node_parser import SentenceSplitter from llama_index.core.schema import Document from llama_index.embeddings.huggingface import HuggingFaceEmbedding from llama_index.vector_stores.qdrant import QdrantVectorStore documents = [ Document( text="LlamaIndex is a simple, flexible data framework for connecting custom data sources to large language models.", metadata={ "library": "llama-index", }, ), Document( text="LlamaIndex is a framework that can be integrated with Qdrant.", metadata={ "library": "llama-index", }, ), Document( text="Qdrant is a vector database & vector similarity search engine.", metadata={ "library": "qdrant", }, ), Document( text="Qdrant is implemented by Rust.", metadata={ "library": "qdrant", }, ), Document( text="Qdrant has a tutorial on integrating with LlamaIndex.", metadata={ "library": "qdrant", }, ), ] Settings.embed_model = HuggingFaceEmbedding( model_name="BAAI/bge-small-en-v1.5" ) Settings.node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=32) client = QdrantClient("http://172.17.0.2:6333") vector_store = QdrantVectorStore( collection_name="my_collection", client=client ) storage_context = StorageContext.from_defaults(vector_store=vector_store) VectorStoreIndex.from_documents( documents=documents, storage_context=storage_context ) client.create_payload_index( collection_name="my_collection", field_name="metadata.library", field_type=PayloadSchemaType.KEYWORD, ) client.update_collection( collection_name="my_collection", hnsw_config=HnswConfigDiff(payload_m=16, m=0), )
QdrantClient
は直接使うところはかなり限定されます。
こちらはQdrantにポイントとして登録するドキュメントです。チュートリアルから、少しサンプルを増やしておきました。
documents = [ Document( text="LlamaIndex is a simple, flexible data framework for connecting custom data sources to large language models.", metadata={ "library": "llama-index", }, ), Document( text="LlamaIndex is a framework that can be integrated with Qdrant.", metadata={ "library": "llama-index", }, ), Document( text="Qdrant is a vector database & vector similarity search engine.", metadata={ "library": "qdrant", }, ), Document( text="Qdrant is implemented by Rust.", metadata={ "library": "qdrant", }, ), Document( text="Qdrant has a tutorial on integrating with LlamaIndex.", metadata={ "library": "qdrant", }, ), ]
Defining and Customizing Documents - LlamaIndex 🦙 v0.10.12
使用する埋め込みモデルの設定。
Settings.embed_model = HuggingFaceEmbedding(
model_name="BAAI/bge-small-en-v1.5"
)
今回はBAAI/bge-small-en-v1.5
をHugging Faceからダウンロードして使います。
Embeddings - LlamaIndex 🦙 v0.10.12
次に、ドキュメントをチャンクまたはノードに分割する設定をします。
Settings.node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=32)
Configuring Settings - LlamaIndex 🦙 v0.10.12
QdrantClient
を作成し、LlamaIndexで使うベクトルデータベース(QdrantVectorStore
)として指定します。
client = QdrantClient("http://172.17.0.2:6333") vector_store = QdrantVectorStore( collection_name="my_collection", client=client )
QdrantVectorStore
をストレージにしてVectorStoreIndex
経由でドキュメントを登録します。
storage_context = StorageContext.from_defaults(vector_store=vector_store) VectorStoreIndex.from_documents( documents=documents, storage_context=storage_context )
Qdrant Vector Store - LlamaIndex 🦙 v0.10.12
最後に、metadata.library
フィールドに対してインデックスを付与して検索を効率化します。また、インデックス全体を検索することが
ないので(※)HNSWグラフをグローバルに構築しないようにします。
※クエリー時に常にmetadata.library
をフィルター条件似指定します
client.create_payload_index( collection_name="my_collection", field_name="metadata.library", field_type=PayloadSchemaType.KEYWORD, ) client.update_collection( collection_name="my_collection", hnsw_config=HnswConfigDiff(payload_m=16, m=0), )
プログラムができたので、実行します。
$ python3 add_documents.py
QdrantのWeb UIを使って、ドキュメント(Qdrantではポイント)が登録されたことを確認します。
OKですね。
今度は、検索をしてみます。
作成したプログラムはこちら。
search.py
import sys from llama_index.core.vector_stores.types import FilterOperator, MetadataFilter, MetadataFilters from qdrant_client import QdrantClient from llama_index.core import Settings, VectorStoreIndex from llama_index.core.node_parser import SentenceSplitter from llama_index.embeddings.huggingface import HuggingFaceEmbedding from llama_index.vector_stores.qdrant import QdrantVectorStore target_library = sys.argv[1] search_keyword = sys.argv[2] Settings.embed_model = HuggingFaceEmbedding( model_name="BAAI/bge-small-en-v1.5" ) Settings.node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=32) client = QdrantClient("http://172.17.0.2:6333") vector_store = QdrantVectorStore( collection_name="my_collection", client=client ) index = VectorStoreIndex.from_vector_store( vector_store=vector_store ) print(f"target filter: {target_library}") print(f"search keyword: {search_keyword}") qdrant_retriver = index.as_retriever( filters=MetadataFilters( filters=[ MetadataFilter( key="library", operator=FilterOperator.EQ, value=target_library, ) ] ) ) nodes_with_scores = qdrant_retriver.retrieve(search_keyword) print() print("results:") for node in nodes_with_scores: print(f""" document id: {node.id_} text: {node.text} metadata: {node.metadata} score: {node.score}""")
検索時にドキュメントを登録した時に指定したメタデータのlibrary
を指定するのですが、この値と検索キーワードはコマンドライン引数で
指定するようにしました。
target_library = sys.argv[1] search_keyword = sys.argv[2]
LlamaIndexの設定とQdrantVectorStore
のインスタンスの作成。
Settings.embed_model = HuggingFaceEmbedding( model_name="BAAI/bge-small-en-v1.5" ) Settings.node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=32) client = QdrantClient("http://172.17.0.2:6333") vector_store = QdrantVectorStore( collection_name="my_collection", client=client )
この後、VectorStoreIndex
から検索で使うインデックスを取得するのですが、ここではドキュメントは登録しないので
VectorStoreIndex#from_vector_store
でベクトルデータベース(今回はQdrantVectorStore
)のみを指定します。
index = VectorStoreIndex.from_vector_store( vector_store=vector_store )
ここで取得したVectorStoreIndex
のインスタンスから、VectorStoreIndex#as_retriever
でRetriverのインスタンスを取得するのですが、
この時にフィルターを指定します。ドキュメント登録時にllama-index
とqdrant
のどちらかを指定しましたが、検索結果に対する絞り込みを
行います。
print(f"target filter: {target_library}") print(f"search keyword: {search_keyword}") qdrant_retriver = index.as_retriever( filters=MetadataFilters( filters=[ MetadataFilter( key="library", operator=FilterOperator.EQ, value=target_library, ) ] ) ) nodes_with_scores = qdrant_retriver.retrieve(search_keyword)
あとは検索キーワードで検索ですね。
結果表示。
print() print("results:") for node in nodes_with_scores: print(f""" document id: {node.id_} text: {node.text} metadata: {node.metadata} score: {node.score}""")
では、実行してみます。
まずはlibrary
にqdrant
を指定。
$ python3 search.py qdrant 'large language models' target filter: qdrant search keyword: large language models results: document id: bba4d033-7c06-4869-a5a0-b73410e68ddb text: Qdrant is a vector database & vector similarity search engine. metadata: {'library': 'qdrant'} score: 0.6055155 document id: e7e4ac84-0ef4-48f8-844b-cc2e8b6f97be text: Qdrant has a tutorial on integrating with LlamaIndex. metadata: {'library': 'qdrant'} score: 0.58532107
次は、llama-index
を指定。
$ python3 search.py llama-index 'large language models' target filter: llama-index search keyword: large language models results: document id: 2a1d4535-001d-4d93-937d-c34f99ea356c text: LlamaIndex is a simple, flexible data framework for connecting custom data sources to large language models. metadata: {'library': 'llama-index'} score: 0.63576734 document id: 7d1c5050-ea41-4f4e-bd29-e7cb7c1b63f1 text: LlamaIndex is a framework that can be integrated with Qdrant. metadata: {'library': 'llama-index'} score: 0.5662359
良さそうです。
library
はllama-index
で、キーワードは「qdrant」。
$ python3 search.py llama-index 'qdrant' target filter: llama-index search keyword: qdrant results: document id: 7d1c5050-ea41-4f4e-bd29-e7cb7c1b63f1 text: LlamaIndex is a framework that can be integrated with Qdrant. metadata: {'library': 'llama-index'} score: 0.68183947 document id: 2a1d4535-001d-4d93-937d-c34f99ea356c text: LlamaIndex is a simple, flexible data framework for connecting custom data sources to large language models. metadata: {'library': 'llama-index'} score: 0.5274638
こんなところでしょうか。
おわりに
Qdrantのチュートリアルから、「LlamaIndexを使ったマルチテナント(Multitenancy with LlamaIndex)」を試してみました。
初めてのLlamaIndexでもあったのですが、今回はさらっと流すつもりがQdrantのチュートリアルで使われていたLlamaIndexの内容が
古かったみたいで、結局そこそこLlamaIndexの情報も見ることになってしまいました。まあ、それもよいでしょう。
こうなると、LangChainと組み合わせたりしたくなりますね。