انتقل إلى المحتوى الرئيسي

الوحدة 4 — محمّلات المستندات والتقطيع

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

محمّلات المستندات: واجهة موحّدة

يُعرّف LangChain واجهة BaseLoader بمُخرَج واحد: قائمة Document، حيث كلّ مستند page_content نصّيّ وmetadata قاموس. عشرات المحمّلات تنفّذ هذه الواجهة، مع خصوصيّة كلّ صيغة:

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("politique_notes_frais_2026.pdf")
documents = loader.load()
print(len(documents), documents[0].page_content[:200])
print(documents[0].metadata) # {'source': '...', 'page': 0}

المحمّل يُنتج مستندًا واحدًا لكلّ صفحة PDF. لصيغ أخرى:

  • الويب: WebBaseLoader يجلب صفحة عبر HTTP ويُنظّف HTML.
  • المكتبيّة: UnstructuredWordDocumentLoader يقرأ .docx، UnstructuredExcelLoader يقرأ .xlsx، عبر مكتبة unstructured.
  • المسح الضوئيّ: PyPDFLoader يفشل مع PDF المسحوب ضوئيًّا؛ يُستعمَل UnstructuredPDFLoader بمعطى strategy="hi_res" لتشغيل OCR ضمنيًّا.
  • قواعد البيانات ومستودعات الشيفرة: محمّلات SQL وGitLoader وNotionDBLoader.

البيانات الوصفيّة: لماذا هي الفاصل بين RAG جيّد وسيّئ

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

  • التصفية: «أظهر لي السقف المُطبَّق منذ يناير 2026» يستدعي تصفية date_effet >= 2026-01-01.
  • الاستشهاد: كلّ إجابة مصحوبة بـ«القسم 3.2 صفحة 12 من سياسة 2026».
  • إعادة الترتيب: مصدر رسميّ يُرجَّح على منشور مدوّنة داخليّة قديم.

عمليًّا، بعد التحميل، نُثري كلّ مستند:

for doc in documents:
doc.metadata["type"] = "politique"
doc.metadata["date_effet"] = "2026-01-01"
doc.metadata["source"] = "politique_notes_frais_2026.pdf"

هذه الخطوة تبدو مملّة، لكنّها ترفع دقّة الاسترجاع بأشواط، والوحدة 5 ستُظهر كيف تُستعمَل هذه الحقول في المسترجِعات.

لماذا نُقطّع

نافذة سياق النموذج محدودة (8k، 32k، 128k رمزًا)، وأداء الاسترجاع يتحسّن حين تكون المقاطع صغيرة ومركّزة. أخيرًا، التضمين المتّجهيّ (embedding) يفقد الدقّة على نصوص طويلة جدًّا: تضمين صفحة كاملة يمزج مواضيع مختلفة في متجه واحد فيُصبح غير ذي معنى.

قاعدة إبهام: مقطع بين 300 و1000 رمز، مع تراكب 50 إلى 200 رمز. الحدّ الأدنى يضمن سياقًا كافيًا؛ الحدّ الأعلى يمنع التخفيف؛ التراكب يحفظ الجملة التي تعبر حدود المقطع.

المقسِّم التكراريّ

RecursiveCharacterTextSplitter هو المقسِّم الافتراضيّ الذي يعمل جيّدًا للنصوص الطبيعيّة:

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=100,
separators=["\n\n", "\n", ". ", " ", ""],
)
morceaux = splitter.split_documents(documents)

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

بديل مفيد للأكواد والمستندات المُهيكلة: MarkdownHeaderTextSplitter يقطع عند العناوين #، ويحفظ التسلسل الهرميّ في البيانات الوصفيّة تلقائيًّا. للأكواد: PythonCodeTextSplitter يقطع عند الدوالّ والفئات.

الفخّ: تقطيع الفواتير كتقطيع النصّ

مسحك الضوئيّ لفاتورة يُنتج نصًّا مشوّشًا: عناوين، أرقام، جدول، توقيع. تقطيعها بـRecursiveCharacterTextSplitter بحدّ 800 رمز يمزج المبلغ الإجماليّ مع رقم الضريبة والاسم التجاريّ في مقاطع لا تخدم أي استفسار. الحلّ: لا تُقطَّع الفواتير أصلًا؛ تُستَخرَج منها الحقول (montant, date, fournisseur) في مرحلة ما قبل الفهرسة، وتُخزَّن كوصف مُهيكل، ويُفهرَس النصّ الكامل كمستند واحد للبحث الاحتياطيّ.

الخيط الأحمر

نُحمّل الآن: سياسة الاسترداد (PDF مباشر، تقطيع بـRecursiveCharacterTextSplitter)، وأمثلة أحكام قديمة (Markdown من مستودع داخليّ، تقطيع بالعناوين)، وفاتورة اختبار (استخراج مباشر للحقول). ثلاث مصادر بنكهات مختلفة، لكن الواجهة النهائيّة تُنتج قائمة Document موحّدة جاهزة للفهرسة.

الفخّ الصامت

تحميل مستند ثم تجاهل حقل metadata يعطي فهرسًا يعمل في المختبر ويفشل في الإنتاج، حين يسأل مستخدم عن «آخر مراجعة». استَحضِر مصدر كلّ مقطع منذ اللحظة الأولى.

الخلاصة

  • محمّلات LangChain تُوحّد الصيغ خلف واجهة BaseLoader وتُخرج Document(page_content, metadata).
  • البيانات الوصفيّة تفتح التصفية والاستشهاد وإعادة الترتيب، وهي فاصل الجودة الحقيقيّ.
  • RecursiveCharacterTextSplitter يقطع بذكاء حول الفواصل الطبيعيّة، مع تراكب لحفظ الجمل الحدوديّة.
  • الفواتير والجداول لا تُقطَّع بنفس طريقة الفقرات؛ استَخرِج الحقول أوّلًا، وفَهرِس النصّ للبحث الاحتياطيّ فقط.