Sanad
نظام RAG يقوده وكيل، ويعرض الصفحة التي أجاب منها
يجيب Sanad عن الأسئلة المتعلقة بمجلد من الوثائق. كل معلومة في الإجابة تحمل علامة إحالة، وكل علامة تحيل إلى صفحة ومربع إحاطة (أو إلى كتلة وإزاحات أحرف): انقر عليها فينفتح الملف الأصلي مع تظليل النص المقصود بعينه. وإذا لم تتضمن الوثائق الإجابة، قال الوكيل ذلك صراحة بدل أن يخمن.
- النوع
- مشروع شخصي، 2026، من تصميمي وتنفيذي بالكامل
- يقرأ
- ملفات PDF و DOCX و PPTX و XLSX و HTML و Markdown و JSON، والنصوص والصور
- الواجهة الخلفية
- Python، FastAPI، LangGraph، ChromaDB، SQLite FTS5، PyMuPDF، SQLModel
- النماذج
- نموذج Claude للإجابات ولـ OCR بالرؤية؛ وتضمينات (embeddings) من OpenAI أو محلية (BGE و E5 و MiniLM)
- الواجهة الأمامية
- تطبيق Angular، مع عرض حي لمراحل البحث وعارض للمصادر بصيغتي PDF و HTML
- الاختبارات
- أكثر من 350 اختبارًا بـ pytest تعمل دون اتصال بالشبكة
الاسم
سَنَد، من الجذر س-ن-د: ما يُعتمد عليه ويُستند إليه. للكلمة ثلاثة معانٍ، والمشروع يقع حيث تلتقي.
- الدعامة
- السند في اللغة ما يستند إليه ويعتمد عليه. والإجابة التي لا سند لها ليست سوى جملة تبدو معقولة.
- سلسلة الرواة
- في علم الحديث، السند هو سلسلة الرواة الذين نقلوا الخبر. وكان الخبر يُقبَل أو يُرَدّ بحسب اتصال سنده إلى مصدره، لا بحسب قوة إقناعه. وهذا هو العقد الذي يلتزم به هذا النظام: كل معلومة تحمل علامة إحالة، وكل علامة تحيل إلى صفحة، وكل اقتباس لا يُعثَر عليه في المقطع الذي يستشهد به يُنبَّه إليه صراحة.
- المستند المؤيد
- في لغة التجارة، السند هو الورقة التي تثبت المعاملة: سند التسليم، سند الطلب. وهذه بالضبط هي الأوراق التي بُني Sanad لقراءتها.
كيف تصبح الوثيقة قابلة للبحث
كل ملف في مجلد المشروع يمرّ بالمسار نفسه مهما كانت صيغته. تتبّع سند تسليم مصوَّرًا.
- إدخال أو إخراج
- استدعاء LLM
- شيفرة
- مخزن بيانات
- ملف جديد سند تسليم مصور يصل إلى مجلد المشروع؛ فيضعه المراقب في قائمة الانتظار.
- تحليل إلى كتل الملف صورة، فيحيله المحلل إلى الرؤية.
- OCR بالرؤية تستخرج رؤية Claude نص السند وتصف محتواه، فتصير الصورة وثيقة كأي وثيقة أخرى.
- تحليل إلى كتل يعود الملف في صورة كتل، مع الحفاظ على العناوين والجداول والسجلات. والملفات التي ليست بصيغة PDF تحول أيضًا إلى HTML بمعرف ثابت لكل كتلة، من أجل العارض.
- التقطيع العناوين والسجلات تبدأ مقاطع جديدة؛ ويجمع النص في مقاطع من نحو 700 رمز مع تداخل بنسبة 15%، ولا يشطر صف جدول إلى نصفين أبدًا.
- إضافة السياق نموذج منخفض التكلفة يكتب جملة أو جملتين تحددان موقع كل مقطع من وثيقته، من أجل التضمين فقط. وتخزن مؤقتًا بحسب بصمة الملف.
- التضمين يُحسب التضمين بنموذج المشروع؛ ويذهب النص نفسه إلى SQLite FTS5 للبحث بالكلمات المفتاحية.
- ChromaDB الحفظ في المجموعة الخاصة بهذا المشروع وهذا النموذج، فلا تختلط فضاءات المتجهات أبدًا. والعمل الموقوف مؤقتًا يُستأنف من المقطع الذي توقف عنده.
هذا الطلب لا يمرّ من هنا. المسارات الأخرى مذكورة في الأسفل.
هذا هو الطلب كاملًا. هل تريد شيئًا كهذا يعمل على بياناتك؟
اختر أي خانة في المخطط، أو تتبّع الطلب خطوة بخطوة.
-
تحليل كل الصيغ إلى نوع واحد من الكتل
ملفات PDF و Word و PowerPoint (مع ملاحظات المحاضر) و Excel وصفحات الويب المحفوظة و Markdown و JSON والصور تتحول كلها إلى النوع نفسه من الكتل، مع الحفاظ على العناوين والجداول والسجلات. أما الوثائق الممسوحة ضوئيًّا والصور الملتقطة بالكاميرا فتمر عبر رؤية Claude، فيستخرج نصها ويوصف محتواها، وبذلك يصير سند تسليم مصور وثيقة كأي وثيقة أخرى.
-
التقطيع بحسب البنية، ثم بحسب الرموز
العناوين وأوراق العمل والسجلات تبدأ مقاطع جديدة أولًا؛ ثم يجمع النص في مقاطع من نحو 700 رمز (token) مع تداخل بنسبة 15% لا يقطع جملة أبدًا. والجدول مقطع مستقل دائمًا؛ فإن كان كبيرًا قُسِّم بحسب الصفوف مع تكرار ترويسته في كل جزء، فلا يشطر صف إلى نصفين أبدًا.
-
وضع كل مقطع في سياق وثيقته
الاسترجاع السياقي (Contextual Retrieval): نموذج منخفض التكلفة يكتب جملة أو جملتين تحددان موقع كل مقطع من وثيقته، وتضافان إلى مقدمة النص الذي يحسب تضمينه فقط، لا إلى ما يراه المستخدم ولا إلى النص الذي تقارن به الاستشهادات. وهكذا يصير صف نصه «| 80 | 6.00 | 480.00 |» قابلًا للعثور عليه بسؤال عن أسعار الملحقات. وتخزن هذه الجمل مؤقتًا بحسب بصمة الملف (hash)، فإعادة استيعابه لا تكلف شيئًا.
-
تحويل مرة واحدة، واستشهاد على الدوام
كل ملف ليس بصيغة PDF يحول عند استيعابه إلى HTML مع معرف ثابت لكل فقرة وصف وسجل. فلا يحتاج العارض بعدها إلا إلى مسارين، PDF و HTML، وتتطابق إزاحات أحرف المقطع مع النسخة المحولة لأن الاثنين ناتجان عن عملية التحليل نفسها.
-
التضمين في فهرسين
تذهب المتجهات إلى ChromaDB، في مجموعة (collection) واحدة لكل مشروع ونموذج تضمين، فتبديل النموذج لا يخلط فضاءات المتجهات أبدًا. ويذهب النص نفسه إلى SQLite FTS5 للبحث بالكلمات المفتاحية.
-
إبقاء الفهرس مطابقا للمجلد
لكل مشروع مجلده. مراقب يستوعب الملفات فور ظهورها، وأداة مطابقة تعيد فحص المجلد عند بدء التشغيل، وشريط تقدم يتابع كل ملف عبر التحليل والتقطيع وإضافة السياق والتضمين. ويمكن إيقاف أي ملف، أو قائمة الانتظار كلها، مؤقتًا ثم استئنافه من المقطع الذي توقف عنده، دون دفع تكلفة أي استدعاء للرؤية أو لنموذج لغوي كبير (LLM) مرتين، حتى بعد إعادة التشغيل.
بحثان، وترتيب واحد
التضمينات تجيد التعميم لكنها تخطئ السلاسل النصية الدقيقة؛ فالسؤال عن مرجع قطعة أو إجمالي فاتورة أو رمز مثل VT-COB/SKU98496 سؤال لفظي. هذه الأسئلة يلتقطها BM25، أما التضمينات فتلتقط سؤالًا مثل «ماذا استنتج التقرير بشأن النمو؟». والاثنان يعملان في كل عملية بحث.
درجات تشابه جيب التمام ودرجات BM25 تقع على سلمين غير متوافقين، ولذلك تدمج بطريقة Reciprocal Rank Fusion التي لا تحتاج إلا إلى الرتب: لا شيء يعاير لكل مجموعة وثائق، وتبقى صحيحة حين لا يعيد أحد المسترجعين شيئًا. بعد ذلك يقرأ مُرمِّز متقاطع (cross-encoder) كل مرشح من المرشحين الـ 20 مع السؤال، وهذا أدق بكثير، لكن تكلفته لا تحتمل إلا على قائمة قصيرة. ثم يرسل إلى النموذج القسم الأب لكل مقطع: فالمقطع هو ما طابق السؤال، والقسم هو ما يجيب عنه.
التضمينات تعثر على المقاطع المتقاربة في المعنى، ولو اختلفت الألفاظ.
المقاطع الستة الباقية تُرسل إلى النموذج مع أقسامها الأم، وكلٌّ منها موسوم بالملف والصفحة والقسم ليتمكّن من الاستشهاد بها.
وكيل يعرف متى يمتنع
وكيل LangGraph بدل RAG ذي البحث الواحد، لأن الأسئلة المتعدّدة الأجزاء تحتاج إلى أكثر من بحث، ولأن «بحثتُ والجواب غير موجود» خلاصة تستحق أن نصل إليها عن قصد.
- إدخال أو إخراج
- استدعاء LLM
- شيفرة
- السؤال «والثاني؟»، وقد طُرح مباشرة بعد سؤال عن مرجعي منتجين.
- إعادة الصياغة تعتمد على آخر ستة أدوار ليصير السؤال مستقلًّا بذاته: «سعر الوحدة للمرجع 40564101 في produits.xlsx».
- التخطيط يصنف الدور على أنه بحث عن معلومة، فلا شيء يحتاج إلى تفكيك. ولو كان دردشة عابرة لانتهى المسار هنا دون استرجاع.
- الوكيل الجولة الأولى ملزمة بالبحث، فلا يستطيع النموذج أن يجيب من ذاكرته. فيستدعي أداة البحث.
- بحث هجين يعيد كل من BM25 والبحث بالمتجهات 30 مرشحًا؛ فيبقي Reciprocal Rank Fusion على 20 منها، ثم يبقي المُرمِّز المتقاطع على 6 تُرسَل في صورة أقسامها الأب.
- الوكيل صارت لديه أدلة، فيتوقف عن استدعاء الأدوات.
- النقد يقدّر هل تجيب الأدلة عن السؤال: كافية.
- الإجابة تكتب بصيغة JSON: نص الإجابة مع علامة إحالة على كل معلومة، واقتباس حرفي لكل استشهاد.
- ربط الاستشهادات يعثر على كل اقتباس في مقطعه (مطابقة حرفية، ثم بعد التوحيد، ثم تقريبية) ويربطه بإحداثيات على الصفحة. ودون الدرجة 72 يعرض على أنه غير متحقق منه.
- إجابة موثَّقة الإجابة، ومع كل معلومة شارة مصدر؛ والنقر على إحداها يفتح الملف مع تظليل الأسطر المقتبسة.
هذا الطلب لا يمرّ من هنا. المسارات الأخرى مذكورة في الأسفل.
هذا هو الطلب كاملًا. هل تريد شيئًا كهذا يعمل على بياناتك؟
اختر أي خانة في المخطط، أو تتبّع الطلب خطوة بخطوة.
مسارات أخرى في المخطط
- دردشة عابرة: عقدة التخطيط توجه إلى رد مباشر وينتهي المخطط، دون استرجاع.
- الأسئلة المتعددة الخطوات وأسئلة المقارنة: تفكك إلى ما بين 2 و4 أجزاء، ويبحث عن كل جزء على حدة.
- نُفِّذت الأدوات ولم تعثر على شيء: يبحث الوكيل مرة أخرى بصياغة مختلفة.
- النقد يرى الأدلة غير كافية: عودة إلى الوكيل باستعلام أفضل، بحد أقصى 3 جولات في المجموع.
- النموذج يتوقف عن استدعاء الأدوات، أو يبلغ حد الجولات، أو يقع خطأ: انتقال مباشر إلى الإجابة، وهي تذكر ما ينقص.
عقدة الإجابة تُرجع JSON منظَّمًا. كل معلومة تحمل علامة، وكل اقتباس يُنسخ حرفًا بحرف ليمكن العثور عليه من جديد:
{
"answer_markdown": "Le total est 82 900,00 DH [1], conforme au bon de livraison [2].",
"citations": [
{ "marker": 1, "source": 1, "quote": "Total : 3 240 unites - 82 900,00 DH" }
],
"sufficient": true
} كل استشهاد يُتحقَّق منه
النماذج تقتبس شبه حرفيًّا: تعدّل علامات التنصيص، وتدمج المسافات، وتُسقط نقطة أخيرة. لذا يُحدَّد موضع كل اقتباس في مقطعه الأصلي على ثلاث مراحل، والدرجة تقرّر طريقة عرضه.
| المرحلة | الدرجة |
|---|---|
| تطابق حرفي لجزء من النص | 100 |
| تطابق بعد التوحيد (المسافات، علامات التنصيص المنحنية، الشرطات) | 97 |
| نافذة منزلقة بمطابقة تقريبية، ثم تدقيق | نسبة التطابق |
في ملف PDF يتحوّل الاقتباس إلى صفحة ومربعات إحاطة، مع تجربة أجزاء أقصر حين يعترضه فاصل سطر؛ وفي بقية الصيغ يتحوّل إلى معرّف كتلة ومواضع أحرف في نسخة HTML. دون الدرجة 72 يظلّل العارض المقطع كله: تظليل واثق على السطر الخاطئ أسوأ من تظليل تقريبي.
-
استشهادات متحقق منها
الاقتباس الذي يطابق مقطعه بدرجة 72 أو أكثر يُعَدّ متحققًا منه. وما دون ذلك يعرض باللون الكهرماني مع وسم «غير متحقق منه»، وتحمل الإجابة شارة «2 من 3 مصادر متحقق منها». ولا تحذف الاستشهادات غير المتحقق منها أبدًا: فالتطابق الناقص قليلًا يكون في الغالب مصدرًا حقيقيًّا أعيدت صياغته بتصرف يسير، وإخفاؤه يخفي مشكلة في الاسترجاع.
-
أرقام بلا استشهاد
تُحصى كل جملة تذكر رقمًا دون علامة إحالة. ففي الفواتير وسندات التسليم، الإجمالي المختلق هو الخطأ الأخطر، وعدد الاستشهادات وحده لا يكشفه.
-
علامات يتيمة
كل علامة إحالة في النص لا يقابلها مصدر تُرصَد وتُسجَّل.
مَقيسٌ لا مُدَّعًى
تقيس منظومة التقييم الاسترجاع بمؤشرات recall@k و MRR و nDCG لكل فئة، وتقيس الإجابات الكاملة بمدى استنادها إلى المصادر (groundedness) وبدقة الامتناع عن الإجابة. والتشغيلات المقتصرة على الاسترجاع لا تستدعي أي LLM، فتجربة ستة نماذج تضمين لا تكلف شيئًا. والأسئلة المرجعية تتضمن عمدًا أسئلة لا جواب لها في الوثائق: فالنظام الذي يجيب عن كل شيء يحقق درجة كاملة في recall وهو عديم الفائدة.
| طريقة الاسترجاع | all-MiniLM-L6-v2 | bge-small-en-v1.5 |
|---|---|---|
| المتجهات فقط | 0.53 | 0.81 |
| BM25 فقط | 0.57 | 0.57 |
| هجين + إعادة ترتيب | 0.94 | 0.94 |
السُلَّم من 0 إلى 1؛ كلما ارتفعت القيمة كان المقطع الصحيح أقرب إلى الصدارة.
يبين الجدول لماذا يهم تحديد المسترجع الذي يُقاس. فعبر مسار المعالجة الكامل يحصل نموذج تضمين ضعيف ونموذج قوي على الدرجة نفسها، لأن BM25 ومعيد الترتيب ينقذان الضعيف. لاختيار نموذج تضمين، قس المتجهات وحدها؛ وللحكم على النظام، قسه كاملًا. والمقارنات تُجرى سؤالًا بسؤال، فيظهر المتوسط الثابت الذي يخفي تقلبات في الاتجاهين على حقيقته: تراجع في الأداء.
أضعف الفئات هي التجميع (recall@6 يساوي 0.50 و MRR يساوي 0.10 مع MiniLM): فنظام RAG يسترجع مقاطع، ولا يجمع ثلاثة إجماليات من ثلاثة ملفات. والحل أداة حساب تعمل على صفوف الجداول المسترجعة، وقد وُجدت منظومة التقييم كي تُبنى هذه الأداة على رقم لا على زعم.
خيارات هندسية
-
النماذج خيار يحدد وقت التشغيل
نموذج التضمين يُختار لكل مشروع (مجلد من سندات التسليم بالفرنسية والعربية يناسبه BGE-M3، والوثائق الإنجليزية أداؤها أفضل مع BGE-base)؛ أما نموذج الإجابة فيُضبَط على مستوى النظام كله. ويمكن تبديل الاثنين دون إعادة تشغيل، وتجري إعادة الفهرسة في الخلفية بينما تواصل المحادثة الإجابة بالنموذج القديم.
-
النماذج الإنجليزية فقط موسومة بوضوح
هذه النماذج لا تفشل فشلًا ظاهرًا مع العربية أو الفرنسية، بل تسترجع بثقة نتائج لا معنى لها، ولذلك تميزها قائمة الاختيار باللون الكهرماني.
-
نموذج واحد للاستعلام وللوثائق
يُحدَّد النموذج مرة واحدة لكل استعلام، ثم يُستخدَم في حساب التضمين وفي تكوين اسم المجموعة معًا. وإذا خالفته البيانات الوصفية للمجموعة، رفع البحث خطأ بدل أن يعيد مقاطع خاطئة.
-
الوثيقة الموقوفة مؤقتًا غير مرئية
الاحتفاظ بالعمل المدفوع ثمنه أثناء الإيقاف المؤقت يجب ألا يجعل نصف وثيقة قابلًا للإجابة منه، ولذلك تنتظر المتجهات في منطقة تخزين مؤقتة (spool) ثم تصل إلى الفهرس في عملية كتابة واحدة.
-
يعرض خطوات عمله
كل عملية بحث تعرض مراحلها مباشرة (التضمين، البحث الدلالي، البحث بالكلمات المفتاحية، الدمج، إعادة الترتيب)، ثم تطوى تحت الإجابة في قسم «كيف عُثر على هذا»، وتحفظ مع الرسالة.
-
يعمل دون اتصال
مع نموذج تضمين محلي، لا يحتاج الاستيعاب ولا الاسترجاع إلى أي مفتاح API للتضمين؛ وتوليد الإجابة وحده يستدعي LLM. وكل استدعاء لنقطة وصول وكل إجراء داخلي يسجل في قاعدة البيانات.
التقنيات
- الوكيل
- LangGraph، استدعاء الأدوات، إجابات JSON منظمة، نقاط حفظ SQLite، مطابقة الاقتباسات بـ rapidfuzz
- الاسترجاع
- ChromaDB، SQLite FTS5 (BM25)، Reciprocal Rank Fusion، bge-reranker-v2-m3
- الاستيعاب
- PyMuPDF، python-docx / python-pptx / openpyxl، Claude vision OCR، tiktoken، watchdog
- الواجهة الخلفية
- Python، FastAPI، Server-Sent Events، SQLModel، Alembic
- الواجهة الأمامية
- Angular 20 (signals)، Tailwind CSS، قارئ Server-Sent Events
- الجودة
- pytest، مجموعات أسئلة مرجعية، recall@k, MRR, nDCG, groundedness
هل تبني شيئًا مشابهًا؟
يسعدني الحديث عن الاسترجاع في وثائق الأعمال غير المرتّبة، والاستشهاد القابل للتدقيق، أو كيف نقيّم نظام RAG قبل الوثوق به.