MilvusとLlamaIndexによるハイブリッド検索を使用したRAG
ハイブリッド検索は、セマンティック検索とキーワードマッチングの両方の長所を活用し、より正確で文脈に関連した結果を提供します。セマンティック検索とキーワードマッチングの長所を組み合わせることで、ハイブリッド検索は複雑な情報検索タスクにおいて特に効果的です。
このノートブックでは、LlamaIndexRAGパイプラインでMilvusをハイブリッド検索に使用する方法を説明します。推奨されるデフォルトのハイブリッド検索(セマンティック+BM25)から始め、他の代替スパース埋め込み方法とハイブリッドリランカーのカスタマイズを探ります。
前提条件
依存関係のインストール
始める前に、以下の依存関係がインストールされていることを確認してください:
$ pip install llama-index-vector-stores-milvus
$ pip install llama-index-embeddings-openai
$ pip install llama-index-llms-openai
Google Colabを使用している場合、ランタイムを再起動する必要があるかもしれません(インターフェースの上部にある "Runtime "メニューに移動し、ドロップダウンメニューから "Restart session "を選択してください)。
アカウントの設定
このチュートリアルでは、テキスト埋め込みと回答生成にOpenAIを使います。OpenAIのAPIキーを準備する必要があります。
import openai
openai.api_key = "sk-"
Milvusベクターストアを使用するには、MilvusサーバをURI (オプションでTOKEN)で指定します。Milvusサーバを立ち上げるには、Milvusのインストールガイドに従うか、Zilliz Cloudを無料で試すことができる。
全文検索は現在、Milvus Standalone、Milvus Distributed、Zilliz Cloudでサポートされていますが、Milvus Liteではまだサポートされていません(将来実装予定)。詳細は support@zilliz.com までお問い合わせください。
URI = "http://localhost:19530"
# TOKEN = ""
サンプルデータのロード
以下のコマンドを実行し、サンプルドキュメントを "data/paul_graham "ディレクトリにダウンロードします:
$ mkdir -p 'data/paul_graham/'
$ wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham/paul_graham_essay.txt'
次にSimpleDirectoryReaderLoad 、Paul Grahamのエッセイ "What I Worked On "をロードする:
from llama_index.core import SimpleDirectoryReader
documents = SimpleDirectoryReader("./data/paul_graham/").load_data()
# Let's take a look at the first document
print("Example document:\n", documents[0])
Example document:
Doc ID: f9cece8c-9022-46d8-9d0e-f29d70e1dbbe
Text: What I Worked On February 2021 Before college the two main
things I worked on, outside of school, were writing and programming. I
didn't write essays. I wrote what beginning writers were supposed to
write then, and probably still are: short stories. My stories were
awful. They had hardly any plot, just characters with strong feelings,
which I ...
BM25によるハイブリッド検索
このセクションでは、BM25 を使ってハイブリッド検索を実行する方法を示す。始めに、MilvusVectorStore を初期化し、サンプル文書のインデックスを作成します。デフォルトの設定では
- デフォルトの埋め込みモデル(OpenAIの
text-embedding-ada-002)からの密な埋め込み。 - enable_sparseがTrueの場合、全文検索のためのBM25
- ハイブリッド検索が有効な場合、k=60のRRFRankerを結果の結合に使用します。
# Create an index over the documnts
from llama_index.vector_stores.milvus import MilvusVectorStore
from llama_index.core import StorageContext, VectorStoreIndex
vector_store = MilvusVectorStore(
uri=URI,
# token=TOKEN,
dim=1536, # vector dimension depends on the embedding model
enable_sparse=True, # enable the default full-text search using BM25
overwrite=True, # drop the collection if it already exists
)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
2025-04-17 03:38:16,645 [DEBUG][_create_connection]: Created new connection using: cf0f4df74b18418bb89ec512063c1244 (async_milvus_client.py:547)
Sparse embedding function is not provided, using default.
Default sparse embedding function: BM25BuiltInFunction(input_field_names='text', output_field_names='sparse_embedding').
MilvusVectorStore の密フィールドと疎フィールドを設定するための引数についての詳細は以下の通りです:
密フィールド
enable_dense (bool):密な埋め込みを有効または無効にするブール値のフラグ。デフォルトはTrue。dim (int, optional):コレクションの埋め込みベクトルの次元。embedding_field (str, optional):コレクションの密埋め込みフィールドの名前。デフォルトは DEFAULT_EMBEDDING_KEY です。index_config (dict, optional):密埋め込みインデックスの構築に使用される設定。デフォルトはなし。search_config (dict, optional):Milvus密インデックスを検索するための設定。これはindex_configで指定されたインデックスタイプと互換性がなければならないことに注意してください。デフォルトはなし。similarity_metric (str, optional):密な埋め込みに使用する類似度メトリックで、現在は IP, COSINE, L2 をサポートしています。
スパースフィールド
enable_sparse (bool):スパース埋め込みを有効または無効にするブール値のフラグ。デフォルトはFalse。sparse_embedding_field (str):スパース埋め込みフィールドの名前。デフォルトは DEFAULT_SPARSE_EMBEDDING_KEY です。sparse_embedding_function (Union[BaseSparseEmbeddingFunction, BaseMilvusBuiltInFunction], optional):enable_sparse が True の場合、テキストをスパース埋め込みに変換するためにこのオブジェクトを提供する必要があります。Noneの場合、デフォルトのスパース埋め込み関数(BM25BuiltInFunction)が使用されるか、組み込み関数のない既存のコレクションではBGEM3SparseEmbeddingが使用される。sparse_index_config (dict, optional):スパース埋め込みインデックスの構築に使用される設定。デフォルトはNoneです。
クエリの段階でハイブリッド検索を有効にするには、vector_store_query_mode を "hybrid "に設定します。これにより、セマンティック検索とフルテキスト検索の両方からの検索結果が結合され、再ランク付けされる。サンプルクエリでテストしてみよう:「著者はViawebで何を学びましたか?
import textwrap
query_engine = index.as_query_engine(
vector_store_query_mode="hybrid", similarity_top_k=5
)
response = query_engine.query("What did the author learn at Viaweb?")
print(textwrap.fill(str(response), 100))
The author learned about retail, the importance of user feedback, and the significance of growth
rate as the ultimate test of a startup at Viaweb.
テキストアナライザーをカスタマイズする
アナライザーは、文章をトークンに分割し、ステミングやストップワード除去などの語彙処理を行うことで、全文検索において重要な役割を果たします。アナライザは通常、言語固有です。詳細はMilvus Analyzer Guideをご参照ください。
Milvusは2種類のアナライザをサポートしています:ビルトイン アナライザと カスタム アナライザです。デフォルトでは、enable_sparse が True に設定されている場合、MilvusVectorStore はデフォルト設定のBM25BuiltInFunction を利用し、句読点に基づいてテキストをトークン化する標準の組み込みアナライザを採用します。
別の解析器を使用したり、既存の解析器をカスタマイズしたりするには、BM25BuiltInFunction を構築する際にanalyzer_params 引数に値を指定します。その後、この関数をMilvusVectorStore のsparse_embedding_function として設定します。
from llama_index.vector_stores.milvus.utils import BM25BuiltInFunction
bm25_function = BM25BuiltInFunction(
analyzer_params={
"tokenizer": "standard",
"filter": [
"lowercase", # Built-in filter
{"type": "length", "max": 40}, # Custom cap size of a single token
{"type": "stop", "stop_words": ["of", "to"]}, # Custom stopwords
],
},
enable_match=True,
)
vector_store = MilvusVectorStore(
uri=URI,
# token=TOKEN,
dim=1536,
enable_sparse=True,
sparse_embedding_function=bm25_function, # BM25 with custom analyzer
overwrite=True,
)
2025-04-17 03:38:48,085 [DEBUG][_create_connection]: Created new connection using: 61afd81600cb46ee89f887f16bcbfe55 (async_milvus_client.py:547)
他のスパース埋め込みとのハイブリッド検索
Milvusはセマンティック検索とBM25の組み合わせ以外にも、BGE-M3のようなスパース埋め込み関数を使ったハイブリッド検索もサポートしています。以下の例では、組み込みのBGEM3SparseEmbeddingFunction を使ってスパース埋め込みを生成します。
まず、FlagEmbedding パッケージをインストールします:
$ pip install -q FlagEmbedding
そして、OpenAIのデフォルトモデルであるdensen embeddingと、組み込みのBGE-M3を使ったsparse embeddingを使って、ベクトルストアとインデックスを構築します:
from llama_index.vector_stores.milvus.utils import BGEM3SparseEmbeddingFunction
vector_store = MilvusVectorStore(
uri=URI,
# token=TOKEN,
dim=1536,
enable_sparse=True,
sparse_embedding_function=BGEM3SparseEmbeddingFunction(),
overwrite=True,
)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
Fetching 30 files: 100%|██████████| 30/30 [00:00<00:00, 68871.99it/s]
2025-04-17 03:39:02,074 [DEBUG][_create_connection]: Created new connection using: ff4886e2f8da44e08304b748d9ac9b51 (async_milvus_client.py:547)
Chunks: 100%|██████████| 1/1 [00:00<00:00, 1.07it/s]
それでは、サンプル質問でハイブリッド検索クエリを実行してみましょう:
query_engine = index.as_query_engine(
vector_store_query_mode="hybrid", similarity_top_k=5
)
response = query_engine.query("What did the author learn at Viaweb??")
print(textwrap.fill(str(response), 100))
Chunks: 100%|██████████| 1/1 [00:00<00:00, 17.29it/s]
The author learned about retail, the importance of user feedback, the value of growth rate in a
startup, the significance of pricing strategy, the benefits of working on things that weren't
prestigious, and the challenges and rewards of running a startup.
スパース埋め込み関数のカスタマイズ
以下のメソッドを含むBaseSparseEmbeddingFunction を継承する限り、スパース埋め込み関数をカスタマイズすることもできます:
encode_queries:このメソッドは、テキストをクエリ用のスパース埋め込みリストに変換します。encode_documents:このメソッドは、テキストを文書用のスパース埋め込みリストに変換します。
各メソッドの出力は、辞書のリストであるスパース埋込みの形式に従う必要があります。各辞書は,次元を表すキー(整数)と,その次元における埋込みの大きさを表す対応する値(浮動小数点数)を持つ必要があります(例えば,{1: 0.5, 2: 0.3}).
例えば、BGE-M3を使ったカスタムスパース埋め込み関数の実装です:
from FlagEmbedding import BGEM3FlagModel
from typing import List
from llama_index.vector_stores.milvus.utils import BaseSparseEmbeddingFunction
class ExampleEmbeddingFunction(BaseSparseEmbeddingFunction):
def __init__(self):
self.model = BGEM3FlagModel("BAAI/bge-m3", use_fp16=False)
def encode_queries(self, queries: List[str]):
outputs = self.model.encode(
queries,
return_dense=False,
return_sparse=True,
return_colbert_vecs=False,
)["lexical_weights"]
return [self._to_standard_dict(output) for output in outputs]
def encode_documents(self, documents: List[str]):
outputs = self.model.encode(
documents,
return_dense=False,
return_sparse=True,
return_colbert_vecs=False,
)["lexical_weights"]
return [self._to_standard_dict(output) for output in outputs]
def _to_standard_dict(self, raw_output):
result = {}
for k in raw_output:
result[int(k)] = raw_output[k]
return result
ハイブリッドリランカーのカスタマイズ
Milvusは2種類のリランキング戦略をサポートしています:Reciprocal Rank Fusion (RRF)とWeighted Scoringです。MilvusVectorStore ハイブリッド検索のデフォルトランカーはk=60のRRFです。ハイブリッドランカーをカスタマイズするには、以下のパラメータを変更します:
hybrid_ranker (str):ハイブリッド検索クエリで使用するランカーのタイプを指定します。現在は ["RRFRanker", "WeightedRanker"] のみをサポートしています。デフォルトは "RRFRanker" です。hybrid_ranker_params (dict, optional):ハイブリッドランカーの設定パラメータ。この辞書の構造は使用される特定のランカーに依存する:- RRFRanker "の場合、以下を含むべきである:
- "k"(int):k」(int):RRF(Reciprocal Rank Fusion)で使用されるパラメータ。この値はRRFアルゴリズムの一部としてランクスコアを計算するために使用され、複数のランキング戦略を1つのスコアにまとめ、検索の関連性を向上させます。デフォルト値は60です。
- WeightedRanker "には、次のようなものが期待される:
- "weights"(floatのリスト):2つの重みのリスト:
- 密な埋め込みコンポーネントの重み。
- これらの重みは,ハイブリッド検索処理において,埋め込み成分の密な成分と疎な成分の重要度のバランスをとるために用いられます.デフォルトの重みは[1.0, 1.0]です。
- "weights"(floatのリスト):2つの重みのリスト:
- RRFRanker "の場合、以下を含むべきである:
vector_store = MilvusVectorStore(
uri=URI,
# token=TOKEN,
dim=1536,
overwrite=False, # Use the existing collection created in the previous example
enable_sparse=True,
hybrid_ranker="WeightedRanker",
hybrid_ranker_params={"weights": [1.0, 0.5]},
)
index = VectorStoreIndex.from_vector_store(vector_store)
query_engine = index.as_query_engine(
vector_store_query_mode="hybrid", similarity_top_k=5
)
response = query_engine.query("What did the author learn at Viaweb?")
print(textwrap.fill(str(response), 100))
2025-04-17 03:44:00,419 [DEBUG][_create_connection]: Created new connection using: 09c051fb18c04f97a80f07958856587b (async_milvus_client.py:547)
Sparse embedding function is not provided, using default.
No built-in function detected, using BGEM3SparseEmbeddingFunction().
Fetching 30 files: 100%|██████████| 30/30 [00:00<00:00, 136622.28it/s]
Chunks: 100%|██████████| 1/1 [00:00<00:00, 1.07it/s]
The author learned several valuable lessons at Viaweb, including the importance of understanding
growth rate as the ultimate test of a startup, the significance of user feedback in shaping the
software, and the realization that web applications were the future of software development.
Additionally, the experience at Viaweb taught the author about the challenges and rewards of running
a startup, the value of simplicity in software design, and the impact of pricing strategies on
attracting customers.