Open In Colab GitHub Repository

Búsqueda de texto completo con Milvus y Haystack

Labúsqueda de texto completo es un método tradicional para recuperar documentos mediante la búsqueda de palabras clave o frases específicas en el texto. Clasifica los resultados basándose en puntuaciones de relevancia calculadas a partir de factores como la frecuencia de los términos. Mientras que la búsqueda semántica es mejor para comprender el significado y el contexto, la búsqueda de texto completo destaca en la búsqueda precisa de palabras clave, lo que la convierte en un complemento útil de la búsqueda semántica. El algoritmo BM25 se utiliza ampliamente para la clasificación en la búsqueda de texto completo y desempeña un papel clave en la Generación Mejorada de Recuperación (RAG).

Milvus 2.5 introduce capacidades nativas de búsqueda de texto completo utilizando BM25. Este enfoque convierte el texto en vectores dispersos que representan las puntuaciones BM25. Basta con introducir el texto en bruto y Milvus generará y almacenará automáticamente los vectores dispersos, sin necesidad de generar manualmente la incrustación dispersa.

Haystack es ahora compatible con esta función de Milvus, lo que facilita la incorporación de la búsqueda de texto completo a las aplicaciones RAG. Puede combinar la búsqueda de texto completo con la búsqueda semántica de vectores densos para obtener un enfoque híbrido que se beneficia tanto de la comprensión semántica como de la precisión de la concordancia de palabras clave. Esta combinación mejora la precisión de la búsqueda y ofrece mejores resultados a los usuarios.

Este tutorial muestra cómo implementar la búsqueda de texto completo e híbrida en su aplicación utilizando Haystack y Milvus.

Para utilizar el almacén vectorial Milvus, especifique su servidor Milvus URI (y opcionalmente con el TOKEN). Para iniciar un servidor Milvus, puede configurar un servidor Milvus siguiendo la guía de instalación de Milvus o simplemente probando Zilliz Cloud(Milvus totalmente gestionado) de forma gratuita.

  • La búsqueda de texto completo está disponible actualmente en Milvus Standalone, Milvus Distributed y Zilliz Cloud, aunque todavía no es compatible con Milvus Lite (que tiene prevista esta función para una futura implementación). Póngase en contacto con support@zilliz.com para obtener más información.
  • Antes de continuar con este tutorial, asegúrese de tener una comprensión básica de la búsqueda de texto completo y el uso básico de la integración de Haystack Milvus.

Requisitos previos

Antes de ejecutar este cuaderno, asegúrate de tener instaladas las siguientes dependencias:

$ pip install --upgrade --quiet pymilvus milvus-haystack

Si utilizas Google Colab, para habilitar las dependencias que acabas de instalar, es posible que tengas que reiniciar el tiempo de ejecución (haz clic en el menú "Tiempo de ejecución" en la parte superior de la pantalla y selecciona "Reiniciar sesión" en el menú desplegable).

Utilizaremos los modelos de OpenAI. Debes preparar la clave api OPENAI_API_KEY como variable de entorno.

import os

os.environ["OPENAI_API_KEY"] = "sk-***********"

Prepara los datos

Importa los paquetes necesarios en este cuaderno. A continuación, prepare algunos documentos de ejemplo.

from haystack import Pipeline
from haystack.components.embedders import OpenAIDocumentEmbedder, OpenAITextEmbedder
from haystack.components.writers import DocumentWriter
from haystack.utils import Secret
from milvus_haystack import MilvusDocumentStore, MilvusSparseEmbeddingRetriever
from haystack.document_stores.types import DuplicatePolicy
from milvus_haystack.function import BM25BuiltInFunction
from milvus_haystack import MilvusDocumentStore
from milvus_haystack.milvus_embedding_retriever import MilvusHybridRetriever

from haystack.utils import Secret
from haystack.components.builders import PromptBuilder
from haystack.components.generators import OpenAIGenerator
from haystack import Document

documents = [
    Document(content="Alice likes this apple", meta={"category": "fruit"}),
    Document(content="Bob likes swimming", meta={"category": "sport"}),
    Document(content="Charlie likes white dogs", meta={"category": "pets"}),
]

La integración de la búsqueda de texto completo en un sistema RAG equilibra la búsqueda semántica con una recuperación precisa y predecible basada en palabras clave. También puede optar por utilizar sólo la búsqueda de texto completo, aunque se recomienda combinar la búsqueda de texto completo con la búsqueda semántica para obtener mejores resultados de búsqueda. A modo de demostración, mostraremos aquí la búsqueda de texto completo sola y la búsqueda híbrida.

BM25 búsqueda sin incrustación

Crear el proceso de indexación

Para la búsqueda de texto completo Milvus MilvusDocumentStore acepta un parámetro builtin_function. A través de este parámetro, puede pasar una instancia de BM25BuiltInFunction, que implementa el algoritmo BM25 en el lado del servidor Milvus. Establezca el builtin_function especificado como la instancia de la función BM25. Por ejemplo

connection_args = {"uri": "http://localhost:19530"}
# connection_args = {"uri": YOUR_ZILLIZ_CLOUD_URI, "token": Secret.from_env_var("ZILLIZ_CLOUD_API_KEY")}

document_store = MilvusDocumentStore(
    connection_args=connection_args,
    sparse_vector_field="sparse_vector",  # The sparse vector field.
    text_field="text",
    builtin_function=[
        BM25BuiltInFunction(  # The BM25 function converts the text into a sparse vector.
            input_field_names="text",
            output_field_names="sparse_vector",
        )
    ],
    consistency_level="Bounded",  # Supported values are (`"Strong"`, `"Session"`, `"Bounded"`, `"Eventually"`).
    drop_old=True,  # Drop the old collection if it exists and recreate it.
)

Para connection_args:

  • Puede configurar un servidor Milvus de mayor rendimiento en docker o kubernetes. En esta configuración, utilice la dirección del servidor, por ejemplohttp://localhost:19530, como su uri.
  • Si desea utilizar Zilliz Cloud, el servicio en la nube totalmente gestionado para Milvus, ajuste uri y token, que corresponden al punto final público y a la clave Api en Zilliz Cloud.

Cree una canalización de indexación para escribir documentos en el almacén de documentos de Milvus.

writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.NONE)

indexing_pipeline = Pipeline()
indexing_pipeline.add_component("writer", writer)
indexing_pipeline.run({"writer": {"documents": documents}})
{'writer': {'documents_written': 3}}

Cree la canalización de recuperación

Crear un canal de recuperación que recupere documentos del almacén de documentos de Milvus utilizando MilvusSparseEmbeddingRetriever, que es una envoltura alrededor de document_store.

retrieval_pipeline = Pipeline()
retrieval_pipeline.add_component(
    "retriever", MilvusSparseEmbeddingRetriever(document_store=document_store)
)

question = "Who likes swimming?"

retrieval_results = retrieval_pipeline.run({"retriever": {"query_text": question}})

retrieval_results["retriever"]["documents"][0]
Document(id=bd334348dd2087c785e99b5a0009f33d9b8b8198736f6415df5d92602d81fd3e, content: 'Bob likes swimming', meta: {'category': 'sport'}, score: 1.2039074897766113)

Creación del proceso de indexación

En la búsqueda híbrida, utilizamos la función BM25 para realizar la búsqueda de texto completo, y especificamos el campo vectorial denso vector, para realizar la búsqueda semántica.

document_store = MilvusDocumentStore(
    connection_args=connection_args,
    vector_field="vector",  # The dense vector field.
    sparse_vector_field="sparse_vector",  # The sparse vector field.
    text_field="text",
    builtin_function=[
        BM25BuiltInFunction(  # The BM25 function converts the text into a sparse vector.
            input_field_names="text",
            output_field_names="sparse_vector",
        )
    ],
    consistency_level="Bounded",  # Supported values are (`"Strong"`, `"Session"`, `"Bounded"`, `"Eventually"`).
    drop_old=True,  # Drop the old collection and recreate it.
)

Crear una cadena de indexación que convierta los documentos en incrustaciones. A continuación, los documentos se escriben en el almacén de documentos de Milvus.

writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.NONE)

indexing_pipeline = Pipeline()
indexing_pipeline.add_component("dense_doc_embedder", OpenAIDocumentEmbedder())
indexing_pipeline.add_component("writer", writer)
indexing_pipeline.connect("dense_doc_embedder", "writer")
indexing_pipeline.run({"dense_doc_embedder": {"documents": documents}})

print("Number of documents:", document_store.count_documents())
Calculating embeddings: 100%|██████████| 1/1 [00:01<00:00,  1.15s/it]


Number of documents: 3

Crear la cadena de recuperación

Crear una canalización de recuperación que recupere documentos del almacén de documentos Milvus utilizando MilvusHybridRetriever, que contiene el document_store y recibe parámetros sobre la búsqueda híbrida.

# from pymilvus import WeightedRanker
retrieval_pipeline = Pipeline()
retrieval_pipeline.add_component("dense_text_embedder", OpenAITextEmbedder())
retrieval_pipeline.add_component(
    "retriever",
    MilvusHybridRetriever(
        document_store=document_store,
        # top_k=3,
        # reranker=WeightedRanker(0.5, 0.5),  # Default is RRFRanker()
    ),
)

retrieval_pipeline.connect("dense_text_embedder.embedding", "retriever.query_embedding")
<haystack.core.pipeline.pipeline.Pipeline object at 0x3383ad990>
🚅 Components
  - dense_text_embedder: OpenAITextEmbedder
  - retriever: MilvusHybridRetriever
🛤️ Connections
  - dense_text_embedder.embedding -> retriever.query_embedding (List[float])

Al realizar la búsqueda híbrida utilizando MilvusHybridRetriever, podemos establecer opcionalmente los parámetros topK y reranker. Se encargará automáticamente de las incrustaciones vectoriales y las funciones incorporadas y, por último, utilizará un reranker para refinar los resultados. Los detalles de implementación subyacentes del proceso de búsqueda se ocultan al usuario.

Para más información sobre la búsqueda híbrida, puede consultar la introducción a la búsqueda híbrida.

question = "Who likes swimming?"

retrieval_results = retrieval_pipeline.run(
    {
        "dense_text_embedder": {"text": question},
        "retriever": {"query_text": question},
    }
)

retrieval_results["retriever"]["documents"][0]
Document(id=bd334348dd2087c785e99b5a0009f33d9b8b8198736f6415df5d92602d81fd3e, content: 'Bob likes swimming', meta: {'category': 'sport'}, score: 0.032786883413791656, embedding: vector of size 1536)

Personalizar el analizador

Los analizadores son esenciales en la búsqueda de texto completo, ya que descomponen la frase en tokens y realizan análisis léxicos como la separación de palabras y la eliminación de palabras vacías. Los analizadores suelen ser específicos de cada idioma. Puede consultar esta guía para obtener más información sobre los analizadores en Milvus.

Milvus admite dos tipos de analizadores: Analizadores incorporados y Analizadores personalizados. Por defecto, BM25BuiltInFunction utilizará el analizador incorporado estándar, que es el analizador más básico que tokeniza el texto con puntuación.

Si desea utilizar un analizador diferente o personalizar el analizador, puede pasar el parámetro analyzer_params en la inicialización de BM25BuiltInFunction.

analyzer_params_custom = {
    "tokenizer": "standard",
    "filter": [
        "lowercase",  # Built-in filter
        {"type": "length", "max": 40},  # Custom filter
        {"type": "stop", "stop_words": ["of", "to"]},  # Custom filter
    ],
}

document_store = MilvusDocumentStore(
    connection_args=connection_args,
    vector_field="vector",
    sparse_vector_field="sparse_vector",
    text_field="text",
    builtin_function=[
        BM25BuiltInFunction(
            input_field_names="text",
            output_field_names="sparse_vector",
            analyzer_params=analyzer_params_custom,  # Custom analyzer parameters.
            enable_match=True,  # Whether to enable match.
        )
    ],
    consistency_level="Bounded",
    drop_old=True,
)

# write documents to the document store
writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.NONE)
indexing_pipeline = Pipeline()
indexing_pipeline.add_component("dense_doc_embedder", OpenAIDocumentEmbedder())
indexing_pipeline.add_component("writer", writer)
indexing_pipeline.connect("dense_doc_embedder", "writer")
indexing_pipeline.run({"dense_doc_embedder": {"documents": documents}})
Calculating embeddings: 100%|██████████| 1/1 [00:00<00:00,  1.39it/s]





{'dense_doc_embedder': {'meta': {'model': 'text-embedding-ada-002-v2',
   'usage': {'prompt_tokens': 11, 'total_tokens': 11}}},
 'writer': {'documents_written': 3}}

Podemos echar un vistazo al esquema de la colección Milvus y asegurarnos de que el analizador personalizado está configurado correctamente.

document_store.col.schema
{'auto_id': False, 'description': '', 'fields': [{'name': 'text', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 65535, 'enable_match': True, 'enable_analyzer': True, 'analyzer_params': {'tokenizer': 'standard', 'filter': ['lowercase', {'type': 'length', 'max': 40}, {'type': 'stop', 'stop_words': ['of', 'to']}]}}}, {'name': 'id', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 65535}, 'is_primary': True, 'auto_id': False}, {'name': 'vector', 'description': '', 'type': <DataType.FLOAT_VECTOR: 101>, 'params': {'dim': 1536}}, {'name': 'sparse_vector', 'description': '', 'type': <DataType.SPARSE_FLOAT_VECTOR: 104>, 'is_function_output': True}], 'enable_dynamic_field': True, 'functions': [{'name': 'bm25_function_7b6e15a4', 'description': '', 'type': <FunctionType.BM25: 1>, 'input_field_names': ['text'], 'output_field_names': ['sparse_vector'], 'params': {}}]}

Para más detalles sobre los conceptos, por ejemplo, analyzer, tokenizer, filter, enable_match, analyzer_params, consulte la documentación del analizador.

Uso de la búsqueda híbrida en la canalización RAG

Hemos aprendido a utilizar la función básica BM25 incorporada en Haystack y Milvus y hemos preparado un document_store cargado. Vamos a introducir una implementación optimizada de RAG con búsqueda híbrida.

Este diagrama muestra el proceso Hybrid Retrieve & Reranking, que combina BM25 para la concordancia de palabras clave y la búsqueda vectorial densa para la recuperación semántica. Los resultados de ambos métodos se combinan, se reordenan y se pasan a un LLM para generar la respuesta final.

La búsqueda híbrida equilibra la precisión y la comprensión semántica, mejorando la exactitud y la solidez para diversas consultas. Recupera candidatos con la búsqueda de texto completo BM25 y la búsqueda vectorial, garantizando una recuperación semántica, precisa y consciente del contexto.

Probemos una implementación optimizada de GAR con búsqueda híbrida.

prompt_template = """Answer the following query based on the provided context. If the context does
                     not include an answer, reply with 'I don't know'.\n
                     Query: {{query}}
                     Documents:
                     {% for doc in documents %}
                        {{ doc.content }}
                     {% endfor %}
                     Answer:
                  """

rag_pipeline = Pipeline()
rag_pipeline.add_component("text_embedder", OpenAITextEmbedder())
rag_pipeline.add_component(
    "retriever", MilvusHybridRetriever(document_store=document_store, top_k=1)
)
rag_pipeline.add_component("prompt_builder", PromptBuilder(template=prompt_template))
rag_pipeline.add_component(
    "generator",
    OpenAIGenerator(
        api_key=Secret.from_token(os.getenv("OPENAI_API_KEY")),
        generation_kwargs={"temperature": 0},
    ),
)
rag_pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
rag_pipeline.connect("retriever.documents", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder", "generator")

results = rag_pipeline.run(
    {
        "text_embedder": {"text": question},
        "retriever": {"query_text": question},
        "prompt_builder": {"query": question},
    }
)
print("RAG answer:", results["generator"]["replies"][0])
RAG answer: Bob likes swimming.

Para obtener más información sobre cómo utilizar milvus-haystack, consulte el repositorio oficial de milvus-haystack.