RAG utilizando a pesquisa híbrida com Milvus e LlamaIndex
A pesquisa híbrida aproveita os pontos fortes da recuperação semântica e da correspondência de palavras-chave para fornecer resultados mais precisos e contextualmente relevantes. Ao combinar as vantagens da pesquisa semântica e da correspondência de palavras-chave, a pesquisa híbrida é particularmente eficaz em tarefas complexas de recuperação de informações.
Este caderno demonstra como usar o Milvus para pesquisa híbrida nos pipelines RAG do LlamaIndex. Começaremos com a pesquisa híbrida padrão recomendada (semântica + BM25) e, em seguida, exploraremos outros métodos alternativos de incorporação esparsa e a personalização do reranker híbrido.
Pré-requisitos
Instalar dependências
Antes de começar, certifique-se de que tem as seguintes dependências instaladas:
$ pip install llama-index-vector-stores-milvus
$ pip install llama-index-embeddings-openai
$ pip install llama-index-llms-openai
Se estiver a utilizar o Google Colab, poderá ser necessário reiniciar o tempo de execução (navegue até ao menu "Tempo de execução" na parte superior da interface e selecione "Reiniciar sessão" no menu pendente).
Configurar contas
Este tutorial usa o OpenAI para incorporação de texto e geração de respostas. É necessário preparar a chave da API do OpenAI.
import openai
openai.api_key = "sk-"
Para utilizar o armazenamento de vectores Milvus, especifique o seu servidor Milvus URI (e, opcionalmente, com o TOKEN). Para iniciar um servidor Milvus, pode configurar um servidor Milvus seguindo o guia de instalação do Milvus ou simplesmente experimentar o Zilliz Cloud gratuitamente.
A pesquisa de texto integral é atualmente suportada no Milvus Standalone, Milvus Distributed e Zilliz Cloud, mas ainda não no Milvus Lite (planeado para implementação futura). Contacte support@zilliz.com para obter mais informações.
URI = "http://localhost:19530"
# TOKEN = ""
Carregar dados de exemplo
Execute os seguintes comandos para descarregar documentos de exemplo para o diretório "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'
De seguida, utilize SimpleDirectoryReaderLoad para carregar o ensaio "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 ...
Pesquisa híbrida com BM25
Esta secção mostra como realizar uma pesquisa híbrida utilizando o BM25. Para começar, vamos inicializar o MilvusVectorStore e criar um índice para os documentos de exemplo. A configuração padrão usa:
- Embeddings densos do modelo de embedding padrão (
text-embedding-ada-002da OpenAI) - BM25 para pesquisa de texto completo se enable_sparse for True
- RRFRanker com k=60 para combinar resultados se a pesquisa híbrida estiver activada
# 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').
Aqui estão mais informações sobre os argumentos para configurar campos densos e esparsos no MilvusVectorStore:
campo denso
enable_dense (bool): Um sinalizador booleano para ativar ou desativar a incorporação densa. A predefinição é Verdadeiro.dim (int, optional): A dimensão dos vectores de incorporação para a coleção.embedding_field (str, optional): O nome do campo de incorporação densa para a coleção; a predefinição é DEFAULT_EMBEDDING_KEY.index_config (dict, optional): A configuração usada para construir o índice de incorporação densa. A predefinição é Nenhum.search_config (dict, optional): A configuração utilizada para pesquisar o índice denso Milvus. Note que isto tem de ser compatível com o tipo de índice especificado porindex_config. A predefinição é Nenhum.similarity_metric (str, optional): A métrica de similaridade a utilizar para a incorporação densa, atualmente suporta IP, COSINE e L2.
campo esparso
enable_sparse (bool): Um sinalizador booleano para ativar ou desativar a incorporação esparsa. O padrão é False.sparse_embedding_field (str): O nome do campo de incorporação esparsa, com a predefinição DEFAULT_SPARSE_EMBEDDING_KEY.sparse_embedding_function (Union[BaseSparseEmbeddingFunction, BaseMilvusBuiltInFunction], optional): Se enable_sparse for True, este objeto deve ser fornecido para converter o texto numa incorporação esparsa. Se None, será utilizada a função de incorporação esparsa predefinida (BM25BuiltInFunction) ou será utilizado BGEM3SparseEmbedding, dada a coleção existente sem funções incorporadas.sparse_index_config (dict, optional): A configuração utilizada para construir o índice de incorporação esparso. A predefinição é Nenhum.
Para ativar a pesquisa híbrida durante a fase de consulta, defina vector_store_query_mode para "hybrid". Isto irá combinar e classificar os resultados de pesquisa da pesquisa semântica e da pesquisa de texto completo. Vamos testar com uma consulta de exemplo: "O que é que o autor aprendeu na 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.
Personalizar o analisador de texto
Os analisadores desempenham um papel vital na pesquisa de texto integral, dividindo as frases em tokens e efectuando o processamento lexical, tal como a remoção de palavras-chave e de palavras de paragem. Normalmente, são específicos do idioma. Para mais pormenores, consulte o Milvus Analyzer Guide.
O Milvus suporta dois tipos de analisadores: Analisadores incorporados e Analisadores personalizados. Por padrão, se enable_sparse estiver definido como True, MilvusVectorStore utiliza o BM25BuiltInFunction com configurações padrão, empregando o analisador incorporado padrão que tokeniza o texto com base na pontuação.
Para usar um analisador diferente ou personalizar o existente, você pode fornecer valores para o argumento analyzer_params ao criar o BM25BuiltInFunction. Em seguida, defina essa função como sparse_embedding_function em 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)
Pesquisa híbrida com outras incorporações esparsas
Para além de combinar a pesquisa semântica com a BM25, o Milvus também suporta a pesquisa híbrida utilizando uma função de incorporação esparsa como a BGE-M3. O exemplo seguinte utiliza o sítio BGEM3SparseEmbeddingFunction para gerar embeddings esparsos.
Primeiro, precisamos de instalar o pacote FlagEmbedding:
$ pip install -q FlagEmbedding
Em seguida, vamos construir o armazenamento e o índice de vectores utilizando o modelo predefinido do OpenAI para a incorporação de densen e o BGE-M3 incorporado para a incorporação esparsa:
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]
Agora vamos executar uma consulta de pesquisa híbrida com uma pergunta de exemplo:
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.
Personalizar a função de incorporação esparsa
Você também pode personalizar a função de incorporação esparsa, desde que ela herde de BaseSparseEmbeddingFunction, incluindo os seguintes métodos:
encode_queries: Este método converte os textos em uma lista de embeddings esparsos para consultas.encode_documents: Este método converte o texto numa lista de embeddings esparsos para documentos.
A saída de cada método deve seguir o formato do embedding esparso, que é uma lista de dicionários. Cada dicionário deve ter uma chave (um número inteiro) que representa a dimensão e um valor correspondente (um float) que representa a magnitude da incorporação nessa dimensão (por exemplo, {1: 0,5, 2: 0,3}).
Por exemplo, aqui está uma implementação de função de incorporação esparsa personalizada usando 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
Personalizar o reranker híbrido
Milvus suporta dois tipos de estratégias de reranking: Reciprocal Rank Fusion (RRF) e Weighted Scoring. O classificador padrão na pesquisa híbrida do MilvusVectorStore é o RRF com k=60. Para personalizar o classificador híbrido, modifique os seguintes parâmetros:
hybrid_ranker (str): Especifica o tipo de classificador utilizado nas consultas de pesquisa híbrida. Atualmente só suporta ["RRFRanker", "WeightedRanker"]. A predefinição é "RRFRanker".hybrid_ranker_params (dict, optional): Parâmetros de configuração para o classificador híbrido. A estrutura deste dicionário depende do classificador específico que está a ser utilizado:- Para "RRFRanker", deve incluir:
- "k" (int): Um parâmetro utilizado no Reciprocal Rank Fusion (RRF). Este valor é utilizado para calcular as pontuações de classificação como parte do algoritmo RRF, que combina várias estratégias de classificação numa única pontuação para melhorar a relevância da pesquisa. O valor predefinido é 60 se não for especificado.
- Para "WeightedRanker", espera-se:
- "weights" (lista de float): Uma lista de exatamente dois pesos:
- O peso para o componente de incorporação densa.
- Estes pesos são utilizados para equilibrar a importância dos componentes densos e esparsos das incorporações no processo de recuperação híbrida. Os pesos predefinidos são [1.0, 1.0] se não forem especificados.
- "weights" (lista de float): Uma lista de exatamente dois pesos:
- Para "RRFRanker", deve incluir:
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.