مطوّر يبني عميل Ollama بـ KMP: خطآن في التسلسل والبث
عرض مطوّر كيفية ربط تطبيق جوال مبني بـ Kotlin Multiplatform بخادم Ollama محلي: واجهة مشتركة بـ Compose، وKoin للتبعيات، وKtor للشبكة. يُفتح الخادم أمام الهواتف في الشبكة بمتغير واحد هو OLLAMA_HOST=0.0.0.0. وذهب معظم الوقت في خطأين خفيّين: اختفاء الحقل stream، ووصول الرد كاملاً دفعة واحدة بدلاً من تدفق. ويُحَل كلاهما بسطرين إذا عرفتَ أين تبحث.
معالج بواسطة الذكاء الاصطناعي من Habr AI؛ بتحرير Hamidun News
أصبح تشغيل نموذج محلي على حاسوب محمول أمراً سهلاً اليوم. أما الأصعب فهو بناء عميل جوال جيد له يعمل على أندرويد وآيفون معاً دون أن يضطرك إلى كتابة الواجهة مرتين. سلك كاتب مقال على Habr هذا الطريق بلغة Kotlin Multiplatform ووصف بصدق المواضع التي تعثّر فيها. وهذا أنفع لمن ينوي فعل الأمر نفسه من أي نظرية.
الخادم: ثلاثة أوامر ومتغير واحد
يُثبَّت Ollama عبر brew أو عبر سكربت التثبيت على Ubuntu. افتراضياً يستمع الخادم إلى localhost فقط، لذا لا يستطيع المحاكي ولا الهاتف الحقيقي في الشبكة نفسها الوصول إليه. ولفتح الوصول يُشغَّل الخادم بالمتغير OLLAMA_HOST=0.0.0.0:11434. بعد ذلك يُنزَّل النموذج عبر ollama pull، وعلى أجهزة ماك بشرائح Apple يستخدم الكاتب إصدارات MLX، مثل gemma4:12b-mlx. ويُظهر الأمر ollama ps أن النموذج موجود بالكامل في ذاكرة وحدة الرسوميات (في المخرجات: PROCESSOR 100% GPU).
يمكن فحص الخادم بأمر curl عادي على /api/generate. وإذا كان الطلب يحتوي على stream: false فسيصل الرد في JSON واحد.
العميل: Compose وKoin وKtor
مشروع KMP بواجهة مشتركة: يتولى Koin التبعيات وViewModel، وKtor الشبكة، وNavigation3 التنقل. اختار الكاتب composable KoinApplication بدلاً من startKoin العام. تُنشأ الحاوية داخل الدالة App()، ولذلك لا يتعارض @Preview في Android Studio مع حالة عامة، ولا حاجة لتعديل شيفرة Swift التي ولّدها المحرر. وثمن هذا القرار أن جزءاً من الواجهة البرمجية غير متاح، ومنه by inject() وby viewModel() وandroidContext(). أما كيفية تقسيم تطبيق كبير إلى وحدات بهذا الأسلوب فلم يتوصل الكاتب إليها بعد.
ولا غنى عن Xcode وmacOS للبناء على iOS. فـ KMP يوفّر عليك كتابة الواجهة بـ Swift، لكنه لا يعفيك من أدوات Apple.
فخّان كلّفا ساعات
يتعلق الفخ الأول بالمُسلسِل. فمكتبة Kotlinx.Serialization لا تكتب افتراضياً في JSON الحقول ذات القيم الافتراضية. فإذا كانت لديك stream = false، يختفي الحقل ببساطة من الطلب، وعندما يغيب يفعّل Ollama وضع البث. والنتيجة أنك تنتظر ردّاً واحداً فتتلقى سيلاً من أسطر JSON. ويُعالَج ذلك بالإعداد Json { encodeDefaults = true }. وقد ساعد Network Inspector في Android Studio على اكتشاف السبب.
يتعلق الفخ الثاني بالبث نفسه. كان الرد يصل كاملاً في النهاية، وظن الكاتب في البداية أن السبب تخزين مؤقت في OkHttp، بل وزّع الشيفرة على المنصات عبر expect/actual. لكن السبب كان مختلفاً: بدلاً من post() يجب استخدام preparePost() وقراءة الجسم عبر execute وbodyAsChannel سطراً بسطر. وكل سطر هو جزء JSON مستقل يحوي الحقول message وdone وerror. وفي العميل يتحول كل ذلك إلى Flow من الأحداث: Token وThinking وDone وError. وإلغاء الكوروتين يغلق الاتصال ويوقف التوليد. أما رموز التفكير (Thinking) فتصل منفصلة إن كان النموذج يرسلها.
وثمة تفصيل آخر: في /api/chat يكون الدور إلزامياً لكل رسالة، بينما لا توجد أدوار في /api/generate، إذ يُمرَّر prompt فقط.
ما يجب مراعاته عملياً
إذا كان الخادم يعمل عبر HTTP فيلزم usesCleartextTraffic="true" في أندرويد، وفي iOS يجب إضافة NSAllowsArbitraryLoads إلى Info.plist. غير أن محاكي آيفون في تجربة الكاتب مرّر الطلبات حتى بدون ذلك. وفي بيئة الإنتاج لا يجوز استخدام HTTP غير مشفّر ولا خادم على 0.0.0.0 بلا مصادقة: فالنص لا يتطرق إلى الحماية بكلمة، ولذلك فهذه بيئة منزلية أو داخلية للتجربة وليست بنية جاهزة.
لا يزال المشروع خاماً: الواجهة بحاجة إلى صقل، ولا يوجد سجل للمحادثات. ويضيف الكاتب أن عميلاً للنماذج السحابية من cloud.ru يدعم الصور والصوت قد كُتب بالفعل. واستغرقت مهمة إعادة تسمية المشروع نحو خمس دقائق من النموذج وحوالي 50 روبلاً، لكن السعر يعتمد كثيراً على النموذج المختار.
الخلاصة للمطوّر: إذا كنت تبني عميلاً جوالاً لـ Ollama، ففعّل encodeDefaults فوراً، واستخدم preparePost للتدفقات من البداية، ولا تبحث عن المشكلة في OkHttp. المصادر متاحة على GitHub (app-z/OllameChatKmp) ويمكن اتخاذها قالباً للانطلاق.
- 12 نوفمبر 2026n8n تطلق Nod: وكيل ذكاء اصطناعي مفتوح المصدر يعمل على جهازك
- 12 نوفمبر 2026Claude Code وCodex يضيّقان الخناق على الهندسة العكسية: OpenAI حظرت حسابات عن طريق الخطأ
- 12 نوفمبر 2026مهندسو الجودة في SpaceWeb كلّفوا الذكاء الاصطناعي بكتابة الاختبارات الآلية: سكربت عملي في 50 دقيقة
- 12 نوفمبر 2026MiniMax تفتح أوزان H3-Image: صور 4K خلال 10–16 ثانية ودعم 16 صورة مرجعية
أسئلة وأجوبة حول الخبر
ما أبرز التفاصيل حول الخادم: ثلاثة أوامر ومتغير واحد؟
يُثبَّت Ollama عبر brew أو عبر سكربت التثبيت على Ubuntu. افتراضياً يستمع الخادم إلى localhost فقط، لذا لا يستطيع المحاكي ولا الهاتف الحقيقي في الشبكة نفسها الوصول إليه. ولفتح الوصول يُشغَّل الخادم بالمتغير OLLAMA_HOST=0.0.0.0:11434. بعد ذلك يُنزَّل النموذج عبر ollama pull، وعلى أجهزة ماك بشرائح Apple يستخدم الكاتب إصدارات MLX، مثل gemma4:12b-mlx. ويُظهر الأمر ollama ps أن النموذج موجود بالكامل في ذاكرة وحدة الرسوميات (في المخرجات: PROCESSOR 100% GPU). يمكن فحص الخادم بأمر curl عادي على /api/generate.
ما أبرز التفاصيل حول العميل: Compose وKoin وKtor؟
مشروع KMP بواجهة مشتركة: يتولى Koin التبعيات وViewModel، وKtor الشبكة، وNavigation3 التنقل. اختار الكاتب composable KoinApplication بدلاً من startKoin العام. تُنشأ الحاوية داخل الدالة App()، ولذلك لا يتعارض @Preview في Android Studio مع حالة عامة، ولا حاجة لتعديل شيفرة Swift التي ولّدها المحرر. وثمن هذا القرار أن جزءاً من الواجهة البرمجية غير متاح، ومنه by inject() وby viewModel() وandroidContext(). أما كيفية تقسيم تطبيق كبير إلى وحدات بهذا الأسلوب فلم يتوصل الكاتب إليها بعد.
ما أبرز التفاصيل حول فخّان كلّفا ساعات؟
يتعلق الفخ الأول بالمُسلسِل. فمكتبة Kotlinx.Serialization لا تكتب افتراضياً في JSON الحقول ذات القيم الافتراضية. فإذا كانت لديك stream = false، يختفي الحقل ببساطة من الطلب، وعندما يغيب يفعّل Ollama وضع البث. والنتيجة أنك تنتظر ردّاً واحداً فتتلقى سيلاً من أسطر JSON. ويُعالَج ذلك بالإعداد Json { encodeDefaults = true }. وقد ساعد Network Inspector في Android Studio على اكتشاف السبب. يتعلق الفخ الثاني بالبث نفسه.
Test Frontier LLMs & Autonomous Agents in One Workspace
Run Claude Opus 5.5, GPT-6, Gemini 3.1, and DeepSeek side-by-side in Nomad AI (nomadnet.ai) — unified model routing, deep research, and multimodal workflows under one subscription.
أهم ما في عالم الذكاء الاصطناعي — مرة كل أسبوع
سبع قصص مهمة فعلاً هذا الأسبوع، مختارة بعناية. بلا ضجيج ولا بيانات صحفية.
تم! تحقق من بريدك للتأكيد.