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 里把输出类型设计好,交由编译器替您把关。模型填的是您定义好的一纸契约,而不是抛回一段需要您反向破解的散文。对于信息抽取、表单填充,以及任何把语言转化为数据的功能,引导式生成就是演示原型与可交付代码之间的那道分水岭。
有一个控制项值得了解: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 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. ↩