# Apple Foundation Models Framework: Cihaz Üstü LLM'i Production'a Taşımak
WWDC25'te Apple, FoundationModels framework'üyle Apple Intelligence'ı besleyen ~3 milyar parametreli on-device dil modeline doğrudan Swift API'siyle erişim açtı. Bir yıl sonra WWDC26'da bu API'yi LanguageModel protokolüyle soyutlayıp üçüncü parti sağlayıcılara (Anthropic, Google) kapı araladılar. Bu yazıda framework'ü gerçek bir üründe kullanırken karşılaştığımız üç konuya odaklanıyorum: guided generation ile tip-güvenli çıktı, Tool protocol ile gerçek veriye erişim, ve 4096 token'lık context penceresinin production'da yarattığı sürprizler.
Bu framework'ü Core ML tabanlı özel model pipeline'larıyla karıştırmayın — orada kendi eğittiğiniz modeli çalıştırıyorsunuz, burada Apple'ın hazır ve sürekli güncellenen sistem modelini prompt'luyorsunuz. İki yaklaşımın ne zaman hangisini gerektirdiğini "Core ML on-device AI pipeline" yazımda ayrıca ele almıştım.
Mimari: SystemLanguageModel ve Session Yaşam Döngüsü
Framework'ün merkezinde SystemLanguageModel var — cihazdaki sistem modeline referans. Her etkileşim bir LanguageModelSession üzerinden yürüyor ve session, konuşma geçmişini kendi içinde tutuyor:
swift
1import FoundationModels2 3let session = LanguageModelSession(4 instructions: "Sen kısa, teknik ve doğrudan cevaplar veren bir asistansın."5)6 7let response = try await session.respond(to: "SwiftData ile CloudKit senkronizasyonunun temel riski ne?")8print(response.content) // StringProduction kodunda ilk yapmanız gereken şey respond çağırmak değil, kullanılabilirliği kontrol etmek. Model üç sebepten "yok" olabilir: cihaz uygun değil, Apple Intelligence kapalı, ya da model henüz indirilmedi.
swift
1switch SystemLanguageModel.default.availability {2case .available:3 // devam et4case .unavailable(.deviceNotEligible):5 showFallbackUI(reason: .unsupportedDevice)6case .unavailable(.appleIntelligenceNotEnabled):7 showFallbackUI(reason: .settingsRequired)8case .unavailable(.modelNotReady):9 showFallbackUI(reason: .downloading)10case .unavailable:11 showFallbackUI(reason: .unknown)12}Pro Tip: Bu switch'i uygulamanın en tepesinde, feature flag mantığıyla birlikte çalıştırın. "Model var ama indirilmemiş" durumu simülatörde hiç görünmez, gerçek cihazda Apple Intelligence ilk açıldığında sık karşılaşılan bir senaryodur — QA checklist'inize mutlaka ekleyin.
Guided Generation: @Generable ve @Guide
Framework'ün en pratik özelliği, modelin serbest metin yerine doğrudan Swift struct'ı üretmesini sağlayan guided generation. @Generable makrosu derleme zamanında bir şema üretiyor, model bu şemaya constrained decoding ile uyuyor — yani JSON parse hatası, eksik alan, halüsinasyonla uydurulmuş enum değeri gibi klasik LLM entegrasyon sorunları yapısal olarak ortadan kalkıyor.
swift
1@Generable2struct MeetingSummary {3 @Guide(description: "Toplantının tek cümlelik özeti")4 let headline: String5 6 @Guide(description: "Aksiyon maddeleri, her biri kısa ve emir kipinde")7 let actionItems: [String]8 9 @Guide(description: "Toplantının önem derecesi, 1 düşük 5 kritik", .range(1...5))10 let priority: Int11}12 13let session = LanguageModelSession()14let result = try await session.respond(15 to: "Bu toplantı notlarını özetle: \(transcript)",16 generating: MeetingSummary.self17)18 19let summary = result.content // MeetingSummary, String değil.range, regex tabanlı .pattern gibi @Guide kısıtları sadece dokümantasyon değil — modelin decode sırasında uyması gereken gerçek kısıtlar. Pratikte bu, backend'e giden bir API şemasını LLM'e prompt olarak açıklamaktan çok daha güvenilir.
Pro Tip: @Generable struct'ları asla doğrudan domain modeliniz yapmayın. Aradaki bir mapping katmanı (MeetingSummary → Meeting) hem şema değişikliklerini izole eder hem de modelin ürettiği alanları backend'e giden alanlardan ayırır — model şemasını genişletmek istediğinizde domain modelinizi kırmazsınız.
Streaming: Partial Generation ile Algılanan Gecikme
3B'lik model hızlı olsa da, uzun bir yanıtı tek seferde beklemek UX'i öldürür. streamResponse her adımda T.PartiallyGenerated tipinde kısmi bir sonuç veriyor — derleyici bunu @Generable struct'ınızdan otomatik türetiyor, yani stream sırasında bile alanlara tip-güvenli erişiyorsunuz:
swift
1let stream = session.streamResponse(2 to: "5 tane yaratıcı fitness uygulaması ismi öner",3 generating: AppNameSuggestions.self4)5 6for try await partial in stream {7 // partial.names bir [String]?, dolarken UI'ı güncelleyin8 await MainActor.run {9 viewModel.names = partial.names ?? []10 }11}SwiftUI tarafında bu, ChatGPT tarzı "yazarken görünme" efektini ekstra state machine kurmadan verir — for try await döngüsü zaten @Observable view model'inizi besleyebilir.
Tool Calling: Modeli Gerçek Veriyle Besleme
3B parametreli bir model, eğitim verisinin dondurulduğu andan sonrasını veya sizin özel veritabanınızı bilemez. Tool protocol bu boşluğu kapatıyor: modele hangi fonksiyonları çağırabileceğini söylüyorsunuz, o da hangi durumda hangi tool'u, hangi argümanlarla çağıracağına kendisi karar veriyor — framework paralel ve seri tool çağrı grafiğini otomatik yönetiyor.
swift
1struct FindSavedArticlesTool: Tool {2 let name = "findSavedArticles"3 let description = "Kullanıcının kaydettiği makaleler arasında arama yapar."4 let index: ArticleIndex5 6 @Generable7 struct Arguments {8 @Guide(description: "Kullanıcının sorusundan kısa bir arama ifadesi")9 var query: String10 11 @Guide(description: "Döndürülecek maksimum makale sayısı", .range(1...5))12 var limit: Int13 }14 15 func call(arguments: Arguments) async throws -> ToolOutput {16 let results = await index.search(arguments.query, limit: arguments.limit)17 return ToolOutput(results.map(\.title).joined(separator: "\n"))18 }19}20 21let session = LanguageModelSession(tools: [FindSavedArticlesTool(index: articleIndex)])22let response = try await session.respond(to: "Geçen ay kaydettiğim SwiftUI makalelerini bul")Burada kritik nokta: tool çağrısı cihazda gerçekleşiyor, sizin call(arguments:) implementasyonunuz network'e çıkabilir ama modelin kendisi hiçbir zaman ham veriyi Apple sunucularına göndermiyor — sadece kendi ürettiği argümanları ve sizin döndürdüğünüz sonucu görüyor.
Birden fazla tool tanımladığınızda framework, gerekirse bunları paralel çağırıp sonuçları tek bir nihai yanıtta birleştiriyor — siz orkestra mantığını elle yazmıyorsunuz, sadece her tool'un name ve description'ını yeterince açık yazmakla sorumlusunuz. Pratikte en sık yapılan hata, iki tool'un açıklamasının birbirine çok benzemesi: model hangisini çağıracağına kararsız kalıp yanlış tool'u tetikliyor. Tool sayısı beşi geçtiğinde açıklamaları gözden geçirip çakışan kelimeleri ayıklamak, prompt mühendisliğinden daha çok kazandırıyor.
Context Window: 4096 Token Duvarı
Burada gerçek üründe en çok canımızı yakan konuya geliyoruz. SystemLanguageModel.default'ün context penceresi 4096 token — GPT-4 sınıfı bulut modellerine alışkın bir ekip için şaşırtıcı derecede küçük. Pencere aşıldığında framework sessizce kısaltmaz, .exceededContextWindowSize hatası fırlatır ve o session artık kullanılamaz hale gelir.
Apple Developer Technote TN3193, tam da bu sorunu çözmek için context yönetimi rehberi yayınladı; iOS 26.4 ile SystemLanguageModel üzerine contextSize property'si ve tokenCount(for:) metodu eklendi — artık prompt'u göndermeden önce kaç token tükettiğini ölçebiliyorsunuz.
swift
1let model = SystemLanguageModel.default2let promptTokens = model.tokenCount(for: longTranscript)3 4if promptTokens > model.contextSize - reservedForResponse {5 longTranscript = summarizeOrTruncate(longTranscript)6}Senaryo | Riskli mi? | Öneri |
|---|---|---|
Tek seferlik kısa prompt (özet, sınıflandırma) | Düşük | Doğrudan gönder, ölçüm gereksiz |
Çok turlu sohbet (session state birikir) | Yüksek | Her turda `tokenCount` kontrolü + eski turları özetleyip session'ı sıfırlama |
Uzun transcript/log analizi | Çok yüksek | Chunk'lara böl, her chunk'ı ayrı session'da işle |
Tool sonuçları büyük JSON döndürüyor | Orta-yüksek | Tool çıktısını modele göndermeden önce özetle |
Pro Tip: Çok turlu bir sohbet arayüzü kuruyorsanız, session'ı asla sınırsız büyütmeyin. N turdan sonra eski geçmişi kendi respond(to:generating:) çağrınızla özetleyip yeni bir session'a "context özeti" olarak enjekte edin — kullanıcı akışı kesintisiz görür, siz .exceededContextWindowSize crash'inden kurtulursunuz.
Gizlilik: Somut Kazanım Nerede?
Bulut LLM entegrasyonlarının en büyük operasyonel yükü KVKK/GDPR veri işleme sözleşmeleri, veri saklama süreleri ve "hangi veri hangi sunucuya gidiyor" denetimidir. Foundation Models'te on-device model çalıştığında:
- Prompt, tool argümanları ve tool sonuçları cihazdan hiç çıkmıyor.
- Apple Intelligence kapalıyken framework zaten
.unavailabledönüyor — veri sızıntısı riski framework seviyesinde yapısal olarak yok. - Server tarafı gereken daha büyük görevler için Private Cloud Compute devreye giriyor; Apple'ın iddiası bu isteklerin de doğrulanabilir şekilde loglanmadığı ve sadece isteği işlemek için kullanıldığı yönünde — ama bu ayrı bir güven modeli, on-device ile aynı garanti seviyesinde değil.
Bu, "kullanıcı verisi asla üçüncü tarafa gitmesin" gereksinimi olan sağlık, finans veya kurumsal içerik uygulamaları için mimari kararı kolaylaştırıyor: hassas metni önce on-device modelle işleyip, gerekirse sadece anonimleştirilmiş özet bulut servisine gidiyor.
WWDC26: LanguageModel Protokolü ve Üçüncü Parti Sağlayıcılar
WWDC26'da framework'e gelen en büyük mimari değişiklik, model soyutlama katmanının açılması oldu. Yeni LanguageModel protokolü sayesinde hem SystemLanguageModel hem de sunucu tabanlı modeller aynı LanguageModelSession'ı besleyebiliyor; Anthropic ve Google kendi Swift paketlerini yayınlayarak frontier modellerine bu API üzerinden erişim açtı. Pratik sonucu: aynı @Generable/Tool kod tabanınızı koruyup, ihtiyaç anında on-device model yerine bulut modeline geçiş yapabiliyorsunuz — guided generation ve tool calling sözleşmesi değişmiyor.
Bu, ekip için gerçek bir mimari kazanç: özellik geliştirme sırasında on-device modelle hızlıca prototip çıkarıp, kullanıcı testlerinde 4096 token limitine veya akıl yürütme sınırına takılan senaryoları tespit edip yalnızca o akışları bulut sağlayıcısına yönlendirebiliyorsunuz. Tool protokolü ve @Generable şemanız değişmediği için bu geçiş, ayrı bir entegrasyon projesi değil, session başlatma satırında tek bir parametre değişikliği oluyor.
Ne Zaman Kullanmalı, Ne Zaman Kullanmamalı
Kullanım senaryosu | Foundation Models | Neden |
|---|---|---|
Uygulama içi metin özetleme, sınıflandırma | ✅ Uygun | Kısa prompt, düşük gecikme, offline çalışır |
Yapılandırılmış form/veri çıkarımı (fiş, form OCR sonrası) | ✅ Uygun | Guided generation tip güvenliği veriyor |
Basit asistan + kullanıcının kendi verisine erişim (tool calling) | ✅ Uygun | Veri cihazdan çıkmadan zenginleştirilmiş yanıt |
Uzun doküman/kod tabanı analizi | ⚠️ Dikkatli | 4096 token limiti — chunking şart |
Genel bilgi soru-cevap, güncel olaylar | ❌ Uygun değil | 3B model, bulut modelleri kadar geniş bilgiye sahip değil |
Çok dilli karmaşık akıl yürütme | ⚠️ Dikkatli | Guardrail false-positive oranı hâlâ bulut modellerinden yüksek |
Sonuç
Foundation Models framework, "her AI özelliği için bulut API'sine bağlan" varsayımını kırıyor — özellikle kısa, yapılandırılmış, gizlilik-hassas görevlerde on-device model production'da gerçekten yeterli. Ama 4096 token context penceresi ve 3B parametrenin akıl yürütme sınırları, bunu "her LLM ihtiyacının çözümü" değil, doğru yerde kullanılan bir araç yapıyor. Bizim kuralımız basit: kullanıcı verisi cihazdan çıkmamalıysa ve görev kısa/yapılandırılmışsa önce Foundation Models'i deneyin, context veya akıl yürütme sınırına çarptığınız anda WWDC26'nın açtığı LanguageModel protokolüyle bulut tarafına sorunsuzca geçin.

