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