Open In Colab GitHub Repository

RAG utilise la recherche hybride avec Milvus et LlamaIndex

La recherche hybride exploite les forces de la recherche sémantique et de la recherche par mots-clés pour fournir des résultats plus précis et plus pertinents sur le plan contextuel. En combinant les avantages de la recherche sémantique et de la correspondance de mots-clés, la recherche hybride est particulièrement efficace dans les tâches de recherche d'informations complexes.

Ce carnet montre comment utiliser Milvus pour la recherche hybride dans les pipelines RAG de LlamaIndex. Nous commencerons par la recherche hybride par défaut recommandée (sémantique + BM25), puis nous explorerons d'autres méthodes alternatives d'intégration éparse et la personnalisation du reranker hybride.

Conditions préalables

Installer les dépendances

Avant de commencer, assurez-vous que les dépendances suivantes sont installées :

$ pip install llama-index-vector-stores-milvus
$ pip install llama-index-embeddings-openai
$ pip install llama-index-llms-openai

Si vous utilisez Google Colab, vous pouvez avoir besoin de redémarrer le runtime (Naviguez vers le menu "Runtime" en haut de l'interface, et sélectionnez "Restart session" dans le menu déroulant).

Configurer les comptes

Ce tutoriel utilise OpenAI pour l'intégration de texte et la génération de réponses. Vous devez préparer la clé API OpenAI.

import openai

openai.api_key = "sk-"

Pour utiliser le magasin de vecteurs Milvus, indiquez votre serveur Milvus URI (et éventuellement TOKEN). Pour démarrer un serveur Milvus, vous pouvez le configurer en suivant le guide d'installation Milvus ou en essayant simplement Zilliz Cloud gratuitement.

La recherche en texte intégral est actuellement prise en charge dans Milvus Standalone, Milvus Distributed et Zilliz Cloud, mais pas encore dans Milvus Lite (prévu pour une mise en œuvre future). Contactez support@zilliz.com pour plus d'informations.

URI = "http://localhost:19530"
# TOKEN = ""

Charger des données d'exemple

Exécutez les commandes suivantes pour télécharger les documents d'exemple dans le répertoire "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'

Utilisez ensuite SimpleDirectoryReaderLoad pour charger l'essai "What I Worked On" de Paul Graham :

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 ...

Recherche hybride avec BM25

Cette section montre comment effectuer une recherche hybride à l'aide de la BM25. Pour commencer, nous allons initialiser le site MilvusVectorStore et créer un index pour les documents d'exemple. La configuration par défaut utilise :

  • des encastrements denses à partir du modèle d'encastrement par défaut (OpenAI's text-embedding-ada-002)
  • BM25 pour la recherche en texte intégral si enable_sparse est True
  • RRFRanker avec k=60 pour combiner les résultats si la recherche hybride est activée.
# 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').

Voici plus d'informations sur les arguments permettant de configurer les champs denses et épars dans le site MilvusVectorStore:

champ dense

  • enable_dense (bool): Un indicateur booléen permettant d'activer ou de désactiver l'intégration dense. La valeur par défaut est True.
  • dim (int, optional): La dimension des vecteurs d'intégration pour la collection.
  • embedding_field (str, optional): Le nom du champ d'intégration dense pour la collection, la valeur par défaut étant DEFAULT_EMBEDDING_KEY.
  • index_config (dict, optional): La configuration utilisée pour construire l'index d'intégration dense. La valeur par défaut est None.
  • search_config (dict, optional): La configuration utilisée pour la recherche dans l'index dense Milvus. Notez qu'elle doit être compatible avec le type d'index spécifié par index_config. La valeur par défaut est None.
  • similarity_metric (str, optional): La métrique de similarité à utiliser pour l'intégration dense, actuellement IP, COSINE et L2.

champ sparse

  • enable_sparse (bool): Un indicateur booléen permettant d'activer ou de désactiver l'incorporation éparse. La valeur par défaut est False.
  • sparse_embedding_field (str): Le nom du champ d'intégration éparse, par défaut DEFAULT_SPARSE_EMBEDDING_KEY.
  • sparse_embedding_function (Union[BaseSparseEmbeddingFunction, BaseMilvusBuiltInFunction], optional): Si enable_sparse est True, cet objet doit être fourni pour convertir le texte en un encodage clairsemé. Si None, la fonction d'incorporation éparse par défaut (BM25BuiltInFunction) sera utilisée, ou utiliser BGEM3SparseEmbedding si la collection existante n'a pas de fonctions intégrées.
  • sparse_index_config (dict, optional): La configuration utilisée pour construire l'index d'intégration éparse. La valeur par défaut est None.

Pour activer la recherche hybride lors de l'étape de recherche, définissez vector_store_query_mode à "hybrid". Cela combinera et classera les résultats de la recherche sémantique et de la recherche en texte intégral. Testons avec un exemple de requête : "Qu'a appris l'auteur à 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.

Personnaliser l'analyseur de texte

Les analyseurs jouent un rôle essentiel dans la recherche en texte intégral en décomposant les phrases en jetons et en effectuant un traitement lexical, tel que l'élimination des troncs et des mots vides. Ils sont généralement spécifiques à une langue. Pour plus de détails, voir le Guide de l'analyseur Milvus.

Milvus prend en charge deux types d'analyseurs : Les analyseurs intégrés et les analyseurs personnalisés. Par défaut, si enable_sparse est défini sur True, MilvusVectorStore utilise BM25BuiltInFunction avec les configurations par défaut, en employant l'analyseur intégré standard qui génère du texte en fonction de la ponctuation.

Pour utiliser un autre analyseur ou personnaliser l'analyseur existant, vous pouvez fournir des valeurs à l'argument analyzer_params lors de la construction de BM25BuiltInFunction. Ensuite, définissez cette fonction comme sparse_embedding_function dans MilvusVectorStore.

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)

La recherche hybride avec d'autres encodages épars

Outre la combinaison de la recherche sémantique avec BM25, Milvus prend également en charge la recherche hybride à l'aide d'une fonction d'intégration éparse telle que BGE-M3. L'exemple suivant utilise la fonction intégrée BGEM3SparseEmbeddingFunction pour générer des encastrements épars.

Tout d'abord, nous devons installer le paquetage FlagEmbedding:

$ pip install -q FlagEmbedding

Ensuite, construisons le magasin de vecteurs et l'index en utilisant le modèle OpenAI par défaut pour l'intégration densen et la fonction intégrée BGE-M3 pour l'intégration sparse :

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]

Effectuons maintenant une requête de recherche hybride avec un exemple de question :

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.

Personnaliser la fonction d'intégration éparse

Vous pouvez également personnaliser la fonction d'intégration éparse tant qu'elle hérite de BaseSparseEmbeddingFunction, y compris les méthodes suivantes :

  • encode_queries: Cette méthode convertit les textes en une liste d'intégrations éparses pour les requêtes.
  • encode_documents: Cette méthode convertit les textes en liste d'encastrements épars pour les documents.

La sortie de chaque méthode doit suivre le format de l'intégration éparse, qui est une liste de dictionnaires. Chaque dictionnaire doit avoir une clé (un entier) représentant la dimension et une valeur correspondante (un flottant) représentant l'ampleur de l'intégration dans cette dimension (par exemple, {1 : 0,5, 2 : 0,3}).

Par exemple, voici une implémentation personnalisée d'une fonction d'intégration éparse utilisant 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

Personnalisation du reranker hybride

Milvus prend en charge deux types de stratégies de reclassement: Reciprocal Rank Fusion (RRF) et Weighted Scoring (notation pondérée). Le classeur par défaut dans la recherche hybride MilvusVectorStore est RRF avec k=60. Pour personnaliser le classificateur hybride, modifiez les paramètres suivants :

  • hybrid_ranker (str): Spécifie le type de classificateur utilisé dans les requêtes de recherche hybride. Actuellement, seuls ["RRFRanker", "WeightedRanker"] sont pris en charge. La valeur par défaut est "RRFRanker".
  • hybrid_ranker_params (dict, optional): Paramètres de configuration du classificateur hybride. La structure de ce dictionnaire dépend du classificateur spécifique utilisé :
    • Pour "RRFRanker", il doit comprendre les éléments suivants
      • "k" (int) : Paramètre utilisé dans la fusion réciproque des rangs (RRF). Cette valeur est utilisée pour calculer les scores de classement dans le cadre de l'algorithme RRF, qui combine plusieurs stratégies de classement en un seul score afin d'améliorer la pertinence de la recherche. La valeur par défaut est 60 si elle n'est pas spécifiée.
    • Pour "WeightedRanker", il attend :
      • "weights" (liste de flottants) : Une liste d'exactement deux poids :
        1. Le poids de la composante d'intégration dense.
        2. Ces poids sont utilisés pour équilibrer l'importance des composantes denses et éparses des encastrements dans le processus de recherche hybride. Les poids par défaut sont [1.0, 1.0] s'ils ne sont pas spécifiés.
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.

Try Managed Milvus for Free

Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

Get Started
Feedback

Cette page a-t - elle été utile ?