تواجه أنظمة توليد المعزز بالاسترجاع (RAG) التقليدية صعوبة في التعامل مع الاستعلامات متعددة القفزات. هذه أسئلة تتطلب دمج المعلومات من مصادر متعددة أو خطوات استدلال مختلفة. على سبيل المثال، السؤال "ما كان أول منتج أطلقته الشركة التي أسسها مؤلف لغة بايثون؟" يتطلب أولاً تحديد المؤلف (Guido van Rossum)، ثم إيجاد الشركة التي أسسها (على الرغم من أنه كان يعمل في Cwi، قد يشير السؤال إلى شركة ناشئة معينة انضم إليها لاحقاً أو غادرها)، وأخيراً استرجاع بيانات إطلاق المنتج. نادراً ما يوفر خطوة استرجاع واحدة السياق الكامل اللازم لسد هذه الفجوات.
RAG ذاتي التحسين يعالج هذا القيد من خلال تقديم حلقة تكرارية يتصرف فيها نموذج اللغة (LLM) ليس فقط كمولّد، بل كمخطط ونقد. يقيّم ما إذا كان السياق المسترجع كافياً للإجابة على الاستعلام. إذا لم يكن كذلك، يولّد استعلامات بحث جديدة أكثر استهدافاً بناءً على الفهم الجزئي الحالي، ويسترجع مستندات جديدة، ويكرر العملية حتى يتمكن من إنتاج إجابة واثقة أو يصل إلى حد أقصى للتكرارات.
بنية الحلقة التكرارية
تدور المنطق الأساسي لنظام RAG ذاتي التحسين حول ثلاث مراحل داخل كل تكرار:
- تفكيك الاستعلام والتقييم: يستقبل نموذج اللغة (LLM) السؤال الأصلي والسياق الحالي (المقاطع المسترجعة). يحدد ما إذا كان السياق يحتوي على معلومات كافية للإجابة على السؤال بشكل قاطع.
- توليد استعلام جديد: إذا كان السياق غير كافٍ، يحدد نموذج اللغة (LLM) الجزء المفقود من المعلومات ويصيغ استعلام بحث محدد لاسترجاعه.
- الاسترجاع والتراكم: يُرسل الاستعلام الجديد إلى قاعدة البيانات المتجهية. تُضاف النتائج إلى السياق الموجود، وتُكرر الحلقة.
التنفيذ بلغة بايثون
فيما يلي تنفيذ مبسط باستخدام مفاهيم LangChain لإظهار التدفق. يفترض هذا المثال أن لديك `vectorstore` و `llm` تم تهيئتهما.
from langchain.llms import LLM
from langchain.vectorstores import VectorStore
from langchain.chains import RetrievalQA
class SelfRefiningRAG:
def __init__(self, llm: LLM, vectorstore: VectorStore, max_iterations: int = 3):
self.llm = llm
self.vectorstore = vectorstore
self.max_iterations = max_iterations
def retrieve(self, query: str) -> list:
# بحث تشابه متجهي قياسي
return self.vectorstore.similarity_search(query, k=3)
def is_sufficient(self, question: str, context: list) -> bool:
prompt = f"""
Question: {question}
Context: {context}
Does the context contain sufficient information to answer the question?
Answer with only "Yes" or "No".
"""
response = self.llm(prompt)
return response.strip().lower() == "yes"
def generate_next_query(self, question: str, context: list) -> str:
prompt = f"""
Original Question: {question}
Current Context: {context}
The current context is not sufficient to answer the question.
Identify the missing information and generate a single search query
to retrieve that missing information.
Output only the search query.
"""
return self.llm(prompt).strip()
def run(self, question: str) -> str:
context_docs = []
for i in range(self.max_iterations):
# 1. استرجاع
if not context_docs:
# يستخدم التكرار الأول السؤال الأصلي
context_docs = self.retrieve(question)
else:
# تستخدم التكرارات اللاحقة استعلاماً محسناً
new_query = self.generate_next_query(question, context_docs)
new_docs = self.retrieve(new_query)
context_docs.extend(new_docs)
# 2. تقييم
if self.is_sufficient(question, [d.page_content for d in context_docs]):
break
# 3. التوليد النهائي
final_context = "\n\n".join([d.page_content for d in context_docs])
final_prompt = f"""
Context: {final_context}
Question: {question}
Answer:
"""
return self.llm(final_prompt)
تحسين الكفاءة والجودة
بينما المنطق الوظيفي أعلاه، تتطلب أنظمة الإنتاج تحسينات لتجنب الحلقات اللانهائية وتقليل زمن الاستجابة.
1. إزالة التكرار من السياق
غالباً ما يجلب الاسترجاع التكراري نفس المستندات عدة مرات. قم بتنفيذ خطوة إزالة التكرار (على سبيل المثال، باستخدام تجزئة معرّف المستند أو تشابه المحتوى) قبل إضافة النتائج الجديدة إلى context_docs. يمنع هذا تضخم الموجه (prompt) بشكل غير ضروري.
2. معايير التوقف المبكر
فحص is_sufficient مكلف (يتطلب استدعاء نموذج اللغة). يمكنك تحسين هذا باستخدام معيار أرخص أولاً (على سبيل المثال، تداخل الكلمات المفتاحية) أو بتقييد تقييم نموذج اللغة فقط إذا كانت المستندات المسترجعة تحقق أقل من عتبة صلة معينة.
3. مخرجات منظمة للاستعلامات
بدلاً من الاستعلامات التالية كنص حر، استخدم مخرجات منظمة (JSON) للتأكد من أن نموذج اللغة يوفر مصطلح بحث واضح ومحدد. يحسن هذا دقة البحث المتجهي في التكرارات اللاحقة.
مثال عملي: الاستدلال متعدد القفزات
فكّر في الاستعلام: "من كان الرئيس التنفيذي للشركة التي استحوذت على شركة التقنية الناشئة التي أسسها مؤلف React؟"
- التكرار 1:
- الاسترجاع: البحث عن "مؤلف React" -> يجد "Jordan Walke".
- التقييم: لا، نحن لا نعرف الشركة الناشئة أو الرئيس التنفيذي.
- استعلام جديد: "ما هي الشركة الناشئة التي أسسها Jordan Walke؟"
- التكرار 2:
- الاسترجاع: يجد "Jordan Walke أسس 8080 Labs".
- التقييم: لا، نحن لا نعرف من استحوذ عليها أو من هو الرئيس التنفيذي الحالي.
- استعلام جديد: "من استحوذ على 8080 Labs ومن هو رئيسها التنفيذي؟"
- التكرار 3:
- الاسترجاع: يجد أن 8080 Labs استحوذت عليها Facebook (Meta). يسترجع الرئيس التنفيذي لـ Meta (Mark Zuckerberg).
- التقييم: نعم، معلومات كافية متاحة.
- الإجابة النهائية: "Mark Zuckerberg هو الرئيس التنفيذي لـ Meta، التي استحوذت على 8080 Labs".
الخلاصة
يحوّل RAG ذاتي التحسين الاسترجاع الثابت إلى عملية ديناميكية ووكيلية. من خلال السماح لنموذج اللغة بتحسين فهمه بشكل تكراري وطلب قطع معلومات مفقودة محددة، يمكنك تحسين الدقة بشكل كبير على الاستعلامات المعقدة متعددة القفزات. ومع ذلك، يأتي هذا على حساب زيادة زمن الاستجابة وتكاليف API بسبب استدعاءات متعددة لنموذج اللغة والاسترجاع. استخدم هذا النمط بحكمة، واحتفظ به للاستعلامات عالية القيمة حيث يبرر عمق الاستدلال التكلفة. للمهام الأبسط، يبقى RAG ذو المرور الواحد الخيار الأكثر كفاءة.