Open In Colab GitHub Repository

Volltextsuche mit Milvus und Haystack

DieVolltextsuche ist eine traditionelle Methode zum Auffinden von Dokumenten durch die Suche nach bestimmten Schlüsselwörtern oder Phrasen im Text. Sie ordnet die Ergebnisse auf der Grundlage von Relevanzwerten ein, die aus Faktoren wie der Häufigkeit von Begriffen berechnet werden. Während die semantische Suche besser in der Lage ist, die Bedeutung und den Kontext zu verstehen, zeichnet sich die Volltextsuche durch einen präzisen Abgleich von Schlüsselwörtern aus, was sie zu einer nützlichen Ergänzung der semantischen Suche macht. Der BM25-Algorithmus wird häufig für das Ranking in der Volltextsuche verwendet und spielt eine Schlüsselrolle bei der Retrieval-Augmented Generation (RAG).

Milvus 2.5 führt native Volltextsuchfunktionen unter Verwendung von BM25 ein. Dieser Ansatz wandelt Text in spärliche Vektoren um, die BM25-Scores darstellen. Sie können einfach Rohtext eingeben und Milvus generiert und speichert die Sparse-Vektoren automatisch, ohne dass eine manuelle Sparse-Einbettung erforderlich ist.

Haystack unterstützt jetzt diese Milvus-Funktion und macht es einfach, RAG-Anwendungen mit einer Volltextsuche zu versehen. Sie können die Volltextsuche mit der semantischen Suche in dichten Vektoren kombinieren, um einen hybriden Ansatz zu erhalten, der sowohl vom semantischen Verständnis als auch von der Präzision der Schlüsselwortsuche profitiert. Diese Kombination verbessert die Suchgenauigkeit und liefert dem Benutzer bessere Ergebnisse.

Dieses Tutorial zeigt Ihnen, wie Sie mit Haystack und Milvus eine Volltext- und eine hybride Suche in Ihrer Anwendung implementieren können.

Um den Milvus-Vektorspeicher zu verwenden, geben Sie Ihren Milvus-Server URI (und optional mit TOKEN) an. Um einen Milvus-Server zu starten, können Sie einen Milvus-Server einrichten, indem Sie die Milvus-Installationsanleitung befolgen oder einfach Zilliz Cloud(voll verwaltetes Milvus) kostenlos ausprobieren.

  • Die Volltextsuche ist derzeit in Milvus Standalone, Milvus Distributed und Zilliz Cloud verfügbar, obwohl sie in Milvus Lite noch nicht unterstützt wird (diese Funktion ist für eine zukünftige Implementierung geplant). Wenden Sie sich für weitere Informationen an support@zilliz.com.
  • Bevor Sie mit diesem Tutorial fortfahren, stellen Sie sicher, dass Sie ein grundlegendes Verständnis der Volltextsuche und der grundlegenden Nutzung der Haystack Milvus Integration haben.

Voraussetzungen

Bevor Sie dieses Notebook ausführen, stellen Sie sicher, dass Sie die folgenden Abhängigkeiten installiert haben:

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

Wenn Sie Google Colab verwenden, müssen Sie möglicherweise die Runtime neu starten, um die soeben installierten Abhängigkeiten zu aktivieren (klicken Sie auf das Menü "Runtime" am oberen Rand des Bildschirms und wählen Sie "Restart session" aus dem Dropdown-Menü).

Wir werden die Modelle von OpenAI verwenden. Sie sollten den Api-Schlüssel OPENAI_API_KEY als Umgebungsvariable vorbereiten.

import os

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

Bereiten Sie die Daten vor

Importieren Sie die notwendigen Pakete in dieses Notebook. Bereiten Sie dann einige Beispieldokumente vor.

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"}),
]

Die Integration der Volltextsuche in ein RAG-System schafft ein Gleichgewicht zwischen semantischer Suche und präzisem und vorhersagbarem schlagwortbasiertem Retrieval. Sie können sich auch dafür entscheiden, nur die Volltextsuche zu verwenden, obwohl es empfehlenswert ist, die Volltextsuche mit der semantischen Suche zu kombinieren, um bessere Suchergebnisse zu erzielen. Zu Demonstrationszwecken zeigen wir hier die Volltextsuche allein und die hybride Suche.

BM25-Suche ohne Einbettung

Erstellen der Indizierungspipeline

Für die Volltextsuche akzeptiert Milvus MilvusDocumentStore einen builtin_function Parameter. Über diesen Parameter können Sie eine Instanz von BM25BuiltInFunction übergeben, die den BM25-Algorithmus auf der Milvus-Server-Seite implementiert. Setzen Sie die als BM25-Funktionsinstanz angegebene builtin_function. Ein Beispiel:

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

Für die connection_args:

  • Sie können einen leistungsfähigeren Milvus-Server auf Docker oder Kubernetes einrichten. In diesem Setup verwenden Sie bitte die Serveradresse, z.B.http://localhost:19530, als Ihre uri.
  • Wenn Sie Zilliz Cloud, den vollständig verwalteten Cloud-Service für Milvus, verwenden möchten, passen Sie uri und token an, die dem öffentlichen Endpunkt und dem Api-Schlüssel in Zilliz Cloud entsprechen.

Erstellen Sie eine Indizierungspipeline, um Dokumente in den Milvus-Dokumentenspeicher zu schreiben.

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

Erstellen Sie die Abruf-Pipeline

Erstellen Sie eine Retrieval-Pipeline, die Dokumente aus dem Milvus-Dokumentenspeicher abruft, indem Sie MilvusSparseEmbeddingRetriever verwenden, das ein Wrapper um document_store ist.

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)

Erstellen der Indizierungspipeline

Bei der hybriden Suche verwenden wir die Funktion BM25, um eine Volltextsuche durchzuführen, und geben das dichte Vektorfeld vector an, um eine semantische Suche durchzuführen.

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

Erstellen Sie eine Indizierungspipeline, die die Dokumente in Einbettungen umwandelt. Die Dokumente werden dann in den Milvus-Dokumentenspeicher geschrieben.

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

Erstellen Sie die Abruf-Pipeline

Erstellen Sie eine Retrieval-Pipeline, die Dokumente aus dem Milvus-Dokumentenspeicher abruft, indem sie MilvusHybridRetriever verwendet, die document_store enthält und Parameter für die hybride Suche empfängt.

# 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])

Wenn Sie die hybride Suche mit MilvusHybridRetriever durchführen, können Sie optional die Parameter topK und reranker einstellen. Es werden automatisch die Vektoreinbettungen und die eingebauten Funktionen verarbeitet und schließlich ein Reranker zur Verfeinerung der Ergebnisse verwendet. Die zugrundeliegenden Implementierungsdetails des Suchprozesses sind für den Benutzer nicht sichtbar.

Weitere Informationen zur hybriden Suche finden Sie in der Einführung zur hybriden Suche.

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)

Anpassen des Analyzers

Analyzer sind bei der Volltextsuche unerlässlich, da sie den Satz in Token zerlegen und lexikalische Analysen wie Stemming und Stoppwortentfernung durchführen. Analyzer sind in der Regel sprachspezifisch. In diesem Leitfaden erfahren Sie mehr über Analysatoren in Milvus.

Milvus unterstützt zwei Arten von Analysatoren: Eingebaute Analyzer und benutzerdefinierte Analyzer. Standardmäßig verwendet BM25BuiltInFunction den standardmäßig eingebauten Analysator, der der einfachste Analysator ist, der den Text mit Interpunktion tokenisiert.

Wenn Sie einen anderen Analyzer verwenden oder den Analyzer anpassen möchten, können Sie den Parameter analyzer_params in der Initialisierung von BM25BuiltInFunction übergeben.

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

Wir können einen Blick auf das Schema der Milvus-Sammlung werfen und sicherstellen, dass der angepasste Analyzer korrekt eingerichtet ist.

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': {}}]}

Weitere Details zu den Konzepten, z.B. analyzer, tokenizer, filter, enable_match, analyzer_params, finden Sie in der Dokumentation des Analyzers.

Verwendung der hybriden Suche in der RAG-Pipeline

Wir haben gelernt, wie man die grundlegende BM25-Build-in-Funktion in Haystack und Milvus verwendet und ein geladenes document_store vorbereitet. Nun wollen wir eine optimierte RAG-Implementierung mit hybrider Suche vorstellen.

Dieses Diagramm zeigt den Hybrid Retrieve & Reranking Prozess, der BM25 für das Keyword Matching und die dichte Vektorsuche für das semantische Retrieval kombiniert. Die Ergebnisse beider Methoden werden zusammengeführt, neu eingestuft und an einen LLM weitergeleitet, um die endgültige Antwort zu generieren.

Die hybride Suche sorgt für ein Gleichgewicht zwischen Präzision und semantischem Verständnis und verbessert die Genauigkeit und Robustheit bei verschiedenen Abfragen. Sie ruft Kandidaten mit der BM25-Volltextsuche und der Vektorsuche ab und gewährleistet eine semantische, kontextbewusste und genaue Suche.

Probieren wir eine optimierte RAG-Implementierung mit hybrider Suche aus.

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.

Weitere Informationen über die Verwendung von milvus-haystack finden Sie im offiziellen milvus-haystack-Repository.