الاسترجاع من الأبناء إلى الآباء ومن الصغير إلى الكبير
خزّنوا مقاطع أبناء صغيرة للاسترجاع الدقيق، لكن أعيدوا مقاطع الآباء الأكبر إلى LLM لتوفير سياق أغنى، مع تحقيق التوازن بين دقة الاسترجاع وجودة التوليد.
الاسترجاع من الأبناء إلى الآباء ومن الصغير إلى الكبير درس مجاني في AI Engineering Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Engineering Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.
معضلة الدقة مقابل السياق
تواجه أنظمة RAG مفاضلة مهمة: إذ تُسترجع الأجزاء الصغيرة بدقة عالية لأن كل جزء يركّز على فكرة واحدة، لكنها تفتقر إلى السياق المحيط الذي يحتاج إليه LLM لتوليد إجابة كاملة. أما الأجزاء الكبيرة فتقدم سياقًا غنيًا، لكنها تقلل دقة الاسترجاع لأنها تتطابق مع استعلامات كثيرة بشكل ضعيف بدلًا من تطابقها بقوة مع استعلام واحد. ويحل تقسيم الأصل والفرع هذه المعضلة.
بنية الأصل والفرع
في تقسيم الأصل والفرع، تنشئ طبقتين من الأجزاء انطلاقًا من المستند نفسه. تكون الأجزاء الفرعية صغيرة (مثل جملة إلى ثلاث جمل)، وتُضمّن وتُفهرس لأغراض الاسترجاع. أما الأجزاء الأصلية فتكون أقسامًا أكبر (مثل فقرات أو صفحات كاملة) تُخزّن بشكل منفصل. وعند استرجاع جزء فرعي، تعيد إلى LLM الجزء الأصلي بدلًا منه.
إنشاء التسلسل الهرمي للأجزاء
تتمثل الخطوة الأولى في تقسيم المستند إلى أجزاء أصلية كبيرة، ثم تقسيم كل جزء أصلي إلى أجزاء فرعية أصغر. ويحتفظ كل جزء فرعي بمرجع — يكون عادةً حقل بيانات وصفية باسم parent_id — يشير إلى أصله. ويتيح لك هذا الربط البحث عن المقطع الأصلي الكامل باستخدام أي جزء فرعي مسترجع.
from langchain.text_splitter import RecursiveCharacterTextSplitter
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1500, chunk_overlap=0)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=30)
parent_chunks = parent_splitter.split_documents(docs)
child_chunks = []
for i, parent in enumerate(parent_chunks):
children = child_splitter.split_documents([parent])
for child in children:
child.metadata['parent_id'] = i
child_chunks.extend(children)فهرسة الأجزاء الفرعية فقط
تُضمّن الأجزاء الفرعية فقط وتُخزّن في قاعدة بيانات المتجهات. أما الأجزاء الأصلية فتُخزّن في مخزن منفصل للقيمة والمفتاح (مثل قاموس في الذاكرة أو Redis أو قاعدة بيانات مستندات). ويحافظ ذلك على كثافة فهرس المتجهات ودقته، بينما يبقى السياق الغني خارجه.
# Store parents in a docstore
parent_store = {i: chunk.page_content for i, chunk in enumerate(parent_chunks)}
# Embed and index only children
vectorstore = Chroma.from_documents(
child_chunks,
embedding=OpenAIEmbeddings()
)الاسترجاع: إدخال فرعي، وإخراج أصلي
أثناء الاسترجاع، يُضمّن استعلام المستخدم ويُطابق مع الأجزاء الفرعية. ثم تُسترجع الأجزاء الفرعية الأعلى k، وتُحل مراجع parent_id الخاصة بها من خلال البحث في مخزن الأجزاء الأصلية. وبعد ذلك تُحقن المقاطع الأصلية — وليس الأجزاء الفرعية — في prompt الخاص بـ LLM. وهكذا يتلقى LLM سياقًا واسعًا، بينما يظل الاسترجاع دقيقًا.
def retrieve_with_parents(query, vectorstore, parent_store, k=5):
child_results = vectorstore.similarity_search(query, k=k)
seen_parent_ids = set()
parent_contexts = []
for child in child_results:
pid = child.metadata['parent_id']
if pid not in seen_parent_ids:
parent_contexts.append(parent_store[pid])
seen_parent_ids.add(pid)
return parent_contextsLangChain ParentDocumentRetriever
توفر LangChain الفئة ParentDocumentRetriever التي تطبّق هذا النمط مباشرةً. حيث تحدد مُجزّئًا للمستندات الأب، ومُجزّئًا للمستندات الابنة، ومخزن متجهات لتضمينات المستندات الابنة، ومخزن مستندات للمستندات الأب. وتتولى الفئة إنشاء التسلسل الهرمي وإدارة الاسترجاع بشفافية.
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
store = InMemoryStore()
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter
)
retriever.add_documents(docs)
results = retriever.invoke('What is the refund policy?')شرح الاسترجاع من الأصغر إلى الأكبر
يُعد الاسترجاع من الأصغر إلى الأكبر اسمًا آخر للمفهوم نفسه: إذ تسترجع مقاطع صغيرة ودقيقة، ثم توسّعها لتشمل السياق المحيط بها قبل إرسالها إلى LLM. توسّع بعض التطبيقات النطاق ليشمل نافذة من الجمل المتجاورة بدلًا من مستند أب ثابت، فتمنح النموذج الجمل السابقة واللاحقة للمقطع المطابق للحفاظ على الاستمرارية السياقية.
def retrieve_with_window(query, vectorstore, sentences, window=2, k=5):
results = vectorstore.similarity_search(query, k=k)
expanded = []
for r in results:
idx = r.metadata['sentence_index']
start = max(0, idx - window)
end = min(len(sentences), idx + window + 1)
expanded.append(' '.join(sentences[start:end]))
return expandedإزالة التكرار من المقاطع الأب
قد يُسترجع عدد من المقاطع الابنة من المستند الأب نفسه للاستعلام الواحد. ومن دون إزالة التكرار، سيظهر المقطع الأب نفسه عدة مرات في المطالبة، مما يهدر الرموز. احرص دائمًا على إزالة التكرار باستخدام معرّف المستند الأب قبل تجميع السياق. يعالج مثال الشيفرة في دالة الاسترجاع أعلاه ذلك باستخدام مجموعة seen_parent_ids.
متى تستخدم التجزئة الأب والابن
يعمل الاسترجاع الأب والابن بأفضل صورة عندما تكون مستنداتك ذات بنية هرمية واضحة: فصولًا تتضمن أقسامًا، أو مقالات تتضمن فقرات، أو ويكيات تتضمن أقسامًا فرعية. وهو فعّال خصوصًا مع الوثائق التقنية الطويلة التي تحتاج فيها الأسئلة الدقيقة إلى إجابات محددة الموضع، لكن هذه الإجابات لا تكون مفهومة إلا ضمن سياق قسم أوسع.
اختيار أحجام المقاطع الابنة والأب
الإعداد المعتاد هو: مقاطع ابنة تتكون من 200-400 رمز (أفكار مفردة مركزة)، ومقاطع أب تتكون من 1000-2000 رمز (أقسام كاملة). إذا كانت المقاطع الابنة صغيرة جدًا، تحولت إلى جمل منفردة تفتقر إلى المعنى بمفردها. وإذا كانت المقاطع الأب كبيرة جدًا، فستعيد إدخال مشكلة تخفيف السياق التي كنت تحاول تجنبها.
مقارنة الأساليب: خلاصة
خلاصة استراتيجيات التجزئة التي تناولناها حتى الآن: الحجم الثابت سريع لكنه يقطع السياق؛ والتجزئة الدلالية تحافظ على ترابط الموضوع؛ أما التجزئة الأب والابن فتحسّن دقة الاسترجاع وغنى سياق LLM معًا. وفي معظم أنظمة RAG الإنتاجية التي تتعامل مع مستندات طويلة، توفر التجزئة الأب والابن أفضل جودة استرجاع بدرجة تعقيد يمكن التحكم فيها.
تحقق سريع
اختبر مدى فهمك للتجزئة الأب والابن من هذا الدرس.
مراجعة الدرس
تعلمت في هذا الدرس أن: المقاطع الابنة توفر استرجاعًا دقيقًا، بينما توفر المقاطع الأب سياقًا غنيًا، وأن ParentDocumentRetriever في LangChain يطبّق ذلك تلقائيًا، وأن إزالة التكرار باستخدام معرّف المستند الأب تمنع تكرار السياق. بعد ذلك سنستكشف استراتيجيات التجزئة الخاصة بالمستندات لملفات الشيفرة ومستندات HTML.
الأسئلة الشائعة
هل درس «الاسترجاع من الأبناء إلى الآباء ومن الصغير إلى الكبير» مجاني؟
نعم — نص درس «الاسترجاع من الأبناء إلى الآباء ومن الصغير إلى الكبير» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Engineering Academy، انتقل إلى CoddyKit PRO. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.
ماذا ستتعلم في «الاسترجاع من الأبناء إلى الآباء ومن الصغير إلى الكبير»؟
خزّنوا مقاطع أبناء صغيرة للاسترجاع الدقيق، لكن أعيدوا مقاطع الآباء الأكبر إلى LLM لتوفير سياق أغنى، مع تحقيق التوازن بين دقة الاسترجاع وجودة التوليد. تتمرن على AI Engineering Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ AI Engineering Academy؟
لا تُشترط خبرة سابقة. AI Engineering Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «الاسترجاع من الأبناء إلى الآباء ومن الصغير إلى الكبير»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس AI Engineering Academy هذا؟
نعم. كل درس في AI Engineering Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- لماذا يضر التقسيم الساذج بالاسترجاع
- التقسيم الدلالي باستخدام تشابه التضمينات
- الاسترجاع من الأبناء إلى الآباء ومن الصغير إلى الكبير
- استراتيجيات خاصة بالمستندات للبرمجيات البرمجية وHTML