实战:用 Foundation Models 框架为 app 加入端侧 AI
Code-along: Bring on-device AI to your app using the Foundation Models framework
2025年6月9日
一句话判断
如果你想给 app 加端侧 AI 功能但不知道从哪里开始,这场 Session 是手把手级别的教程——从 prompt 工程到 tool calling 到流式输出到性能优化,完整走了一遍构建旅行规划 app 的全过程。
这场 Session 讲了什么
Naomy 从零开始构建了一个旅行规划 app,逐步引入 Foundation Models 框架的核心能力:
Prompt 工程与 Guided Generation:利用 Xcode 更新后的 Playground 功能实时迭代 prompt。通过 @Generable 注解让模型直接生成自定义数据结构(Itinerary),无需手动解析 JSON。@Guide 宏可以对属性添加约束——描述说明、候选值集合、数组元素数量等。Instructions 用 builder API 构建,支持传入字符串和 Generable 实例作为示例。
Tool Calling:模型可以自主决定何时调用外部工具。实现 Tool 协议需要定义名称、描述和 call 方法。示例中创建了一个调用 MapKit 搜索兴趣点的工具,模型根据 landmark 类型自动选择搜索类别(比如大堡礁搜索码头、约书亚树搜索沙漠景点)。工具参数也是 Generable 类型,由模型自动生成。
流式输出:将 respond 方法改为流式版本后,返回 PartiallyGenerated 类型(所有属性自动变为 Optional)。通过 AsyncSequence 逐帧更新 UI,配合 SwiftUI 的 content transition 和动画实现丝滑的渐进式展示。
性能优化:Foundation Models Instrument 可以追踪模型加载、推理和 tool calling 的时间。prewarm() 在用户明确意图后提前加载模型(比如点击 landmark 时),消除首次请求的模型加载延迟。includeSchemaInPrompt: false 在多轮对话或 instructions 已包含完整示例时可以省去 schema token,减少输入 token 数量和延迟。
可用性处理:通过 SystemLanguageModel.default.availability 检查模型状态(不可用/未启用/未就绪),Xcode scheme 可以覆盖可用性状态进行测试。根据不同状态设计降级方案:不可用时隐藏生成功能、未启用时引导用户开启、未就绪时提示稍后重试。
值得深挖的点
@Generable 是整个框架的核心设计。它不仅仅是一个序列化标记——框架会自动将你的 Swift 类型层次结构转换为模型能理解的文本格式,同时生成 PartiallylyGenerated 版本用于流式输出。这意味着你只需要维护一套数据结构定义,就能同时获得完整的类型安全和流式更新能力。
Tool Calling 的自主性很高。模型自己决定”什么时候调用工具”和”用什么参数调用”。你只需要提供自然语言描述,模型会根据上下文判断是否需要调用。这比传统的 if/else 规则灵活得多,但也意味着你需要在描述中写清楚工具的使用条件。
prewarm() 的时机选择是关键。在用户明确表达意图但还没开始请求时预热——比如点击 landmark 查看描述时。太早预热浪费资源,太晚则失去了消除延迟的意义。这是典型的”在用户犹豫时偷偷准备”模式。
模拟器可以测试 Foundation Models。只要开发机器运行最新 macOS 且 Apple Intelligence 已启用,iPhone 和 visionOS 模拟器都能运行。这对没有测试设备的开发者是好消息。
代码片段
import FoundationModels
// 1. 定义 Generable 类型
@Generable
struct Itinerary {
@Guide(description: "行程标题")
let title: String
@Guide(description: "行程描述", .count(3))
let days: [DayPlan]
}
@Generable
struct DayPlan {
@Guide(description: "当天活动安排")
let activities: [String]
}
// 2. 创建 Tool
struct POISearchTool: Tool {
let name = "searchPOI"
let description = "搜索地标附近的兴趣点"
@Generable
struct Arguments {
let category: POICategory
let query: String
}
func call(arguments: Arguments) async throws -> String {
// 调用 MapKit 搜索
let results = await searchMapKit(
category: arguments.category,
query: arguments.query
)
return formatResults(results)
}
}
// 3. 使用 session
let session = LanguageModelSession(
tools: [POISearchTool(landmark: selectedLandmark)]
)
session.prewarm() // 在合适时机调用
let stream = session.stream(
generating: Itinerary.self,
includeSchemaInPrompt: false
) {
"为 \(selectedLandmark.name) 规划三天行程"
}
for try await partial in stream {
// partial.title 是 String?,partial.days 是 [PartialDayPlan?]?
updateUI(with: partial)
}
最佳实践
-
用 Xcode Playground 迭代 prompt。不要在 app 运行循环里反复调试 prompt——Playground 提供即时反馈,效率高得多。
-
在 Instructions 中提供完整示例。包含一个 Generable 实例作为 few-shot 示例,比纯文字描述更有效。如果设了
includeSchemaInPrompt: false,示例需要覆盖所有 Optional 属性的有值和无值情况。 -
Tool 描述要写清楚触发条件。模型通过 description 决定何时调用工具,描述越精确,调用越准确。比如”搜索景点附近的餐厅和活动场所”比”搜索附近的地点”好。
-
流式输出配合 SwiftUI content transition。给每个属性的展示加上
.contentTransition(.interpolate)和适当的动画,让内容渐进出现而不是闪烁出现。 -
必须处理模型不可用的情况。不要假设模型永远可用——设备不支持、用户未启用 Apple Intelligence、模型尚未下载都是常见场景。给每种状态设计合理的降级方案。
还有什么值得关注
- 模拟器支持测试 Foundation Models,但性能数据不准确——M4 Mac 模拟器上的速度不代表真实 iPhone
- Foundation Models Instrument 是新加入 Instruments 的专门工具,可以追踪模型加载(Asset Loading track)、推理(Inference track)和工具调用(Tool Calling track)
- 系统语言模型由操作系统管理,长时间不使用会被卸载出内存,prewarm 可以主动拉回
- 输入 token 数量与 instructions 和 prompt 的大小成正比,控制 token 数量是优化延迟的直接手段