Tüm Yazılar
KategoriAI
Okuma Süresi
8 dk
Yayın Tarihi
2026-10-15
Kelime Sayısı
1.374kelime

Kahveni hazırla - bu içerikli bir makale!

SwiftIntelligence 2.0: uygulamanın kendi metni için cihaz üzerinde arama, cevap ve maskeleme

Özet

SwiftIntelligence 2.0 neden tek ve küçük bir Swift target: hibrit BM25 ve vektör arama, kaynaklı cihaz üstü cevaplar, sızmayan maskeleme.

  • Pek çok uygulama, kullanıcının içinde arama yapmak ya da soru sormak isteyeceği metinler tutar: notlar, yardım makaleleri, kaydedilmiş mesajlar.
  • Tek target, sıfır bağımlılık. 1.x'teki çok modüllü yapı, kodun ne kadar azının gerçekten iş yaptığını gizliyordu.
  • https://github.com/muhittincamdali/SwiftIntelligence.git adresini Swift Package Manager ile 2.0.0 sürümünden itibaren ekleyin, ardından dokümanları ekleyip arayın.
  • Kaynak kod: [github.com/muhittincamdali/SwiftIntelligence](https://github.com/muhittincamdali/SwiftIntelligence)
SwiftIntelligence 2.0: uygulamanın kendi metni için cihaz üzerinde arama, cevap ve maskeleme

İçindekiler


Problem

Pek çok uygulama, kullanıcının içinde arama yapmak ya da soru sormak isteyeceği metinler

tutar: notlar, yardım makaleleri, kaydedilmiş mesajlar. Apple bunu cihaz üzerinde yapmak için

gereken parçaların çoğunu zaten veriyor. NaturalLanguage metni kelimelere ayırıp embedding'e

çeviriyor, Accelerate matrisleri hızlı çarpıyor, iOS 26 ve macOS 26'dan itibaren de Foundation

Models cihaz üstü bir dil modeline erişim sağlıyor. Eksik olan, bunları birleştiren katman:

dokümanı al, pasajlara böl, kişisel veriyi çıkar, embedding'e çevir, indeksle, ara, en iyi

pasajları atıf yapması talimatıyla modele ver ve hepsini uygulama açılışları arasında sakla.

SwiftIntelligence 2.0 bu katmandır, yalnızca bu katman. iOS 18 ve macOS 15 için, bağımlılığı

olmayan tek bir library target. Model eğitmez, görüntü ya da ses işlemez, sunucu tarafında

çalışan bir vektör veritabanı da değildir.

Bu dar kapsam bir yeniden yazımın sonucu. Repo Ağustos 2025'te geniş kapsamlı bir "AI/ML

framework" olarak açıldı. 1.2.2 sürümüne gelindiğinde 16 modüle ulaşmıştı ve 2.0.0

CHANGELOG'unun ifadesiyle "yaklaşık 50.000 satırın çoğu simüle edilmiş sonuç döndürüyordu

(rastgele güven değerleri, sabit cümleler, servis sağlayıcı stub'ları)". Haziran 2026'daki bir

sadeleştirme commit'i

(`32a1ca7`) release

script'lerini ve kullanılmayan modülleri kaldırdı, ama aynı zamanda kardeş bir klasöre,

../SwiftAI'ye path dependency ekledi; manifest artık başka hiçbir makinede çözülmüyordu.

14 Ekim 2026'da yeniden yazımın ilk commit'i

(`0e2dcbd`) eski

modülleri sildi, 15 Ekim'de de

PR #9 sonucu v2.0.0 olarak

birleştirdi. 1.x API'sinden hiçbir şey korunmadı; major sürüm olmasının nedeni bu.

Tasarım kararları ve alternatifler

Tek target, sıfır bağımlılık. 1.x'teki çok modüllü yapı, kodun ne kadar azının gerçekten

iş yaptığını gizliyordu. Yaklaşık 2.400 satırlık kaynak için ayrı library ürünleri (core,

search, privacy, generation) bakım yükü getirir, tek bir paket eklemek isteyen kullanıcıya

da bir şey kazandırmazdı. Kod büyürse sonradan bölünebilir.

KnowledgeBase bir actor. Swift 6 dil modunda actor, değişebilir indeksleri data race'ten

korumanın en kısa yolu. Ancak actor'ler reentrant'tır: bir çağrı await noktasında beklerken

başka bir çağrı çalışabilir. Bu yüzden add önce bir partideki bütün embedding'leri hesaplayıp

doğruluyor (bekleyen tek kısım bu), ardından hepsini hiçbir askıya alma noktası olmadan

depoluyor. Kaynakta bu açıkça yazıyor: "No suspension point from here on: concurrent calls

cannot interleave their changes." Böylece bir embedding hatası, KnowledgeBase'i yarım

indekslenmiş değil, hiç değişmemiş halde bırakıyor. search de aynı kurala uyuyor: tek

await'i sorgunun embedding'i. Kilitle korunan bir class'ı derleyici denetleyemezdi,

@MainActor ise aramaları UI thread'inde çalıştırırdı.

Hibrit arama ve reciprocal rank fusion. Her pasaj iki kez indeksleniyor: KeywordIndex

içinde kelimeleriyle (BM25) ve VectorIndex içinde vektör olarak. Vektörler başka

kelimelerle yazılmış pasajları buluyor; BM25 ise küçük bir embedding modelinin kaçırabileceği

"E-4012" gibi tam kodları yakalıyor. İki skor farklı ölçeklerde (cosine similarity sınırlı,

BM25 sınırsız), dolayısıyla ağırlıklı toplamak keyfi bir normalizasyon gerektirirdi.

RankFusion bunun yerine yalnızca sıralara bakıyor: bir öğenin skoru, listeler üzerinden

weight / (k + rank) toplamı; defaultSmoothing = 60.0, kaynaktaki yorumla "Cormack, Clarke

ve Büttcher'ın (2009) olağan smoothing sabiti". Beraberlikte öğelerin listelerde ilk görüldüğü

sıra korunuyor, yani sonuçlar deterministik.

Tam arama, yaklaşık index yok. VectorIndex birim uzunluktaki satırları tek düz bir dizide

saklıyor; cosine similarity böylece bir dot product oluyor ve arama tüm satırlar üzerinde tek

bir vDSP_mmul çağrısı. Hedef, tek bir kullanıcının ya da tek bir uygulamanın dokümanları;

README bunu "on binlerce pasaja kadar" diye tarif ediyor. Bu boyutta tam arama basit ve

recall kaybı yok. HNSW gibi yaklaşık bir index bu ölçekte hiçbir kazanç sağlamadan bellek,

karmaşıklık ve belirsizlik eklerdi. Bedeli, arama süresinin vektör sayısıyla doğrusal artması.

Varsayılan olarak hashing embedder. HashingEmbedder kelimeleri ve üç karakterlik

parçaları 512 boyuta hash'liyor. Model gerektirmiyor, her dilde ve iOS simülatöründe çalışıyor,

testlerde deterministik. Kendi 64-bit FNV-1a fonksiyonunu kullanıyor, çünkü kaynak yorumunun

açıkladığı gibi Swift'in Hasher'ı her process'te farklı seed'leniyor ve kaydedilip sonra

karşılaştırılan vektörler için kullanılamıyor. SentenceEmbedder ve ContextualEmbedder

Apple'ın embedding'lerini sarmalıyor; TextEmbedder protokolüne uyan her tip takılabiliyor.

Embedder'ın kimliği veriyle birlikte kaydediliyor ve farklı bir embedder ile yükleme,

karşılaştırılamaz vektörleri karşılaştırmak yerine KnowledgeBaseError.embedderMismatch

fırlatıyor.

Gerekirse cevap vermeyen, kaynağa dayalı cevaplar. GroundedPrompt pasajları

numaralandırıyor ve modele yalnızca onlardan cevap vermesini, [1] biçiminde atıf yapmasını

söylüyor. Pasaj bütçesi varsayılan olarak 6.000 karakter. Hiçbir şey eşleşmezse answer(_:)

modeli hiç çağırmadan AnswerError.noRelevantPassages fırlatıyor. Cevap kodu

canImport(FoundationModels) ve availability kontrollerinin arkasında; paket iOS 18 ve

macOS 15 için de derlenmeye devam ediyor.

Gerçek bir hata: benzer görünen metni sızdıran maskeleme

PersonalDataRedactor e-posta adreslerini, kart numaralarını, IBAN'ları ve diğer kişisel

verileri [EMAIL_1] gibi yer tutucularla değiştiriyor. İlk sürümü kalıplarını ham metin

üzerinde çalıştırıyordu; testler düzgün biçimlendirilmiş örnekler kullandığı için geçiyordu.

Oysa bir maskeleme aracının asıl işi tam da düzgün biçimlendirilmemiş metin.

`3e96601` commit'i,

sistematik bir denemenin bulduğunu kaydediyor: eski koda karşı 12 örnek değerden 10'u

tamamen sızdı. En dash, nokta ya da satır sonuyla gruplanmış kart numaraları, başka bir rakam

grubundan sonra gelen kart ("Order 12 4111 1111 1111 1111"), full-width ve aksanlı e-posta

adresleri, hemen arkasından bir kod gelen IBAN'lar ve full-width rakamlarla yazılmış IPv4

adresleri. Çakışan eşleşmelerde kısa olan atılıyor, onun çakışmayan kısmı da görünür kalıyordu.

Düzeltme, eşleştirmenin yapıldığı yeri değiştirdi. Kalıplar artık metnin katlanmış bir

kopyasında (FoldedText) çalışıyor: format karakterleri siliniyor, NFKC uyumluluk biçimleri

uygulanıyor, birleşik işaretler kaldırılıyor, her tire "-" ve her boşluk " " oluyor. Değişen

karakterlerin haritası her eşleşmeyi orijinal metne geri taşıyor. Kart adayları rakam

gruplarından oluşan koşular; her koşu içinde Luhn kontrolünden geçen en uzun pencere

kazanıyor. IBAN'lar ülkelerinin uzunluğuna kesilip mod 97 ile doğrulanıyor. Çakışan

eşleşmeler birleştiriliyor. Sonraki commit'ler aynı sınıftan başka varyantları da kapattı;

RedactionBypassTests suite'i bunların hepsini tutuyor, checksum'dan geçmeyen numaralar gibi

görünür kalması gereken benzerleri de dahil.

Aynı haftadan bir düzeltme de yakın bir dersi gösteriyor. VectorMath.normalized bir vektörü

vDSP ile en büyük değerine bölüyordu; Accelerate subnormal sayıları sıfıra çektiği için

[1e-39, 0, 0, 0] gibi sonlu bir vektör [inf, nan, nan, nan] oluyordu. Ekleme ve kaydetme

başarılı oluyor, ama yükleme kayıtlı vektörleri reddediyor ve KnowledgeBase bir daha

açılamıyordu.

`aabec82` commit'i bu

tür vektörleri önce düz Swift'te 2^64 ile ölçekliyor; bir test de embedding'i subnormal olan

bir dokümanı ekleyip kaydediyor, yüklüyor ve arıyor. Ders: bir invariant'ı veri yazılırken

ve okunurken ayrı ayrı kontrol etmek ve gidiş-dönüşü test etmek.

Kullanım

https://github.com/muhittincamdali/SwiftIntelligence.git adresini Swift Package Manager ile

2.0.0 sürümünden itibaren ekleyin, ardından dokümanları ekleyip arayın. Bu örnek README'den

birebir alındı; orada örnek uygulama tarafından derleniyor ve ReadmeSamples.swift üzerinden

paket testlerinde çalıştırılıyor:

swift
1let knowledge = KnowledgeBase()
2try await knowledge.add("You can return any item within 30 days of delivery for a full refund.", id: "returns")
3try await knowledge.add("Orders ship within two business days; delivery takes three to five days.", id: "shipping")
4try await knowledge.add("Every laptop comes with a two-year warranty against defects.", id: "warranty")
5 
6let results = try await knowledge.search("How do I return an item for a refund?")
7print(results.first?.documentID ?? "nothing found") // returns
8print(results.first?.text ?? "") // You can return any item within 30 days …

Maskeleme geri çevrilebilir; model yer tutucularla çalışabilir, uygulama da değerleri modelin

cevabına geri koyabilir:

swift
1let redactor = PersonalDataRedactor()
2let redacted = redactor.redact("Mail jane@example.com or pay with 4111 1111 1111 1111.")
3print(redacted.text) // "Mail [EMAIL_1] or pay with [CARD_1]."
4 
5// After a model has answered from the redacted text, put the values back.
6let reply = redacted.restoringOriginals(in: "I wrote to [EMAIL_1].")
7print(reply) // "I wrote to jane@example.com."

v2.0.0 etiketinde macOS'ta swift test 22 suite içinde 156 test çalıştırıyor; README iPhone

simülatöründe 155 test bildiriyor. Simülatörde eksik NaturalLanguage modellerine ihtiyaç duyan

testler gerekçesiyle birlikte atlanıyor.

Sınırlar

  • Ölçek. İndeksler tam ve bellekte. Tek bir kullanıcının ya da uygulamanın dokümanlarına,

on binlerce pasaja kadar uygun. 2.0.0 sürümü yayımlanmış bir benchmark içermiyor.

  • Orijinal metin kaydedilmiyor. Kaydedilen KnowledgeBase maskelenmiş pasajları ve

vektörlerini tutuyor. Embedder değiştirmek, dokümanları yeniden eklemek demek.

  • Maskeleme bir güvenlik ağı, garanti değil. Yazıyla yazılmış ("jane at example dot com"),

sıradan kelimelerle bölünmüş ya da checksum'ı geçersiz veri bulunmuyor; isim tespiti

istatistiksel.

  • Anlamsal arama kalitesi Apple'ın modellerine bağlı. Bunlar küçük, genel modeller ve iOS

simülatöründe yoklar. README, kendi testleriniz aksini göstermedikçe hibrit modda kalmayı

öneriyor.

  • Cevaplar iOS 26 ya da macOS 26 gerektiriyor; cihazda Apple Intelligence açık olmalı. Model,

dokümanların içine yazılmış talimatlara uyabilir; SECURITY.md bunu bilinen bir sınır olarak

listeliyor.

  • Kaydedilen dosyalar paket tarafından şifrelenmiyor. SECURITY.md kaydederken

.completeFileProtection kullanılmasını öneriyor.

README'deki yol haritasında iki adım var: doküman kaynağı olarak PDF'ler ve görseller (PDFKit

ve Vision metin tanıma) ve Apple'ın yerleşik embedding'lerinden daha güçlü anlamsal arama için

bir Core ML sentence-encoder embedder'ı.

Kaynak kod: github.com/muhittincamdali/SwiftIntelligence

Etiketler

#swift#on-device#search#foundation-models#privacy#swift-package
Muhittin Çamdalı

Muhittin Çamdalı

Senior Mobile Engineer

12+ yıllık deneyime sahip Senior Mobile Engineer. iOS, Flutter ve React Native ile mobil ürünler geliştiriyorum; full-stack ve AI-native çalışıyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Onay e-postasındaki bağlantıyı açıp “Aboneliğimi onayla” düğmesine bastığında aboneliğin başlar. Bültende açılma/tıklama istatistikleri tutulur; dilediğin an tek tıkla ayrılabilirsin. Gizlilik

Paylaş

İlgili İçerik