Open In Colab GitHub Repository

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 引数に値を指定します。その後、この関数をMilvusVectorStoresparse_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. 密な埋め込みコンポーネントの重み。
        2. これらの重みは,ハイブリッド検索処理において,埋め込み成分の密な成分と疎な成分の重要度のバランスをとるために用いられます.デフォルトの重みは[1.0, 1.0]です。
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.