انتقل للمحتوى
كل دراسات الحالة
مساعد RAG للتحليلاتمدة القراءة: 9 دقايق

Sanad AI: إجابات بالعربي والإنجليزي مبنية على تقارير Twiscope

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

قائد فريق برمجيات · ASAS IT · مسؤول عن الـ repository ومراجعة الكود
نطاقات للـ chat
3
أنواع تقارير قابلة للـ routing
7
retrieval هجين
BM25 + kNN
لغات الأسئلة
AR / EN

المشكلة

إيه المشكلة اللي كنا بنحلها؟

Twiscope بتطلّع تقارير تحليلات للكلمات المفتاحية والحسابات على X، وللكلمات المفتاحية والحسابات والهاشتاجات على TikTok، وللهاشتاجات والحسابات على Instagram. والمحللين كانوا عايزين يسألوا أسئلة عن التقارير دي — «أنهي حسابات جابت أعلى engagement؟»، «الهاشتاج ده عمل إيه مقارنةً بالربع اللي فات؟» — بالعربي أو بالإنجليزي، بدل ما يقروا لوحات التحكم.

chatbot عام مكانش مقبول: الإجابات كان لازم تكون مبنية على بيانات التقرير نفسه، وفيها الأرقام اللي موجودة في التقرير فعلًا، وتقارن بين كذا تقرير لما يتطلب منها، ومتستخدمش أبدًا غير التقارير اللي اشتراك المستخدم يسمح بيها.

القيود

إيه القيود اللي أثّرت على التصميم؟

  • الأسئلة بتيجي بالعربي الفصحى، أو بالعامية الخليجية والمصرية، أو بالإنجليزي، أو خليط منهم.
  • التحكم في الصلاحيات لازم يكون deterministic: الـ entitlements بتاعة الاشتراك وتحديد نطاق التقارير بيطبّقهم الكود، وعمرهم ما بيتسابوا لموديل لغوي.
  • الـ reciprocal rank fusion كان لازم يتنفّذ في التطبيق بدل ما يعتمد على دعم native في Elasticsearch، عشان يفضل متوافق مع الـ cluster المستهدف.
  • التقارير بتفضل توصل من منصة Twiscope على طول؛ فالـ ingestion لازم يكون idempotent، ويقدر يستكمل معالجة تقرير توقفت قبل ما تخلص.
  • كل call زيادة للموديل بيزوّد الـ latency والتكلفة، فعدد الـ LLM calls لكل سؤال كان لازم يفضل قليل وليه حد أقصى.

الـ architecture

النظام متصمم إزاي؟

خدمة FastAPI ورا Nginx بترد على طلبات الـ chat بشكل synchronous مع async I/O؛ و worker منفصل بيعمل ingestion للتقارير من SQS لـ Elasticsearch. الـ retrieval هجين (lexical و vector)، والموديل اللغوي مبيشوفش غير الـ documents اللي الكود حصرها قبل كده في تقارير الـ caller.

  1. مسار الـ request

    • تطبيق الويب بتاع Twiscope

      بيكلّم المساعد بالـ JWT بتاع المستخدم؛ والأسئلة بالعربي أو بالإنجليزي.

    • FastAPI API

      التحقق من الـ JWT بـ RS256، وبعدها التحقق من الـ entitlements قصاد خدمة الاشتراكات قبل أي retrieval.

    • الـ intent classifier

      الـ rules الأول (التحيات، والـ small talk، والكلام المسيء)، و LLM بس للأسئلة اللي مش واضحة.

    • الـ LLM index router

      بيقترح أنهي indices يدوّر فيها؛ والكود بياخد التقاطع بين إجابته و allow-list لكل منصة.

  2. الـ retrieval

    • BGE-M3 embeddings

      dense vectors بـ 1024 dimension من سيرفر Text Embeddings Inference متشغّل عندنا (self-hosted).

    • BM25 + kNN لكل index

      لكل index فيه اتنين queries بيشتغلوا بالتوازي، وكل query متفلترة على التقرير والمنصة المصرّح بيهم.

    • Reciprocal rank fusion

      على الـ client side، من غير أوزان، و k = 60، مع إزالة التكرار بالـ document id.

    • الـ context builder

      quota لكل تقرير وحد أقصى لعدد الحروف (character budget)؛ والتقارير اللي البحث ملقاش فيها نتايج بيتم ذكرها بالاسم.

  3. الـ generation

    • موديلات OpenAI GPT عن طريق OpenRouter

      إجابة واحدة grounded لكل سؤال؛ temperature 0.3، ومن غير streaming.

    • الـ response

      إجابة بعد تنقيحها (sanitised)، والـ source documents اللي اتستخدمت، والتوقيت بتاع كل مرحلة، وعدد الـ tokens.

  4. الـ ingestion والتشغيل

    • SQS worker

      بيقرا التقرير من PostgreSQL، ويحوّله حسب المنصة، ويعمل embedding، و bulk indexing، ويكتب ملخص بلغتين.

    • الـ processing traces

      كل مرحلة من كل تقرير بتتسجّل، فمعالجة التقرير بتكمل من المرحلة اللي توقفت عندها.

    • Elastic APM

      tracing على الـ API والـ queue workers الاتنين، وكمان logs للـ requests والأخطاء.

    • الـ deployment

      GitHub Actions على self-hosted runner: backup، restart، health check، rollback، وبعدين machine image جديدة.

القرارات الهندسية

اخترنا إيه، وليه؟

  1. الموديل بيختار يدوّر فين؛ والكود بيقرر إيه اللي مسموح يتشاف

    الـ LLM router بيرجّع أسماء indices وبس. الكود بياخد التقاطع بينها وبين allow-list للمنصات اللي في الـ request، وبيتجاهل أي hints تانية، ولو الـ output فاضي أو مش صالح بيرجع لـ defaults ثابتة. فلاتر التقرير والمنصة بتتبني في Python وبتتضاف لكل query رايحة لـ Elasticsearch، والتحقق من الـ entitlements بيشتغل قبل الـ retrieval. سؤال فيه prompt injection ممكن يغيّر أنهي index من الـ indices المسموحة هيتدوّر فيه، لكن مش أنهي تقارير تبان.

  2. retrieval هجين، و reciprocal rank fusion على الـ client side

    BM25 بيلقط الكلمات العربية بشكلها الحرفي، والهاشتاجات، والـ handles، اللي بتتوه في الـ embeddings؛ و kNN على BGE-M3 بيلقط الصياغات التانية لنفس المعنى والـ matches بين اللغات. كل index بيروح له الـ queries الاتنين بالتوازي، والقوايم المترتبة بتتدمج بـ RRF (k = 60) جوه الخدمة، عشان إصدار الـ cluster المستهدف أقدم من الـ RRF الـ native. ولو sub-query واحدة فشلت، القايمة التانية بتتستخدم لوحدها.

  3. الإجابة دايمًا مبنية على الـ aggregates بتاعة التقرير

    الـ aggregates والملخصات على مستوى التقرير بتتضاف لكل search مهما كان الـ routing، والـ prompts بتذكر بالاسم الـ field بتاع إجمالي التقرير لكل منصة. من غير كده، الموديل كان بيتعامل مع الكام post اللي رجعوا على إنهم التقرير كله، ويطلّع totals غلط.

  4. الـ rules قبل موديل الـ classifier

    أول مرحلة rule-based بترد على التحيات والـ small talk والكلام المسيء من غير retrieval، وبتعمل normalization للنص العربي (التشكيل، وأشكال الألف والتاء المربوطة) قبل الـ matching. والأسئلة اللي الـ rules مش قادرة تصنّفها هي بس اللي بتحتاج LLM call للتصنيف.

  5. ingestion idempotent ويقدر يكمّل من مكان ما وقف

    الـ document ids بتبقى deterministic ومربوطة بالتقرير، فإعادة الـ indexing بتكتب فوق القديم بدل ما تكرّره. والـ processing trace لكل تقرير بيخلّي الـ worker يعدّي المراحل اللي خلصت. الأعطال المؤقتة بتسيب الـ message على الـ queue عشان تتبعت تاني؛ والـ messages اللي صيغتها غير سليمة (malformed) أو مش صالحة بشكل نهائي بتتشال.

  6. استمرار الخدمة بإمكانيات أقل وقت العطل

    لو batch من الـ embeddings فشلت وقت الـ ingestion، الـ documents دي بتتعمل لها index من غير vectors، وبتفضل متاحة للبحث بـ BM25. ولو الـ classifier أو الـ router فشلوا، بيرجعوا للـ defaults بدل ما الـ request يفشل.

المفاضلات

كسبنا إيه، وإيه اللي اتنازلنا عنه؟

الـ index routing بالـ LLM

call زيادة للموديل في كل سؤال (latency ومصاريف)، ومحكوم بـ fallbacks deterministic.

الـ RRF على الـ client side

اتنين round-trips لـ Elasticsearch لكل index، و candidate depth ثابت 20 لكل قايمة؛ والـ fusion من غير أوزان.

الـ embeddings بتاعة الملخصات بالإنجليزي بس

vector space واحد لكل الأسئلة، بس نص الملخص العربي مش داخل في البحث بشكل مباشر.

cache للـ entitlements لمدة 30 ثانية

calls أقل لخدمة الاشتراكات؛ بس الصلاحية اللي اتسحبت ممكن تفضل شغالة لحد 30 ثانية في كل process، ولو الخدمة دي وقعت الـ chat بيقف.

المقارنات بيكتبها الموديل

الموديل هو اللي بيكتب الترتيب والفروق بناءً على الأرقام المسترجعة وقواعد صارمة في الـ prompt؛ المقارنة مش ناتج حسابات بكود deterministic.

النتايج

وصلنا لإيه؟

تلات نطاقات للـ chat

تقرير واحد، وتقارير المستخدم نفسه (لحد 1,000 في الـ request الواحد)، و mode لكل التقارير محكوم بالـ entitlements — ولكل واحد منهم استراتيجية retrieval خاصة بيه.

إجابات بتوضّح مصادرها

كل إجابة بترجّع الـ documents اللي استخدمتها، والتوقيت بتاع كل مرحلة، وعدد الـ tokens، عشان نقدر نحدد مصدر أي مشكلة في الجودة: الـ retrieval أو الـ generation.

معالجة المشاعر على دفعات: حوالي 3 أضعاف المعدل في بيئة التطوير

في خدمة تحليل المشاعر المنفصلة اللي بتشتغل جنب Sanad، قُدت تغيير طريقة الاستدلال من استدعاء الموديل لكل نص لوحده لمعالجة النصوص في batches مجمّعة حسب الموديل. التغييرات شملت تشغيل الاستدلال اللي بيحجز التنفيذ في threadpool، وتحسين تهيئة الموديلات وفحص جاهزيتها والـ caching واستخدام الموارد. الـ benchmark المسجّل على Apple Silicon/MPS باستخدام fp16 و PyTorch 2.10.0 سجّل زيادة من 142 لـ 441 نص في الثانية عند حجم دفعة 100 (3.1×)، والدفعات الأكبر وصلت لحوالي 460 نص في الثانية. وفحص مقارنة على 24 نص حافظ على نفس تصنيفات المشاعر، بهامش فرق 1e-3 في درجة الثقة. دي قياسات تطوير للخدمة المنفصلة، مش لأداء شات الـ RAG ولا توقع لمعدل المعالجة في الإنتاج.

سبعة أنواع تقارير

كلمة مفتاحية وحساب على X، وكلمة مفتاحية وحساب وهاشتاج على TikTok، وهاشتاج وحساب على Instagram، ولكل نوع منهم الـ indices المسموحة والـ ranking fields الخاصة بيه.

delivery قابل للتكرار

أي push على الـ production branch بينزل عن طريق GitHub Actions مع backup و health check و rollback، وبعدين بيجهّز machine image جديدة.

دوري

  • أسست خدمة FastAPI والـ repository، وحددت هيكل المشروع.
  • بنيت أول SQS ingestion worker، والـ pipeline اللي بيعالج التقارير من PostgreSQL لـ Elasticsearch، ومن ضمنه الـ query builders بتوع الكلمات المفتاحية والحسابات على X.
  • عملت التحقق من الـ JWT بـ RS256 في الـ API.
  • بنيت الـ deployment workflows (systemd ← GitHub Actions، مع backup و rollback، وبعدين تحديث الـ machine image).
  • ضفت tracing بـ Elastic APM للـ API وللـ queue workers الاتنين.
  • بنيت خدمة sentiment منفصلة على GPU للهجات العربية (تلات موديلات Hugging Face، و inference بـ fp16 على batches)؛ وبتشتغل جنب Sanad، بس الـ chat مش بيستخدمها.
  • قُدت تصميم النظام واستراتيجية البحث والـ prompts والـ vector search، وراجعت التنفيذ. اتنين من مهندسي الـ AI في الفريق نفّذوا مكوّنات البحث: التصنيف، والـ router، والبحث الهجين، والـ RRF، والـ prompts، والربط بالموديلات. وشاركت في تنسيق عمل الـ agents وربط الخدمة بالمنصة.

حدود الشغل

  • أرقام معدل معالجة المشاعر من benchmark بيئة التطوير المسجّل في الـ repository بتاريخ 27 أبريل 2026. تطابق التصنيفات يخص عينة الـ 24 نص، ومش ضمان عام للدقة. مفيش هنا قياسات منشورة لزمن استجابة الـ RAG أو دقته أو تكلفته أو حجم استخدامه.
  • الإجابات مش بتيجي streaming، والنظام مش real-time: التقارير بيتعملها ingestion بشكل asynchronous.
  • الـ API ومهام تجهيز التقارير بيستخدموا Elastic APM والـ logs في Elasticsearch. وعلى مستوى التشغيل، بنستخدم كمان Prometheus و Grafana وأدوات متابعة مخصصة.
  • الـ repository فيه unit-test suite كبيرة لسه الـ CI مبيشغّلهاش. التحسينات الجاية: تشغيل الاختبارات في الـ CI، وتظبيط الـ error handling في الـ ingestion والـ visibility windows بتاعة الـ queue، ووضع حد للـ fan-out في الـ requests الكبيرة جدًا اللي بتشمل كذا تقرير.
FastAPIElasticsearchBGE-M3Reciprocal rank fusionAWS SQSOpenRouterElastic APM

دراسة الحالة اللي بعدها

Fendix

أداة CLI مفتوحة المصدر مكتوبة بـ Go، ومعاها منصة hosted، بيحوّلوا نتايج SAST و DAST و SCA ونتايج SARIF المستوردة لـ findings ليها درجة ثقة (confidence)، ولقرار إصدار بياخد التغطية (coverage) في اعتباره. أنا اللي عملت Fendix، وبتابع صيانته لوحدي.

اقرأها