--- name: foundation-models-on-device description: 適用於 Apple FoundationModels 框架的裝置端 LLM 指南 — 包含文字建構、使用 @Generable 進行引導式建構、工具呼叫以及 iOS 26+ 的快照串流。 --- # FoundationModels:裝置端 LLM (iOS 26) 使用 FoundationModels 框架將 Apple 的裝置端語言模型整合至 App 中的模式。內容涵蓋文字建構、使用 `@Generable` 的結構化輸出、自定義工具呼叫以及快照串流 (Snapshot Streaming) — 所有功能皆在裝置端執行,以保護隱私並支援離線作業。 ## 何時啟用 - 使用 Apple Intelligence 裝置端模型開發 AI 驅動的功能。 - 在不依賴雲端的情況下建構或總結文字。 - 從自然語言輸入中擷取結構化資料。 - 針對領域特有 (Domain-specific) 的 AI 行為實作自定義工具呼叫。 - 為了即時 UI 更新而串流結構化回應。 - 需要極高隱私保護的 AI 功能 (資料不離開裝置)。 ## 核心模式 — 可用性檢查 (Availability Check) 在建立會話 (Session) 之前,請務必檢查模型可用性: ```swift struct GenerativeView: View { private var model = SystemLanguageModel.default var body: some View { switch model.availability { case .available: ContentView() case .unavailable(.deviceNotEligible): Text("此裝置不符合 Apple Intelligence 使用資格") case .unavailable(.appleIntelligenceNotEnabled): Text("請在設定中啟用 Apple Intelligence") case .unavailable(.modelNotReady): Text("模型正在下載或尚未就緒") case .unavailable(let other): Text("模型不可用:\(other)") } } } ``` ## 核心模式 — 基礎會話 (Basic Session) ```swift // 單次對話 (Single-turn):每次都建立新會話 let session = LanguageModelSession() let response = try await session.respond(to: "去巴黎旅遊的最佳月份是幾月?") print(response.content) // 多輪對話 (Multi-turn):重複使用會話以保留上下文 let session = LanguageModelSession(instructions: """ 你是一位烹飪助手。 請根據食材提供食譜建議。 建議應簡明扼要且具備實作性。 """) let first = try await session.respond(to: "我有雞肉和米飯") let followUp = try await session.respond(to: "那素食的選項呢?") ``` 設定指令 (Instructions) 的重點: - 定義模型角色 (例如:「你是一位導師」)。 - 指定具體任務 (例如:「幫助擷取行事曆事件」)。 - 設定風格偏好 (例如:「盡可能簡短地回應」)。 - 增加安全措施 (例如:「針對危險請求,請回應『我無法協助處理此事』」)。 ## 核心模式 — 使用 @Generable 的引導式建構 建構結構化的 Swift 型別,而非原始字串: ### 1. 定義 Generable 型別 ```swift @Generable(description: "關於一隻貓的基本檔案資訊") struct CatProfile { var name: String @Guide(description: "貓的年齡", .range(0...20)) var age: Int @Guide(description: "一句話描述貓的個性") var profile: String } ``` ### 2. 要求結構化輸出 ```swift let response = try await session.respond( to: "生成一隻可愛的流浪貓資訊", generating: CatProfile.self ) // 直接存取結構化欄位 print("名稱:\(response.content.name)") print("年齡:\(response.content.age)") print("描述:\(response.content.profile)") ``` ### 支援的 @Guide 約束 (Constraints) - `.range(0...20)` — 數值範圍。 - `.count(3)` — 陣列元素數量。 - `description:` — 提供生成內容的語義化指引。 ## 核心模式 — 工具呼叫 (Tool Calling) 讓模型調用自定義程式碼來執行領域特定任務: ### 1. 定義工具 (Tool) ```swift struct RecipeSearchTool: Tool { let name = "recipe_search" let description = "根據給定的關鍵字搜尋食譜並回傳結果清單。" @Generable struct Arguments { var searchTerm: String var numberOfResults: Int } func call(arguments: Arguments) async throws -> ToolOutput { let recipes = await searchRecipes( term: arguments.searchTerm, limit: arguments.numberOfResults ) return .string(recipes.map { "- \($0.name): \($0.description)" }.joined(separator: "\n")) } } ``` ### 2. 建立帶有工具的會話 ```swift let session = LanguageModelSession(tools: [RecipeSearchTool()]) let response = try await session.respond(to: "幫我找一些義大利麵食譜") ``` ### 3. 處理工具錯誤 ```swift do { let answer = try await session.respond(to: "尋找番茄湯的食譜。") } catch let error as LanguageModelSession.ToolCallError { print(error.tool.name) if case .databaseIsEmpty = error.underlyingError as? RecipeSearchToolError { // 處理特定工具錯誤 } } ``` ## 核心模式 — 快照串流 (Snapshot Streaming) 使用 `PartiallyGenerated` 型別為即時 UI 串流結構化回應: ```swift @Generable struct TripIdeas { @Guide(description: "未來旅行的點子") var ideas: [String] } let stream = session.streamResponse( to: "有哪些令人興奮的旅行點子?", generating: TripIdeas.self ) for try await partial in stream { // partial 類型為 TripIdeas.PartiallyGenerated (所有屬性皆為 Optional) print(partial) } ``` ### SwiftUI 整合範例 ```swift @State private var partialResult: TripIdeas.PartiallyGenerated? @State private var errorMessage: String? var body: some View { List { ForEach(partialResult?.ideas ?? [], id: \.self) { idea in Text(idea) } } .overlay { if let errorMessage { Text(errorMessage).foregroundStyle(.red) } } .task { do { let stream = session.streamResponse(to: prompt, generating: TripIdeas.self) for try await partial in stream { partialResult = partial } } catch { errorMessage = error.localizedDescription } } } ``` ## 關鍵設計決策 | 決策 | 理由 | |----------|-----------| | 裝置端執行 | 隱私保護 — 資料不離開裝置;支援離線工作 | | 4,096 Token 限制 | 裝置端模型的物理約束;大數據需跨會話切割處理 | | 快照串流 (而非 Deltas) | 對結構化輸出更友善;每個快照都是完整的局部狀態 | | `@Generable` 巨集 | 結構化生成的編譯時安全性;自動建構 `PartiallyGenerated` 型別 | | 每個會話單次請求 | `isResponding` 可防止併發請求;需要時可建立多個會話 | | 使用 `response.content` | 正確的 API 名稱 — 始終透過 `.content` 屬性獲取結果 | ## 最佳實踐 - **建立會話前務必檢查 `model.availability`** — 妥善處理各種不可用的情況。 - **使用 `instructions` 指導模型行為** — 它們的優先權高於提示詞 (Prompts)。 - **發送新請求前檢查 `isResponding`** — 會話一次僅處理一個請求。 - **存取 `response.content` 獲取結果** — 而非使用舊有的 `.output`。 - **將大型輸入切割為多個區塊** — 4,096 Token 限制包含指令、提示詞與輸出的總和。 - **針對結構化輸出使用 `@Generable`** — 比解析原始字串更具可靠保障。 - **使用 `GenerationOptions(temperature:)` 調整創意度** (數值越高越具備創意性)。 - **使用 Instruments 監控效能** — 使用 Xcode Instruments 分析請求效能。 ## 應避免的反模式 - 未先檢查 `model.availability` 就建立會話。 - 發送超過 4,096 Token 上下文窗口的輸入內容。 - 嘗試在單一會話上併發發起多個請求。 - 使用 `.output` 而非 `.content` 來獲取回應資料。 - 在可以使用 `@Generable` 結構化輸出的情況下,仍手動解析原始字串回應。 - 在單一提示詞中建置過於複雜的多步驟邏輯 — 應將其拆分為多個聚焦的提示詞。 - 假設模型始終可用 — 裝置資格與使用者設定各有不同。 ## 適用情境 - 針對隱私敏感型 App 提供裝置端文字建構。 - 從使用者輸入中擷取結構化資料 (表單、自然語言指令)。 - 必須支援離線工作的 AI 輔助功能。 - 逐步顯示生成內容的串流 UI。 - 透過工具呼叫執行領域特定 AI 行為 (搜尋、運算、查閱)。