الكتابات/tutorial/2026/07
Tutorial7 يوليو 2026·28 دقيقة

بناء تطبيقات ذكاء اصطناعي تعمل على الجهاز باستخدام إطار Foundation Models من Apple بلغة Swift

تعلّم كيف تضيف ذكاءً اصطناعياً خاصاً يعمل على الجهاز إلى تطبيقات iOS وmacOS باستخدام إطار Foundation Models من Apple. يغطي هذا الدرس العملي بلغة Swift التحقق من التوفّر، والتوليد الموجّه عبر @Generable، والبثّ المباشر، واستدعاء الأدوات — بدون مفاتيح API، بدون شبكة، وبدون تكلفة لكل رمز.

يمنح إطار Foundation Models من Apple كل تطبيق وصولاً مباشراً إلى نفس النموذج اللغوي الكبير الذي يعمل على الجهاز ويشغّل Apple Intelligence. لا توجد مفاتيح API، ولا رحلات ذهاب وإياب عبر الشبكة، ولا فوترة لكل رمز. يعمل الاستدلال بالكامل على جهاز المستخدم — سواء كان جهاز Mac أو iPhone أو iPad يعمل بمعالج Apple silicon — ما يعني أن بيانات المستخدم لا تغادر الجهاز أبداً، وأن ميزاتك تستمر بالعمل حتى في وضع الطيران.

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

المتطلبات الأساسية

قبل البدء، تأكد من توفّر ما يلي:

  • Xcode 26 أو أحدث — يأتي الإطار ضمن أحدث SDK
  • جهاز أو محاكي يعمل بنظام iOS 26+ أو iPadOS 26+ أو macOS 26+
  • جهاز بمعالج Apple silicon مع تفعيل Apple Intelligence (الإعدادات ← Apple Intelligence وSiri)
  • إلمام بلغة Swift وأساسيات SwiftUI
  • نحو 4 غيغابايت من المساحة الحرة — يُنزَّل النموذج الذي يعمل على الجهاز تلقائياً عند الحاجة إليه أول مرة

لا حاجة إلى حساب لدى Anthropic أو OpenAI أو Google. كل ما هنا يعمل محلياً.

ما الذي ستبنيه

في النهاية ستحصل على شاشة SwiftUI بها محرّر نص وزر «تلخيص». عند الضغط عليه يُستدعى النموذج الذي يعمل على الجهاز، فيعيد:

  • ملخصاً موجزاً من فقرة واحدة للملاحظات
  • مصفوفة مُنمَّطة من المهام، كل واحدة مع task وowner وقيمة تعداد priority

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

فهم إطار Foundation Models

نموذج ذهني سريع قبل كتابة الشيفرة.

النموذج الذي يعمل على الجهاز

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

الأنواع الأساسية

النوعالدور
SystemLanguageModelيمثّل النموذج الذي يعمل على الجهاز؛ يكشف عن .availability
LanguageModelSessionمحادثة ذات حالة؛ تستدعي عليها respond(to:)
@Generableماكرو يحوّل نوع Swift إلى مخطّط يملؤه النموذج
@Guideيضيف أوصافاً لغوية وقيوداً إلى الحقول
Toolبروتوكول للدوال التي يمكن للنموذج استدعاؤها أثناء التوليد

لماذا يهمّ التوليد الموجّه

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

الخطوة 1: استيراد الإطار والتحقق من التوفّر

لا تفترض أبداً أن النموذج جاهز. قد لا يكون الجهاز مؤهّلاً، وقد تكون ميزة Apple Intelligence مُطفأة، وقد يكون النموذج لا يزال يُنزَّل. تحقّق دائماً من availability أولاً.

import FoundationModels
import SwiftUI
 
struct SmartNotesView: View {
    // مرجع إلى نموذج النظام الذي يعمل على الجهاز.
    private let model = SystemLanguageModel.default
 
    var body: some View {
        switch model.availability {
        case .available:
            NotesEditor()
        case .unavailable(.deviceNotEligible):
            ContentUnavailableView(
                "غير مدعوم",
                systemImage: "cpu",
                description: Text("هذا الجهاز لا يستطيع تشغيل الذكاء الاصطناعي على الجهاز.")
            )
        case .unavailable(.appleIntelligenceNotEnabled):
            ContentUnavailableView(
                "فعّل Apple Intelligence",
                systemImage: "sparkles",
                description: Text("فعّل Apple Intelligence من الإعدادات لاستخدام الملاحظات الذكية.")
            )
        case .unavailable(.modelNotReady):
            ContentUnavailableView(
                "جارٍ تنزيل النموذج",
                systemImage: "arrow.down.circle",
                description: Text("لا يزال النموذج يُنزَّل. حاول بعد قليل.")
            )
        case .unavailable(let other):
            ContentUnavailableView(
                "غير متاح",
                systemImage: "exclamationmark.triangle",
                description: Text("الذكاء الاصطناعي على الجهاز غير متاح: \(String(describing: other))")
            )
        }
    }
}

يُنزَّل النموذج تلقائياً بناءً على حالة الشبكة ومستوى البطارية وحمل النظام — ولهذا توجد الحالة modelNotReady. تعامل معها بلطف بدل إظهار خطأ.

الخطوة 2: أول عملية توليد

تمثّل LanguageModelSession محادثة جارية واحدة. أنشئها مع instructions اختيارية — توجيه نظام يشكّل النبرة والسلوك — ثم استدعِ respond(to:).

import FoundationModels
 
func quickSummary(of notes: String) async throws -> String {
    let session = LanguageModelSession(
        instructions: """
        أنت مساعد اجتماعات موجز. لخّص الملاحظات في فقرة قصيرة واحدة. \
        لا تخترع تفاصيل غير موجودة.
        """
    )
 
    let response = try await session.respond(
        to: "لخّص هذه الملاحظات:\n\n\(notes)"
    )
 
    return response.content
}

الدالة respond(to:) هي async throws. تكشف القيمة المُعادة عن .content للنص المُولَّد. تحتفظ الجلسة بسجلّها الخاص، لذا تتذكّر الاستدعاءات اللاحقة على الجلسة نفسها الأدوار السابقة — مفيد للدردشة، لكن للملخّص لمرة واحدة تكفي جلسة جديدة لكل طلب.

الخطوة 3: التوليد الموجّه عبر @Generable

النص العادي يكفي للملخص، لكن المهام تحتاج بنية. عرّف الشكل المطلوب كأنواع Swift موسومة بـ @Generable. استخدم @Guide لوصف كل حقل لكي يملأه النموذج بشكل صحيح.

import FoundationModels
 
@Generable
struct NotesDigest {
    @Guide(description: "ملخص موجز من فقرة واحدة للملاحظات.")
    let summary: String
 
    @Guide(description: "كل مهمة قابلة للتنفيذ ذُكرت في الملاحظات.")
    let actionItems: [ActionItem]
}
 
@Generable
struct ActionItem {
    @Guide(description: "وصف قصير وأمري للمهمة.")
    let task: String
 
    @Guide(description: "الشخص المسؤول، أو 'غير مُسنَد' إذا لم يكن واضحاً.")
    let owner: String
 
    let priority: Priority
}
 
@Generable
enum Priority {
    case low
    case medium
    case high
}

الآن اطلب من الجلسة توليد هذا النوع بالضبط. مرّر النوع كوسيط generating: فيقيّد الإطار فكّ الترميز ليطابق مخطّطك.

func makeDigest(from notes: String) async throws -> NotesDigest {
    let session = LanguageModelSession(
        instructions: """
        استخرج ملخصاً وقائمة مهام ملموسة من ملاحظات الاجتماع. \
        أدرِج فقط المهام المذكورة صراحةً.
        """
    )
 
    let response = try await session.respond(
        to: "حلّل هذه الملاحظات:\n\n\(notes)",
        generating: NotesDigest.self
    )
 
    // response.content هي NotesDigest مُنمَّطة بالكامل — دون تحليل.
    return response.content
}

القيمة response.content هي NotesDigest حقيقية. الحقل priority هو تعداد Priority صحيح، لا نصّ عليك التحقق منه. هذا هو أكبر مكسب من حيث سهولة الاستخدام في الإطار.

تقييد الحقول أكثر

تقبل @Guide أكثر من الأوصاف. يمكنك تحديد الأعداد، أو فرض أنماط، أو تقييد نصّ بمجموعة من القيم المسموحة.

@Generable
struct TaggedNote {
    @Guide(description: "متن الملاحظة.")
    let body: String
 
    // أجبِر النموذج على إعادة ما بين وسم واحد وثلاثة وسوم.
    @Guide(description: "وسوم مواضيع ذات صلة.")
    @Guide(.count(3))
    let tags: [String]
}

تُفرَض هذه القيود أثناء فكّ الترميز، فلا يمكن للنموذج فيزيائياً أن يعيد أربعة وسوم عندما طلبتَ ثلاثة.

الخطوة 4: الربط مع SwiftUI

إليك شاشة محرّر مُدمَجة تشغّل التلخيص وتعرض النتيجة المُنمَّطة. لاحظ حارس isWorking — تعالج الجلسة طلباً واحداً في كل مرة.

import FoundationModels
import SwiftUI
 
struct NotesEditor: View {
    @State private var notes = ""
    @State private var digest: NotesDigest?
    @State private var isWorking = false
    @State private var errorText: String?
 
    var body: some View {
        Form {
            Section("الملاحظات الخام") {
                TextEditor(text: $notes)
                    .frame(minHeight: 160)
            }
 
            Button("تلخيص") {
                Task { await run() }
            }
            .disabled(notes.isEmpty || isWorking)
 
            if let digest {
                Section("الملخص") {
                    Text(digest.summary)
                }
                Section("المهام") {
                    ForEach(Array(digest.actionItems.enumerated()), id: \.offset) { _, item in
                        VStack(alignment: .leading, spacing: 4) {
                            Text(item.task).font(.headline)
                            Text("\(item.owner) · \(String(describing: item.priority))")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                        }
                    }
                }
            }
 
            if let errorText {
                Text(errorText).foregroundStyle(.red)
            }
        }
        .overlay {
            if isWorking { ProgressView() }
        }
    }
 
    private func run() async {
        isWorking = true
        errorText = nil
        defer { isWorking = false }
        do {
            digest = try await makeDigest(from: notes)
        } catch {
            errorText = error.localizedDescription
        }
    }
}

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

الخطوة 5: بثّ الاستجابة

لأي شيء أطول من جملة، يجعل البثّ الواجهة تبدو حيّة. بدلاً من respond، استدعِ streamResponse التي تعيد تسلسلاً غير متزامن من اللقطات المتزايدة الاكتمال.

func streamSummary(of notes: String, onUpdate: @escaping (String) -> Void) async throws {
    let session = LanguageModelSession(
        instructions: "لخّص ملاحظات الاجتماع بوضوح وإيجاز."
    )
 
    let stream = session.streamResponse(
        to: "لخّص هذه الملاحظات:\n\n\(notes)"
    )
 
    for try await partial in stream {
        // كل لقطة هي كامل النص المُولَّد حتى الآن.
        onUpdate(partial.content)
    }
}

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

let stream = session.streamResponse(
    to: "حلّل هذه الملاحظات:\n\n\(notes)",
    generating: NotesDigest.self
)
 
for try await partial in stream {
    // قد يُملأ partial.content.summary قبل اكتمال actionItems.
    await MainActor.run { self.livePartial = partial.content }
}

الخطوة 6: إضافة أداة

أحياناً يحتاج النموذج معلومات حيّة لا يملكها — قائمة فريقك الحالية، أو سجلّ قاعدة بيانات، أو الطقس. تتيح الأدوات للنموذج استدعاء شيفرة Swift الخاصة بك أثناء التوليد. طبّق بروتوكول Tool: امنحه name وdescription ونوع Arguments موسوماً بـ @Generable ودالة call.

import FoundationModels
 
struct TeamDirectoryTool: Tool {
    let name = "lookupTeamMember"
    let description = "ابحث عن الاسم الكامل والدور لعضو فريق من اسم جزئي."
 
    @Generable
    struct Arguments {
        @Guide(description: "اسم جزئي أو كامل للبحث عنه.")
        var query: String
    }
 
    // بديل صغير لمصدر بيانات حقيقي.
    private let roster = [
        "sara": "سارة بن علي — قائدة المنتج",
        "youssef": "يوسف الطرابلسي — مهندس واجهة خلفية",
        "lina": "لينا حداد — مصمّمة"
    ]
 
    func call(arguments: Arguments) async throws -> String {
        let key = arguments.query.lowercased()
        if let match = roster.first(where: { key.contains($0.key) }) {
            return match.value
        }
        return "لم يُعثر على عضو فريق يطابق '\(arguments.query)'."
    }
}

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

let session = LanguageModelSession(
    tools: [TeamDirectoryTool()],
    instructions: """
    أسنِد المهام إلى أعضاء فريق حقيقيين. عندما تذكر ملاحظة شخصاً باسم \
    جزئي، استخدم أداة البحث لاستنتاج اسمه الكامل.
    """
)
 
let response = try await session.respond(
    to: "حلّل هذه الملاحظات:\n\n\(notes)",
    generating: NotesDigest.self
)

إذا قالت ملاحظاتك «سارة تطلق صفحة التسعير»، يستدعي النموذج lookupTeamMember بالوسيط query: "سارة"، فيتلقّى «سارة بن علي — قائدة المنتج»، ويملأ حقل owner بالاسم المُستنتَج. يمكنك فحص كل خطوة عبر session.transcript الذي يعدّد التعليمات والتوجيهات واستدعاءات الأدوات ومخرجاتها والاستجابات — لا يُقدَّر بثمن عند التنقيح.

الخطوة 7: ضبط التوليد والأداء

لمستان صغيرتان تُحدثان فرقاً حقيقياً في الإنتاج.

تحكّم في العشوائية عبر GenerationOptions. اخفِض درجة الحرارة لمهام الاستخراج حيث تريد خرجاً حتمياً وأميناً.

let response = try await session.respond(
    to: prompt,
    generating: NotesDigest.self,
    options: GenerationOptions(temperature: 0.3)
)

سخّن مسبقاً الجلسة عندما تعلم أن طلباً قادم — مثلاً حين يبدأ المستخدم الكتابة — لكي يكون النموذج مقيماً في الذاكرة قبل ضغطه الزر.

let session = LanguageModelSession(instructions: "…")
session.prewarm()   // حمّل الموارد قبل أول طلب حقيقي.

اختبار تنفيذك

للتحقق من الميزة من طرف إلى طرف:

  1. شغّل على جهاز فعلي بمعالج Apple silicon مع تفعيل Apple Intelligence — دعم المحاكي متفاوت، والعتاد الحقيقي يعكس زمن الاستجابة الفعلي.
  2. الصق ملاحظات تذكر مهمة وشخصاً، مثل: «نوقش إطلاق الربع الثالث. سارة تُنهي التسعير بحلول الجمعة. يوسف يحتاج إصلاح خلل تسجيل الدخول — عاجل.»
  3. تأكد أن الملخص أمين وأن مصفوفة المهام تحتوي على مدخلين بالمسؤولين والأولويات الصحيحة.
  4. فعّل وضع الطيران وشغّل مجدداً — يجب أن يستمر بالعمل، ما يثبت أن الاستدلال يجري بالكامل على الجهاز.
  5. افتح session.transcript في المنقّح للتأكد من استدعاء الأداة.

استكشاف الأخطاء وإصلاحها

unavailable(.modelNotReady) لا تُحلّ أبداً. يُنزَّل النموذج انتهازياً. تأكد أن الجهاز على Wi-Fi، ومشحون، وخامل لبضع دقائق، ثم أعد التشغيل.

التوليد الموجّه يعيد قيماً غريبة. اشحذ أوصاف @Guide. يعاملها النموذج كتعليمات — الأوصاف الغامضة تنتج حقولاً غامضة. أضِف قيوداً مثل .count(_:) حيث تهمّ البنية.

الجلسة تطلق استثناءً عند استدعاء متزامن ثانٍ. تعالج الجلسة طلباً واحداً في كل مرة. احرس بعلَم isResponding، أو أنشئ جلسات منفصلة للعمل المتوازي.

الخرج يبدو عاماً أو مُهلوَساً. هذا نموذج مُدمَج من فئة 3 مليارات معامِل، لا نموذج حدوديّ. أبقِ المهام مركّزة، واخفِض درجة الحرارة، ووجّهه صراحةً لاستخدام المعلومات الموجودة في المدخل فقط.

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

  • استكشف تعدادات @Generable ذات القيم المرتبطة لنمذجة أشجار قرار أغنى.
  • اجمع الأدوات مع WeatherKit أو EventKit أو HealthKit لتأصيل التوليد في بيانات الجهاز الحقيقية.
  • أضِف مساراً احتياطياً إلى نموذج سحابي (Claude أو Gemini) للمهام التي تتجاوز قدرة النموذج على الجهاز، مع إبقاء المسار الخاص افتراضياً.
  • اقرأ أدلّتنا ذات الصلة: بناء وكلاء الذكاء الاصطناعي بـ Claude Agent SDK وواجهة Prompt API المدمجة في Chrome مع Gemini Nano للذكاء الاصطناعي على الجهاز في الويب.

الخاتمة

لقد بنيت ميزة ذكاء اصطناعي كاملة وخاصة تعمل على الجهاز بلغة Swift دون مفتاح API واحد. تحقّقت من توفّر النموذج، وولّدت نصاً عادياً، واستخرجت بيانات مُنمَّطة بقوة عبر @Generable، وبثثت النتائج إلى SwiftUI، واستدعيت شيفرة Swift الخاصة بك عبر أداة، وضبطت النموذج للموثوقية. يحوّل إطار Foundation Models الذكاء الاصطناعي على الجهاز إلى واجهة Swift عادية — مُنمَّطة وسريعة ومجانية وخاصة افتراضياً. هذا المزيج يجعله الخيار الأول الطبيعي لفئة كبيرة من ميزات التطبيقات التي كانت تتطلب خادماً وفاتورة.