Apple Foundation Models:端侧LLM框架详解
Foundation Models 框架让应用得以直接、免费、离线地访问驱动 Apple Intelligence 的同一个端侧大语言模型1。无需 API 密钥,没有按 token 计费,没有网络往返,也没有任何数据离开设备。对于过去意味着一次云端LLM调用加一轮隐私审查的那一类功能而言,如今的成本几乎归零。代价在于能力:端侧模型很小,上下文窗口有限,框架也对自己愿意做与不愿做的事划下了清晰的边界。摸清这些边界,就是全部的关键。
本文是这套框架本身的参考:您真正会调用的类型、让它值得一用的那一个特性,以及您应当就此打住、转而求助于更大模型的那个临界点。
太长不看
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 并不是对某个云端接口的封装。模型驻留在设备上,随操作系统一同发布,运行在神经网络引擎之上。这一个事实,驱动着 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 的。它在模型工作时挂起,并在请求超出上下文窗口、模型不可用或防护栏拒绝内容时抛出错误。这其中每一种,都是您要处理的真实分支,而非可以无视的边角情形。
要让界面有响应感,就用流式输出而非干等。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、一个 @Generable 的 Arguments 类型,以及一个负责干活的 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 的用户都会崩溃的——功能。
常见问题
Apple 的 Foundation Models 框架是免费使用的吗?
是的。这套框架让应用得以直接、免费、离线地访问驱动 Apple Intelligence 的同一个端侧模型。没有 API 密钥,没有按 token 计费,也没有网络往返1。
Foundation Models 需要什么设备和 iOS 版本?
它需要 iOS 26 以及一台支持 Apple Intelligence 的设备。低于这个门槛,框架不存在;而即便在受支持的系统上,模型在不符合条件的设备上、Apple Intelligence 关闭时、或模型下载期间也都不存在。使用之前,务必先检查 SystemLanguageModel.default.availability5。
我该如何获得结构化、类型安全的输出,而不是一段字符串?
给某个 Swift 类型标注 @Generable,模型便会返回这个类型——已填充、且经过类型检查,而不是一段还得您解析的字符串。这种引导式生成,正是让这套框架值得一用的那一个特性3。
Apple 端侧模型的上下文窗口有多大?
SystemLanguageModel.default.contextSize 报告的是 token 预算,它由提示词与生成的响应共享6。它在设计上就很小,因此长文档与长的多轮记录都会超出它。要为这个上限做好规划,否则会话就会抛出错误。
Foundation Models 能离线工作吗?它会把数据发给 Apple 吗?
它完全在端侧、运行于神经网络引擎之上。没有任何数据离开设备,也不需要网络往返——这正是它适合那类过去需要云端LLM加隐私审查的功能的原因1。
端侧模型能在生成途中调用我自己的代码吗?
可以。Tool 协议让模型能在生成过程中调用您的代码,去获取数据或执行操作,再把结果融回它的答案里4。
我什么时候不该用 Foundation Models?
当您需要一个前沿模型时,就越过它:开放式推理、代码生成、长文档分析,或世界知识。Apple 把端侧模型构建为应用内部专注的语言任务工具,因此向它索要通用智能,得到的是自信却错误的答案1。
-
Apple Developer, “Foundation Models” framework overview. Apple describes the framework as access to the on-device model that powers Apple Intelligence, suited to focused language tasks such as text generation, summarization, classification, and structured output rather than open-ended reasoning or world knowledge. ↩↩↩↩↩
-
Apple Developer, “LanguageModelSession” and “Generating content and performing tasks with Foundation Models”. A session holds multi-turn context; Apple’s guidance is to create a new session for each distinct single-turn interaction. ↩↩
-
Apple Developer, “Generable” and “Prompting an on-device foundation model”. The
@Generablemacro lets the framework return a populated, type-checked Swift value rather than a string. ↩↩↩ -
Apple Developer, “Tool” protocol. Defines
protocol Tool<Arguments, Output>: Sendablewith requiredname,description, andparameters: GenerationSchema, pluscall(arguments:) async throws -> Output. TheArgumentstype conforms toConvertibleFromGeneratedContentand is typically declared@Generable. ↩↩↩↩ -
Apple Developer, “SystemLanguageModel.Availability” and its
UnavailableReason. Cases:.availableand.unavailable(...)with reasonsdeviceNotEligible,appleIntelligenceNotEnabled, andmodelNotReady.SystemLanguageModel.default.isAvailableis the convenience boolean. ↩↩↩ -
Apple Developer, “SystemLanguageModel.contextSize”. An instance property (reached through
SystemLanguageModel.default) documented as the maximum context size, representing the total tokens across input prompt and generated response. ↩↩↩ -
Apple Developer, “LanguageModelSession.streamResponse(to:)”. Streams partial generated output as the model produces it, for incremental UI updates. ↩
-
Apple Developer, “Guide(description:_:)”. A peer macro that attaches a natural-language description and optional constraints (numeric ranges, regular-expression guides) to a
@Generableproperty. Requires iOS 26.0+. ↩ -
Apple Developer, “respond(to:schema:includeSchemaInPrompt:options:)”.
includeSchemaInPromptdefaults totrue; Apple’s discussion recommends keeping the default unless the model already knows the expected format. ↩ -
Apple Developer, “LanguageModelSession.prewarm()”. Asks the framework to load model resources ahead of a known upcoming request to reduce first-response latency. ↩
-
Author’s related analysis: On-Device LLMs with Apple’s Foundation Models, Custom Adapters for Foundation Models, Foundation Models Use Cases, and Agentic Workflows on Foundation Models. The App Intents and tool-surface argument is developed in App Intents Are Apple’s New API to Your App. ↩