0Pricing
AI Engineering Academy · درس

بنية RAG: الفهرسة والاسترجاع

حدّدوا مرحلتي RAG: مرحلة الفهرسة غير المتصلة التي تقسّم المستندات وتحولها إلى embeddings وتخزّنها، ومرحلة الاسترجاع المتصلة التي تعثر على السياق ذي الصلة بكل استعلام.

بنية RAG: الفهرسة والاسترجاع درس مجاني في AI Engineering Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Engineering Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.

يمتلك RAG مرحلتين متميزتين

يعمل نظام RAG في مرحلتين مختلفتين جوهريًا، وتُنفذ كل منهما في وقت مختلف. تعالج مرحلة الفهرسة غير المتصلة مستنداتك مرة واحدة (أو عند تغيرها) وتجهز فهرسًا قابلًا للبحث. أما مرحلة الاسترجاع المتصلة فتعمل في الوقت الفعلي مع كل استعلام من المستخدم. ويُعد فهم هذا الفصل ضروريًا لتصميم أنظمة سريعة وقت الاستعلام وقابلة للصيانة بمرور الوقت.

مرحلة الفهرسة: الخطوة الأولى — التحميل

تبدأ الفهرسة بـتحميل المستندات، أي قراءة الملفات الخام من أنظمة المصادر. يمكن أن تكون المستندات ملفات PDF أو Word أو صفحات HTML أو ملفات Markdown أو صفوفًا في قاعدة بيانات أو أي مصدر نصي. يُحمّل كل مستند إلى الذاكرة كنص عادي، مع الحفاظ على بنيته حيثما أمكن. وتتولى مكتبات مثل pypdf وpython-docx وunstructured معظم أعمال تحليل التنسيقات الخاصة.

from pypdf import PdfReader

def load_pdf(path):
    reader = PdfReader(path)
    pages = []
    for i, page in enumerate(reader.pages):
        text = page.extract_text()
        pages.append({'text': text, 'page': i + 1, 'source': path})
    return pages

docs = load_pdf('company_policy.pdf')
print(f'Loaded {len(docs)} pages')

مرحلة الفهرسة: الخطوة الثانية — التقسيم إلى مقاطع

تمتلك LLMs نوافذ سياق محدودة، كما أن استرجاع المستندات كاملةً غير فعّال. لذلك يُقسّم النص المحمّل إلى مقاطع أصغر يتراوح حجم كل منها تقريبًا بين 200 و1000 رمز مميز. ويحافظ التقسيم الجيد على الترابط الدلالي؛ إذ ينبغي أن يعبّر المقطع عن فكرة مكتملة. وتستخدم إحدى الاستراتيجيات الشائعة نوافذ متداخلة بحيث تظهر الجمل القريبة من حدود المقاطع في مقطعين، مما يمنع فقدان المعلومات عند نقاط التقسيم.

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,      # characters per chunk
    chunk_overlap=50,    # overlap between chunks
    separators=['\n\n', '\n', '. ', ' ']
)

for page in docs:
    chunks = splitter.split_text(page['text'])
    for chunk in chunks:
        # Each chunk carries metadata from its source page
        print(f'Chunk ({len(chunk)} chars): {chunk[:80]}...')

مرحلة الفهرسة: الخطوة الثالثة — التضمين

يُحوّل كل مقطع نصي إلى تضمين متجه كثيف يلتقط معناه الدلالي بصورة رقمية. تستدعي نموذج embedding — مثل text-embedding-3-small من OpenAI — لكل مقطع، وتتلقى مصفوفة أعداد عائمة عالية الأبعاد. وتنتج المقاطع المتشابهة في المعنى متجهات متقاربة في هذا الفضاء العالي الأبعاد، وهو ما يتيح البحث عن التشابه الدلالي.

from openai import OpenAI

client = OpenAI()

def embed_chunks(chunks):
    texts = [c['text'] for c in chunks]
    response = client.embeddings.create(
        model='text-embedding-3-small',
        input=texts
    )
    for i, chunk in enumerate(chunks):
        chunk['embedding'] = response.data[i].embedding
    return chunks

# Batch up to 2048 texts per API call
embedded = embed_chunks(all_chunks)

مرحلة الفهرسة: الخطوة الرابعة — التخزين

تُخزّن المقاطع المضمّنة في قاعدة بيانات متجهات إلى جانب بياناتها الوصفية (الملف المصدر، ورقم الصفحة، وعنوان القسم). وينشئ مخزن المتجهات بنية فهرس (عادةً HNSW) تتيح البحث السريع عن أقرب الجيران بالتقريب. ويُحفظ هذا الفهرس على القرص كي يبقى بعد إعادة التشغيل. وتحدث الفهرسة عادةً مرة واحدة عند الإعداد، وبصورة تدريجية عند إضافة مستندات جديدة.

import pinecone

pc = pinecone.Pinecone(api_key='YOUR_KEY')
index = pc.Index('rag-documents')

# Upsert vectors with metadata
vectors_to_upsert = [
    (
        chunk['id'],
        chunk['embedding'],
        {'text': chunk['text'], 'source': chunk['source'], 'page': chunk['page']}
    )
    for chunk in embedded_chunks
]

# Upsert in batches of 100
for i in range(0, len(vectors_to_upsert), 100):
    index.upsert(vectors=vectors_to_upsert[i:i+100])
print('Indexing complete')

مرحلة الاسترجاع: تضمين الاستعلام

عندما يرسل المستخدم استعلامًا، تبدأ مرحلة الاسترجاع المتصلة. وتتمثل الخطوة الأولى في تضمين سؤال المستخدم باستخدام نموذج embedding نفسه المستخدم أثناء الفهرسة. وهذا أمر بالغ الأهمية: فإذا فهرست باستخدام text-embedding-3-small، فيجب أن تستعلم باستخدام text-embedding-3-small أيضًا. ويكون تضمين الاستعلام متجهًا يشفّر المعنى الدلالي لما يسأل عنه المستخدم.

def embed_query(question):
    response = client.embeddings.create(
        model='text-embedding-3-small',  # MUST match indexing model
        input=[question]
    )
    return response.data[0].embedding

user_question = 'What is our parental leave policy?'
query_vector = embed_query(user_question)
print(f'Query embedded: {len(query_vector)}-dim vector')

مرحلة الاسترجاع: بحث ANN

يُرسل تضمين الاستعلام إلى مخزن المتجهات، الذي ينفذ بحث أقرب جار بالتقريب (ANN) للعثور على K من المقاطع التي تكون تضميناتها الأكثر تشابهًا مع متجه الاستعلام. ويتميز هذا البحث بسرعة فائقة (عادةً أقل من 10 مللي ثانية) لأن فهارس HNSW تستبدل قدرًا صغيرًا من الاسترجاع بمكاسب هائلة في السرعة مقارنةً بالبحث الشامل. وتسترجع أفضل المقاطع حتى K، ويكون K عادةً بين 5 و20.

results = index.query(
    vector=query_vector,
    top_k=5,
    include_metadata=True
)

print(f'Retrieved {len(results.matches)} chunks:')
for match in results.matches:
    print(f'  Score: {match.score:.3f} | Source: {match.metadata["source"]}')
    print(f'  Text: {match.metadata["text"][:100]}...')
    print()

ربط المرحلتين

تتمثل الفكرة الأساسية في أن الفهرسة والاسترجاع مصممان للعمل معًا. يجب أن يكون نموذج embedding متطابقًا في المرحلتين، لأن الفضاء الرياضي الذي توجد فيه المتجهات خاص بالنموذج. وإذا بدّلت نموذج embedding، فيجب إعادة فهرسة جميع المستندات. ويُعد مخزن المتجهات حلقة الوصل بين المرحلتين: فهو يستقبل المتجهات أثناء الفهرسة ويعيدها أثناء الاسترجاع، ويفصل المرحلتين زمنيًا مع إبقائهما متوافقتين في فضاء المتجهات.

تصفية البيانات الوصفية أثناء الاسترجاع

يعثر البحث المتجهي على المقاطع المتشابهة دلاليًا، لكنك قد تحتاج أحيانًا أيضًا إلى التصفية حسب البيانات الوصفية. على سبيل المثال: استرجاع المقاطع من المستندات التي حُمّلت في عام 2025 فقط، أو من مجلد قسم الموارد البشرية فقط. وتدعم مخازن المتجهات التصفية المسبقة أو اللاحقة لحقول البيانات الوصفية. وتطبّق التصفية المسبقة (التي يدعمها Pinecone وQdrant) عامل التصفية قبل بحث ANN، ولذلك تكون أسرع وأكثر دقة من التصفية اللاحقة لنتائج أفضل K.

# Retrieve only from HR department documents
results = index.query(
    vector=query_vector,
    top_k=5,
    filter={'department': {'$eq': 'HR'}},
    include_metadata=True
)

# Or filter by date range
results = index.query(
    vector=query_vector,
    top_k=5,
    filter={
        'upload_year': {'$gte': 2024},
        'doc_type': {'$eq': 'policy'}
    },
    include_metadata=True
)

الفهرسة التدريجية للتحديثات

في بيئة الإنتاج، تتغير مجموعة مستنداتك بمرور الوقت. وتدعم بنية الفهرسة الفعالة التحديثات التدريجية: فعند تعديل مستند، احذف متجهاته الحالية حسب المعرّف ثم أدرج المتجهات الجديدة أو حدّثها. وعند حذف المستندات، أزل متجهاتها. وامنح كل مقطع معرّفًا حتميًا يستند إلى مسار المستند المصدر وموضع المقطع، حتى تتمكن دائمًا من العثور على المتجهات الصحيحة وتحديثها من دون إعادة فهرسة كل شيء.

import hashlib

def make_chunk_id(source_path, chunk_index):
    # Deterministic, stable ID for each chunk
    key = f'{source_path}::chunk_{chunk_index}'
    return hashlib.md5(key.encode()).hexdigest()

def update_document(source_path, index):
    # Delete old vectors for this document
    index.delete(filter={'source': source_path})
    # Re-index the updated document
    new_chunks = load_and_chunk(source_path)
    new_embedded = embed_chunks(new_chunks)
    index.upsert(vectors=new_embedded)
    print(f'Updated {source_path}: {len(new_chunks)} chunks')

نظرة عامة على مسار RAG الكامل

تبدو بنية RAG الكاملة كما يلي: غير متصل: المستندات ← المحمّل ← مقسّم المقاطع ← نموذج embedding ← مخزن المتجهات. متصل: استعلام المستخدم ← نموذج embedding ← مخزن المتجهات (بحث ANN) ← أفضل المقاطع حتى K ← تجميع prompt ← LLM ← الإجابة. يعمل المسار غير المتصل مرة واحدة عند كل تحديث للمستند. أما المسار المتصل فيعمل خلال أجزاء من الثانية مع كل استعلام، ولا يرى LLM سوى السياق ذي الصلة، وليس مجموعة المستندات بأكملها.

تحقق سريع

اختبر مدى فهمك لمفاهيم هندسة الذكاء الاصطناعي التي تناولها هذا الدرس.

مراجعة الدرس

تعلمت في هذا الدرس: مرحلة الفهرسة غير المتصلة التي تتكون من خطوات التحميل والتقسيم والتضمين والتخزين، وتُنفذ مرة واحدة لكل مستند، ومرحلة الاسترجاع المتصلة التي تضمّن الاستعلام، وتجري بحث ANN، وتعيد أفضل المقاطع حتى K خلال أجزاء من الثانية، واستراتيجيات الفهرسة التدريجية للحفاظ على حداثة مخزن المتجهات مع تغير المستندات. بعد ذلك، سنستكشف كيفية صياغة prompts معززة فعّالة تستخدم السياق المسترجع جيدًا.

الأسئلة الشائعة

هل درس «بنية RAG: الفهرسة والاسترجاع» مجاني؟

نعم — نص درس «بنية RAG: الفهرسة والاسترجاع» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Engineering Academy، انتقل إلى CoddyKit PRO. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.

ماذا ستتعلم في «بنية RAG: الفهرسة والاسترجاع»؟

حدّدوا مرحلتي RAG: مرحلة الفهرسة غير المتصلة التي تقسّم المستندات وتحولها إلى embeddings وتخزّنها، ومرحلة الاسترجاع المتصلة التي تعثر على السياق ذي الصلة بكل استعلام. تتمرن على AI Engineering Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ AI Engineering Academy؟

لا تُشترط خبرة سابقة. AI Engineering Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «بنية RAG: الفهرسة والاسترجاع»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس AI Engineering Academy هذا؟

نعم. كل درس في AI Engineering Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. المشكلة التي يحلّها RAG
  2. بنية RAG: الفهرسة والاسترجاع
  3. صياغة Prompt المعزّز
  4. RAG مقابل الضبط الدقيق: متى تستخدمون كلًا منهما
← العودة إلى AI Engineering Academy