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 框架的核心是两个协议:LanguageModel 和 LanguageModelExecutor。
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,方便引用来源
核心启发
-
多模型 A/B 测试工具
- 基于统一的
LanguageModelAPI,实现模型切换和对比测试界面 - 从
updateMetadata中提取模型标识和性能指标 - 入口:
LanguageModelSession的respond方法
- 基于统一的
-
本地优先的混合推理引擎
- 本地模型处理简单任务,云端模型处理复杂任务
- 通过
transcript长度和contextOptions动态路由 - 利用
prewarm预加载两个模型,实现无缝切换
-
音频对话应用
- 使用
CustomSegment支持语音输入输出 - 定义
AudioInputSegment和AudioOutputSegment - 模型生成文本后同步调用 TTS,返回音频 segment
- 使用
-
带引用的搜索增强生成
- 实现服务端网页搜索工具
- 将搜索结果和引用链接通过自定义 segment 返回
- 在 UI 中高亮显示引用来源
-
推理成本监控面板
- 从
updateUsage事件收集 token 使用数据 - 按会话、模型类型分组统计
- 用自定义 metadata 追踪每次请求的延迟和吞吐量
- 从
关联 Session
- 241 - Foundation Models — Foundation Models 框架总览
- 242 - Agentic apps — 构建多步骤工具使用的工作流
- 324 - Core AI — 将本地 AI 模型集成到应用
- 325 - Core AI optimization — 优化本地 AI 模型性能
- 347 - Security agentic — 保护 agentic 应用的安全
评论
GitHub Issues · utterances