WWDC Quick Look 💓 By SwiftGGTeam
Bring an LLM provider to the Foundation Models framework

Bring an LLM provider to the Foundation Models framework

观看原视频

Highlight

Foundation Models 框架现在支持任何 LLM 提供商创建符合 LanguageModel 协议的 Swift 包,让开发者可以用统一 API 调用本地或云端模型。

核心内容

以前在 Swift 中集成不同的大语言模型是个麻烦事。

每个模型有自己的 API 设计、认证方式、数据格式。想换一个模型,往往要重写大量代码。本地模型和云端模型的调用方式完全不同,维护成本很高。

Apple 打开了 Foundation Models 框架,让任何 LLM 提供商都能创建符合 LanguageModel 协议的 Swift 包。Anthropic 的 Claude、Google 的 Gemini、Apple 自家的系统模型、Core AI 本地模型、MLX 社区模型,都用同一个 API 调用。

开发者只需初始化不同的 LanguageModel,传入 LanguageModelSession,调用 respond 即可。模型切换变得像替换一行代码一样简单。

详细内容

模型协议设计(04:56

Foundation Models 框架的核心是两个协议:LanguageModelLanguageModelExecutor

LanguageModel 描述模型能力,LanguageModelExecutor 负责执行推理。

// LanguageModel 协议
public protocol LanguageModel: Sendable {
    var capabilities: LanguageModelCapabilities { get }
    var executorConfiguration: Executor.Configuration { get }
}

// LanguageModelExecutor 协议
public protocol LanguageModelExecutor: Sendable {
    init(configuration: Configuration) throws
    func prewarm(model: Model, transcript: Transcript)
    func respond(
        to request: LanguageModelExecutorGenerationRequest,
        model: Model,
        streamingInto channel: LanguageModelExecutorGenerationChannel
    ) async throws
}

关键点:

  • capabilities 声明模型支持的能力,如工具调用、引导生成、推理
  • executorConfiguration 是框架查找和创建 Executor 的配置键,必须可哈希
  • prewarm 用于在首次请求前预加载模型资源
  • respond 是核心生成方法,支持流式输出

资源管理与预热(07:28

模型权重加载是昂贵操作,框架提供了 prewarm 机制提前准备。

struct MyLanguageModelExecutor: LanguageModelExecutor {

    private mutating func loadModelIfNeeded() throws -> LoadedWeights {
        let weights = try loadedModel ?? loadWeights()
        loadedModel = weights
        return weights
    }

    func prewarm(transcript: Transcript) {
        loadedModel = try? loadModelIfNeeded()
    }

    func respond(...) async throws {
        let weights = try loadModelIfNeeded()
        // ...使用 weights 生成...
    }
}

关键点:

  • prewarm 在首次请求前调用,是预加载权重的最佳时机
  • 即使 prewarm 未被调用,loadModelIfNeeded 也能保证只加载一次
  • 无状态服务(如纯云端模型)可将 prewarm 实现为空操作
  • Session 销毁时会自动释放所有缓存的 Executor

会话状态与 KV 缓存(12:22

框架按配置缓存 Executor,让有状态的集成减少网络开销。

// Executor 在每次调用时收到完整 transcript
func respond(to request: ...) async throws {
    let newTranscript = request.transcript

    // 与上次保存的 transcript 比较
    if newTranscript.hasSamePrefix(as: previousTranscript) {
        // 只处理新增的 entries
        await processOnlyNewEntries(newTranscript.suffix)
    } else {
        // transcript 被修改或删除,需要失效缓存
        invalidateKVCache()
        await processFullTranscript(newTranscript)
    }

    previousTranscript = newTranscript
}

关键点:

  • 相同配置的 Model 共享同一个 Executor 实例
  • Executor 可在多次调用间保存 KV 缓存,避免重复计算
  • 比较新旧 transcript 差异,只处理新增内容
  • 发现 entries 被删除或修改时,必须失效相关缓存

流式响应输出(11:47

框架要求特定的输出顺序,确保开发者能及时获取关键信息。

func respond(...) async throws {
    // 1. 首先发送元数据
    await channel.send(.response(action: .updateMetadata([
        "modelID": "my-model-2026-06-08",
        "requestID": request.id.uuidString
    ])))

    // 2. 发送 token 使用量
    await channel.send(.response(action: .updateUsage(
        input: .init(totalTokenCount: promptTokens, cachedTokenCount: cachedTokens),
        output: .init(totalTokenCount: 0, reasoningTokenCount: 0)
    )))

    // 3. 流式发送生成的 token
    for try await token in tokens {
        await channel.send(.response(action: .appendText(token)))
    }
}

关键点:

  • 先发送元数据,方便开发者记录日志和调试
  • 立即发送 prompt token 计费信息,无需等待流结束
  • 逐个发送 text delta,实现打字机效果
  • 框架内部会收集流式事件提供一次性 API

错误处理(13:33

当模型无法满足开发者请求时,Executor 应尽可能近似或抛出明确错误。

// 开发者设置了 greedy sampling,但服务只支持 temperature
if request.generationOptions.sampling?.kind == .greedy {
    serviceRequest.temperature = 0
}

// token 预算太小,无法满足 schema 要求
if let schema = request.schema,
   let budget = request.generationOptions.maximumResponseTokens,
   budget < minimumTokens(for: schema) {
    throw LanguageModelError.unsupportedCapability(
        .init(
            capability: .guidedGeneration,
            debugDescription: "Token budget too small to satisfy this schema."
        )
    )
}

关键点:

  • 优先近似处理,保持开发者意图
  • 无法近似时使用内置 LanguageModelError 类型
  • 内置错误包括:上下文超限、速率限制、拒绝、护栏违规、不支持的能力等
  • 服务特定错误可自定义类型,但尽量复用内置类型

自定义 Segment(17:05

自定义 Segment 让模型支持新的输入输出模态,如音频、视频等。

// 定义自定义 segment
public struct AudioSegment: Transcript.CustomSegment {
    public var id: String
    public var content: URL
}

// 开发者可在 prompt 中直接使用
let recording = AudioSegment(id: UUID().uuidString, content: URL(filePath: "/path/to/recording.m4a"))
let response = try await session.respond {
    "Where was Frank Lloyd Wright's original architecture school located?"
    recording
}

// Executor 接收并返回自定义 segment
for try await event in stream {
    switch event {
    case .audioFileGenerated(let file):
        await channel.send(.response(action: .updateCustomSegment(
            AudioSegment(id: file.id, content: file.url)
        )))
    }
}

关键点:

  • CustomSegment 必须是 PromptRepresentable,可直接用于 prompt
  • Executor 通过 transcript 接收自定义 segment
  • 使用相同的 channel 流式返回自定义 segment
  • segment ID 控制是新增还是更新已存在的 segment

服务端工具(18:09

服务端工具是模型在服务端自主运行的能力,如网页搜索、代码执行。

// 在模型上声明服务端工具
public struct MyLanguageModel: LanguageModel {
    public struct ServerTool: Sendable {
        public static let webSearch: ServerTool = ...
    }
    public init(serverTools: [ServerTool] = []) { }
}

// Executor 接收工具结果并转发
let client = MyServerClient(serverTools: model.serverTools)
let response = try await client.send(prompt: .init(request))
for try await chunk in response {
    switch chunk {
    case .webSearch(let webSearch):
        await channel.send(.response(action: .updateCustomSegment(
            WebSearchSegment(url: webSearch.url, content: webSearch.html)
        )))
    case .textDelta(let textDelta):
        await channel.send(.response(action: .appendText(
            textDelta.text, tokenCount: textDelta.tokenCount
        )))
    }
}

关键点:

  • 服务端工具作为命名类型声明在模型上
  • 有三种展示级别:仅返回答案、附加元数据、完整输出
  • 使用自定义 segment 传递工具的结构化输出
  • 元数据会附加到 text segment,方便引用来源

核心启发

  1. 多模型 A/B 测试工具

    • 基于统一的 LanguageModel API,实现模型切换和对比测试界面
    • updateMetadata 中提取模型标识和性能指标
    • 入口:LanguageModelSessionrespond 方法
  2. 本地优先的混合推理引擎

    • 本地模型处理简单任务,云端模型处理复杂任务
    • 通过 transcript 长度和 contextOptions 动态路由
    • 利用 prewarm 预加载两个模型,实现无缝切换
  3. 音频对话应用

    • 使用 CustomSegment 支持语音输入输出
    • 定义 AudioInputSegmentAudioOutputSegment
    • 模型生成文本后同步调用 TTS,返回音频 segment
  4. 带引用的搜索增强生成

    • 实现服务端网页搜索工具
    • 将搜索结果和引用链接通过自定义 segment 返回
    • 在 UI 中高亮显示引用来源
  5. 推理成本监控面板

    • updateUsage 事件收集 token 使用数据
    • 按会话、模型类型分组统计
    • 用自定义 metadata 追踪每次请求的延迟和吞吐量

关联 Session

评论

GitHub Issues · utterances