مشروع: مساعد مستندات عربية
درس 10 من 10
10مشروع: مساعد مستندات عربية
الصورة الكبيرةفي هذا المشروع تبني مساعدًا يجيب عن أسئلة من مستندات عربية حقيقية، ويستشهد بالمصدر بعد كل معلومة، ويرفض حين لا يعرف. والأهم: كل تغيير تجريه عليه يأتي برقم قبله ورقم بعده، في جدول تجارب تستطيع أن تدافع عنه.
ماذا تشاهد في الرسم: هذا خط المشروع الأول، والمحطة المحاطة بخط متقطّع هي ما يميّزه: قبل أي إجابة يسأل النظام هل وجد شيئًا قريبًا بما يكفي. «ما سعر وجبة الغداء؟» توقّف هنا ورُفض، بينما «كم يوم اجازه سنويه؟» عبر وعاد بمعرّف [leave.md#1].
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
تبني مساعدًا يجيب عن أسئلة من ملفات عربية حقيقية، ويذكر في كل جواب الصفحة التي أخذ منها، مثل طالب يكتب المرجع تحت كل اقتباس. وقبل أي تحسين تقيس النتيجة بأسئلة ثابتة، ثم تقيسها بعده، فتعرف بالرقم هل تحسّن.
خط كامل: تحميل وتطبيع وتقطيع وتضمين وفهرسة في pgvector، ثم بحث هجين ومُعيد ترتيب، ثم توليد بإسناد المصادر ورفض عند غيابها. مع مجموعة ذهبية وجدول يسجّل Recall@k والأمانة قبل كل تحسين وبعده.
قبل أن تبدأ
🔤العربية في الاسترجاع
عندك دالة تطبيع واحدة تمرّ عليها المستندات والأسئلة.
🚧حدود النظام
تعرف كيف تختار عتبة الرفض من بيانات داخل النطاق وخارجه.
📊مقاييس الاسترجاع
تستطيع حساب الاستدعاء والأمانة، وهما عمودان في جدول تجاربك.
الفكرة الجوهرية
في هذا المشروع تبني مساعدًا يجيب من مستندات عربية حقيقية ويستشهد بمصادره. تبدأ بالمستندات والأسئلة الذهبية، ثم الفهرسة، ثم الخط الكامل، ثم جدول التجارب.
1المستندات والأسئلة أولًا
اختر مستندات عربية حقيقية تعرف محتواها، بين 20 و100 مستند. ثم اكتب الأسئلة الذهبية قبل أي كود.
انظر إلى الرسم: خمسون سؤالًا، عشرة منها خارج المستندات، وخمسة مكتوبة بأخطاء شائعة تكشف أي مسار نسي التطبيع.
2الفهرسة
كل قطعة تُخزَّن بنسختين: الأصل للعرض والاستشهاد، والمطبّعة للبحث. ولكل قطعة معرّف مثل leave.md#1 تستشهد به الإجابة.
انظر إلى الرسم: المستند يُقطَّع بالعناوين، ثم تأخذ كل قطعة نسختيها ومعرّفها.
3إجابة بمصدر أو رفض
المساعد يجيب من المستندات فقط، ويضع معرّف القطعة بعد كل معلومة حتى يتحقّق المستخدم. وحين تكون أعلى درجة تحت العتبة يرفض برسالة مفيدة بدل أن يخمّن.
خمس قطع، تطبيع عربي، استرجاع، استشهاد بالمعرّف، ورفض تحت العتبة. هنا «التوليد» يعيد القطعة الأفضل كما هي، وفي مشروعك يحلّ محلّه نموذج لغوي بقالب درس هندسة السياق.
import re
from sklearn.feature_extraction.text import TfidfVectorizer
docs = {
"leave.md#1": "يحق للموظف إجازةٌ سنويةٌ مدتها واحد وعشرون يوم عمل.",
"leave.md#2": "تُقدَّم طلبات الإجازة قبل أسبوعين على الأقل.",
"leave.md#3": "الإجازة المرضية تحتاج تقريراً طبياً إذا زادت عن يومين.",
"travel.md#1": "تُصرف بدلات السفر خلال ١٠ أيام من تقديم الفواتير.",
"it.md#1": "رمز الخطأ E-1043 في بوابة الحجز يعني انتهاء الجلسة.",
}
def normalize(t):
t = re.sub(r"[ً-ْٰـ]", "", t)
t = re.sub("[إأآٱ]", "ا", t).replace("ى", "ي").replace("ة", "ه")
return t.translate(str.maketrans("٠١٢٣٤٥٦٧٨٩", "0123456789"))
ids = list(docs)
vec = TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 4))
matrix = vec.fit_transform([normalize(docs[i]) for i in ids])
def answer(question, threshold=0.25):
scores = (matrix @ vec.transform([normalize(question)]).T).toarray().ravel()
best = scores.argmax()
if scores[best] < threshold:
return f"لا أجد هذا في المستندات المتاحة (أعلى تشابه {scores[best]:.2f})."
return f"{docs[ids[best]]} [{ids[best]}]"
for q in ["كم يوم اجازه سنويه؟", "متى تصرف بدلات السفر؟", "ما سعر وجبة الغداء؟"]:
print(q, "\n ", answer(q))
السؤال الأول مكتوب بلا همزة وبهاء بدل التاء، ووجد المستند لأن الجهتين مطبّعتان. والإجابة تحمل معرّفها [leave.md#1]، فيستطيع المستخدم التحقّق. والسؤال الثالث خارج المستندات، فرفض النظام بدل أن يقدّم أقرب شيء. العتبة 0.25 هنا للتوضيح، وفي مشروعك تختارها بطريقة درس حدود النظام.
4جدول التجارب
ابنِ أبسط نسخة تعمل من أولها إلى آخرها وسجّل رقمها. ثم أضف تحسينًا واحدًا في كل صف، على نفس المجموعة الذهبية.
انظر إلى الرسم: نفس الاسترجاع بأربعة إعدادات على ثمانية أسئلة. الجدول يجيب، لا الحدس.
ثماني أسئلة ذهبية، ونفس الاسترجاع بأربعة إعدادات: كلمات أو مقاطع حروف، مع التطبيع وبدونه.
import re
from sklearn.feature_extraction.text import TfidfVectorizer
docs = ["يحق للموظف إجازةٌ سنويةٌ مدتها واحد وعشرون يوم عمل.",
"تُقدَّم طلبات الإجازة قبل أسبوعين على الأقل.",
"الإجازة المرضية تحتاج تقريراً طبياً إذا زادت عن يومين.",
"يوافق المدير المباشر على الإجازات.",
"تُصرف بدلات السفر خلال ١٠ أيام من تقديم الفواتير.",
"يجب حجز تذاكر السفر عبر بوابة الحجز.",
"رمز الخطأ E-1043 في بوابة الحجز يعني انتهاء الجلسة.",
"العمل عن بعد مسموح يومين في الأسبوع."]
golden = [("كم يوم اجازه سنويه؟", 0), ("متى اقدم طلب الاجازه؟", 1), ("متى احتاج تقرير طبي؟", 2),
("من يوافق على اجازتي؟", 3), ("متى تصرف بدلات السفر خلال 10 ايام؟", 4),
("كيف احجز تذكرة سفر؟", 5), ("E-1043", 6), ("هل العمل من البيت مسموح؟", 7)]
def normalize(t):
t = re.sub(r"[ً-ْٰـ]", "", t)
t = re.sub("[إأآٱ]", "ا", t).replace("ى", "ي").replace("ة", "ه")
return t.translate(str.maketrans("٠١٢٣٤٥٦٧٨٩", "0123456789"))
def run(analyzer, norm):
f = normalize if norm else (lambda s: s)
vec = TfidfVectorizer(**analyzer)
m = vec.fit_transform([f(d) for d in docs])
hits = sum(int((m @ vec.transform([f(q)]).T).toarray().ravel().argmax() == t) for q, t in golden)
return hits
words = dict(analyzer="word", token_pattern=r"[^\s.،؟]+")
chars = dict(analyzer="char_wb", ngram_range=(2, 4))
print("experiment top-1")
for name, a, n in [("words, raw text", words, False), ("words, normalised", words, True),
("char n-grams, raw", chars, False), ("char n-grams, normalised", chars, True)]:
print(f"{name:30} {run(a, n)}/{len(golden)}")
التطبيع رفع البحث بالكلمات من 5 إلى 6، ومقاطع الحروف أصابت الثماني مع التطبيع وبدونه، لأنها تتحمّل اختلافات الإملاء الصغيرة بطبيعتها. هل يعني هذا أن التطبيع لا يلزم مع مقاطع الحروف؟ في هذه الأسئلة الثمانية نعم، وفي مستنداتك لا تعرف حتى تقيس. هذا هو الدرس: الجدول يجيب، لا الحدس.
كيف يعمل، خطوة بخطوة: كيف تبني المساعد خطوة بعد خطوة
تكتب 50 سؤالًا ذهبيًّا قبل أي كود، منها 10 خارج المستندات و5 مكتوبة بأخطاء شائعة.
تبني أبسط نسخة تعمل من أولها لآخرها: بحث بالكلمات، وقالب بسيط، ونموذج واحد، وتسجّل رقمها.
تضيف تحسينًا واحدًا في كل صف: التطبيع، ثم التقطيع الأفضل، ثم المتّجهات، ثم الهجين، ثم إعادة الترتيب.
تعتمد الإعداد الذي يبرّره الجدول، وتكتب جملة تشرح لماذا اخترته مقابل تكلفته وسرعته.
مثال محلول
اختر مجموعة مستندات عربية حقيقية تعرفها: لائحة جامعتك، أو سياسات جهة عملك، أو أدلّة استخدام منتج. بين عشرين ومئة مستند. ثم اكتب المجموعة الذهبية قبل أي كود، كما في درس التقييم أولًا.
جرّب بلا كود
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| ابدأ بأقوى خط ممكن: هجين مع إعادة ترتيب. | بلا رقم أساسي لن تعرف ما أضافه كل تحسين، وكثيرًا ما تكتشف أن بعض التعقيد لا يضيف شيئًا لمستنداتك. |
| التطبيع ضروري دائمًا مهما كان أسلوب البحث. | في الجدول الصغير أصابت الحروف المتتالية 8 من 8 بالتطبيع وبدونه، ورفعه بحث الكلمات من 5 إلى 6. على مستنداتك لا تعرف حتى تقيس. |
| تغيير شيئين معًا يوفّر الوقت. | تغيير يضيف ثلاث نقاط وآخر ينقص اثنتين يبدوان معًا «نقطة أفضل»، فتُبقي التغيير الضار دون أن تعرف. |
جرّب بنفسك
ما أول نسخة تبنيها في هذا المشروع؟
بدون رقم أساس لن تعرف ماذا أضاف كل تحسين. وكثيرًا ما تكتشف أن النسخة البسيطة أفضل مما توقّعت، وأن بعض التعقيد لا يضيف شيئًا.
لماذا تغيّر شيئًا واحدًا في كل صف من جدول التجارب؟
تغيير يرفع الرقم بثلاث نقاط وآخر يخفضه بنقطتين يبدوان معًا «تحسّنًا بنقطة»، وأنت تبقي التغيير الضار دون أن تدري.
بطاقات مراجعة سريعة
تحقق من فهمك
أضفت إعادة الترتيب فارتفعت الصحة من 82% إلى 84% وتضاعف زمن الاستجابة. هل تبقيها؟
يعتمد على مستخدميك، وهذا قرار تكتبه في الجدول لا تخفيه. فرق نقطتين قد يكون ضمن التذبذب، فأعد التشغيل أولًا. إن ثبت، فاسأل: هل يقبل المستخدمون انتظارًا أطول مقابل إجابة صحيحة إضافية من كل خمسين؟ في نظام دعم داخلي غالبًا نعم، وفي واجهة دردشة سريعة ربما لا.
لماذا تطلب من ثلاثة أشخاص لا يعرفون النظام أن يسألوه عشرة أسئلة لكل منهم؟
لأن أسئلتهم تكشف صيغًا ومواضيع لم تتوقّعها مجموعتك الذهبية، فتضيف أفضلها إليها ويصير رقمك أقرب إلى ما سيلقاه النظام فعلًا.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.