عروض الذكاء الاصطناعي التجريبية في كل مكان. يمكن لسكربت Python واحد تبديل وجه في صورة ثابتة، وتبدو النتيجة سحرية. ولكن بناء شيء يمكن للناس الحقيقيين رفعه، ثم تركه والعودة إليه لاحقاً؟ هذه مهمة مختلفة تماماً. قمت مؤخراً ببناء أداة ويب تأخذ ملف GIF متحرك ووجهاً مرجعياً، ثم تعيد نفس الرسوم المتحركة مع تبديل الوجه في كل إطار. يقوم النموذج بالعمل البصري الشاق، ومع ذلك ذهب الجهد الهندسي الحقيقي إلى البنية التحتية المحيطة به: الحفاظ على استمرارية الاتصالات، ومواجهة تحديات تحديث الصفحة، والتأكد من أن عملية الاستدلال (inference) التي تستغرق دقيقتين لا تختفي بسبب خطأ 504 Gateway Timeout.
هذه هي الفجوة بين النموذج الأولي والمنتج. يحب المهندسون التحدث عن بنيات الانتشار (diffusion architectures) ومعاملات الاستدلال (inference parameters). ولكن عندما يرفع مستخدم ملف GIF كثيفاً يحتوي على مائتي إطار، لن يهتم أحد بنموذجك إذا توقفت علامة تبويب المتصفح عن العمل بعد ثلاثين ثانية. البنية التحتية المحيطة بعملية الاستدلال تهم بقدر أهمية عملية الاستدلال نفسها.
مشكلة الانتظار
تفترض بنية الويب القياسية الاستجابات السريعة. ينقر المستخدم على زر، فيجيب الخادم، وتتحدث الصفحة. تبديل الوجوه عبر عشرات إطارات ملف GIF يكسر هذا الافتراض فوراً. يعمل النموذج على وحدات GPU الخاصة بـ Replicate، وليس على خادمي، ويمكن لملف GIF طويل أن يستغرق دقيقة أو أكثر للمعالجة بسهولة. إذا حاولت إبقاء طلب HTTP مفتوحاً لهذه المدة، فأنت تطلب المشاكل. تقوم موازنات التحميل (Load balancers) بقطع الاتصالات الخاملة. وتفترض المتصفحات فشل الشبكة وتحاول مجدداً. بينما يحدق المستخدمون في أيقونة التحميل المتوقفة ويفترضون أن التطبيق معطل.
لقد تجنبت هذا تماماً من خلال فصل عملية الإرسال عن النتيجة. عندما يرفع المستخدم ملف GIF وصورة وجه، يبدأ الجزء الخلفي (backend) الخاص بي المبني بـ Next.js عملية تنبؤ على Replicate ويعيد فوراً معرف المهمة (job ID). يحصل المستخدم على تأكيد فوري. وتصل النتيجة الفعلية لاحقاً عبر webhook بمجرد انتهاء عمل الـ GPU. هذا النمط ليس غريباً، ولكنه ضروري للغاية لمهام الوسائط التي تستغرق وقتاً طويلاً. إنه يحول الانتظار غير المتوقع إلى مصافحة واثقة: تم قبول المهمة، وسيتم إخطارك عند انتهائها.
بناء آلة حالة (State Machine) يمكنك الوثوق بها
بمجرد الانتقال إلى العمل غير المتزامن (asynchronous)، ستحتاج إلى الرؤية والوضوح. سيقوم المستخدمون بتحديث الصفحة. سيغلقون علامة التبويب ويعيدون فتحها. سينسخون الرابط ويرسلونه إلى زميل عمل يتحقق منه بعد ساعتين. بدون سجل دائم لما حدث، ستواجه الفوضى.
استخدمت Supabase كمصدر وحيد للحقيقة. كل عملية رفع تنشئ صفاً بمعرف مهمة فريد، وينتقل هذا الصف عبر حالات محددة: queued (في الانتظار)، processing (قيد المعالجة)، succeeded (تم بنجاح)، failed (فشل)، أو expired (منتهي الصلاحية). عندما يضغط المستخدم على إرسال لأول مرة، يكون الصف في حالة queued. وفي اللحظة التي يقبل فيها Replicate التنبؤ، ينتقل إلى processing. ثم يقوم الـ webhook بنقله إلى succeeded أو failed. لقد أضفت حالة expired للمهام التي تبقى لفترة طويلة جداً دون استجابة (callback)، حتى لا يطارد النظام "أشباحاً" إلى ما لا نهاية.
يقوم الجزء الأمامي (frontend)، المبني بـ TypeScript، بعمل polling لـ Supabase على فترات قصيرة ويعرض ما تشير إليه الحالة الحالية. يبدو هذا الـ polling بدائياً، ولكنه يحل مشكلة التحديث تماماً. يمكن للمستخدم إغلاق حاسوبه المحمول، وفتحه غداً، ورؤية أين وصلت الأمور بالضبط لأن قاعدة البيانات — وليس ذاكرة المتصفح — هي التي تحتفظ بالتقدم. كما تتبع Supabase الرصيد (credits)، بحيث تظل المحاسبة مرتبطة بنفس سجل المهمة الذي يتتبع الحالة. كل شيء موجود في مكان واحد.
التعامل مع الملفات دون إرهاق المتصفح
ملفات GIF أثقل مما يعتقد الناس. الملف الذي يكون حجمه مثالياً لمسار عمل الذكاء الاصطناعي (AI pipeline) قد يزن عدة ميجابايتات. إن عرض ذلك كمعاينة متكررة داخل المتصفح من شأنه أن يدمر الأداء، خاصة على الأجهزة الضعيفة. كنت بحاجة إلى مسارين منفصلين تماماً للملفات: واحد للذكاء الاصطناعي، وآخر لواجهة المستخدم.
للمعاينة، أقوم بتحويل ملفات GIF الكبيرة إلى WebP متحرك باستخدام FFmpeg المجمع بـ WebAssembly. يعمل هذا بالكامل من جانب العميل (client-side) في المتصفح. النتيجة هي معاينة خفيفة الوزن تحافظ على سرعة الواجهة دون المساس بالملف الأصلي. يظل ملف GIF المرسل إلى Replicate دون تغيير. هذا الفصل أمر مهم. فأنت لا تريد أن تتسلل عيوب الضغط (compression artifacts) من المعاينة إلى بيانات التدريب أو إلى النتيجة النهائية، ولا تريد أن تتوقف واجهة المستخدم عند معالجة كتلة بيانات تبلغ عدة ميجابايتات بينما لا يزال الذكاء الاصطناعي يفكر.
كما يتعامل FFmpeg WASM مع مهام أخرى لملفات GIF قبل أن يغادر الرفع المتصفح. أستخدمه لتحليل عدد الإطارات، والتحقق من الأبعاد، واكتشاف الملفات التالفة مبكراً. إن اكتشاف المشكلة قبل أن تستهلك رصيد الـ GPU يوفر المال وصبر المستخدم على حد سواء.
تحويل العرض التجريبي إلى برمجيات يثق بها الناس
هناك درس أوسع هنا ينطبق على كل أداة ذكاء اصطناعي توليدي تقريبًا في السوق. قد يمثل النموذج نفسه ثلاثين بالمائة من العمل، أما السبعون بالمائة المتبقية فهي الأعمال التقنية الأساسية غير الجذابة التي لا يتحدث عنها أحد في تغريداته: استعادة الحالة، وتواقيع الـ webhook، وتحويل الملفات، وتتبع الرصيد، والتعامل اللبق مع الفشل.
مجموعتي التقنية بسيطة ومدروسة. تتولى Next.js و TypeScript إدارة الواجهة ومسارات الـ API. ويقوم Replicate بتشغيل النماذج. بينما تدير Supabase الحالات والبيانات والرصيد. ويتولى FFmpeg WASM معالجة الوسائط من جهة العميل. ويحافظ Animated WebP على سرعة واجهة المستخدم. لكل جزء وظيفة واحدة، وهي تتصل ببعضها عبر انتقالات حالة صريحة بدلاً من الطلبات الهشة وطويلة الأمد.
عندما يستبدل المستخدم رصيده بعملية تبديل الوجوه، فإنه يتوقع الموثوقية، لا ورقة بحثية. إذا فشلت عملية التنبؤ، يجب أن يدرك النظام ذلك ويعلن عنه. وإذا انتظر المستخدم، فيجب أن تتوفر له معاينة خفيفة ليشاهدها وحالة تصمد حتى عند إعادة تشغيل المتصفح. هذه التفاصيل تكون غير مرئية عندما تعمل بشكل صحيح، وتكون قاتلة عندما لا تعمل.
عملية تبديل الوجوه في حد ذاتها هي خدعة ذكية، لكن الأداة لا تبدو حقيقية إلا لأن المستخدم يمكنه الرفع، ثم المغادرة، ثم العودة دون أن يفقد تقدمه. هذا هو ما يحول استدعاء الـ API إلى برمجيات يثق بها الناس حقًا.
