关于 axiom-foundation-models
面向 Apple 平台开发者的 Foundation Models 实现指南,适用于端侧文本生成与分析功能。内容涵盖结构化输出、流式生成、工具调用以及可用性和错误处理。
主要功能
- 01端侧生成与文本分析
- 02@Generable 结构化输出
- 03流式生成与渐进更新
- 04通过工具接入外部数据
- 05可用性检查与错误处理
适用场景
- 01为文本摘要和分类加入端侧模型
- 02生成具备类型约束的结构化数据
- 03逐步展示较长生成结果
- 04将外部数据提供给模型使用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-foundation-models安装后在 Claude Code 中输入/axiom-foundation-models确认已加载。
详细介绍
Foundation Models — 面向 Apple 平台的设备端 AI
何时使用此技能
适用于:
- 使用 Foundation Models 实现设备端 AI 功能
- 添加文本摘要、分类或提取功能
- 根据大模型响应创建结构化输出
- 构建用于集成外部数据的工具调用模式
- 流式生成内容,改善用户体验
- 调试 Foundation Models 问题(上下文溢出、生成速度慢、输出错误)
- 判断应使用 Foundation Models 还是服务端大模型(ChatGPT、Claude 等)
相关技能
- 使用
axiom-foundation-models-diag进行系统化故障排查(上下文超限、护栏违规、可用性问题) - 使用
axiom-foundation-models-ref查阅包含所有 WWDC 代码示例的完整 API 参考
危险信号 — 必定失败的反模式
❌ 用于世界知识
失败原因:设备端模型只有 30 亿个参数,针对摘要、提取和分类进行优化,并不适用于世界知识或复杂推理。
错误用法示例:
// ❌ BAD - Asking for world knowledge
let session = LanguageModelSession()
let response = try await session.respond(to: "What's the capital of France?")
原因:模型会产生幻觉或给出低质量答案。它经过训练,适用于内容生成,而非百科全书式知识。
正确做法:对于世界知识,请使用服务端大模型(ChatGPT、Claude),或者通过工具调用提供事实数据。
❌ 阻塞主线程
失败原因:session.respond() 是 async,但如果在主线程上同步调用,界面会冻结数秒。
错误用法示例:
// ❌ BAD - Blocking main thread
Button("Generate") {
let response = try await session.respond(to: prompt) // UI frozen!
}
原因:生成需要 1-5 秒。用户会看到应用卡死,继而留下差评。
正确做法:
// ✅ GOOD - Async on background
Button("Generate") {
Task {
let response = try await session.respond(to: prompt)
// Update UI with response
}
}
❌ 手动解析 JSON
失败原因:要求输出 JSON,再使用 JSONDecoder 解析,会导致键名幻觉、JSON 无效且缺少类型安全。
错误用法示例:
// ❌ BAD - Manual JSON parsing
let prompt = "Generate a person with name and age as JSON"
let response = try await session.respond(to: prompt)
let data = response.content.data(using: .utf8)!
let person = try JSONDecoder().decode(Person.self, from: data) // CRASHES!
原因:当你期望 {name: "John"} 时,模型可能输出 {firstName: "John"},也可能输出完全无效的 JSON。
正确做法:
// ✅ GOOD - @Generable guarantees structure
@Generable
struct Person {
let name: String
let age: Int
}
let response = try await session.respond(
to: "Generate a person",
generating: Person.self
)
// response.content is type-safe Person instance
❌ 忽略可用性检查
失败原因:Foundation Models 仅在受支持地区的 Apple Intelligence 设备上运行。不检查就会导致应用崩溃或显示错误。
错误用法示例:
// ❌ BAD - No availability check
let session = LanguageModelSession() // Might fail!
正确做法:
// ✅ GOOD - Check first
switch SystemLanguageModel.default.availability {
case .available:
let session = LanguageModelSession()
// proceed
case .unavailable(let reason):
// Show graceful UI: "AI features require Apple Intelligence"
}
❌ 使用单个超大提示词
失败原因:上下文窗口为 4096 个 token(输入 + 输出)。单个超大提示词会触及上限,导致结果不理想。
错误用法示例:
// ❌ BAD - Everything in one prompt
let prompt = """
Generate a 7-day itinerary for Tokyo including hotels, restaurants,
activities for each day, transportation details, budget breakdown...
"""
// Exceeds context, poor quality
正确做法:将任务拆分为更小的部分,使用工具获取外部数据,并采用多轮对话。
❌ 未处理生成错误
失败原因:必须处理以下三种错误,否则应用会在生产环境中崩溃。
do {
let response = try await session.respond(to: prompt)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// Multi-turn transcript grew beyond 4096 tokens
// → Condense transcript and create new session (see Pattern 5)
} catch LanguageModelSession.GenerationError.guardrailViolation {
// Content policy triggered
// → Show graceful message: "I can't help with that request"
} catch LanguageModelSession.GenerationError.unsupportedLanguageOrLocale {
// User input in unsupported language
// → Show disclaimer, check SystemLanguageModel.default.supportedLanguages
}
必须完成的第一步
编写任何 Foundation Models 代码之前,请完成以下步骤:
1. 检查可用性
有关所需模式,请参见上文危险信号中的“忽略可用性检查”。Foundation Models 需要启用了 Apple Intelligence 的设备、受支持的地区,并且用户已选择加入。
2. 明确使用场景
问问自己:我的主要目标是什么?
| 使用场景 | Foundation Models? | 替代方案 |
|---|---|---|
| 摘要 | ✅ 是 | |
| 提取(从文本中提取关键信息) | ✅ 是 | |
| 分类(对内容进行分类) | ✅ 是 | |
| 内容打标签 | ✅ 是(内置适配器!) | |
| 世界知识 | ❌ 否 | ChatGPT、Claude、Gemini |
| 复杂推理 | ❌ 否 | 服务端大模型 |
| 数学计算 | ❌ 否 | 计算器、符号数学 |
关键:如果你的使用场景需要世界知识或高级推理,请停止。Foundation Models 并非合适的工具。
3. 设计 @Generable 架构
如果需要结构化输出(而非纯文本):
错误做法:要求输出“JSON”并手动解析
正确做法:定义 @Generable 类型
@Generable
struct SearchSuggestions {
@Guide(description: "Suggested search terms", .count(4))
var searchTerms: [String]
}
原因:约束解码可保证结构,无需处理解析错误,也不会出现键名幻觉。
4. 考虑使用外部数据工具
如果你的功能需要外部信息:
- 天气 → WeatherKit 工具
- 位置 → MapKit 工具
- 联系人 → Contacts API 工具
- 日历 → EventKit 工具
不要尝试从模型获取这些信息(模型会产生幻觉)。 应该定义工具协议的实现。
5. 为长时间生成规划流式处理
如果生成时间超过 1 秒,请使用流式处理:
let stream = session.streamResponse(
to: prompt,
generating: Itinerary.self
)
for try await partial in stream {
// Update UI incrementally
self.itinerary = partial
}
原因:用户能立即看到进度,感知延迟会大幅降低。
决策树
Need on-device AI?
│
├─ World knowledge/reasoning?
│ └─ ❌ NOT Foundation Models
│ → Use ChatGPT, Claude, Gemini, etc.
│ → Reason: 3B parameter model, not trained for encyclopedic knowledge
│
├─ Summarization?
│ └─ ✅ YES → Pattern 1 (Basic Session)
│ → Example: Summarize article, condense email
│ → Time: 10-15 minutes
│
├─ Structured extraction?
│ └─ ✅ YES → Pattern 2 (@Generable)
│ → Example: Extract name, date, amount from invoice
│ → Time: 15-20 minutes
│
├─ Content tagging?
│ └─ ✅ YES → Pattern 3 (contentTagging use case)
│ → Example: Tag article topics, extract entities
│ → Time: 10 minutes
│
├─ Need external data?
│ └─ ✅ YES → Pattern 4 (Tool calling)
│ → Example: Fetch weather, query contacts, get locations
│ → Time: 20-30 minutes
│
├─ Long generation?
│ └─ ✅ YES → Pattern 5 (Streaming)
│ → Example: Generate itinerary, create story
│ → Time: 15-20 minutes
│
└─ Dynamic schemas (runtime-defined structure)?
└─ ✅ YES → Pattern 6 (DynamicGenerationSchema)
→ Example: Level creator, user-defined forms
→ Time: 30-40 minutes
模式 1:基本会话
适用场景:简单的文本生成、摘要或内容分析。
核心概念
LanguageModelSession:
- 有状态 — 保留所有交互的对话记录
- 指令与提示词:
- 指令(来自开发者):定义模型的角色和静态指导
- 提示词(来自用户):用于生成的动态输入
- 模型经过训练,会优先遵循指令而非提示词(安全特性)
实现
import FoundationModels
func respond(userInput: String) async throws -> String {
let session = LanguageModelSession(instructions: """
You are a friendly barista in a pixel art coffee shop.
Respond to the player's question concisely.
"""
)
let response = try await session.respond(to: userInput)
return response.content
}
要点
- 指令可选 — 省略时会使用合理的默认值
- 绝不要将用户输入插入指令中 — 存在安全风险(提示词注入)
- 指令应简洁 — 每个 token 都会增加延迟
多轮交互
let session = LanguageModelSession()
// First turn
let first = try await session.respond(to: "Write a haiku about fishing")
print(first.content)
// "Silent waters gleam,
// Casting lines in morning mist—
// Hope in every cast."
// Second turn - model remembers context
let second = try await session.respond(to: "Do another one about golf")
print(second.content)
// "Silent morning dew,
// Caddies guide with gentle words—
// Paths of patience tread."
// Inspect full transcript
print(session.transcript)
这样做的原因:会话会自动保留对话记录。模型会利用之前轮次的上下文。
何时使用此模式
✅ 适用于:
- 简单问答
- 文本摘要
- 内容分析
- 单轮生成
❌ 不适用于:
- 结构化输出(使用模式 2)
- 长对话(会达到上下文限制)
- 需要外部数据的情况(使用模式 4)
模式 2:@Generable 结构化输出
适用场景:需要从模型获取结构化数据,而非纯文本。
问题
不使用 @Generable:
// ❌ BAD - Unreliable
let prompt = "Generate a person with name and age as JSON"
let response = try await session.respond(to: prompt)
// Might get: {"firstName": "John"} when you expect {"name": "John"}
// Might get invalid JSON entirely
// Must parse manually, prone to crashes
解决方案:@Generable
@Generable
struct Person {
let name: String
let age: Int
}
let session = LanguageModelSession()
let response = try await session.respond(
to: "Generate a person",
generating: Person.self
)
let person = response.content // Type-safe Person instance!
工作原理(受约束解码)
@Generable宏在编译时生成架构- 架构会自动传递给模型
- 模型生成的 token 受架构约束
- 框架将输出解析为 Swift 类型
- 保证结构正确 — 不会产生幻觉键,也不会出现解析错误
“受约束解码会屏蔽无效 token。模型只能选择符合架构的有效 token。”
支持的类型
支持 String、Int、Float、Double、Bool、数组、嵌套的 @Generable 类型、带关联值的枚举和递归类型。完整列表及示例请参见 axiom-foundation-models-ref。
@Guide 约束
使用 @Guide 控制生成的值。支持描述、数值范围、数组计数和正则表达式模式:
@Generable
struct NPC {
@Guide(description: "A full name")
let name: String
@Guide(.range(1...10))
let level: Int
@Guide(.count(3))
let attributes: [String]
}
运行时验证:@Guide 约束通过受约束解码在生成期间强制执行 — 模型无法生成超出范围的值。不过,仍应对结果进行业务逻辑验证,因为模型可能生成语义错误但结构有效的输出。
完整的 @Guide 参考(范围、正则表达式、最大计数)请参见 axiom-foundation-models-ref。
属性顺序很重要
属性按声明顺序生成:
@Generable
struct Itinerary {
var destination: String // Generated first
var days: [DayPlan] // Generated second
var summary: String // Generated last
}
“你可能会发现,把摘要放在最后一个属性时,模型生成的效果最好。”
原因:后面的属性可以引用前面的属性。为了流式处理,最重要的属性应放在前面。
模式 3:使用 PartiallyGenerated 进行流式处理
适用场景:生成时间超过 1 秒,且希望逐步更新 UI。
问题
不使用流式处理:
// User waits 3-5 seconds seeing nothing
let response = try await session.respond(to: prompt, generating: Itinerary.self)
// Then entire result appears at once
用户体验:感觉缓慢,UI 像是卡住了。
解决方案:流式处理
@Generable
struct Itinerary {
var name: String
var days: [DayPlan]
}
let stream = session.streamResponse(
to: "Generate a 3-day itinerary to Mt. Fuji",
generating: Itinerary.self
)
for try await partial in stream {
print(partial) // Incrementally updated
}
PartiallyGenerated 类型
@Generable 宏会自动创建一个 PartiallyGenerated 类型,其中所有属性都是可选的(会随着模型生成内容逐步填充)。详情请参见 axiom-foundation-models-ref。
SwiftUI 集成
struct ItineraryView: View {
let session: LanguageModelSession
@State private var itinerary: Itinerary.PartiallyGenerated?
var body: some View {
VStack {
if let name = itinerary?.name {
Text(name)
.font(.title)
}
if let days = itinerary?.days {
ForEach(days, id: \.self) { day in
DayView(day: day)
}
}
Button("Generate") {
Task {
let stream = session.streamResponse(
to: "Generate 3-day itinerary to Tokyo",
generating: Itinerary.self
)
for try await partial in stream {
self.itinerary = partial
}
}
}
}
}
}
视图标识
对数组至关重要:
// ✅ GOOD - Stable identity
ForEach(days, id: \.id) { day in
DayView(day: day)
}
// ❌ BAD - Identity changes, animations break
ForEach(days.indices, id: \.self) { index in
DayView(day: days[index])
}
何时使用流式传输
✅ 适用于:
- 行程
- 故事
- 长篇描述
- 多章节内容
❌ 不适用于:
- 简单问答(少于 1 句话)
- 快速分类
- 内容标记
流式传输错误处理
在流式传输期间妥善处理错误——此时可能已经显示了部分结果:
do {
for try await partial in stream {
self.itinerary = partial
}
} catch LanguageModelSession.GenerationError.guardrailViolation {
// Partial content may be visible — show non-disruptive error
self.errorMessage = "Generation stopped by content policy"
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// Too much context — create fresh session and retry
session = LanguageModelSession()
}
模式 4:工具调用
适用场景:模型需要外部数据(天气、地点、联系人)来生成响应。
问题
// ❌ BAD - Model will hallucinate
let response = try await session.respond(
to: "What's the temperature in Cupertino?"
)
// Output: "It's about 72°F" (completely made up!)
原因:3B 参数模型没有实时天气数据。
解决方案:工具调用
让模型自主调用你的代码来获取外部数据。
import FoundationModels
import WeatherKit
import CoreLocation
struct GetWeatherTool: Tool {
let name = "getWeather"
let description = "Retrieve latest weather for a city"
@Generable
struct Arguments {
@Guide(description: "The city to fetch weather for")
var city: String
}
func call(arguments: Arguments) async throws -> ToolOutput {
let places = try await CLGeocoder().geocodeAddressString(arguments.city)
let weather = try await WeatherService.shared.weather(for: places.first!.location!)
let temp = weather.currentWeather.temperature.value
return ToolOutput("\(arguments.city)'s temperature is \(temp) degrees.")
}
}
将工具附加到会话
let session = LanguageModelSession(
tools: [GetWeatherTool()],
instructions: "Help user with weather forecasts."
)
let response = try await session.respond(
to: "What's the temperature in Cupertino?"
)
print(response.content)
// "It's 71°F in Cupertino!"
模型会自主:
- 识别出需要天气数据
- 调用
GetWeatherTool - 获取实际温度
- 将温度融入自然的响应中
关键概念
- 工具协议:需要
name、description、@Generable Arguments和call()方法 - ToolOutput:返回
String(自然语言)或GeneratedContent(结构化内容) - 多个工具:会话接受工具数组;模型会自主决定调用哪些工具
- 有状态工具:当工具需要在多次调用间保持状态时,使用
class(而非struct)
有关 Tool 协议参考、ToolOutput 形式、有状态工具模式和更多示例,请参阅 axiom-foundation-models-ref。
工具调用流程
1. Session initialized with tools
2. User prompt: "What's Tokyo's weather?"
3. Model analyzes: "Need weather data"
4. Model generates tool call: getWeather(city: "Tokyo")
5. Framework calls your tool's call() method
6. Your tool fetches real data from API
7. Tool output inserted into transcript
8. Model generates final response using tool output
“模型会自主决定何时以及多频繁地调用工具。每次请求可以调用多个工具,甚至并行调用。”
工具调用的保证
✅ 有保证:
- 工具名称有效(不会虚构工具)
- 参数有效(通过 @Generable)
- 结构正确
❌ 无法保证:
- 一定会调用工具(模型可能不需要调用)
- 参数的具体值(模型会根据上下文决定)
何时使用工具
✅ 适用于:
- 天气数据
- 地图/地点查询
- 联系人信息
- 日历事件
- 外部 API
❌ 不适用于:
- 数据模型中已有的数据
- 提示词/指令中已有的信息
- 简单计算(模型可以完成)
模式 5:上下文管理
适用场景:多轮对话可能超过 4096 个 token 的限制。
问题
// Long conversation...
for i in 1...100 {
let response = try await session.respond(to: "Question \(i)")
// Eventually...
// Error: exceededContextWindowSize
}
上下文窗口:4096 个 token(输入和输出合计) 平均值:英语中每个 token 约对应 3 个字符
粗略估算:
- 4096 个 token ≈ 12,000 个字符
- ≈ 总计 2,000–3,000 个单词
长对话或冗长的提示词/响应 → 超出限制
处理上下文溢出
基础方法:启动新会话
var session = LanguageModelSession()
do {
let response = try await session.respond(to: prompt)
print(response.content)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// New session, no history
session = LanguageModelSession()
}
问题:会丢失整个对话历史。
更好的方法:精简对话记录
var session = LanguageModelSession()
do {
let response = try await session.respond(to: prompt)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// New session with condensed history
session = condensedSession(from: session)
}
func condensedSession(from previous: LanguageModelSession) -> LanguageModelSession {
let allEntries = previous.transcript.entries
var condensedEntries = [Transcript.Entry]()
// Always include first entry (instructions)
if let first = allEntries.first {
condensedEntries.append(first)
// Include last entry (most recent context)
if allEntries.count > 1, let last = allEntries.last {
condensedEntries.append(last)
}
}
let condensedTranscript = Transcript(entries: condensedEntries)
return LanguageModelSession(transcript: condensedTranscript)
}
此方法有效的原因:
- 始终保留指令
- 保留近期上下文
- 大幅减少总 token 数
如需了解更高级的策略(使用 Foundation Models 自身总结中间条目),请参阅 axiom-foundation-models-ref。
防止上下文溢出
1. 保持提示词简洁:
// ❌ BAD
let prompt = """
I want you to generate a comprehensive detailed analysis of this article
with multiple sections including summary, key points, sentiment analysis,
main arguments, counter arguments, logical fallacies, and conclusions...
"""
// ✅ GOOD
let prompt = "Summarize this article's key points"
2. 使用工具获取数据: 不要将整个数据集放入提示词,而应使用工具按需获取数据。
3. 将复杂任务拆分为多个步骤:
// ❌ BAD - One massive generation
let response = try await session.respond(
to: "Create 7-day itinerary with hotels, restaurants, activities..."
)
// ✅ GOOD - Multiple smaller generations
let overview = try await session.respond(to: "Create high-level 7-day plan")
for day in 1...7 {
let details = try await session.respond(to: "Detail activities for day \(day)")
}
模式 6:采样与生成选项
适用场景:需要控制输出的随机性/确定性。
何时调整采样
| 目标 | 设置 | 使用场景 |
|---|---|---|
| 确定性 | GenerationOptions(sampling: .greedy) | 单元测试、演示、对一致性要求高的场景 |
| 聚焦 | GenerationOptions(temperature: 0.5) | 事实提取、分类 |
| 创造性 | GenerationOptions(temperature: 2.0) | 故事生成、头脑风暴、多样化的 NPC 对话 |
默认设置:随机采样(temperature 1.0)可提供均衡的结果。
注意:贪心采样的确定性仅适用于相同的模型版本。操作系统更新可能会改变输出。
完整的 GenerationOptions API 参考,请参阅 axiom-foundation-models-ref。
压力场景
场景 1:“直接使用 ChatGPT API”
背景:你正在实现一项新的 AI 功能。PM 建议使用 ChatGPT API 来获得“更好的结果”。
压力信号:
- 👔 权威:PM 的职位比你高
- 💸 现有集成:团队已在其他功能中使用 OpenAI
- ⏰ 速度:“ChatGPT 已经过验证,而 Foundation Models 还是新技术”
合理化陷阱:
- “PM 最清楚”
- “ChatGPT 的回答更好”
- “使用现有代码,实施起来更快”
这样做为什么行不通:
-
侵犯隐私:用户数据会发送到外部服务
- 医疗记录、财务文件、私人消息
- 违背用户对设备端隐私的预期
- 可能违反 GDPR 或隐私法规
-
成本:每次 API 调用都要花钱
- Foundation Models 免费
- 扩展到数百万用户时,成本会非常高
-
无法离线使用:需要网络连接
- 飞行模式、信号差 → 功能无法使用
- Foundation Models 可离线运行
-
延迟:网络往返会增加 500-2000ms
- Foundation Models:在设备端运行,启动时间 <100ms
适合使用 ChatGPT 的情况:
- 需要世界知识(例如:“法国总统是谁?”)
- 需要复杂推理(多步逻辑、数学证明)
- 上下文非常长(>4096 个 token)
必须这样回应:
"I understand ChatGPT delivers great results for certain tasks. However,
for this feature, Foundation Models is the right choice for three critical reasons:
1. **Privacy**: This feature processes [medical notes/financial data/personal content].
Users expect this data stays on-device. Sending to external API violates that trust
and may have compliance issues.
2. **Cost**: At scale, ChatGPT API calls cost $X per 1000 requests. Foundation Models
is free. For Y million users, that's $Z annually we can avoid.
3. **Offline capability**: Foundation Models works without internet. Users in airplane
mode or with poor signal still get full functionality.
**When to use ChatGPT**: If this feature required world knowledge or complex reasoning,
ChatGPT would be the right choice. But this is [summarization/extraction/classification],
which is exactly what Foundation Models is optimized for.
**Time estimate**: Foundation Models implementation: 15-20 minutes.
Privacy compliance review for ChatGPT: 2-4 weeks."
节省时间:隐私合规审查与正确实施相比:2-4 周 vs 20 分钟
场景 2:“手动解析 JSON”
背景:队友建议让模型输出 JSON,再用 JSONDecoder 解析。他声称这种方式“简单又熟悉”。
压力信号:
- ⏰ 截止日期:2 天内发布
- 📚 熟悉度:“大家都懂 JSON”
- 🔧 现有代码:已经有 JSON 解析工具
合理化陷阱:
- “JSON 是标准格式”
- “我们已经在各处解析 JSON”
- “比学习新 API 更快”
这样做为什么行不通:
-
生成不存在的键:你期望
{name: "John"},模型却输出{firstName: "John"}- JSONDecoder 崩溃:
keyNotFound - 没有编译时安全保障
- JSONDecoder 崩溃:
-
无效 JSON:模型可能输出:
Here's the person: {name: "John", age: 30}- 不是有效的 JSON(包含前言文本)
- 解析失败
-
没有类型安全保障:手动解析字符串,容易出错
实际示例:
// ❌ BAD - Will fail
let prompt = "Generate a person with name and age as JSON"
let response = try await session.respond(to: prompt)
// Model outputs: {"firstName": "John Smith", "years": 30}
// Your code expects: {"name": ..., "age": ...}
// CRASH: keyNotFound(name)
调试时间:花 2-4 小时排查边缘情况、编写解析补丁
正确做法:
// ✅ GOOD - 15 minutes, guaranteed to work
@Generable
struct Person {
let name: String
let age: Int
}
let response = try await session.respond(
to: "Generate a person",
generating: Person.self
)
// response.content is type-safe Person, always valid
必须这样回应:
"I understand JSON parsing feels familiar, but for LLM output, @Generable is objectively
better for three technical reasons:
1. **Constrained decoding guarantees structure**: Model can ONLY generate valid Person
instances. Impossible to get wrong keys, invalid JSON, or missing fields.
2. **No parsing code needed**: Framework handles parsing automatically. Zero chance of
parsing bugs.
3. **Compile-time safety**: If we change Person struct, compiler catches all issues.
Manual JSON parsing = runtime crashes.
**Real cost**: Manual JSON approach will hit edge cases. Debugging 'keyNotFound' crashes
takes 2-4 hours. @Generable implementation takes 15 minutes and has zero parsing bugs.
**Analogy**: This is like choosing Swift over Objective-C for new code. Both work, but
Swift's type safety prevents entire categories of bugs."
节省时间:调试花费 4-8 小时,而正确实施只需 15 分钟
场景 3:“一个大提示词”
背景:功能需要从发票中提取姓名、日期、金额和类别。队友建议只用一个提示词:“提取所有信息。”
压力信号:
- 🏗️ 架构:“只调用一次 API 更简单”
- ⏰ 速度:“何必把事情搞复杂?”
- 📉 复杂度:“提示词越多,代码越多”
合理化陷阱:
- “越简单越好”
- “一个提示词意味着代码更少”
- “模型足够聪明”
这样做为什么行不通:
- 上下文溢出:复杂提示词 + 大型发票 → 超过 4096 个 token
- 结果不佳:模型试图一次处理太多内容,质量会下降
- 生成缓慢:一次生成大量内容需要 5-8 秒
- 成败与共:一个字段失败,整个生成过程都会失败
更好的做法:拆分任务 + 使用工具
// ❌ BAD - One massive prompt
let prompt = """
Extract from this invoice:
- Vendor name
- Invoice date
- Total amount
- Line items (description, quantity, price each)
- Payment terms
- Due date
- Tax amount
...
"""
// 4 seconds, poor quality, might exceed context
// ✅ GOOD - Structured extraction with focused prompts
@Generable
struct InvoiceBasics {
let vendor: String
let date: String
let amount: Double
}
let basics = try await session.respond(
to: "Extract vendor, date, and amount",
generating: InvoiceBasics.self
) // 0.5 seconds, axiom-high quality
@Generable
struct LineItem {
let description: String
let quantity: Int
let price: Double
}
let items = try await session.respond(
to: "Extract line items",
generating: [LineItem].self
) // 1 second, axiom-high quality
// Total: 1.5 seconds, better quality, graceful partial failures
必须这样回应:
"I understand the appeal of one simple API call. However, this specific task requires
a different approach:
1. **Context limits**: Invoice + complex extraction prompt will likely exceed 4096 token
limit. Multiple focused prompts stay well under limit.
2. **Better quality**: Model performs better with focused tasks. 'Extract vendor name'
gets 95%+ accuracy. 'Extract everything' gets 60-70%.
3. **Faster perceived performance**: Multiple prompts with streaming show progressive
results. Users see vendor name in 0.5s, not waiting 5s for everything.
4. **Graceful degradation**: If line items fail, we still have basics. All-or-nothing
approach means total failure.
**Implementation**: Breaking into 3-4 focused extractions takes 30 minutes. One big
prompt takes 2-3 hours debugging why it hits context limit and produces poor results."
节省时间:调试花费 2-3 小时,而合理设计只需 30 分钟
性能优化
关键优化
-
预热会话:在初始化时创建
LanguageModelSession,而不是等用户点击按钮时再创建。首次生成可缩短 1–2 秒。 -
includeSchemaInPrompt: false:对于后续使用相同@Generable类型的请求,在GenerationOptions中设置此项,可减少 10–20% 的 token 数量。 -
流式生成的属性顺序:在
@Generable结构体中将最重要的属性放在前面。这样用户可在 0.2 秒内看到标题,而不必等待 2.5 秒,直到完整生成结束。 -
Foundation Models Instrument:使用
Instruments > Foundation Models模板分析延迟、查看 token 数量并找出优化机会。
请参阅 axiom-foundation-models-ref,了解每项优化的代码示例。
检查清单
发布 Foundation Models 功能前:
必要检查
- 创建会话前检查可用性
- 结构化输出使用
@Generable(而非手动处理 JSON) - 处理上下文溢出(
exceededContextWindowSize) - 处理护栏违规(
guardrailViolation) - 处理不支持的语言(
unsupportedLanguageOrLocale) - 长时间生成(超过 1 秒)使用流式生成
- 不阻塞 UI(使用
Task {}执行异步操作) - 外部数据使用工具(不要通过提示词请求天气或位置)
- 对延迟敏感时使用预热会话
最佳实践
- 指令简洁(不要冗长)
- 绝不将用户输入插入指令中
- 优化属性顺序,提升流式生成的用户体验
- 使用恰当的温度/采样设置
- 在真实设备上测试(不只是模拟器)
- 使用 Instruments 分析(Foundation Models 模板)
- 错误处理会显示恰当的 UI 提示
- 在离线状态下测试(飞行模式)
- 使用长对话进行测试(上下文处理)
模型能力
- 不用于世界知识
- 不用于复杂推理
- 使用场景为:摘要、提取、分类或生成
- 若功能不可用,准备备用方案(显示提示、禁用功能)
资源
WWDC:286、259、301
技能:axiom-foundation-models-diag、axiom-foundation-models-ref
最后更新:2025-12-03 版本:1.0.0 目标平台:iOS 26+、macOS 26+、iPadOS 26+、axiom-visionOS 26+