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

8.2 KiB
Raw Permalink Blame History

name description
foundation-models-on-device 適用於 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) 之前,請務必檢查模型可用性:

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)

// 單次對話 (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 型別

@Generable(description: "關於一隻貓的基本檔案資訊")
struct CatProfile {
    var name: String

    @Guide(description: "貓的年齡", .range(0...20))
    var age: Int

    @Guide(description: "一句話描述貓的個性")
    var profile: String
}

2. 要求結構化輸出

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)

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. 建立帶有工具的會話

let session = LanguageModelSession(tools: [RecipeSearchTool()])
let response = try await session.respond(to: "幫我找一些義大利麵食譜")

3. 處理工具錯誤

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 串流結構化回應:

@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 整合範例

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