RAG باستخدام البحث الهجين مع Milvus وLlamaIndex
يستفيد البحث الهجين من نقاط القوة في كلٍ من الاسترجاع الدلالي ومطابقة الكلمات المفتاحية لتقديم نتائج أكثر دقة وذات صلة بالسياق. من خلال الجمع بين مزايا البحث الدلالي ومطابقة الكلمات الرئيسية، يكون البحث الهجين فعالاً بشكل خاص في مهام استرجاع المعلومات المعقدة.
يوضح هذا الدفتر كيفية استخدام Milvus للبحث الهجين في خطوط أنابيب LlamaIndex RAG. سنبدأ بالبحث الهجين الافتراضي الموصى به (البحث الهجين الدلالي + BM25) ثم نستكشف طرق التضمين المتناثرة البديلة الأخرى وتخصيص أداة إعادة الترتيب الهجين.
المتطلبات الأساسية
تثبيت التبعيات
قبل البدء، تأكد من تثبيت التبعيات التالية:
$ pip install llama-index-vector-stores-milvus
$ pip install llama-index-embeddings-openai
$ pip install llama-index-llms-openai
إذا كنت تستخدم Google Colab، فقد تحتاج إلى إعادة تشغيل وقت التشغيل (انتقل إلى قائمة "وقت التشغيل" في أعلى الواجهة، وحدد "إعادة تشغيل الجلسة" من القائمة المنسدلة).
إعداد الحسابات
يستخدم هذا البرنامج التعليمي OpenAI لتضمين النص وتوليد الإجابات. تحتاج إلى إعداد مفتاح OpenAI API.
import openai
openai.api_key = "sk-"
لاستخدام مخزن ناقلات Milvus، حدد خادم Milvus الخاص بك URI (واختيارياً مع TOKEN). لبدء تشغيل خادم Milvus، يمكنك إعداد خادم Milvus باتباع دليل تثبيت Milvus أو ببساطة تجربة Zilliz Cloud مجانًا.
البحث في النص الكامل مدعوم حاليًا في Milvus Standalone وMilvus Distributed وZilliz Cloud، ولكن ليس بعد في Milvus Lite (من المخطط تنفيذه في المستقبل). تواصل مع support@zilliz.com لمزيد من المعلومات.
URI = "http://localhost:19530"
# TOKEN = ""
تحميل بيانات الأمثلة
قم بتشغيل الأوامر التالية لتحميل نماذج المستندات في دليل "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'
ثم استخدم SimpleDirectoryReaderLoad لتحميل مقال "ما عملت عليه" لبول غراهام:
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 ...
البحث الهجين مع BM25
يوضح هذا القسم كيفية إجراء بحث هجين باستخدام BM25. للبدء، سنقوم بتهيئة MilvusVectorStore وإنشاء فهرس لمثال المستندات. يستخدم التكوين الافتراضي:
- التضمينات الكثيفة من نموذج التضمين الافتراضي (OpenAI's
text-embedding-ada-002) - BM25 للبحث في النص الكامل إذا كان تمكين_sparse صحيحًا
- RRFRFRanker مع k=60 لدمج النتائج إذا تم تمكين البحث المختلط
# 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').
فيما يلي مزيد من المعلومات حول وسيطات تكوين الحقول الكثيفة والمتناثرة في MilvusVectorStore:
الحقل الكثيف
enable_dense (bool): علامة منطقية لتمكين أو تعطيل التضمين الكثيف. الإعداد الافتراضي إلى صواب.dim (int, optional): بُعد متجهات التضمين للمجموعة.embedding_field (str, optional): :: اسم حقل التضمين الكثيف للمجموعة، افتراضيًا إلى DEFAULT_EMBEDDING_KEY.index_config (dict, optional): التكوين المستخدم لبناء فهرس التضمين الكثيف. الإعداد الافتراضي إلى لا شيء.search_config (dict, optional): التكوين المستخدم للبحث في فهرس ميلفوس الكثيف. لاحظ أن هذا يجب أن يكون متوافقًا مع نوع الفهرس المحدد بواسطةindex_config. الإعداد الافتراضي إلى لا شيء.similarity_metric (str, optional): مقياس التشابه المستخدم للتضمين الكثيف، يدعم حاليًا IP وCOSINE و L2.
حقل متناثر
enable_sparse (bool): علامة منطقية لتمكين أو تعطيل التضمين المتناثر. الإعداد الافتراضي إلى خطأ.sparse_embedding_field (str): اسم حقل التضمين المتناثر، افتراضيًا إلى DEFAULT_SPARSE_EMBEDDING_KEY.sparse_embedding_function (Union[BaseSparseEmbeddingFunction, BaseMilvusBuiltInFunction], optional): إذا كان enable_sparse صحيحًا، فيجب توفير هذا الكائن لتحويل النص إلى تضمين متناثر. إذا كان لا يوجد، فسيتم استخدام دالة التضمين المتناثر الافتراضية (BM25BuiltInFunction)، أو استخدام BGEM3SparseEmbedding في حالة عدم وجود دالة تضمين متناثر في المجموعة الحالية بدون دوال مدمجة.sparse_index_config (dict, optional): التكوين المستخدم لبناء فهرس التضمين المتناثر. الإعداد الافتراضي إلى لا شيء.
لتمكين البحث الهجين أثناء مرحلة الاستعلام، اضبط vector_store_query_mode على "هجين". سيؤدي ذلك إلى دمج نتائج البحث وإعادة ترتيبها من كل من البحث الدلالي والبحث بالنص الكامل. دعنا نختبر باستخدام نموذج استعلام: "ما الذي تعلمه المؤلف في 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.
تخصيص محلل النص
تلعب المحللات دورًا حيويًا في البحث في النص الكامل من خلال تقسيم الجمل إلى رموز وإجراء معالجة معجمية، مثل الجذعية وإزالة كلمات التوقف. وهي عادةً ما تكون خاصة باللغة. لمزيد من التفاصيل، راجع دليل محلل ميلفوس.
يدعم ميلفوس نوعين من المحللات: المحللات المدمجة والمحللات المخصصة. بشكل افتراضي، إذا تم تعيين enable_sparse على صواب، فإن MilvusVectorStore يستخدم BM25BuiltInFunction مع التكوينات الافتراضية، ويستخدم المحلل المدمج القياسي الذي يقوم بترميز النص بناءً على علامات الترقيم.
لاستخدام محلل مختلف أو تخصيص المحلل الموجود، يمكنك توفير قيم للوسيطة analyzer_params عند إنشاء BM25BuiltInFunction. ثم قم بتعيين هذه الدالة على أنها sparse_embedding_function في 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)
البحث الهجين مع التضمين المتناثر الآخر
إلى جانب الجمع بين البحث الدلالي مع BM25، يدعم ميلفوس أيضًا البحث الهجين باستخدام دالة تضمين متفرقة مثل BGE-M3. يستخدم المثال التالي المثال المدمج BGEM3SparseEmbeddingFunction لإنشاء تضمينات متفرقة.
أولاً، نحتاج إلى تثبيت الحزمة FlagEmbedding:
$ pip install -q FlagEmbedding
ثم دعونا ننشئ مخزن المتجهات والفهرس باستخدام نموذج OpenAI الافتراضي لتضمين دنسن و BGE-M3 المدمج للتضمين المتناثر:
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]
الآن دعونا نجري استعلام بحث هجين مع نموذج سؤال:
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.
تخصيص وظيفة التضمين المتناثر
يمكنك أيضًا تخصيص دالة التضمين المتناثر طالما أنها ترث من BaseSparseEmbeddingFunction ، بما في ذلك الطرق التالية:
encode_queries: تقوم هذه الطريقة بتحويل النصوص إلى قائمة تضمينات متناثرة للاستعلامات.encode_documents: تقوم هذه الطريقة بتحويل النص إلى قائمة من التضمينات المتفرقة للمستندات.
يجب أن يتبع ناتج كل طريقة تنسيق التضمين المتناثر، وهو عبارة عن قائمة من القواميس. يجب أن يحتوي كل قاموس على مفتاح (عدد صحيح) يمثل البُعد، وقيمة مقابلة (قيمة عائمة) تمثل مقدار التضمين في هذا البُعد (على سبيل المثال، {1: 0.5، 2: 0.3}).
على سبيل المثال، إليك تطبيق دالة تضمين متفرقة مخصصة باستخدام 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
تخصيص معيد ترتيب هجين
يدعم ميلفوس نوعين من استراتيجيات إعادة الترتيب: اندماج الرتب المتبادلة (RRF) والتسجيل الموزون. المصنف الافتراضي في البحث الهجين MilvusVectorStore هو RRF مع k=60. لتخصيص أداة التصنيف الهجين، قم بتعديل المعلمات التالية:
hybrid_ranker (str): تحديد نوع مصنف التصنيف المستخدم في استعلامات البحث المختلط. يدعم حاليًا فقط ["RRFRanker"، "RRFRanker"، "WeightedRanker"]. الافتراضي إلى "RRFRFRanker".hybrid_ranker_params (dict, optional): معلمات التكوين لمصنّف البحث الهجين. تعتمد بنية هذا القاموس على مصنف التصنيف المحدد المستخدم:- بالنسبة إلى "RRFRFRanker"، يجب أن يتضمن:
- "k" (int): معلمة تُستخدم في دمج الرتب المتبادل (RRF). تُستخدم هذه القيمة لحساب درجات الترتيب كجزء من خوارزمية RRF، والتي تجمع بين استراتيجيات ترتيب متعددة في درجة واحدة لتحسين ملاءمة البحث. القيمة الافتراضية هي 60 إذا لم يتم تحديدها.
- بالنسبة لـ "WeightedRanker"، فإنه يتوقع
- "الأوزان" (قائمة عائمة): قائمة بأوزان اثنين بالضبط:
- الوزن لمكون التضمين الكثيف.
- الوزن لمكون التضمين المتناثر. تُستخدم هذه الأوزان لموازنة أهمية المكونات الكثيفة والمتناثرة للتضمينات في عملية الاسترجاع الهجين. الأوزان الافتراضية هي [1.0، 1.0] إذا لم يتم تحديدها.
- "الأوزان" (قائمة عائمة): قائمة بأوزان اثنين بالضبط:
- بالنسبة إلى "RRFRFRanker"، يجب أن يتضمن:
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.