Open In Colab GitHub Repository

RAG con ricerca ibrida con Milvus e LlamaIndex

La ricerca ibrida sfrutta i punti di forza del reperimento semantico e della corrispondenza delle parole chiave per fornire risultati più accurati e contestualmente rilevanti. Combinando i vantaggi della ricerca semantica e della corrispondenza delle parole chiave, la ricerca ibrida è particolarmente efficace nei compiti complessi di recupero delle informazioni.

Questo quaderno mostra come utilizzare Milvus per la ricerca ibrida nelle pipeline LlamaIndex RAG. Inizieremo con la ricerca ibrida predefinita raccomandata (semantica + BM25) per poi esplorare altri metodi alternativi di inclusione rada e la personalizzazione del reranker ibrido.

Prerequisiti

Installare le dipendenze

Prima di iniziare, assicuratevi di aver installato le seguenti dipendenze:

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

Se si utilizza Google Colab, potrebbe essere necessario riavviare il runtime (andare al menu "Runtime" nella parte superiore dell'interfaccia e selezionare "Riavvia sessione" dal menu a discesa).

Impostazione degli account

Questa esercitazione utilizza OpenAI per l'incorporazione del testo e la generazione delle risposte. È necessario preparare la chiave API di OpenAI.

import openai

openai.api_key = "sk-"

Per utilizzare l'archivio vettoriale Milvus, specificare il server Milvus URI (e facoltativamente TOKEN). Per avviare un server Milvus, è possibile configurarlo seguendo la guida all'installazione di Milvus o semplicemente provando gratuitamente Zilliz Cloud.

La ricerca full-text è attualmente supportata in Milvus Standalone, Milvus Distributed e Zilliz Cloud, ma non ancora in Milvus Lite (prevista in futuro). Per ulteriori informazioni, contattare support@zilliz.com.

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

Caricare i dati di esempio

Eseguite i seguenti comandi per scaricare i documenti di esempio nella directory "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'

Quindi utilizzare SimpleDirectoryReaderLoad per caricare il saggio "What I Worked On" di 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 ...

Ricerca ibrida con BM25

Questa sezione mostra come eseguire una ricerca ibrida con BM25. Per iniziare, inizializzeremo MilvusVectorStore e creeremo un indice per i documenti di esempio. La configurazione predefinita utilizza:

  • Embedding densi dal modello di embedding predefinito ( text-embedding-ada-002 di OpenAI).
  • BM25 per la ricerca full-text se enable_sparse è True
  • RRFRanker con k=60 per combinare i risultati se la ricerca ibrida è abilitata.
# 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').

Ecco ulteriori informazioni sugli argomenti per la configurazione dei campi densi e radi in MilvusVectorStore:

campo denso

  • enable_dense (bool): Un flag booleano per abilitare o disabilitare l'incorporazione densa. L'impostazione predefinita è True.
  • dim (int, optional): La dimensione dei vettori di incorporamento per l'insieme.
  • embedding_field (str, optional): Il nome del campo di incorporamento denso per l'insieme, predefinito a DEFAULT_EMBEDDING_KEY.
  • index_config (dict, optional): La configurazione utilizzata per costruire l'indice di incorporamento denso. Per impostazione predefinita, Nessuno.
  • search_config (dict, optional): Configurazione utilizzata per la ricerca nell'indice denso di Milvus. Si noti che deve essere compatibile con il tipo di indice specificato da index_config. Il valore predefinito è Nessuno.
  • similarity_metric (str, optional): La metrica di somiglianza da utilizzare per l'incorporazione densa; attualmente supporta IP, COSINE e L2.

campo sparse

  • enable_sparse (bool): Un flag booleano per abilitare o disabilitare l'incorporazione rada. L'impostazione predefinita è False.
  • sparse_embedding_field (str): Il nome del campo sparse embedding, predefinito a DEFAULT_SPARSE_EMBEDDING_KEY.
  • sparse_embedding_function (Union[BaseSparseEmbeddingFunction, BaseMilvusBuiltInFunction], optional): Se enable_sparse è True, questo oggetto deve essere fornito per convertire il testo in un'incorporazione rada. Se None, verrà utilizzata la funzione di incorporamento rado predefinita (BM25BuiltInFunction), oppure verrà utilizzato BGEM3SparseEmbedding, data la raccolta esistente senza funzioni incorporate.
  • sparse_index_config (dict, optional): La configurazione utilizzata per costruire l'indice di incorporamento rado. L'impostazione predefinita è Nessuno.

Per abilitare la ricerca ibrida durante la fase di interrogazione, impostare vector_store_query_mode su "hybrid". Questo combinerà e classificherà i risultati della ricerca semantica e della ricerca full-text. Proviamo con una query di esempio: "Cosa ha imparato l'autore in 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.

Personalizzare l'analizzatore di testo

Gli analizzatori svolgono un ruolo fondamentale nella ricerca full-text, suddividendo le frasi in token ed eseguendo l'elaborazione lessicale, come l'eliminazione di stemming e stop-word. In genere sono specifici per la lingua. Per maggiori dettagli, consultare la Guida agli analizzatori di Milvus.

Milvus supporta due tipi di analizzatori: Analizzatori integrati e Analizzatori personalizzati. Per impostazione predefinita, se enable_sparse è impostato su True, MilvusVectorStore utilizza BM25BuiltInFunction con le configurazioni predefinite, impiegando l'analizzatore incorporato standard che tokenizza il testo in base alla punteggiatura.

Per utilizzare un analizzatore diverso o personalizzare quello esistente, è possibile fornire dei valori all'argomento analyzer_params durante la creazione di BM25BuiltInFunction. Quindi, impostare questa funzione come sparse_embedding_function in 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)

Ricerca ibrida con altri incorporamenti sparsi

Oltre a combinare la ricerca semantica con BM25, Milvus supporta anche la ricerca ibrida utilizzando una funzione di incorporamento rada come BGE-M3. L'esempio seguente utilizza il programma integrato BGEM3SparseEmbeddingFunction per generare embedding sparsi.

Per prima cosa è necessario installare il pacchetto FlagEmbedding:

$ pip install -q FlagEmbedding

Quindi costruiamo l'archivio vettoriale e l'indice usando il modello predefinito di OpenAI per l'incorporazione densen e il built-in BGE-M3 per l'incorporazione rada:

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]

Ora eseguiamo una query di ricerca ibrida con una domanda di esempio:

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.

Personalizzare la funzione Sparse Embedding

È possibile personalizzare anche la funzione Sparse Embedding, purché erediti da BaseSparseEmbeddingFunction, compresi i seguenti metodi:

  • encode_queries: Questo metodo converte i testi in elenchi di incorporazioni sparse per le query.
  • encode_documents: Questo metodo converte il testo in un elenco di incorporazioni rade per i documenti.

L'output di ogni metodo deve seguire il formato dell'embedding sparso, che è un elenco di dizionari. Ogni dizionario deve avere una chiave (un intero) che rappresenta la dimensione e un valore corrispondente (un float) che rappresenta la grandezza dell'incorporamento in quella dimensione (ad esempio, {1: 0,5, 2: 0,3}).

Ad esempio, ecco un'implementazione di una funzione di incorporamento rada personalizzata utilizzando 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

Personalizzazione del reranker ibrido

Milvus supporta due tipi di strategie di reranking: Reciprocal Rank Fusion (RRF) e Weighted Scoring. Il ranker predefinito nella ricerca ibrida di MilvusVectorStore è RRF con k=60. Per personalizzare il ranker ibrido, modificare i seguenti parametri:

  • hybrid_ranker (str): Specifica il tipo di ranker utilizzato nelle query di ricerca ibrida. Attualmente supporta solo ["RRFRanker", "WeightedRanker"]. L'impostazione predefinita è "RRFRanker".
  • hybrid_ranker_params (dict, optional): Parametri di configurazione per il ranker ibrido. La struttura di questo dizionario dipende dal ranker specifico utilizzato:
    • Per "RRFRanker", dovrebbe includere:
      • "k" (int): Un parametro utilizzato nella Reciprocal Rank Fusion (RRF). Questo valore viene utilizzato per calcolare i punteggi di rango come parte dell'algoritmo RRF, che combina più strategie di classificazione in un unico punteggio per migliorare la rilevanza della ricerca. Il valore predefinito è 60 se non specificato.
    • Per "WeightedRanker", si aspetta:
      • "weights" (elenco di float): Un elenco di esattamente due pesi:
        1. Il peso per la componente densa dell'incorporazione.
        2. Questi pesi vengono utilizzati per bilanciare l'importanza delle componenti dense e rade delle incorporazioni nel processo di recupero ibrido. I pesi predefiniti sono [1.0, 1.0] se non vengono specificati.
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

Questa pagina è stata utile?