🚀 Coba Zilliz Cloud, Milvus yang sepenuhnya terkelola, secara gratis—rasakan performa 10x lebih cepat! Coba Sekarang>>

milvus-logo
LFAI
Beranda
  • Integrasi
  • Home
  • Docs
  • Integrasi

  • Orkestrasi

  • LangChain

  • Pencarian Hibrida

Anjing Pelacak Hibrida Milvus

Ikhtisar

Pencarian hibrida menggabungkan kekuatan paradigma pencarian yang berbeda untuk meningkatkan akurasi dan ketangguhan pencarian. Pencarian ini memanfaatkan kemampuan pencarian vektor padat dan pencarian vektor jarang, serta kombinasi beberapa strategi pencarian vektor padat, untuk memastikan pengambilan yang komprehensif dan tepat untuk beragam kueri.

Diagram ini mengilustrasikan skenario pencarian hibrida yang paling umum, yaitu pencarian hibrida padat + jarang. Dalam hal ini, kandidat diambil menggunakan kesamaan vektor semantik dan pencocokan kata kunci yang tepat. Hasil dari metode-metode ini digabungkan, diurutkan ulang, dan diteruskan ke LLM untuk menghasilkan jawaban akhir. Pendekatan ini menyeimbangkan ketepatan dan pemahaman semantik, sehingga sangat efektif untuk skenario kueri yang beragam.

Selain pencarian hibrida padat + jarang, strategi hibrida juga dapat menggabungkan beberapa model vektor padat. Misalnya, satu model vektor padat mungkin mengkhususkan diri dalam menangkap nuansa semantik, sementara yang lain berfokus pada penyematan kontekstual atau representasi spesifik domain. Dengan menggabungkan hasil dari model-model ini dan memberi peringkat ulang, jenis pencarian hibrida ini memastikan proses pencarian yang lebih bernuansa dan sadar konteks.

Integrasi LangChain Milvus menyediakan cara yang fleksibel untuk mengimplementasikan pencarian hibrida, mendukung sejumlah bidang vektor, dan model penyematan padat atau jarang, yang memungkinkan LangChain Milvus secara fleksibel beradaptasi dengan berbagai skenario penggunaan pencarian hibrida, dan pada saat yang sama kompatibel dengan kemampuan LangChain lainnya.

Dalam tutorial ini, kita akan mulai dengan kasus padat + jarang yang paling umum, dan kemudian memperkenalkan sejumlah pendekatan penggunaan pencarian hibrida secara umum.

MilvusCollectionHybridSearchRetriever, yang merupakan implementasi lain dari pencarian hibrida dengan Milvus dan LangChain, akan segera ditinggalkan. Silakan gunakan pendekatan dalam dokumen ini untuk mengimplementasikan pencarian hibrida karena lebih fleksibel dan kompatibel dengan LangChain.

Prasyarat

Sebelum menjalankan notebook ini, pastikan Anda telah menginstal dependensi berikut ini:

$ pip install --upgrade --quiet  langchain langchain-core langchain-community langchain-text-splitters langchain-milvus langchain-openai bs4 pymilvus[model] #langchain-voyageai

Jika Anda menggunakan Google Colab, untuk mengaktifkan dependensi yang baru saja terinstal, Anda mungkin perlu memulai ulang runtime (klik menu "Runtime" di bagian atas layar, dan pilih "Restart session" dari menu tarik-turun).

Kita akan menggunakan model dari OpenAI. Anda harus menyiapkan variabel lingkungan OPENAI_API_KEY dari OpenAI.

import os

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

Tentukan server Milvus Anda URI (dan secara opsional TOKEN). Untuk cara menginstal dan menjalankan server Milvus, ikuti panduan berikut.

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

Siapkan beberapa dokumen contoh, yang merupakan ringkasan cerita fiksi yang dikategorikan berdasarkan tema atau genre.

from langchain_core.documents import Document

docs = [
    Document(
        page_content="In 'The Whispering Walls' by Ava Moreno, a young journalist named Sophia uncovers a decades-old conspiracy hidden within the crumbling walls of an ancient mansion, where the whispers of the past threaten to destroy her own sanity.",
        metadata={"category": "Mystery"},
    ),
    Document(
        page_content="In 'The Last Refuge' by Ethan Blackwood, a group of survivors must band together to escape a post-apocalyptic wasteland, where the last remnants of humanity cling to life in a desperate bid for survival.",
        metadata={"category": "Post-Apocalyptic"},
    ),
    Document(
        page_content="In 'The Memory Thief' by Lila Rose, a charismatic thief with the ability to steal and manipulate memories is hired by a mysterious client to pull off a daring heist, but soon finds themselves trapped in a web of deceit and betrayal.",
        metadata={"category": "Heist/Thriller"},
    ),
    Document(
        page_content="In 'The City of Echoes' by Julian Saint Clair, a brilliant detective must navigate a labyrinthine metropolis where time is currency, and the rich can live forever, but at a terrible cost to the poor.",
        metadata={"category": "Science Fiction"},
    ),
    Document(
        page_content="In 'The Starlight Serenade' by Ruby Flynn, a shy astronomer discovers a mysterious melody emanating from a distant star, which leads her on a journey to uncover the secrets of the universe and her own heart.",
        metadata={"category": "Science Fiction/Romance"},
    ),
    Document(
        page_content="In 'The Shadow Weaver' by Piper Redding, a young orphan discovers she has the ability to weave powerful illusions, but soon finds herself at the center of a deadly game of cat and mouse between rival factions vying for control of the mystical arts.",
        metadata={"category": "Fantasy"},
    ),
    Document(
        page_content="In 'The Lost Expedition' by Caspian Grey, a team of explorers ventures into the heart of the Amazon rainforest in search of a lost city, but soon finds themselves hunted by a ruthless treasure hunter and the treacherous jungle itself.",
        metadata={"category": "Adventure"},
    ),
    Document(
        page_content="In 'The Clockwork Kingdom' by Augusta Wynter, a brilliant inventor discovers a hidden world of clockwork machines and ancient magic, where a rebellion is brewing against the tyrannical ruler of the land.",
        metadata={"category": "Steampunk/Fantasy"},
    ),
    Document(
        page_content="In 'The Phantom Pilgrim' by Rowan Welles, a charismatic smuggler is hired by a mysterious organization to transport a valuable artifact across a war-torn continent, but soon finds themselves pursued by deadly assassins and rival factions.",
        metadata={"category": "Adventure/Thriller"},
    ),
    Document(
        page_content="In 'The Dreamwalker's Journey' by Lyra Snow, a young dreamwalker discovers she has the ability to enter people's dreams, but soon finds herself trapped in a surreal world of nightmares and illusions, where the boundaries between reality and fantasy blur.",
        metadata={"category": "Fantasy"},
    ),
]

Penyematan padat + Penyematan jarang

Opsi 1 (Direkomendasikan): penyematan padat + fungsi bawaan Milvus BM25

Gunakan penyematan padat + fungsi bawaan Milvus BM25 untuk menyusun instance penyimpanan vektor pengambilan hibrida.

from langchain_milvus import Milvus, BM25BuiltInFunction
from langchain_openai import OpenAIEmbeddings


vectorstore = Milvus.from_documents(
    documents=docs,
    embedding=OpenAIEmbeddings(),
    builtin_function=BM25BuiltInFunction(),  # output_field_names="sparse"),
    vector_field=["dense", "sparse"],
    connection_args={
        "uri": URI,
    },
    consistency_level="Strong",
    drop_old=True,
)
  • Ketika Anda menggunakan BM25BuiltInFunction, harap dicatat bahwa pencarian teks lengkap tersedia di Milvus Standalone dan Milvus Distributed, tetapi tidak di Milvus Lite, meskipun ada di peta jalan untuk penyertaan di masa mendatang. Fitur ini juga akan segera tersedia di Zilliz Cloud (Milvus yang dikelola sepenuhnya). Silakan hubungi support@zilliz.com untuk informasi lebih lanjut.

Pada kode di atas, kita mendefinisikan sebuah instance dari BM25BuiltInFunction dan mengopernya ke objek Milvus. BM25BuiltInFunction adalah kelas pembungkus yang ringan untuk Function ringan di Milvus. Kita dapat menggunakannya dengan OpenAIEmbeddings untuk menginisialisasi instance penyimpanan vektor Milvus pencarian hibrida padat + jarang.

BM25BuiltInFunction tidak mengharuskan klien untuk memberikan korpus atau pelatihan, semua secara otomatis diproses di server Milvus, sehingga pengguna tidak perlu peduli dengan kosakata dan korpus apa pun. Selain itu, pengguna juga dapat menyesuaikan penganalisis untuk mengimplementasikan pemrosesan teks khusus di BM25.

Untuk informasi lebih lanjut tentang BM25BuiltInFunction, silakan lihat Pencarian Teks Lengkap dan Menggunakan Pencarian Teks Lengkap dengan LangChain dan Milvus.

Opsi 2: Gunakan penyematan jarang LangChain yang padat dan disesuaikan

Anda dapat mewarisi kelas BaseSparseEmbedding dari langchain_milvus.utils.sparse, dan mengimplementasikan metode embed_query dan embed_documents untuk menyesuaikan proses sematan jarang. Hal ini memungkinkan Anda untuk menyesuaikan metode penyematan jarang apa pun baik berdasarkan statistik frekuensi term (misalnya BM25) atau jaringan saraf (misalnya SPADE).

Berikut ini adalah sebuah contoh:

from typing import Dict, List
from langchain_milvus.utils.sparse import BaseSparseEmbedding


class MyCustomEmbedding(BaseSparseEmbedding):  # inherit from BaseSparseEmbedding
    def __init__(self, model_path): ...  # code to init or load model

    def embed_query(self, query: str) -> Dict[int, float]:
        ...  # code to embed query
        return {  # fake embedding result
            1: 0.1,
            2: 0.2,
            3: 0.3,
            # ...
        }

    def embed_documents(self, texts: List[str]) -> List[Dict[int, float]]:
        ...  # code to embed documents
        return [  # fake embedding results
            {
                1: 0.1,
                2: 0.2,
                3: 0.3,
                # ...
            }
        ] * len(texts)

Kami memiliki kelas demo BM25SparseEmbedding yang diwarisi dari BaseSparseEmbedding di langchain_milvus.utils.sparse. Anda dapat meneruskannya ke dalam daftar penyematan inisialisasi dari instance penyimpanan vektor Milvus seperti kelas penyematan padat langchain lainnya.

# BM25SparseEmbedding is inherited from BaseSparseEmbedding
from langchain_milvus.utils.sparse import BM25SparseEmbedding

embedding1 = OpenAIEmbeddings()

corpus = [doc.page_content for doc in docs]
embedding2 = BM25SparseEmbedding(
    corpus=corpus
)  # pass in corpus to initialize the statistics

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

Meskipun ini adalah cara untuk menggunakan BM25, ini mengharuskan pengguna untuk mengelola korpus untuk statistik frekuensi term. Kami merekomendasikan untuk menggunakan fungsi bawaan BM25 (Opsi 1) sebagai gantinya, karena fungsi ini menangani segala sesuatu di sisi server Milvus. Hal ini menghilangkan kebutuhan pengguna untuk khawatir tentang mengelola korpus atau melatih kosakata. Untuk informasi lebih lanjut, silakan lihat Menggunakan Pencarian Teks Lengkap dengan LangChain dan Milvus.

Menentukan beberapa bidang vektor sembarang

Ketika menginisialisasi penyimpanan vektor Milvus, Anda dapat memasukkan daftar penyematan (dan juga daftar fungsi bawaan di masa mendatang) untuk mengimplementasikan pencarian ulang multi-arah, dan kemudian memberi peringkat ulang pada kandidat-kandidat ini:

# from langchain_voyageai import VoyageAIEmbeddings

embedding1 = OpenAIEmbeddings(model="text-embedding-ada-002")
embedding2 = OpenAIEmbeddings(model="text-embedding-3-large")
# embedding3 = 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],  # embedding3],
    builtin_function=BM25BuiltInFunction(output_field_names="sparse"),
    # `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']

Dalam contoh ini, kita memiliki tiga bidang vektor. Diantaranya, sparse digunakan sebagai bidang keluaran untuk BM25BuiltInFunction, sedangkan dua lainnya, dense1 dan dense2, secara otomatis ditetapkan sebagai bidang keluaran untuk dua model OpenAIEmbeddings (berdasarkan urutan).

Menentukan parameter indeks untuk bidang multi-vektor

Secara default, jenis indeks setiap bidang vektor akan secara otomatis ditentukan oleh jenis penyematan atau fungsi bawaan. Namun, Anda juga dapat menentukan jenis indeks untuk setiap bidang vektor untuk mengoptimalkan kinerja pencarian.

dense_index_param_1 = {
    "metric_type": "COSINE",
    "index_type": "HNSW",
}
dense_index_param_2 = {
    "metric_type": "IP",
    "index_type": "HNSW",
}
sparse_index_param = {
    "metric_type": "BM25",
    "index_type": "AUTOINDEX",
}

vectorstore = Milvus.from_documents(
    documents=docs,
    embedding=[embedding1, embedding2],
    builtin_function=BM25BuiltInFunction(output_field_names="sparse"),
    index_params=[dense_index_param_1, dense_index_param_2, sparse_index_param],
    vector_field=["dense1", "dense2", "sparse"],
    connection_args={
        "uri": URI,
    },
    consistency_level="Strong",
    drop_old=True,
)

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

Harap jaga agar urutan daftar parameter indeks tetap konsisten dengan urutan vectorstore.vector_fields untuk menghindari kebingungan.

Beri peringkat ulang kandidat

Setelah tahap pertama pencarian, kita perlu memberi peringkat ulang pada kandidat untuk mendapatkan hasil yang lebih baik. Anda dapat memilih WeightedRanker atau RRFRanker tergantung pada kebutuhan Anda. Anda dapat merujuk ke Perangkingan Ulang untuk informasi lebih lanjut.

Berikut adalah contoh untuk perangkingan ulang tertimbang:

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

query = "What are the novels Lila has written and what are their contents?"

vectorstore.similarity_search(
    query, k=1, ranker_type="weighted", ranker_params={"weights": [0.6, 0.4]}
)
[Document(metadata={'pk': 454646931479252186, 'category': 'Heist/Thriller'}, page_content="In 'The Memory Thief' by Lila Rose, a charismatic thief with the ability to steal and manipulate memories is hired by a mysterious client to pull off a daring heist, but soon finds themselves trapped in a web of deceit and betrayal.")]

Berikut adalah contoh perankingan ulang RRF:

vectorstore.similarity_search(query, k=1, ranker_type="rrf", ranker_params={"k": 100})
[Document(metadata={'category': 'Heist/Thriller', 'pk': 454646931479252186}, page_content="In 'The Memory Thief' by Lila Rose, a charismatic thief with the ability to steal and manipulate memories is hired by a mysterious client to pull off a daring heist, but soon finds themselves trapped in a web of deceit and betrayal.")]

Jika Anda tidak memberikan parameter apa pun tentang perankingan ulang, strategi perankingan ulang tertimbang rata-rata akan digunakan secara default.

Menggunakan Pencarian Hibrida dan Perangkingan Ulang di RAG

Dalam skenario RAG, pendekatan yang paling umum untuk pencarian hibrida adalah pencarian padat + jarang, diikuti dengan perankingan ulang. Contoh berikut ini menunjukkan kode ujung-ke-ujung yang mudah.

Siapkan data

Kami menggunakan Langchain WebBaseLoader untuk memuat dokumen dari sumber web dan membaginya menjadi beberapa bagian menggunakan RecursiveCharacterTextSplitter.

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

Memuat dokumen ke dalam penyimpanan vektor Milvus

Seperti pengantar di atas, kita menginisialisasi dan memuat dokumen yang telah disiapkan ke dalam penyimpanan vektor Milvus, yang berisi dua bidang vektor: dense untuk penyematan OpenAI dan sparse untuk fungsi BM25.

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

Membangun rantai RAG

Kami menyiapkan instance dan prompt LLM, lalu menggabungkannya ke dalam pipeline RAG menggunakan Bahasa Ekspresi LangChain.

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)

Gunakan LCEL (LangChain Expression Language) untuk membangun rantai RAG.

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

Panggil rantai RAG dengan pertanyaan spesifik dan ambil jawabannya

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 perform these tasks.'

Selamat! Anda telah membangun rantai RAG pencarian hibrida (vektor padat + fungsi bm25 yang jarang) yang diberdayakan oleh Milvus dan LangChain.

Coba Milvus yang Dikelola secara Gratis

Zilliz Cloud bebas masalah, didukung oleh Milvus dan 10x lebih cepat.

Mulai
Umpan balik

Apakah halaman ini bermanfaat?