🚀 Testen Sie Zilliz Cloud, die vollständig verwaltete Milvus, kostenlos – erleben Sie 10x schnellere Leistung! Jetzt testen>>

milvus-logo
LFAI
Home
  • Integrationen
  • Home
  • Docs
  • Integrationen

  • Orchestrierung

  • LangChain

  • Volltextsuche

Verwendung der Volltextsuche mit LangChain und Milvus

Open In Colab GitHub Repository

DieVolltextsuche ist eine traditionelle Methode zum Auffinden von Dokumenten, die bestimmte Begriffe oder Phrasen enthalten, indem Schlüsselwörter direkt im Text gesucht werden. Die Ergebnisse werden auf der Grundlage der Relevanz eingestuft, die in der Regel durch Faktoren wie Begriffshäufigkeit und -nähe bestimmt wird. Während sich die semantische Suche durch ihr Verständnis von Absicht und Kontext auszeichnet, bietet die Volltextsuche Präzision beim exakten Abgleich von Schlüsselwörtern, was sie zu einem wertvollen ergänzenden Instrument macht. Der BM25-Algorithmus ist eine beliebte Ranking-Methode für die Volltextsuche, die besonders bei der Retrieval-Augmented Generation (RAG) nützlich ist.

Seit Milvus 2.5 wird die Volltextsuche nativ durch den Sparse-BM25-Ansatz unterstützt, indem der BM25-Algorithmus als Sparse-Vektoren dargestellt wird. Milvus akzeptiert Rohtext als Eingabe und konvertiert ihn automatisch in Sparse-Vektoren, die in einem bestimmten Feld gespeichert werden, wodurch die Notwendigkeit einer manuellen Erzeugung von Sparse Embedding entfällt.

Die Integration von LangChain in Milvus hat diese Funktion ebenfalls eingeführt und vereinfacht den Prozess der Integration von Volltextsuche in RAG-Anwendungen. Durch die Kombination von Volltextsuche und semantischer Suche mit dichten Vektoren können Sie einen hybriden Ansatz erreichen, der sowohl den semantischen Kontext aus dichten Einbettungen als auch die präzise Schlüsselwortrelevanz aus dem Wortabgleich nutzt. Diese Integration verbessert die Genauigkeit, Relevanz und Benutzerfreundlichkeit von Suchsystemen.

Dieses Tutorial zeigt, wie Sie LangChain und Milvus verwenden, um eine Volltextsuche in Ihrer Anwendung zu implementieren.

  • Die Volltextsuche ist in Milvus Standalone und Milvus Distributed verfügbar, aber nicht in Milvus Lite, obwohl sie auf der Roadmap für eine zukünftige Aufnahme steht. In Kürze wird sie auch in Zilliz Cloud (vollständig verwaltetes Milvus) verfügbar sein. Bitte 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 LangChain-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  langchain langchain-core langchain-community langchain-text-splitters langchain-milvus langchain-openai bs4 #langchain-voyageai

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 die Umgebungsvariablen OPENAI_API_KEY von OpenAI vorbereiten.

import os

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

Geben Sie Ihren Milvus-Server URI (und optional die TOKEN) an. Wie Sie den Milvus-Server installieren und starten, erfahren Sie in dieser Anleitung.

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

Bereiten Sie einige Beispieldokumente vor:

from langchain_core.documents import Document

docs = [
    Document(page_content="I like this apple", metadata={"category": "fruit"}),
    Document(page_content="I like swimming", metadata={"category": "sport"}),
    Document(page_content="I like dogs", metadata={"category": "pets"}),
]

Initialisierung mit BM25 Funktion

Für die Volltextsuche akzeptiert Milvus VectorStore einen builtin_function Parameter. Über diesen Parameter können Sie eine Instanz der BM25BuiltInFunction übergeben. Dies unterscheidet sich von der semantischen Suche, bei der normalerweise dichte Einbettungen an die VectorStore übergeben werden,

Hier ist ein einfaches Beispiel für eine hybride Suche in Milvus mit OpenAI dense embedding für die semantische Suche und BM25 für die Volltextsuche:

from langchain_milvus import Milvus, BM25BuiltInFunction
from langchain_openai import OpenAIEmbeddings


vectorstore = Milvus.from_documents(
    documents=docs,
    embedding=OpenAIEmbeddings(),
    builtin_function=BM25BuiltInFunction(),
    # `dense` is for OpenAI embeddings, `sparse` is the output field of BM25 function
    vector_field=["dense", "sparse"],
    connection_args={
        "uri": URI,
    },
    consistency_level="Strong",
    drop_old=True,
)

Im obigen Code definieren wir eine Instanz von BM25BuiltInFunction und übergeben sie an das Milvus Objekt. BM25BuiltInFunction ist eine leichtgewichtige Wrapper-Klasse für Function in Milvus.

Sie können die Eingabe- und Ausgabefelder für diese Funktion in den Parametern von BM25BuiltInFunction angeben:

  • input_field_names (str): Der Name des Eingabefeldes, Standard ist text. Er gibt an, welches Feld diese Funktion als Eingabe liest.
  • output_field_names (str): Der Name des Ausgabefeldes, Standardwert ist sparse. Er gibt an, in welches Feld diese Funktion das berechnete Ergebnis ausgibt.

Beachten Sie, dass wir in den oben erwähnten Initialisierungsparametern von Milvus auch vector_field=["dense", "sparse"] angeben. Da das Feld sparse als das durch BM25BuiltInFunction definierte Ausgabefeld genommen wird, wird das andere Feld dense automatisch dem Ausgabefeld von OpenAIEmbeddings zugewiesen.

In der Praxis, insbesondere bei der Kombination mehrerer Einbettungen oder Funktionen, empfehlen wir, die Eingabe- und Ausgabefelder für jede Funktion explizit anzugeben, um Mehrdeutigkeiten zu vermeiden.

Im folgenden Beispiel geben wir die Eingabe- und Ausgabefelder von BM25BuiltInFunction explizit an, so dass klar ist, für welches Feld die eingebaute Funktion bestimmt ist.

# from langchain_voyageai import VoyageAIEmbeddings

embedding1 = OpenAIEmbeddings(model="text-embedding-ada-002")
embedding2 = OpenAIEmbeddings(model="text-embedding-3-large")
# embedding2 = VoyageAIEmbeddings(model="voyage-3")  # You can also use embedding from other embedding model providers, e.g VoyageAIEmbeddings


vectorstore = Milvus.from_documents(
    documents=docs,
    embedding=[embedding1, embedding2],
    builtin_function=BM25BuiltInFunction(
        input_field_names="text", output_field_names="sparse"
    ),
    text_field="text",  # `text` is the input field name of BM25BuiltInFunction
    # `sparse` is the output field name of BM25BuiltInFunction, and `dense1` and `dense2` are the output field names of embedding1 and embedding2
    vector_field=["dense1", "dense2", "sparse"],
    connection_args={
        "uri": URI,
    },
    consistency_level="Strong",
    drop_old=True,
)

vectorstore.vector_fields
['dense1', 'dense2', 'sparse']

In diesem Beispiel haben wir drei Vektorfelder. Davon wird sparse als Ausgabefeld für BM25BuiltInFunction verwendet, während die beiden anderen, dense1 und dense2, automatisch als Ausgabefelder für die beiden OpenAIEmbeddings -Modelle zugewiesen werden (basierend auf der Reihenfolge).

Auf diese Weise können Sie mehrere Vektorfelder definieren und ihnen verschiedene Kombinationen von Einbettungen oder Funktionen zuweisen, um eine hybride Suche zu implementieren.

Bei der Durchführung der hybriden Suche müssen wir nur den Abfragetext übergeben und optional die Parameter topK und reranker setzen. Die Instanz vectorstore verarbeitet automatisch die Vektoreinbettungen und integrierten Funktionen und verwendet schließlich einen Reranker, um die Ergebnisse zu verfeinern. Die zugrundeliegenden Implementierungsdetails des Suchprozesses sind für den Benutzer nicht sichtbar.

vectorstore.similarity_search(
    "Do I like apples?", k=1
)  # , ranker_type="weighted", ranker_params={"weights":[0.3, 0.3, 0.4]})
[Document(metadata={'category': 'fruit', 'pk': 454646931479251897}, page_content='I like this apple')]

Weitere Informationen zur hybriden Suche finden Sie in der Einführung zur hybriden Suche und in diesem LangChain Milvus Tutorial zur hybriden Suche.

BM25-Suche ohne Einbettung

Wenn Sie nur eine Volltextsuche mit der BM25-Funktion durchführen möchten, ohne eine auf Einbettung basierende semantische Suche zu verwenden, können Sie den Einbettungsparameter auf None setzen und nur die builtin_function als BM25-Funktionsinstanz angeben. Das Vektorfeld hat nur ein "spärliches" Feld. Ein Beispiel:

vectorstore = Milvus.from_documents(
    documents=docs,
    embedding=None,
    builtin_function=BM25BuiltInFunction(
        output_field_names="sparse",
    ),
    vector_field="sparse",
    connection_args={
        "uri": URI,
    },
    consistency_level="Strong",
    drop_old=True,
)

vectorstore.vector_fields
['sparse']

Analyzer anpassen

Analyzer sind für die 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
    ],
}


vectorstore = Milvus.from_documents(
    documents=docs,
    embedding=OpenAIEmbeddings(),
    builtin_function=BM25BuiltInFunction(
        output_field_names="sparse",
        enable_match=True,
        analyzer_params=analyzer_params_custom,
    ),
    vector_field=["dense", "sparse"],
    connection_args={
        "uri": URI,
    },
    consistency_level="Strong",
    drop_old=True,
)

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

vectorstore.col.schema
{'auto_id': True, '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': 'pk', 'description': '', 'type': <DataType.INT64: 5>, 'is_primary': True, 'auto_id': True}, {'name': 'dense', 'description': '', 'type': <DataType.FLOAT_VECTOR: 101>, 'params': {'dim': 1536}}, {'name': 'sparse', 'description': '', 'type': <DataType.SPARSE_FLOAT_VECTOR: 104>, 'is_function_output': True}, {'name': 'category', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 65535}}], 'enable_dynamic_field': False, 'functions': [{'name': 'bm25_function_de368e79', 'description': '', 'type': <FunctionType.BM25: 1>, 'input_field_names': ['text'], 'output_field_names': ['sparse'], 'params': {}}]}

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

Hybride Suche und Reranking in RAG nutzen

Wir haben gelernt, wie man die BM25-Basisfunktion in LangChain und Milvus verwendet. Nun wollen wir eine optimierte RAG-Implementierung mit hybrider Suche und Reranking vorstellen.

Dieses Diagramm zeigt den Hybrid Retrieve & Reranking Prozess, der BM25 für das Keyword Matching und die 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.

Lassen Sie uns mit einem Beispiel beginnen.

Vorbereiten der Daten

Wir verwenden den Langchain WebBaseLoader, um Dokumente aus Webquellen zu laden und sie mit dem RecursiveCharacterTextSplitter in Teile zu zerlegen.

import bs4
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# Create a WebBaseLoader instance to load documents from web sources
loader = WebBaseLoader(
    web_paths=(
        "https://lilianweng.github.io/posts/2023-06-23-agent/",
        "https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/",
    ),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            class_=("post-content", "post-title", "post-header")
        )
    ),
)
# Load documents from web sources using the loader
documents = loader.load()
# Initialize a RecursiveCharacterTextSplitter for splitting text into chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=200)

# Split the documents into chunks using the text_splitter
docs = text_splitter.split_documents(documents)

# Let's take a look at the first document
docs[1]
Document(metadata={'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}, page_content='Fig. 1. Overview of a LLM-powered autonomous agent system.\nComponent One: Planning#\nA complicated task usually involves many steps. An agent needs to know what they are and plan ahead.\nTask Decomposition#\nChain of thought (CoT; Wei et al. 2022) has become a standard prompting technique for enhancing model performance on complex tasks. The model is instructed to “think step by step” to utilize more test-time computation to decompose hard tasks into smaller and simpler steps. CoT transforms big tasks into multiple manageable tasks and shed lights into an interpretation of the model’s thinking process.\nTree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple reasoning possibilities at each step. It first decomposes the problem into multiple thought steps and generates multiple thoughts per step, creating a tree structure. The search process can be BFS (breadth-first search) or DFS (depth-first search) with each state evaluated by a classifier (via a prompt) or majority vote.\nTask decomposition can be done (1) by LLM with simple prompting like "Steps for XYZ.\\n1.", "What are the subgoals for achieving XYZ?", (2) by using task-specific instructions; e.g. "Write a story outline." for writing a novel, or (3) with human inputs.\nAnother quite distinct approach, LLM+P (Liu et al. 2023), involves relying on an external classical planner to do long-horizon planning. This approach utilizes the Planning Domain Definition Language (PDDL) as an intermediate interface to describe the planning problem. In this process, LLM (1) translates the problem into “Problem PDDL”, then (2) requests a classical planner to generate a PDDL plan based on an existing “Domain PDDL”, and finally (3) translates the PDDL plan back into natural language. Essentially, the planning step is outsourced to an external tool, assuming the availability of domain-specific PDDL and a suitable planner which is common in certain robotic setups but not in many other domains.\nSelf-Reflection#')

Laden des Dokuments in den Milvus-Vektorspeicher

Wie oben beschrieben, initialisieren und laden wir die vorbereiteten Dokumente in den Milvus-Vektorspeicher, der zwei Vektorfelder enthält: dense ist für die OpenAI-Einbettung und sparse ist für die BM25-Funktion.

vectorstore = Milvus.from_documents(
    documents=docs,
    embedding=OpenAIEmbeddings(),
    builtin_function=BM25BuiltInFunction(),
    vector_field=["dense", "sparse"],
    connection_args={
        "uri": URI,
    },
    consistency_level="Strong",
    drop_old=True,
)

RAG-Kette aufbauen

Wir bereiten die LLM-Instanz und die Eingabeaufforderung vor und verbinden sie dann mit Hilfe der LangChain Expression Language zu einer RAG-Pipeline.

from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# Initialize the OpenAI language model for response generation
llm = ChatOpenAI(model_name="gpt-4o", temperature=0)

# Define the prompt template for generating AI responses
PROMPT_TEMPLATE = """
Human: You are an AI assistant, and provides answers to questions by using fact based and statistical information when possible.
Use the following pieces of information to provide a concise answer to the question enclosed in <question> tags.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
<context>
{context}
</context>

<question>
{question}
</question>

The response should be specific and use statistics or numbers when possible.

Assistant:"""

# Create a PromptTemplate instance with the defined template and input variables
prompt = PromptTemplate(
    template=PROMPT_TEMPLATE, input_variables=["context", "question"]
)
# Convert the vector store to a retriever
retriever = vectorstore.as_retriever()


# Define a function to format the retrieved documents
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

Verwenden Sie die LCEL (LangChain Expression Language), um eine RAG-Kette zu erstellen.

# Define the RAG (Retrieval-Augmented Generation) chain for AI response generation
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

# rag_chain.get_graph().print_ascii()

Rufen Sie die RAG-Kette mit einer bestimmten Frage auf und rufen Sie die Antwort ab

query = "What is PAL and PoT?"
res = rag_chain.invoke(query)
res
'PAL (Program-aided Language models) and PoT (Program of Thoughts prompting) are approaches that involve using language models to generate programming language statements to solve natural language reasoning problems. This method offloads the solution step to a runtime, such as a Python interpreter, allowing for complex computation and reasoning to be handled externally. PAL and PoT rely on language models with strong coding skills to effectively generate and execute these programming statements.'

Herzlichen Glückwunsch! Sie haben eine hybride (dichte Vektor- + spärliche bm25-Funktion) RAG-Kette auf der Grundlage von Milvus und LangChain erstellt.

Try Managed Milvus for Free

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

Get Started
Feedback

War diese Seite hilfreich?