تقديم توصيات ML داخل تطبيق LINE مصغّر

المعمارية خلف منصة بحث عقاري تُقدَّم بالكامل عبر LINE — خلفية Go، تطبيقات LIFF المصغّرة، خدمة استدلال XGBoost، وخطوط الدفعات التي تُبقي التوصيات طازجة.

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

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

المعمارية الكلية

أربعة مكوّنات، لكلٍّ مهمة واحدة:

المكوّنالتقنياتالدور
الخلفية الرئيسيةGo / Echo~60 نقطة REST لتطبيقات LIFF؛ تملك المستخدمين والعقارات والحجوزات
خدمة الاستدلالPython / FastAPI / XGBoostتقيّم سمات المستخدم إلى ملف تفضيلات مخزّن
لوحة الإدارةPHP / Laravelعمليات داخلية: عقارات، حجوزات، موظفون
مهام الدفعاتGo على ECS (مهام مجدولة)حساب التوصيات مسبقًا، حملات الإشعارات، استيراد البيانات المرجعية

عاشت البيانات في Aurora MySQL؛ والبنية التحتية ECS Fargate خلف ALB مع WAF وCloudFront، كلها مُدارة بـ Terraform، مع تكامل REST موثّق مع نظام خارجي لإدارة العقارات لبيانات القوائم ومزامنة الحجوزات.

فصل Go/Python كان مقصودًا. جانب علم البيانات احتاج منظومة Python — النموذج XGBoost، مدرَّب على ديموغرافيا المستخدمين والجغرافيا وتاريخ التفاعل. وجانب المنتج احتاج تزامن Go وبساطة نشره لطبقة الـ API. رسم الحدود عند «خدمة الاستدلال تقيّم؛ الخلفية تقرر» أبقى الفريقين سريعين.

لماذا الأشجار المعزَّزة لا الـ embeddings

كان الكتالوج عقارات جديدة البناء: بضعة آلاف قائمة نشطة، كل منها غني بالسمات المهيكلة (الموقع، شريحة السعر، التخطيط، المطوّر، موعد الاكتمال). والمستخدمون يصلون بسمات مهيكلة أيضًا — تكوين الأسرة، منطقة العمل، إشارات الميزانية. هذه أرض مطابقة البيانات الجدولية، والأشجار المعزَّزة بالتدرّج فوق سمات مهندسة تتفوق على الأساليب الأفخم في بيانات كهذه، مع بقائها قابلة للتفسير ورخيصة إعادة التدريب.

جمع متجه السمات لزوج (مستخدم، عقار):

  • سمات المستخدم — الديموغرافيا، التفضيلات المعلنة، جغرافيا منطقة السكن
  • سمات العقار — السعر، المساحة، بُعد المحطة، توقيت الاكتمال
  • سمات التقاطع — التهجينات التي تحمل معظم الإشارة: ملاءمة الميزانية، تطابق جغرافيا التنقل، التخطيط مقابل حجم الأسرة
  • إشارات سلوكية — المفضلة، القوائم المشاهدة، تاريخ الجولات

البدء البارد — أي معظم المستخدمين، فالناس لا يتسوقون البيوت أسبوعيًا — انحدر بأناقة إلى مطابقة السمات وحدها، وهي بالضبط المنطقة التي يرتاح فيها نموذج شجري.

احسب مسبقًا، لا تقيّم وقت الطلب

القرار المعماري الأهم: التوصيات تُحسب قبل الطلب، لا أثناءه.

يعمل النموذج مرة واحدة لكل مستخدم — عند تسجيل الملف الشخصي، يقيّم سماته إلى ملف تفضيلات يُخزَّن مع المستخدم. ثم تتولى مهام ECS المجدولة التركيب: تمشي على المستخدمين، تبني مجموعات مرشحين (مرشّحات الجغرافيا والميزانية تختصر آلاف القوائم لكل مستخدم)، تطابقها مع الملف المخزّن، وتكتب النتائج المرتّبة في سجل توصيات في MySQL. طبقة الـ API — وبوت المحادثة — تقدّم التوصيات قراءةً مفهرسة فوق ذلك السجل، ولا تعيد الحساب من الملف المخزّن إلا عند الغياب. خدمة الاستدلال لا تجلس على مسار القراءة إطلاقًا.

رسم متحرك: مساران منفصلان — مسار دفعات بطيء يقيّم ويكتب النتائج المرتبة في المخزن، ومسار طلب سريع وقت الغداء يقرأ المخزن ويعرض شريط بطاقات

التقييم الآني كان الخيار الأكثر فخامةً في الوصف. الحساب المسبق فاز على كل محور مهم هنا:

  • الطزاجة لم تتطلبه. مخزون العقارات يتغير يوميًا لا كل ثانية. دورات أسبوعية ونصف أسبوعية فوق جولة ليلية لتحديث البيانات المرجعية والتقييم كانت طازجة بما يكفي فعلًا.
  • عزل الفشل. إذا سقطت خدمة الاستدلال، تدهورت تحديثات الملفات — لكن التوصيات واصلت التقديم من النتائج المخزنة، ولم يرَ المستخدمون خطأً قط. في تصميم مسار-الطلب، يصبح انقطاع الـ ML انقطاعًا للمنتج.
  • إشعارات الدفع تحتاج الدفعات أصلًا. خط الحملات — «عقارات جديدة تطابق ملفك» — يمشي على المستخدمين ويفحص النتائج ويرسل الرسائل. ذلك هو عمل دفعات؛ وجعل مسار التقديم يشارك مخرجاته يعني مصدر حقيقة واحدًا للتوصيات بدل مصدرين ينحرفان.

طبقة التسليم في LINE

منصة المحادثة ليست متصفحًا، ومعاملتها كمتصفح تنتج منتجات خرقاء. القطع التي أحدثت الفرق:

  • تطبيقات LIFF المصغّرة لكل ما هو نموذجي أو قوائمي — البحث، المرشحات، المفضلة، الحجز. من منظور الخلفية هي عملاء ويب عاديون يطرقون نقاط REST برمز هوية من المنصة؛ تحقق من الرمز، اربط مستخدم المنصة بمستخدمك، وامضِ كالمعتاد.
  • أشرطة بطاقات Flex Message لتسليم التوصيات في المحادثة. دفعة التوصية ليست رابطًا — بل شريط بطاقات قابل للتمرير يُعرض أصليًا داخل الحوار، وكل بطاقة تفتح عرض التفاصيل في LIFF. نسبة النقر تحيا أو تموت على هذا العرض.
  • القائمة الغنية + تدفقات البوت للتنقل، بحيث يصل المستخدم إلى كل ميزة دون كتابة حرف.
  • حملات دفع محدودة المعدل. واجهات المراسلة تفرض حصصًا، والمستخدمون ينفرون بقسوة من إزعاج الإشعارات. دفعات الحملات كانت مخنوقة، ومنقّاة من التكرار مقابل الإرسالات الحديثة، ومسقوفة لكل مستخدم في كل فترة. على منصة محادثة، الإفراط في المراسلة أسرع طريق لفقدان القناة كلها — يحظرك المستخدم، ومعك كل توصية قادمة.

درس تشغيلي من طبقة الـ API: مع تزامن Go يغري التوزيع — فحص نتيجة هنا، جلب مفضلة هناك. معظم مشاكلنا الإنتاجية في تلك الطبقة كانت أخطاء nil-safety وتنازع أقفال من توازٍ بلا داعٍ. المعالجات المتسلسلة المملة تبيّن أنها أسرع وأسهل تشخيصًا من الذكية.

ما الذي جعله يعمل

بالنظر خلفًا، جاءت جودة النظام من ثلاث حدود، لا من النموذج:

  1. الاستدلال خدمة بعقد API — يمكن إعادة تدريب النموذج أو استبداله أو تجربته A/B دون علم طبقة المنتج.
  2. التقديم منفصل عن التقييم — الحساب المسبق بالدفعات يجعل استجابة المستخدم وتوافر الـ ML مشكلتين مستقلتين.
  3. القناة قيد من الدرجة الأولى — صُممت التوصيات للشكل الذي يستقبلها الناس به فعلًا (شريط بطاقات في خيط محادثة)، لا فكرةً لاحقة فوق API عام.

كان النموذج نحو 20% من العمل. الـ 80% الباقية كانت ضمان أن نتيجة حُسبت مسبقًا تصير بطاقة تُضغط وقت الغداء — وألا يستطيع أي شيء بين اللحظتين إسقاط المنتج.


هذه المقالة ترسو عليها سلسلة قصيرة عن المنصة نفسها: بناء منتج واجهته خيط محادثة، خط الدفع الذي لم يُحظر، تنازع الأقفال الذي لم يظهر في خطة الاستعلام.