← 所有文章

Apple Foundation Models:裝置端 LLM 框架完全解析

Foundation Models 框架讓應用程式得以直接、免費、離線存取那顆驅動 Apple Intelligence 的裝置端大型語言模型1。不需要 API 金鑰,沒有按 token 計費的帳單,沒有網路往返,資料也不會離開裝置。對於過去動輒得搭一套雲端 LLM、跑一輪隱私審查才能做出來的那一類功能而言,如今的成本幾乎可以歸零。代價在於能力:裝置端模型體積小、上下文視窗有限,而框架本身對「它願意做什麼、不願意做什麼」劃下了明確界線。摸清這些界線,才是真正的功夫所在。

本文是這套框架本身的參考指南:您實際會呼叫的型別、那項讓它值得一用的關鍵功能,以及您應當停手、轉向更大模型的那個臨界點。

TL;DR

  • LanguageModelSession 是進入點。建立一個會話、呼叫 respond(to:)、取回文字。多輪對話的上下文存在會話裡;單輪工作則每次都用全新的會話2
  • 引導式生成正是使用這套框架的理由。 為 Swift 型別標註 @Generable,模型便會回傳那個型別——已填妥資料、通過型別檢查——而不是一段您還得自行解析的字串3
  • Tool 協定讓模型能在生成過程中呼叫您的程式碼,取得資料或執行動作,再把結果摺回它的回答之中4
  • 動手之前,務必先檢查 SystemLanguageModel.default.availability。在不符資格的裝置上、在 Apple Intelligence 關閉時,或在模型仍在下載的期間,模型都不存在5
  • 上下文視窗真實存在,而且很小。SystemLanguageModel.default.contextSize 會回報提示與回應共用的 token 預算6。請為它預先規劃,否則會話便會拋出例外。
  • 需要 iOS 26 與一台具備 Apple Intelligence 能力的裝置。低於這道門檻,這套框架根本不存在。

這套框架是什麼,又不是什麼

Foundation Models 並非雲端端點的包裝層。模型就住在裝置上,隨作業系統一同出貨,並倚靠 Neural Engine 執行。光是這一個事實,就牽動了 API 中的每一項設計決策,以及您使用它時所做的每一個決定。

您能得到的:文字生成、摘要、分類、抽取、短篇改寫,以及結構化輸出——全都在裝置端完成,而且全部免費。您得不到的:一顆頂尖的前沿模型。Apple 打造這顆裝置端模型,是為了應用程式內部聚焦的語言任務,而非開放式推理、長文件分析,也不是可以拿來考它的世界知識。Apple 自己也是這麼說的,而這個定位很重要,因為它設定了一組期望——否則 API 會放任您去違反它1

能讓您免於踩雷的心智模型是:把這顆裝置端模型當成一位快速、私密、免費的實習生——他極擅長雕琢文字,卻極不擅長掌握事實。把材料和明確的任務交給他,別問他那些他根本無從回答的問題。

LanguageModelSession:進入點

每一次互動都從一個會話開始。

import FoundationModels

let session = LanguageModelSession()
let response = try await session.respond(to: "Summarize this review in one sentence: \(reviewText)")
print(response.content)

會話持有對話狀態。每一次呼叫 respond(to:) 都會附加到持續累積的對話記錄中,因此一個您一直留著的會話會記得先前發生過的事。對於聊天功能,這正是您要的。至於各自獨立的一次性任務(摘要這個、分類那個),則應該每次呼叫都建立全新的會話,如此陳舊的上下文才不會滲進來、把您的 token 預算吃掉2

respond(to:)async throws。它會在模型運算時暫停,並在請求超出上下文視窗、模型不可用,或內容遭防護機制拒絕時拋出例外。上述每一種情況都是您要處理的真實分支,而非可以忽略的邊角案例。

若要打造反應靈敏的 UI,請改用串流而非等待。streamResponse(to:) 會在模型逐步產出時就交付部分輸出,把原本三秒的卡頓變成隨著文字成形而逐漸浮現的內容7

引導式生成:讓這套框架值回票價的功能

這裡就是值得買票入場的部分。多數 LLM 整合,有三分之一的程式碼花在哄模型吐出合法的 JSON,另外三分之二則花在防範它仍然失敗的那些時刻。Foundation Models 把這些工作一筆勾銷。

為一個 Swift 型別標註 @Generable,請會話生成它,模型就會回傳那個型別的一個實例——已填妥資料、型別安全3

@Generable
struct Recipe {
    @Guide(description: "The dish name")
    let title: String

    @Guide(description: "Ingredients, each as 'quantity item'")
    let ingredients: [String]

    @Guide(description: "Total minutes, start to finish", .range(5...240))
    let minutes: Int
}

let session = LanguageModelSession()
let response = try await session.respond(
    to: "A weeknight pasta for two.",
    generating: Recipe.self
)
let recipe = response.content   // a Recipe, not a String

不必解析。不需要 JSONDecoder。沒有因應格式錯誤輸出的重試迴圈。@Guide 巨集則約束個別欄位:一段模型會當作指示來讀的描述,以及可選的限制,例如數值範圍,或輸出必須符合的正規表示式8。框架不是客客氣氣地請模型給一個介於 5 到 240 之間的數字;它直接約束解碼過程,讓這個欄位不可能以其他樣貌回傳。

它所強制建立的紀律,才是真正的價值所在。您先在 Swift 裡設計輸出型別,並由編譯器替您把關。模型填的是您定義好的契約,而不是回傳一段您得逆向工程的散文。對於抽取、表單填寫,以及任何把語言轉成資料的功能,引導式生成正是「展示用 demo」與「可出貨程式碼」之間的分野。

有一項值得了解的控制項:respond(to:generating:)includeSchemaInPrompt 預設為 true,這會把您型別的形狀注入提示中,引導模型朝它靠攏。除非模型已經從訓練中或從會話先前的輪次得知該格式,否則請保持開啟;為了在一個模型沒見過的格式上省 token 而把它關掉,正是讓您拿回一堆垃圾的方法9

工具呼叫:讓模型伸手觸及您的程式碼

引導式生成塑造的是「輸出什麼」。工具呼叫改變的則是「輸入什麼」。一個工具是一段您的程式碼,模型可以在生成過程中呼叫它,以取得它手上沒有的資訊或執行某個動作,再運用結果繼續它的回答4

一個工具會遵循 Tool 協定:一個 name、一段供模型判讀何時該呼叫它的 description、一個 @GenerableArguments 型別,以及一個負責實際幹活的 call(arguments:) 方法4

struct FindContacts: Tool {
    let name = "findContacts"
    let description = "Find a specific number of contacts from the address book"

    @Generable
    struct Arguments {
        @Guide(description: "How many contacts to return", .range(1...10))
        let count: Int
    }

    func call(arguments: Arguments) async throws -> [String] {
        // Fetch contacts, return formatted names.
    }
}

let session = LanguageModelSession(tools: [FindContacts()])
let response = try await session.respond(to: "Draft a dinner invite to three of my contacts.")

流程是這樣的:模型判定它需要聯絡人,以一個已驗證的 count 呼叫您的工具,您回傳資料,模型便用真實的名字寫出邀請。引數是透過同一套引導式生成機制、經過型別檢查後抵達的,因此您絕不必從自由文字中去解析模型的意圖。工具描述是您唯一能左右「模型何時伸手取用它」的槓桿,所以請把它寫得像一份函式文件——一位毫無其他背景脈絡的工程師,必須光憑它就能正確讀懂並使用。

這裡也正是 Foundation Models 與整個代理故事其餘部分接合的縫線。一個由裝置端模型呼叫的工具,和一個由 Apple Intelligence 呼叫的 App Intent,是形狀相同、表面不同的兩者:一個具名、有描述、有型別的能力。能力只設計一次,您就能透過兩條路徑同時對外揭露它。

可用性:您不能略過的那道檢查

模型並非隨時都在。在不支援 Apple Intelligence 的裝置上、在使用者把它關掉時,以及在作業系統仍在下載模型資產的那段空窗期裡,它都不存在。出貨一份假定模型必然存在的程式碼,它就會在一群您從未測試過的使用者身上崩潰、無聲降級,或卡住不動。

請檢查 SystemLanguageModel.default.availability,並依其原因分支處理5

switch SystemLanguageModel.default.availability {
case .available:
    // Show the intelligence feature.
case .unavailable(.deviceNotEligible):
    // Hide it. This device will never have the model.
case .unavailable(.appleIntelligenceNotEnabled):
    // Prompt the user to turn on Apple Intelligence.
case .unavailable(.modelNotReady):
    // Downloading or otherwise not ready yet. Try again later.
case .unavailable(let other):
    // Unknown reason. Fail closed.
}

這三種原因要求三種不同的產品回應,而把它們混為一談,正是讓這些功能感覺「壞掉了」最常見的方式。deviceNotEligible 是永久的:把功能隱藏起來,別去煩使用者。appleIntelligenceNotEnabled 是一項由使用者掌控的設定:一次性的提示是合理的。modelNotReady 是暫時的:重試,別顯示錯誤。請以對待正常路徑同等的用心去打造不可用路徑,因為對於真實存在的一部分裝置而言,它是唯一的路徑。

當模型可用、而您也知道有一個請求即將到來時,在會話上呼叫 prewarm() 可預熱模型,讓第一個真正的回應更快落地10。在使用者即將要操作的畫面上,這值得做;若您只是臆測性地呼叫它,則是一種浪費。

上下文視窗,以及它何時開始不夠用

SystemLanguageModel.default.contextSize 會回報模型運作所在的 token 預算,而這份預算是共用的:提示加上回應必須一起塞得進去6。相對於雲端模型,這個數字很小,而在真實輸入下,您很快就會感受到它的存在。一份長文件、一整段聊天記錄、一筆肥大的工具結果——其中任何一個,都可能撐爆預算、讓 respond 拋出例外。

由此衍生兩種失敗模式,而兩者都該由您防範。第一種是緩慢的蔓延:多輪會話不斷累積對話記錄,直到再多一輪就溢位。應對之道,是為不相干的工作開啟全新的會話,並讓每一輪的輸入保持精簡。第二種是單次的超大請求:一份 20 頁的 PDF 就是塞不進去,沒得商量。把它切塊、為每一塊做摘要,再就這些摘要進行推理(也就是 LLM 工程師熟知的 map-reduce),或者接受這項任務的形狀根本不適合裝置端模型。

上下文視窗,正是這套框架真正關鍵那個抉擇最清晰的訊號:何時該留在裝置端,何時該離開。

何時不該使用 Foundation Models

這套框架免費、私密又能離線,這使得人們忍不住處處都想搬它出來。請忍住。遇到以下情況時,請越過它去尋求別的方案:

  • 您需要真正的推理,或需要廣博的世界知識。 裝置端模型在設計上就是小的。開放式推理、程式碼生成,以及深度分析,屬於前沿的雲端模型。拿這些去問裝置端模型,只會得到自信卻錯誤的答案。
  • 輸入塞不進上下文視窗,而切塊又會摧毀其意義。有些任務需要一次看見全部。
  • 您需要一個由自己掌控的模型: 特定的檢查點、微調過的版本、自訂權重,或跨作業系統更新仍能維持的確定性版本控管。Apple 是按它自己的時程出貨並更新模型,而非按您的。
  • 您執行於 iOS 26 以下,或在一台不符資格的裝置上。 框架就是不在那裡,而每一次執行時,可用性檢查都會這麼告訴您。

至於這套框架未涵蓋的那些裝置端情境(自訂模型、您自己的權重、在裝置上訓練),下一層是 Core ML 與 Apple 的 MLX。而對於那些真正需要規模的情境,一套位於隱私邊界之後的雲端 LLM,仍然是誠實的答案。Foundation Models 並不能取代其中任何一者。對於就您手上已有的文字所進行的、聚焦的語言工作,它是第一個該伸手去拿的正確選擇;而對於其他一切,它都是錯誤的選擇。

這套框架所獎勵的能耐,不是提示工程的技巧。它是一種關於範圍的品味:餵給模型它擅長的任務、設計出恰好捕捉您所需內容的 @Generable 型別,並認出工作量超出裝置負荷的那一刻。帶著這些直覺去打造,裝置端模型就能免費完成多到令人驚訝的真實工作。忽視它們,您出貨的功能就會在每一位輸入恰好多出一個 token 的使用者身上壞掉。



  1. Apple Developer,「Foundation Models」框架總覽。Apple 將此框架描述為對驅動 Apple Intelligence 的裝置端模型的存取,適用於文字生成、摘要、分類與結構化輸出等聚焦的語言任務,而非開放式推理或世界知識。 

  2. Apple Developer,「LanguageModelSession」「Generating content and performing tasks with Foundation Models」。會話持有多輪上下文;Apple 的建議是為每一次各自獨立的單輪互動建立新的會話。 

  3. Apple Developer,「Generable」「Prompting an on-device foundation model」@Generable 巨集讓框架得以回傳一個已填妥、通過型別檢查的 Swift 值,而非一段字串。 

  4. Apple Developer,「Tool」協定。定義了 protocol Tool<Arguments, Output>: Sendable,其中包含必要的 namedescriptionparameters: GenerationSchema,以及 call(arguments:) async throws -> OutputArguments 型別遵循 ConvertibleFromGeneratedContent,且通常宣告為 @Generable。 

  5. Apple Developer,「SystemLanguageModel.Availability」UnavailableReason。案例:.available 與帶有 deviceNotEligibleappleIntelligenceNotEnabledmodelNotReady 等原因的 .unavailable(...)SystemLanguageModel.default.isAvailable 則是方便使用的布林值。 

  6. Apple Developer,「SystemLanguageModel.contextSize」。一個實例屬性(透過 SystemLanguageModel.default 取用),記載為最大上下文大小,代表輸入提示與生成回應加總的 token 總量。 

  7. Apple Developer,「LanguageModelSession.streamResponse(to:)」。在模型產出時即串流部分生成輸出,供 UI 增量更新之用。 

  8. Apple Developer,「Guide(description:_:)」。一個對等巨集,為 @Generable 屬性附加一段自然語言描述與可選的限制(數值範圍、正規表示式引導)。需要 iOS 26.0 以上。 

  9. Apple Developer,「respond(to:schema:includeSchemaInPrompt:options:)」includeSchemaInPrompt 預設為 true;Apple 的說明建議保留預設值,除非模型已經得知預期的格式。 

  10. Apple Developer,「LanguageModelSession.prewarm()」。請求框架在一個已知即將到來的請求之前先載入模型資源,以降低首次回應的延遲。 

  11. 作者的相關分析:以 Apple Foundation Models 打造裝置端 LLMFoundation Models 的自訂 AdapterFoundation Models 使用案例,以及 Foundation Models 上的代理式工作流程。App Intents 與工具表面的論點,則在 App Intents 是 Apple 通往您應用程式的全新 API 中展開。 

相關文章

Foundation Models 使用案例:通用模型與內容標記

iOS 26 Foundation Models 提供 .general 與 .contentTagging 兩種使用案例。運用 Apple 的規則,判斷何時提示工程勝過專業化模型。

3 分鐘閱讀

Foundation Models 自訂 Adapter:何時應該訓練一個

iOS 26 Foundation Models 自訂 adapter 訓練 LoRA 權重、匯出 .fmadapter 套件、透過 Background Assets 配送,並需要 Apple 的權限授權。

4 分鐘閱讀

當維護者就是攻擊者:jqwik 1.10.0

jqwik 1.10.0會在Maven輸出中發出破壞性的提示注入字串。ANSI跳脫序列會把它藏過人類視線。維護者是刻意加入的。

2 分鐘閱讀