Bring an LLM provider to the Foundation Models framework
Swift & Data 高级 20m

将 LLM 提供平台引入 Foundation Models 框架

Bring an LLM provider to the Foundation Models framework

2026年6月10日

在 Apple 官方观看视频

一句话判断

✅ “Apple 把 Foundation Models 变成了 iOS 界的 Vercel AI SDK,通过 LanguageModelExecutor 协议彻底抹平了本地和云端大模型的调用差异。“

这场 Session 讲了什么

这场 Session 最值得关注的一件事是,Apple 终于把 Foundation Models 框架开放给第三方大模型了。以前我们在 iOS 里接 Claude、Gemini 或者跑本地 MLX 模型,每个都要写一套网络请求、流式解析、上下文管理的样板代码,换个模型就得重构一半逻辑。

现在情况变了。Apple 推出了 LanguageModelLanguageModelExecutor 两个协议。只要模型提供商(或者你自己)实现这两个协议,把底层推理引擎包装起来,App 开发者就能用同一套 LanguageModelSessionrespond(to:) API 无缝切换模型。从设备端的 System Language Model,到云端的 Private Cloud Compute (PCC),再到第三方的 Anthropic 和 Google,调用方式完全一样。

这对 AI 应用开发者是巨大利好,换模型就像换皮肤一样简单。对模型提供商也是个明确的信号:赶紧出个 Swift Package 抢占生态,谁先接入谁就能被开发者无痛集成。

值得深挖的点

Executor 缓存与 KV Cache 复用机制

Apple 在设计 LanguageModelExecutor 时,用了一个很聪明的机制:基于 Configuration 的 Hash 值来缓存 Executor 实例。当你在 LanguageModelSession 里发起多次请求时,只要配置没变,框架就会复用同一个 Executor。

这个设计的关键是状态复用。对于本地模型,这意味着权重只需要加载一次;对于云端模型,这意味着你可以跨请求保留 KV 缓存(Key-Value Cache)或持久化连接,大幅降低网络开销和首字延迟。不过这里有个坑:框架每次都会把完整的 Transcript(对话记录)传给你,如果你的 App 为了省 token 裁剪了历史消息,Executor 需要自己做 diff 对比。如果发现上下文被删改,你得自己处理状态回滚,Apple 并没有帮你做这层脏活。

流式输出的“元数据优先”握手协议

用过第三方大模型 SDK 的人都知道,流式输出时最怕的就是“不知道这次请求花了多少钱”或者“不知道底层到底路由到了哪个模型”。Apple 在 LanguageModelExecutorGenerationChannel 里强制规定了一个发送顺序:先发 metadata(模型和请求 ID),再发 usage(token 消耗),最后才是 text delta(文本增量)。

这个设计非常老道。它让开发者可以在流式输出刚开始时,就拿到计费和路由信息,提前做拦截或 UI 展示,不用傻等到整个流结束。如果你自己写 Executor,千万别一上来就发文本,严格遵守这个顺序,否则上层的 LanguageModelSession 状态机会乱套。

代码片段

1. 无缝切换底层模型

场景:展示如何用同一套 API 切换设备端模型和第三方开源模型。

import FoundationModels
import MLXFoundationModels

// 使用苹果设备端模型
let model = SystemLanguageModel()

// 或者无缝切换到 HuggingFace 上的 MLX 开源模型
// let model = MLXLanguageModel(modelID: "mlx-community/my-model")

// 创建会话并发起请求,API 完全一致
let session = LanguageModelSession(model: model)
let response = try await session.respond(to: "解释一下 Swift 的 actor 机制")
print(response.content)

坑:切换模型时,如果新模型不支持旧模型配置的某些 GenerationOptions(比如特定的 sampling strategy),Executor 可能会直接抛出 LanguageModelError,记得做好 fallback。

2. 实现标准的流式输出顺序

场景:在自己的 LanguageModelExecutor 中,按 Apple 推荐的顺序向 channel 发送事件。

func respond(
    to request: LanguageModelExecutorGenerationRequest,
    model: MyLanguageModel,
    streamingInto channel: LanguageModelExecutorGenerationChannel
) async throws {
    // 先发送元数据,让开发者知道请求 ID 和模型版本
    await channel.send(.response(action: .updateMetadata([
        "modelID": "my-model-v2",
        "requestID": request.id.uuidString
    ])))
    
    // 接着发送 token 消耗,方便提前计费或限流
    await channel.send(.response(action: .updateUsage(
        input: .init(totalTokenCount: 150, cachedTokenCount: 50),
        output: .init(totalTokenCount: 0)
    )))
    
    // 最后才开始流式发送文本增量
    for try await token in myInferenceEngine.stream(request) {
        await channel.send(.response(action: .appendTextDelta(token)))
    }
}

坑:updateUsage 里的 cachedTokenCount 对于支持 Prompt Caching 的模型(如 Claude)非常重要,漏传会导致开发者无法准确计算成本。

最佳实践

已有项目如果已经自己封装了各大模型的 SDK,建议逐步将网络层和流式解析层抽离,包装成独立的 Swift Package 并实现 LanguageModelExecutor。这样业务层就可以统一迁移到 LanguageModelSession,彻底消灭各种自定义的 MessageChatStream 结构体。

新项目强烈建议直接基于 Foundation Models 框架开发。不要自己去写 SSE (Server-Sent Events) 解析或者 WebSocket 状态管理,Apple 的框架已经处理好了断线重连和内存释放。

实战中最容易踩的坑是凭证管理。官方明确反对在初始化器里直接接收 String 类型的 API Key。一定要用 Keychain 存储 token,如果是云端服务,尽量结合 App Attest 做设备认证,防止你的 API 被脚本刷爆。

还有什么值得关注

  • PCC 模型开放:Private Cloud Compute 模型现在支持 32K 上下文窗口,并且可以直接在 Foundation Models 里调用,隐私级别最高。
  • 自定义 Segment 类型:协议允许定义全新的输入输出模态,以后在 Transcript 里直接塞音频或视频流将成为可能。
  • 官方生态扩充:Anthropic 和 Google 即将推出官方的 Swift Package,Claude 和 Gemini 将作为一等公民接入该框架。
Foundation Models LanguageModelExecutor LLM Swift Package AI