تنتهي معظم دروس RAG عند مرحلة العرض التجريبي (demo). تقوم بتقسيم النص بناءً على عدد الرموز (token count)، وتضع كل شيء في قاعدة بيانات متجهية (vector database)، وتعتبر المهمة قد أُنجزت. هذا الأسلوب ينجح عندما يسأل المستخدم "ما هي سياسة الإرجاع؟" في قسم أسئلة شائعة منظم. لكنه ينهار عندما يقوم شخص ما بلصق نصف عقد ويسأل عن البند الثالث، أو عندما يكتب مطور رمز خطأ غامض في بحث التوثيق الخاص بك.

تؤدي نوافذ الرموز (token windows) الثابتة إلى قطع الاتفاقيات القانونية في منتصف الجملة. كما أن الأجزاء (chunks) الكبيرة تدفن مراجع الـ API تحت فقرات من الضجيج. والأسوأ من ذلك كله، أن عملية الاسترجاع البطيئة تجعل المستخدمين يتخلون عن الاستعلام قبل أن يبدأ النموذج حتى في التوليد. لقد تعلمنا هذا بالطريقة الصعبة. عندما نقلنا طبقة الاسترجاع لدينا من مرحلة "التمني" إلى مرحلة "القياس"، خفضنا زمن الاستجابة (latency) بنسبة أربعين بالمائة ورفعنا نسبة الاستدعاء (recall) إلى خمسة وتسعين بالمائة. إليكم بالضبط ما تغير.

التقسيم الذكي

توقف عن التفكير في حجم الجزء (chunk size) كرقم سحري. فنافذة مكونة من 512 رمزاً (token) لا معنى لها في وثيقة قانونية حيث يمتد بند واحد عبر فقرات متعددة، وهي عديمة الفائدة بنفس القدر في وثائق الـ API حيث يجب أن تظل توقيعات الدوال (function signatures) ووصفها المكون من سطرين معاً. لقد انتقلنا إلى التقسيم المدرك للبنية (structure-aware splitting).

بالنسبة للنصوص القانونية، يحترم التقسيم المتكرر (recursive chunking) التسلسل الهرمي للوثيقة، مما يحافظ على سلامة البنود. أما بالنسبة لتوثيق الـ API، فنحن نستخدم التقسيم المدرك للدوال (function-aware splitting) الذي يبقي التوقيعات والمعاملات (parameters) والأمثلة معاً كوحدات ذرية (atomic units). أما تذاكر الدعم والبيانات الحوارية فتحتاج إلى حدود دلالية (semantic boundaries)، بحيث يتم التقسيم عند تحول الموضوع بدلاً من التقسيم عند عدد عشوائي من الحروف. والنتيجة هي أن كل جزء يحمل سياقاً كافياً ليكون مفيداً، ولكن ليس لدرجة تؤدي إلى إضعاف الإشارة (signal). إن نموذج التضمين (embedding model) الخاص بك لديه ميزانية انتباه محدودة؛ فاستخدمها بحكمة.

الاسترجاع الهجين

يتفوق البحث المتجهي (Vector search) في العثور على المحتوى المتشابه مفاهيمياً؛ فإذا سألت عن استعلامات قاعدة البيانات البطيئة، فسيظهر لك أدلة تحسين الأداء. ولكن إذا طلبت "Error 0x80070057"، فإن البحث الدلالي (semantic search) سينحرف إلى مناطق غير ذات صلة لأن التضمينات الكثيفة (dense embeddings) لا تتعامل جيداً مع المطابقات الدقيقة. ومن ناحية أخرى، يبرع خوارزم BM25 في تحديد السلاسل النصية الدقيقة والمصطلحات النادرة، ومع ذلك، فإنه لا يدرك أن "latency" و"slow response time" يعنيان الشيء نفسه.

نحن نقوم بتشغيل كليهما بالتوازي وندمجهما باستخدام تقنية Reciprocal Rank Fusion (RRF). إن RRF بسيطة وفعالة؛ فهي تأخذ القوائم المرتبة من كل طريقة وتمنح درجات للوثائق بناءً على موقعها، مما يعطي المرشحين الأقوياء من أي من النظامين فرصة عادلة. بعد الدمج، نقوم بتشغيل إعادة ترتيب (reranker) باستخدام cross-encoder على النتائج المدمجة ونعيد أفضل خمسة نتائج فقط. تضيف عملية إعادة الترتيب حوالي خمسين مللي ثانية من زمن الاستجابة (latency)، لكنها حسنت نسبة الاستدعاء (recall) لدينا بنسبة خمسة عشر بالمائة. وهذه المقايضة تستحق العناء وتؤتي ثمارها أضعافاً مضاعفة في جودة التوليد.

توسيع الاستعلام

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

نحن الآن نقوم بتحويل كل استعلام قبل وصوله إلى الفهرس (index). أولاً، نقوم بإنشاء نسخ متعددة معاد صياغتها من السؤال الأصلي لتغطية المرادفات والصياغات البديلة. ثانياً، نقوم بتفكيك الأسئلة المعقدة إلى أسئلة فرعية أصغر. فاستعلام مثل "لماذا تفشل عملية استرداد الأموال للعملاء الدوليين وكيف يمكنني إصلاح ذلك؟" يصبح بحثين منفصلين: أحدهما عن فشل استرداد الأموال دولياً والآخر عن خطوات الإصلاح. أدى توسيع الاستعلام وحده إلى رفع نسبة الاستدعاء لدينا من ثمانية وسبعين بالمائة إلى أربعة وتسعين بالمائة. الدرس بسيط: لا تثق في المسودة الأولى للمستخدم، بل ساعده.

توقف عن التخمين، وابدأ بالبحث الحقيقي

يتفاعل حجم الجزء (chunk size)، ونسبة التداخل (overlap percentage)، وحد الـ top-k، وعمق إعادة الترتيب (reranker depth) بطرق يستحيل ضبطها يدوياً. لقد أضعنا الكثير من الوقت في الجدال حول ما إذا كان 256 رمزاً أفضل من 512، بينما كنا نتجاهل إعداد التداخل الذي كان يدمر الترابط الفعلي للنصوص.

لقد استبدلنا الحدس بالتحسين البايزي (Bayesian optimization). فبدلاً من البحث الشبكي (grid search)، الذي يهدر القدرة الحسابية في مناطق سيئة بوضوح، تبني الأساليب البايزية نموذجاً احتمالياً لما ينجح، وتبحث بنشاط عن "جبهة باريتو" (Pareto frontier) التي تزيد من نسبة الاستدعاء (recall) إلى أقصى حد مع تقليل زمن الاستجابة (latency) إلى أدنى حد. بالنسبة لمجموعتنا التقنية (stack)، كان ذلك يعني العثور على المزيج المحدد من حجم الجزء، والتداخل، والـ top-k الذي منحنا نسبة استدعاء بلغت خمسة وتسعين بالمائة دون تجاوز ميزانية زمن الاستجابة المتاحة لنا. وقد استقرت حالات الاستخدام المختلفة عند نقاط مختلفة على طول تلك الجبهة؛ حيث أعطت روبوتات الدردشة الموجهة للعملاء الأولوية للسرعة، بينما أعطت الأبحاث القانونية الداخلية الأولوية للاستدعاء. وقد سمح لنا التحسين الآلي بخدمة كليهما دون الحاجة إلى النسخ واللصق اليدوي لملفات الإعدادات (config files).

النتائج

الأرقام تتحدث بوضوح. ارتفع معدل Recall@10 لدينا من ثمانية وسبعين بالمئة إلى خمسة وتسعين بالمئة. وانخفض زمن الاستجابة p95 من 850 ميلي ثانية إلى 320 ميلي ثانية. ولأن النموذج بدأ أخيراً في تلقي سياق ذي صلة بدلاً من الضجيج، انخفض معدل الهلوسة من اثني عشر بالمئة إلى ثلاثة بالمئة. الاسترجاع الأفضل لا يجعل الإجابات أسرع فحسب، بل يجعلها صحيحة أيضاً.

ما الخطوة التالية

إذا كنت تعيد بناء طبقة الاسترجاع الخاصة بك، فابدأ من هنا:

  • قم بالتقسيم (chunking) بناءً على بنية المستند، وليس عدد الرموز (token count). اجعل استراتيجية التقسيم متوافقة مع شكل بياناتك.
  • استخدم الاسترجاع الهجين (hybrid retrieval). ادمج بين البحث الشعاعي (vector search) وBM25، واستخدم تقنية Reciprocal Rank Fusion للدمج، ثم أعد الترتيب (rerank) قبل عملية التوليد.
  • قم بتوسيع الاستعلامات لتحقيق تغطية أفضل. أعد صياغة الاستعلامات وفككها قبل بدء عملية البحث.
  • قم ببناء مجموعة بيانات ذهبية (golden dataset) للاختبار. لا يمكنك تحسين ما لا يمكنك قياسه.
  • قم بتحسين المعلمات باستخدام أدوات مؤتمتة. سيعثر البحث البايزي (Bayesian search) على إعدادات أفضل مما قد يتوقعه حدسك.

الاسترجاع ليس مجرد ملف إعدادات تضبطه مرة واحدة ثم تنساه. إنه بنية تحتية، والبنية التحتية تستحق نفس الدقة التي يتطلبها كود الإنتاج: الاختبارات، وال