FoundationModels в iOS 26: Пълно ръководство за on-device AI със Swift

Как да използвате FoundationModels в iOS 26: LanguageModelSession, guided generation с @Generable, streaming, tool calling и cross-platform особености за iPad, Mac и visionOS.

FoundationModels iOS 26: Swift Guide

Обновено: 19 август, 2026

FoundationModels е нативен Swift framework на Apple, който дава директен достъп до on-device езиковия модел с ~3 милиарда параметра, вграден в iOS 26, iPadOS 26, macOS 26 и visionOS 26. Без мрежа, без API ключове, без разходи за инференция. За разлика от външни LLM услуги, всичко се изпълнява локално през Apple Silicon Neural Engine, а по-тежките задачи прозрачно се пренасочват към Private Cloud Compute. Ето как да построите първата си интеграция със Swift 6 и как да избегнете най-честите клопки при работа между платформите.

  • FoundationModels предоставя SystemLanguageModel и LanguageModelSession, двата основни типа за диалог с локалния модел на Apple.
  • Guided generation с макроса @Generable връща типизирани Swift структури директно, без ръчно парсване на JSON.
  • Streaming работи през AsyncSequence и се интегрира плавно с Task и MainActor в SwiftUI.
  • Tool calling позволява моделът да извиква ваши функции чрез Tool протокола, идеално за data lookup или календарни операции.
  • Моделът е достъпен на устройства с Apple Intelligence: iPhone 15 Pro и по-нови, всички M-серия Mac и iPad, Apple Vision Pro.
  • Контекстният прозорец е около 4096 токена, по-малък от cloud модели, така че управлението на историята е критично.

Какво представлява FoundationModels framework?

FoundationModels е Swift-first framework, представен на WWDC 2025 и стабилизиран в iOS 26.0. Той излага същия модел с ~3B параметъра, който задвижва Apple Intelligence (Writing Tools, Genmoji, семантичното търсене в Photos и обобщенията на нотификации). За вас като разработчик това означава, че вече не се налага да опаковате llama.cpp или да поддържате отделни MLX модели за прости случаи като извличане на данни, класификация или генериране на текст. Импортирате FoundationModels, отваряте сесия и получавате отговор.

Ключовата разлика от Core ML е абстракцията. Core ML работи с тензори и очаква вие да носите модела; FoundationModels носи модела и говори на нивото на естествения език. Ключовата разлика от OpenAI/Anthropic SDK-та е, че тук няма мрежова заявка по подразбиране. Заявката минава през Apple Neural Engine, а само при заявка за по-мощен модел (чрез Writing Tools или Image Playground API) се използва Private Cloud Compute с криптографски проверяем изходен код.

Framework-ът е част от системата. Не се дистрибутира отделно, не заема място в бинарния файл на приложението и не изисква App Store конфигурация. Единственото условие е таргетиране на iOS 26+ и устройство с Apple Intelligence. Ако сте работили през App Intents в iOS 26, познавате същата философия: Swift макроси, деклариране на форма, компилаторът върши тежката работа.

Как да проверя дали FoundationModels е наличен?

Наличността зависи от три фактора: версия на OS (iOS/iPadOS/macOS/visionOS 26+), хардуер с Apple Intelligence и потребителят да е активирал Apple Intelligence в Настройки. Пропуск на който и да е от тях води до .unavailable статус, така че проверката трябва да е първото нещо в потока ви. Иначе рискувате LanguageModelSession.GenerationError.assetsUnavailable в runtime.

import FoundationModels

@MainActor
final class AIAvailabilityChecker {
    enum State {
        case ready
        case downloading
        case unsupportedDevice
        case aiDisabled
        case osTooOld
    }

    func currentState() -> State {
        let model = SystemLanguageModel.default
        switch model.availability {
        case .available:
            return .ready
        case .unavailable(.deviceNotEligible):
            return .unsupportedDevice
        case .unavailable(.appleIntelligenceNotEnabled):
            return .aiDisabled
        case .unavailable(.modelNotReady):
            return .downloading
        @unknown default:
            return .osTooOld
        }
    }
}

За SwiftUI изгледи, третирайте availability като реактивно състояние. То може да се промени, докато потребителят е в приложението (например изтегляне на модела завършва). Честно казано, в моя опит най-чистото решение е да експонирате availability през @Observable клас и да реагирате чрез onChange, а не да го проверявате еднократно в onAppear. Хващал съм се в тази клопка два пъти в един и същи проект.

Първа сесия с LanguageModelSession

LanguageModelSession е основният контекст, който помни разговора. Единична инстанция държи историята, инструкциите и токен бюджета. За краткотрайни, stateless задачи (превод, обобщение, класификация) създавайте нова сесия за всяка заявка. За чат интерфейс задържайте една сесия за целия разговор и я освобождавайте, когато потребителят го изчисти.

import FoundationModels

func summarize(_ article: String) async throws -> String {
    let session = LanguageModelSession(
        instructions: Instructions {
            "Ти си асистент, който създава кратки обобщения."
            "Отговаряй на български. Максимум 3 изречения."
        }
    )

    let response = try await session.respond(to: article)
    return response.content
}

Обърнете внимание на Instructions { ... } result builder. Той се компилира в системен prompt с по-висок приоритет от потребителското въвеждане. Това е вашата защита срещу prompt injection: инструкциите вътре в Instructions имат по-голяма тежест от каквото и да е, което потребителят напише след това. Никога не поставяйте потребителски вход в Instructions. Това ефективно прави моделa доверителен към произволен текст, и лично го научих по трудния начин, когато пропуснах едно санитизиране в demo приложение.

За токен бюджета: сесията автоматично отхвърля най-старите съобщения, когато се приближи до 4096 токена. Ако имате приложение с дълги разговори, слушайте свойството transcriptTokenCount и подавайте компактирано резюме, преди да достигнете лимита. Аналогичен подход прилагаме за реактивните потоци в Combine framework: управлявате натиск (backpressure), преди да е станал проблем.

Guided generation с макроса @Generable

Guided generation е причината, поради която FoundationModels е толкова важен за нас, iOS разработчиците. Вместо да молите модела за JSON, а после да се борите с невалидни двойни кавички, декларирате Swift структура и получавате обект. Макросът @Generable генерира схема, която моделът е задължен да спазва на ниво декодер. Това не е prompt engineering, а constrained decoding, при което невъзможните токени просто не могат да бъдат emit-нати.

import FoundationModels

@Generable
struct RecipeCard {
    @Guide(description: "Име на ястието на български")
    let name: String

    @Guide(description: "Време за приготвяне в минути", .range(5...240))
    let cookingMinutes: Int

    @Guide(description: "Съставки, по един ред", .count(3...12))
    let ingredients: [String]

    @Guide(description: "Ниво на трудност")
    let difficulty: Difficulty
}

@Generable
enum Difficulty: String {
    case easy, medium, hard
}

func extractRecipe(from text: String) async throws -> RecipeCard {
    let session = LanguageModelSession(
        instructions: Instructions { "Извлечи рецепта от текста." }
    )
    let response = try await session.respond(
        to: text,
        generating: RecipeCard.self
    )
    return response.content
}

Ограниченията през .range(_:), .count(_:), .pattern(_:) и .anyOf(_:) се прилагат по време на декодиране, а не като post-hoc валидация. Ако поискате число между 5 и 240, моделът физически не може да върне 300. Това ви освобождава от втори защитен слой и премахва цяла категория runtime грешки. За enum-и, @Generable с raw String дава най-доброто от двата свята: типова безопасност в Swift, естествен език в модела.

Streaming отговори в реално време

За чат UI, streaming е задължителен. Потребителите очакват символите да се появяват, докато моделът пише, а не да чакат 4 секунди за цял абзац. LanguageModelSession излага streamResponse(to:), който връща AsyncSequence от String.PartialResult. Всеки елемент съдържа последния снапшот, така че просто задавате целия текст, а не го конкатенирате.

import SwiftUI
import FoundationModels

@Observable
@MainActor
final class ChatViewModel {
    var currentReply: String = ""
    var isStreaming: Bool = false
    private let session = LanguageModelSession(
        instructions: Instructions { "Отговаряй сбито на български." }
    )

    func send(_ prompt: String) async {
        isStreaming = true
        currentReply = ""
        defer { isStreaming = false }

        do {
            for try await partial in session.streamResponse(to: prompt) {
                currentReply = partial.content
            }
        } catch {
            currentReply = "Грешка: \(error.localizedDescription)"
        }
    }
}

struct ChatView: View {
    @State private var vm = ChatViewModel()
    @State private var input = ""

    var body: some View {
        VStack {
            ScrollView {
                Text(vm.currentReply)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding()
            }
            HStack {
                TextField("Питай...", text: $input)
                Button("Изпрати") {
                    Task { await vm.send(input); input = "" }
                }
                .disabled(vm.isStreaming)
            }
            .padding()
        }
    }
}

Понеже ChatViewModel е @MainActor и @Observable, обновяванията са безопасни и предизвикват UI diff автоматично. Ако сте нови в структурираната конкурентност на Swift, започнете от нашето ръководство за Swift Concurrency. Task, MainActor и AsyncSequence са задължителни за интеграция с FoundationModels.

Streaming също работи с guided generation. streamResponse(to:generating:) връща PartialGenerated<YourType>, където всяко поле е Optional, докато не се финализира. Полезно е за прогресивен рендеринг: първо се появява заглавието, после описанието, накрая списък със стъпки.

Tool calling: как моделът извиква вашите функции

Tools превръщат модела от текстов генератор в агент, който може да предприема действия. Дефинирате Tool, Swift структура с @Generable arguments и async call(arguments:) метод, и я подавате на сесията. Моделът решава кога да я извика, вие изпълнявате логиката, резултатът се връща в разговора, моделът формулира финалния отговор. Класически ReAct pattern, но с типова безопасност.

import FoundationModels
import WeatherKit

struct WeatherTool: Tool {
    let name = "get_weather"
    let description = "Връща текущата температура за даден град."

    @Generable
    struct Arguments {
        @Guide(description: "Име на град")
        let city: String
    }

    func call(arguments: Arguments) async throws -> ToolOutput {
        let service = WeatherService.shared
        let location = try await geocode(arguments.city)
        let weather = try await service.weather(for: location)
        let celsius = weather.currentWeather.temperature.value
        return ToolOutput("В \(arguments.city) сега е \(Int(celsius))°C.")
    }
}

let session = LanguageModelSession(
    tools: [WeatherTool()],
    instructions: Instructions {
        "Ти си асистент. При въпрос за времето, използвай get_weather."
    }
)

let reply = try await session.respond(
    to: "Какво е времето в София днес?"
)
print(reply.content) // "В София сега е 22°C. Приятен ден!"

Tool calling отваря вратата към „hybrid AI" архитектура: локалният модел решава оркестрацията, а специализирани компоненти (Core Data заявка, MapKit route, HealthKit query) вършат конкретната работа. Моделът никога не вижда суровите ви данни, само това, което върнете от ToolOutput. Това е чиста граница за поверителност, която cloud LLM-ите не могат да предложат.

Cross-platform особености: iPad, Mac и visionOS

Framework-ът е един и същ на всички Apple платформи, но поведението се различава по три оси, които засегнах на скорошна вътрешна презентация. Първо: размер на модела. На iPhone 15 Pro и M1 iPad работи 3B квантизиран (~2GB на диска), докато Mac Pro с M4 Max използва по-голям 7B вариант с по-широк контекст. Не се доверявайте на еднакво поведение. Тестовете за качество трябва да минат на minimum eligible device.

Второ: латентност. Neural Engine на A17 Pro генерира около 30 токена/сек; M4 Max над 90. Ако имате streaming UI, който изглежда естествен на Mac, той ще изглежда бавен на iPhone. Компенсирайте с по-къси prompts, по-агресивно guided generation (по-малко токени) и визуален индикатор за прогрес.

Трето: thermal throttling. На iPad Mini след около 30 секунди непрекъсната генерация системата дроселира честотата на Neural Engine. За приложения с дълги разговори, вмъквайте Task.yield() между заявките и предупреждавайте потребителя, ако ProcessInfo.processInfo.thermalState е .serious. На visionOS платформата допълнително ограничава фонов inference, докато потребителят е в Immersive Space, така че планирайте съответно UX.

ХарактеристикаiPhone 15 ProiPad Pro M4Mac (M4 Max)Vision Pro (M2)
Размер на модела3B3B7B3B
Контекстен прозорец4096409681924096
Скорост (токени/сек)~30~55~90~35
Streaming в backgroundДаДаДаОграничено
Thermal throttling след~45s~90sНяма~30s

Ако вашето приложение е крос-платформено, разгледайте нашето ръководство за SwiftUI в iOS 26. Liquid Glass и адаптивните layout API-та вървят ръка за ръка с FoundationModels в приложения, които трябва да изглеждат нативни на iPad, Mac и Vision Pro.

Производителност, ограничения и типични клопки

След три месеца production използване, ето най-често срещаните проблеми, които виждам. Първо, cold start: първата заявка след стартиране на приложението отнема 800–1500ms, докато моделът се зарежда в паметта. Стартирайте „загрявка" (празна session.respond(to: " ")) при applicationDidFinishLaunching, ако приложението разчита на бърз първи отговор. Хванах този бъг в едно от последните ми приложения, което трябваше да отговаря веднага при отваряне на share extension.

Второ, халюцинациите при guided generation остават възможни. Constrained decoding гарантира валидна форма, не истинност. Ако извличате имена на лекарства или юридически справки, добавете второ ниво валидация срещу списък с истина. Моделът може напълно уверено да измисли ISBN, който съответства на формата, но не съществува.

Трето, токен броенето не е тривиално. Един кирилски символ обикновено струва 2–3 токена срещу 1 за латинска буква, така че български prompts бързо консумират контекстния прозорец. Мерете с session.transcriptTokenCount, не гадайте.

Четвърто, App Store review очаква декларация. От iOS 26 App Privacy формулярът има нова секция „Използва Apple Intelligence", маркирайте я, дори когато използвате само локален inference. Липсата води до забавен review.

За задълбочени детайли по API-то, вижте официалната FoundationModels документация и WWDC 2025 сесията „Meet the Foundation Models framework". Sample project-ът на Apple apple/sample-foundation-models е добра стартова точка за chat интерфейс с tool calling.

Често задавани въпроси

FoundationModels работи ли офлайн?

Да. Стандартните заявки към SystemLanguageModel.default се изпълняват изцяло на устройството през Apple Neural Engine и не изискват интернет. Мрежа е нужна само ако изрично поискате „server model" или използвате Writing Tools API, който може да маршрутизира към Private Cloud Compute.

Кои устройства поддържат FoundationModels?

Всички устройства с Apple Intelligence: iPhone 15 Pro / 15 Pro Max, цялата iPhone 16 и 17 гама, iPad с M1 или по-нов, всички Mac с Apple Silicon (M1+) и Apple Vision Pro. Устройства с A16 или по-стари Intel Mac-ове връщат .deviceNotEligible.

Каква е разликата между FoundationModels и Core ML?

Core ML е нискoнивеен: работи с тензори и очаква да носите обучен модел (например Stable Diffusion, YOLO). FoundationModels е високoнивеен и Swift-first: доставя предварително обучен ~3B LLM с API за prompts, tool calling и guided generation. За NLP задачи започнете с FoundationModels; за custom vision или audio модели, Core ML.

Мога ли да fine-tune-на модела на FoundationModels?

Не в iOS 26. Базовият модел е read-only. Apple предлага LoRA-подобни „адаптери" за някои системни задачи (например Genmoji), но публичното fine-tune API все още не е налично. За custom поведение използвайте Instructions, guided generation и few-shot примери в prompt-а.

Колко струва използването на FoundationModels?

Нула, както за вас като разработчик, така и за потребителя. Няма API такси, няма месечен лимит на заявки, няма квота. Единственият „разход" е батерията и thermal budget-ът на устройството, което прави оптимизацията на дължината на prompts и честотата на заявки съществена част от UX дизайна.

Hiroshi Sato
За Автора Hiroshi Sato

Apple Platforms specialist building for iOS, macOS, visionOS, and the occasional watchOS app nobody asked for.