الخطوة الثالثة في RAG: استرجاع المعلومات المطابقة لسؤال المستخدم (Retriever)
ما هي الخطوة الثالثة في RAG ولماذا تُعد قلب النظام الحقيقي؟
بعد أن قمنا في الخطوات السابقة باستخراج النصوص من الملفات، ثم تقسيمها، ثم تحويلها إلى Embeddings وتخزينها داخل Vector Database كما شرحنا في مقال الخطوة الثانية في RAG: تحويل قطع النصوص إلى Vectors وتخزينها في قاعدة البيانات، نصل الآن إلى أهم مرحلة تشغيلية: استرجاع المقاطع الأكثر صلة بسؤال المستخدم.
وظيفة Retriever ليست مجرد تنفيذ بحث نصي تقليدي، بل هي طبقة منطقية تقوم بتحويل سؤال المستخدم إلى تمثيل عددي، ثم تقارن هذا التمثيل مع المتجهات المخزنة لاختيار المقاطع الأقرب دلالياً. هذه النقطة هي ما يمنح أنظمة RAG قدرتها على الإجابة استناداً إلى معرفة خارجية محدثة بدلاً من الاعتماد الكامل على ذاكرة النموذج الداخلية.
بمعنى هندسي، إذا كانت قاعدة البيانات المتجهة هي “المخزن”، فإن Retriever هو “وحدة الانتقاء الذكي” التي تحدد أي أجزاء من المخزن يجب تمريرها إلى LLM. وكلما كانت هذه الوحدة مضبوطة بعناية، ارتفعت الدقة، وانخفضت الهلوسة، وتحسن استهلاك Tokens.
المعمارية الداخلية لتدفق البيانات داخل Retriever
لفهم هذه المرحلة عملياً، يجب النظر إليها كخط أنابيب Pipeline مكوّن من عدة طبقات مترابطة:
- استقبال سؤال المستخدم بصيغته الخام.
- تحويل السؤال إلى متجه باستخدام نفس نموذج
Embeddingsالمستخدم أثناء الفهرسة. - تنفيذ
Similarity Searchداخل القاعدة المتجهة. - ترتيب النتائج حسب درجة التشابه أو المسافة.
- إرجاع أفضل
Chunksمع بياناتها الوصفيةMetadata. - تمرير هذه المقاطع إلى النموذج ضمن Prompt مضبوط.
المهم هنا أن Retriever لا “يفهم” النص كما يفهمه الإنسان، بل يعمل على مستوى القرب الرياضي بين المتجهات. لذلك فإن جودة النتائج مرتبطة مباشرة بجودة التقطيع، وبنوع نموذج التضمين، وباختيار طريقة الاسترجاع المناسبة.
أنواع الاسترجاع الأكثر استخداماً في أنظمة RAG
1) الاسترجاع بالتشابه الدلالي Similarity Retrieval
هذا هو النمط الأبسط والأكثر شيوعاً. يتم فيه جلب أعلى النتائج تشابهاً مع سؤال المستخدم. وهو مناسب عندما تكون الوثائق منظمة جيداً، والتشابه الدلالي كافياً للوصول إلى الإجابة.
2) الاسترجاع بالحد الأدنى من التشابه المتكرر MMR
في بعض الحالات، قد تجلب أعلى النتائج مقاطع متشابهة جداً مع بعضها، ما يسبب تكراراً في السياق المرسل إلى النموذج. هنا تظهر تقنية Max Marginal Relevance التي توازن بين الصلة والتنوع، فتقلل التكرار وتزيد التغطية المعرفية.
3) الاسترجاع المفلتر بالبيانات الوصفية Metadata Filtering
عندما تتعامل مع مستندات متعددة المصادر، يصبح من المفيد فرض قيود مثل نوع الملف، اسم العميل، السنة، أو القسم. هذا يضيف طبقة حاكمة فوق البحث الدلالي ويمنع استرجاع بيانات صحيحة دلالياً لكنها من سياق أعمال غير مطلوب.
بناء Retriever باستخدام LangChain
إطار LangChain يوفّر طبقة جاهزة لتحويل قاعدة البيانات المتجهة إلى كائن استرجاع يمكن دمجه بسهولة مع السلاسل Chains. المثال التالي يفترض أنك خزّنت البيانات مسبقاً في ChromaDB:
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embedding_model
)
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 4}
)
query = "ما هي آلية تقليل الهلوسة في أنظمة RAG؟"
documents = retriever.invoke(query)
for i, doc in enumerate(documents, 1):
print(f"Result {i}:")
print(doc.page_content)
print(doc.metadata)
print("-" * 50)
هذا الكود يحوّل مخزن المتجهات إلى Retriever بسيط يعيد أفضل 4 نتائج. المفتاح هنا هو الوسيط k، لأنه يحدد حجم السياق المسترجع. إذا كان صغيراً جداً فقد تفقد معلومات مهمة، وإذا كان كبيراً جداً سترسل ضجيجاً زائدًا إلى النموذج.
استخدام MMR لتحسين التنوع
retriever = vectorstore.as_retriever(
search_type="mmr",
search_kwargs={
"k": 5,
"fetch_k": 12,
"lambda_mult": 0.7
}
)
documents = retriever.invoke("اشرح الفرق بين embeddings و vector database")
في هذا النمط، يقوم النظام أولاً بجلب عدد أكبر من المرشحين عبر fetch_k، ثم يختار منها نتائج أكثر تنوعاً. هذه التقنية مفيدة خصوصاً عندما تكون البيانات مليئة بمقاطع متشابهة أو مكررة.
كيف يندمج Retriever مع النموذج التوليدي؟
الاسترجاع وحده لا يكفي. الهدف النهائي هو بناء سياق موجّه للنموذج بحيث يجيب اعتماداً على المقاطع المسترجعة فقط. لهذا السبب يجب تصميم برومبت واضح يفرض على LLM ألا يخرج عن الوثائق.
أنت مساعد تقني. استخدم المعلومات الموجودة في المقاطع المسترجعة فقط للإجابة عن سؤال المستخدم. إذا لم تجد الجواب بوضوح داخل السياق، فقل: “المعلومة غير متوفرة في الوثائق المسترجعة”. لا تخمّن ولا تضف معرفة خارجية.
هذا النمط من الضبط يرتبط مباشرة بمفاهيم Prompt Engineering، لأن جودة البرومبت تحدد ما إذا كان النموذج سيتعامل مع النتائج كمرجع ملزم أو كإشارات عامة فقط.
مثال عملي: تمرير النتائج المسترجعة إلى النموذج
from openai import OpenAI
client = OpenAI()
query = "كيف يساعد retriever في تقليل الهلوسة؟"
docs = retriever.invoke(query)
context = "\n\n".join([doc.page_content for doc in docs])
prompt = f"""
أنت مساعد تقني متخصص في RAG.
أجب اعتماداً على السياق التالي فقط:
{context}
سؤال المستخدم:
{query}
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": prompt}
],
temperature=0
)
print(response.choices[0].message.content)
لاحظ هنا أهمية ضبط temperature=0 عند الحاجة إلى إجابات دقيقة ومنضبطة، وهو ما يتكامل مع ما شرحناه سابقاً في مقال التحكم في إبداع الذكاء الاصطناعي: فهم وإعداد معاملات Temperature و Top-K برمجياً.
مشكلات شائعة عند تصميم Retriever
استرجاع نتائج صحيحة شكلياً لكنها ضعيفة معرفياً
قد يحدث ذلك عندما تكون المقاطع طويلة جداً أو غير متجانسة موضوعياً. الحل غالباً يبدأ من مرحلة Text Splitters وليس من قاعدة البيانات نفسها.
عدم تطابق نموذج التضمين بين الفهرسة والاستعلام
إذا خزنت المتجهات باستخدام نموذج، ثم استخدمت نموذجاً مختلفاً عند البحث، ستنخفض جودة التشابه بشكل واضح. لذلك يجب الحفاظ على الاتساق الكامل بين مرحلتي Indexing وQuerying.
الإفراط في عدد النتائج المسترجعة
زيادة k لا تعني دائماً نتائج أفضل. أحياناً تؤدي إلى توسيع السياق بلا داعٍ، ما يربك النموذج ويرفع التكلفة. الأفضل اختبار عدة إعدادات ومراقبة جودة الإجابات فعلياً.
أفضل الممارسات الهندسية لبناء طبقة استرجاع قوية
- استخدم نفس نموذج
Embeddingsفي التخزين والاستعلام. - جرّب أكثر من نمط استرجاع مثل
similarityوmmr. - أضف
metadata filtersعندما يكون لديك أكثر من مصدر أو نطاق معرفي. - راقب عدد الرموز المرسلة إلى النموذج بعد الاسترجاع لتفادي التضخم السعري.
- صمّم برومبتاً يمنع النموذج من اختلاق معلومات خارج السياق.
- اختبر جودة الاسترجاع بسيناريوهات حقيقية بدلاً من الاكتفاء بتشغيل الكود بنجاح.
الخلاصة الهندسية
الخطوة الثالثة في RAG ليست تفصيلاً تقنياً ثانوياً، بل هي الآلية التي تحدد ما إذا كان النظام سيستدعي المعرفة الصحيحة في اللحظة المناسبة أم لا. كل ما بُني في المراحل السابقة، من استخراج النصوص إلى التقطيع إلى توليد المتجهات، يصبح بلا قيمة حقيقية إذا كان Retriever ضعيفاً أو غير مضبوط.
لهذا، يجب التعامل مع طبقة الاسترجاع كعنصر هندسي قابل للقياس والتحسين، لا كوظيفة جاهزة فقط. في التطبيق الاحترافي، أنت لا تسأل: “هل النظام يسترجع شيئاً؟” بل تسأل: “هل يسترجع أفضل شيء، بأقل ضجيج، وبأعلى قابلية للاستخدام داخل LLM؟”. وهنا تبدأ فعلياً هندسة RAG الحقيقية.
15 comments