---
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 行為 (搜尋、運算、查閱)。
