Code-along: Bring on-device AI to your app using the Foundation Models framework
Machine Learning & AI 进阶 2m

实战:用 Foundation Models 框架为 app 加入端侧 AI

Code-along: Bring on-device AI to your app using the Foundation Models framework

2025年6月9日

在 Apple 官方观看视频

一句话判断

如果你想给 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)
}

最佳实践

  1. 用 Xcode Playground 迭代 prompt。不要在 app 运行循环里反复调试 prompt——Playground 提供即时反馈,效率高得多。

  2. 在 Instructions 中提供完整示例。包含一个 Generable 实例作为 few-shot 示例,比纯文字描述更有效。如果设了 includeSchemaInPrompt: false,示例需要覆盖所有 Optional 属性的有值和无值情况。

  3. Tool 描述要写清楚触发条件。模型通过 description 决定何时调用工具,描述越精确,调用越准确。比如”搜索景点附近的餐厅和活动场所”比”搜索附近的地点”好。

  4. 流式输出配合 SwiftUI content transition。给每个属性的展示加上 .contentTransition(.interpolate) 和适当的动画,让内容渐进出现而不是闪烁出现。

  5. 必须处理模型不可用的情况。不要假设模型永远可用——设备不支持、用户未启用 Apple Intelligence、模型尚未下载都是常见场景。给每种状态设计合理的降级方案。

还有什么值得关注

  • 模拟器支持测试 Foundation Models,但性能数据不准确——M4 Mac 模拟器上的速度不代表真实 iPhone
  • Foundation Models Instrument 是新加入 Instruments 的专门工具,可以追踪模型加载(Asset Loading track)、推理(Inference track)和工具调用(Tool Calling track)
  • 系统语言模型由操作系统管理,长时间不使用会被卸载出内存,prewarm 可以主动拉回
  • 输入 token 数量与 instructions 和 prompt 的大小成正比,控制 token 数量是优化延迟的直接手段
机器学习