إنشاء نظام توليد معزز بالاسترجاع (RAG) لا يقتصر على ربط نموذج لغة كبير بقاعدة بيانات متجهات. عنق الزجاجة الحقيقي للدقة يكمن في مرحلة الاسترجاع. إذا جلب المسترجع سياقًا غير ذي صلة إلى نموذج اللغة، فستكون الإجابة النهائية معيبة بغض النظر عن ذكاء النموذج. لتحقيق RAG عالي الدقة، يجب علينا تجاوز المطابقة البسيطة بالكلمات المفتاحية أو الدلالية من خلال دمج نقاط قوة استراتيجيات الاسترجاع المتعددة وتحسين النتائج عبر إعادة الترتيب.
قيود الاسترجاع أحادي الاستراتيجية
تعتمد معظم تطبيقات RAG الأولية إما على الاسترجاع المتناثر (مثل BM25) أو الاسترجاع الكثيف (باستخدام التضمينات مثل Sentence-BERT). لكل منهما نقاط عمياء مميزة.
- الاسترجاع المتناثر (BM25): يتفوق في المطابقة الدقيقة للكلمات المفتاحية وفهم أسماء الكيانات المحددة، أو الأكواد، أو المصطلحات التقنية. ومع ذلك، يفشل في التقاط التشابه الدلالي أو إعادة الصياغة.
- الاسترجاع الكثيف (التضمينات): يلتقط المعنى الدلالي والسياق جيدًا، مما يتيح له العثور على مستندات تناقش نفس المفهوم ولكن باستخدام كلمات مختلفة. ومع ذلك، غالبًا ما يعاني في التعامل مع المعرفات الفريدة، أو أرقام SKUs للمنتجات المحددة، أو المصطلحات التقنية النادرة.
باستخدام استراتيجية واحدة فقط، تضحي إما بالدقة في المصطلحات المحددة أو بالاستدعاء في التغيرات الدلالية. يجمع البحث الهجين بين الإشارتين للتخفيف من هذه نقاط الضعف.
بناء مسترجع هجين في Haystack
يوفر إطار عمل Haystack تجريدًا أنيقًا لدمج المسترجعات. يمكنك تنسيق BM25Retriever و EmbeddingRetriever بالتوازي ثم دمج نتائجهما.
from haystack import Pipeline
from haystack.components.retrievers.in_memory import BM25Retriever, EmbeddingRetriever
from haystack.components.routers import SwitchRouter
from haystack.components.joiners import DocumentJoiner
from haystack.components.builders import DocumentJoiner
# Initialize Retriever Components
# Note: In a real pipeline, you would connect these to your InMemoryDocumentStore
bm25_retriever = BM25Retriever(document_store, top_k=20)
embedding_retriever = EmbeddingRetriever(document_store, top_k=20)
# Initialize the Joiner
# This component merges documents from both retrievers
# The score_type determines how scores are combined (e.g., 'distributional', 'arithmetic')
doc_joiner = DocumentJoiner(
join_mode="concatenate",
duplicate_documents="skip",
score_normalization="min_max",
score_threshold=0.3
)
# Construct the Hybrid Search Pipeline
pipeline = Pipeline()
pipeline.add_component("bm25", bm25_retriever)
pipeline.add_component("embedding", embedding_retriever)
pipeline.add_component("joiner", doc_joiner)
# Connect the components
# Both retrievers run in parallel, and their outputs are fed into the joiner
pipeline.connect("bm25", "joiner")
pipeline.connect("embedding", "joiner")
في هذا التكوين، يستعلم خط الأنابيب عن كلا الفهرسين في نفس الوقت. ثم يدمج DocumentJoiner قوائم المستندات. من الضروري بشكل حاسم أن تطبعم (توحّد) الدرجات لأن درجات BM25 (غير المحددة وتعتمد على الاستعلام) ودرجات التشابه الكوسيني (المحدودة بين -1 و 1) غير قابلة للمقارنة المباشرة. باستخدام score_normalization="min_max"، يتم توسيع كلا المجموعتين من الدرجات إلى نطاق مشترك، مما يسمح بمنافسة عادلة أثناء عملية الدمج.
الدور الحاسم لإعادة الترتيب
بينما يحسن البحث الهجين الاستدعاء، فإن المستندات الـ 20 أو 30 الأولى المسترجعة لا تزال "كيسًا من النتائج". قد تكون بعضها ذات صلة هامشية فقط. تغذية جميع هذه المستندات إلى نموذج اللغة تهدر مساحة نافذة السياق ويمكن أن introduce ضوضاء. هنا يأتي دور إعادة الترتيب.
تستخدم أدوات إعادة الترتيب نماذج Cross-Encoder. على عكس Bi-Encoders المستخدمة في الاسترجاع المتجهي (التي ترمز الاستعلام والمستند بشكل مستقل)، تعالج Cross-Encoders الاستعلام والمستند معًا في رأس انتباه واحد. هذا يتيح للنموذج إجراء تفاعل عميق على مستوى الرموز (tokens)، مما يوفر درجة صلة أكثر دقة بشكل كبير.
تكامل أداة إعادة الترتيب في خط الأنابيب
نضيف SentenceTransformersReRanker أو أداة إعادة ترتيب مخصصة تعتمد على API (مثل Cohere أو Jina) إلى نهاية مرحلة الاسترجاع. تأخذ أداة إعادة الترتيب المستندات العلوية N المدمجة من البحث الهجين وتعيد ترتيبها، منتقيةً فقط الأكثر صلة K للمستولد.
from haystack.components.rerankers import TransformersSimilarityRanker
# Initialize Re-Ranker
# Use a strong cross-encoder model for best performance
re_ranker = TransformersSimilarityRanker(
model="cross-encoder/ms-marco-MiniLM-L-6-v2",
top_k=5,
batch_size=16
)
# Update the Pipeline
pipeline.add_component("re_ranker", re_ranker)
# Connect the joiner to the re-ranker
pipeline.connect("joiner", "re_ranker")
من خلال تعيين top_k=5 في أداة إعادة الترتيب، نخفض بشكل حاد السياق المرسل إلى نموذج اللغة. بدلاً من إرسال 40 مستندًا محتملًا مليئًا بالضوضاء، نرسل فقط 5 مستندات أكد المشفر المتقاطع أنها ذات صلة عالية. يؤدي هذا عادةً إلى تحسين قابل للقياس في دقة الإجابة وتقليل زمن الاستجابة بسبب أحجام الموجهات (prompts) الأصغر.
اعتبارات عملية وتحسين
يتطلب تنفيذ هذا الهيكل موازنة عدة عوامل:
- استراتيجية التقطيع (Chunking): تأكد من تقطيع مستنداتك بشكل مناسب قبل الفهرسة. إذا كانت القطع كبيرة جدًا، فقد تجد صعوبة أداة إعادة الترتيب في إيجاد الجملة ذات الصلة المحددة. إذا كانت صغيرة جدًا، تفقد السياق. نطاق 200-500 رمزًا مع تداخل 10-20% هو نقطة بداية جيدة.
- عتبات التقييم (Scoring Thresholds): استخدم مخرجات أداة إعادة الترتيب لتصفية المطابقات الضعيفة. إذا كانت الدرجة العليا بعد إعادة الترتيب أقل من عتبة معينة (مثل 0.4 للدرجات المطبّعة)، فمن الأفضل غالبًا للنظام أن يعترف بـ "لا أعرف" بدلاً من الهلوسة (Hallucinate) بإجابة مبنية على أدلة ضعيفة.
- اختيار النموذج: للبيئات الإنتاجية، فكّر في استخدام واجهات API المستضافة لإعادة الترتيب (مثل Rerank من Cohere أو Reranker من Jina AI) لتخفيف التكلفة الحسابية للتشفير المتقاطع من بنيتك التحتية المحلية، خاصةً للأنظمة عالية الإنتاجية.
خاتمة
إن RAG عالي الدقة ليس حيلة سحرية؛ بل هو نتيجة هندسة خط أنابيب استرجاع قوي. من خلال الاستفادة من بنية Haystack المبنية على المكونات، يمكنك تكامل البحث الهجين بسلاسة لالتقاط الإشارات الدلالية والمفردية معًا، متبوعًا بأداة إعادة ترتيب Cross-Encoder للتأكد من وصول السياق الأكثر صلة فقط إلى نموذج اللغة. يقلل هذا النهج متعدد المراحل الهلوسة بشكل كبير ويحسن الدقة الواقعية لتطبيقات الذكاء الاصطناعي الخاصة بك، محولًا روبوت دردشة أساسي إلى مساعد معرفة موثوق بمستوى المؤسسات.