claude-code/claude-zh/skills/foundation-models-on-device/SKILL.md

244 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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