axiom-foundation-models by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-foundation-models在以下情况下使用:
axiom-foundation-models-diag 进行系统性故障排除(上下文超出、护栏违规、可用性问题)axiom-foundation-models-ref 获取包含所有 WWDC 代码示例的完整 API 参考失败原因:设备端模型只有 30 亿参数,专为摘要、提取、分类优化 —— 不适用于世界知识或复杂推理。
错误使用示例:
// ❌ 错误 - 询问世界知识
let session = LanguageModelSession()
let response = try await session.respond(to: "What's the capital of France?")
原因:模型会产生幻觉或给出低质量答案。它训练用于内容生成,而非百科全书式知识。
:使用服务器端 LLM(ChatGPT、Claude)处理世界知识,或通过工具调用提供事实数据。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
失败原因:session.respond() 是 async 的,但如果在主线程上同步调用,会导致 UI 冻结数秒。
错误使用示例:
// ❌ 错误 - 阻塞主线程
Button("Generate") {
let response = try await session.respond(to: prompt) // UI 冻结了!
}
原因:生成需要 1-5 秒。用户会看到应用冻结,随之而来的是差评。
正确方法:
// ✅ 正确 - 在后台异步执行
Button("Generate") {
Task {
let response = try await session.respond(to: prompt)
// 使用响应更新 UI
}
}
失败原因:提示生成 JSON 并使用 JSONDecoder 解析会导致幻觉键、无效 JSON、缺乏类型安全。
错误使用示例:
// ❌ 错误 - 手动 JSON 解析
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) // 崩溃!
原因:模型可能输出 {firstName: "John"},而你期望的是 {name: "John"}。或者输出完全无效的 JSON。
正确方法:
// ✅ 正确 - @Generable 保证结构
@Generable
struct Person {
let name: String
let age: Int
}
let response = try await session.respond(
to: "Generate a person",
generating: Person.self
)
// response.content 是类型安全的 Person 实例
失败原因:Foundation Models 仅在支持区域的 Apple Intelligence 设备上运行。不进行检查会导致应用崩溃或显示错误。
错误使用示例:
// ❌ 错误 - 没有可用性检查
let session = LanguageModelSession() // 可能会失败!
正确方法:
// ✅ 正确 - 先检查
switch SystemLanguageModel.default.availability {
case .available:
let session = LanguageModelSession()
// 继续执行
case .unavailable(let reason):
// 显示优雅的 UI:"AI 功能需要 Apple Intelligence"
}
失败原因:上下文窗口为 4096 个令牌(输入 + 输出)。一个巨大的提示会触及限制,导致结果不佳。
错误使用示例:
// ❌ 错误 - 所有内容放在一个提示中
let prompt = """
Generate a 7-day itinerary for Tokyo including hotels, restaurants,
activities for each day, transportation details, budget breakdown...
"""
// 超出上下文,质量差
正确方法:分解为更小的任务,使用工具获取外部数据,进行多轮对话。
失败原因:必须处理三种错误,否则你的应用在生产环境中会崩溃。
do {
let response = try await session.respond(to: prompt)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// 多轮对话记录增长超过 4096 个令牌
// → 压缩记录并创建新会话(见模式 5)
} catch LanguageModelSession.GenerationError.guardrailViolation {
// 内容策略被触发
// → 显示优雅消息:"我无法处理该请求"
} catch LanguageModelSession.GenerationError.unsupportedLanguageOrLocale {
// 用户输入了不支持的语言
// → 显示免责声明,检查 SystemLanguageModel.default.supportedLanguages
}
在编写任何 Foundation Models 代码之前,完成以下步骤:
查看上方危险信号中的"忽略可用性检查"以了解所需模式。Foundation Models 需要启用 Apple Intelligence 的设备、支持的区域以及用户选择加入。
问自己:我的主要目标是什么?
| 用例 | Foundation Models? | 替代方案 |
|---|---|---|
| 摘要 | ✅ 是 | |
| 提取(从文本中提取关键信息) | ✅ 是 | |
| 分类(对内容进行分类) | ✅ 是 | |
| 内容标记 | ✅ 是(内置适配器!) | |
| 世界知识 | ❌ 否 | ChatGPT、Claude、Gemini |
| 复杂推理 | ❌ 否 | 服务器端 LLM |
| 数学计算 | ❌ 否 | 计算器、符号数学 |
关键:如果你的用例需要世界知识或高级推理,停止。Foundation Models 是错误的工具。
如果你需要结构化输出(不仅仅是纯文本):
错误方法:提示生成"JSON"并手动解析 正确方法:定义 @Generable 类型
@Generable
struct SearchSuggestions {
@Guide(description: "Suggested search terms", .count(4))
var searchTerms: [String]
}
原因:约束解码保证结构。没有解析错误,没有幻觉键。
如果你的功能需要外部信息:
不要尝试从模型获取此信息(它会幻觉)。要定义 Tool 协议实现。
如果生成时间 >1 秒,使用流式传输:
let stream = session.streamResponse(
to: prompt,
generating: Itinerary.self
)
for try await partial in stream {
// 增量更新 UI
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
使用时机:简单的文本生成、摘要或内容分析。
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
}
let session = LanguageModelSession()
// 第一轮
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."
// 第二轮 - 模型记住上下文
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."
// 检查完整记录
print(session.transcript)
为何有效:会话自动保留记录。模型使用之前轮次的上下文。
✅ 适用于:
❌ 不适用于:
使用时机:你需要从模型获取结构化数据,而不仅仅是纯文本。
没有 @Generable:
// ❌ 错误 - 不可靠
let prompt = "Generate a person with name and age as JSON"
let response = try await session.respond(to: prompt)
// 可能得到:{"firstName": "John"},而你期望的是 {"name": "John"}
// 可能得到完全无效的 JSON
// 必须手动解析,容易崩溃
@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 // 类型安全的 Person 实例!
@Generable 宏在编译时生成模式"约束解码会屏蔽无效令牌。模型只能选择根据模式有效的令牌。"
支持 String、Int、Float、Double、Bool、数组、嵌套的 @Generable 类型、带关联值的枚举以及递归类型。完整列表及示例请参见 axiom-foundation-models-ref。
使用 @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 // 首先生成
var days: [DayPlan] // 其次生成
var summary: String // 最后生成
}
"你可能会发现,当摘要作为最后一个属性时,模型能生成最好的摘要。"
原因:后面的属性可以引用前面的属性。对于流式传输,将最重要的属性放在前面。
使用时机:生成时间 >1 秒,并且你希望逐步更新 UI。
没有流式传输:
// 用户等待 3-5 秒,什么也看不到
let response = try await session.respond(to: prompt, generating: Itinerary.self)
// 然后整个结果一次性出现
用户体验:感觉缓慢,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) // 增量更新
}
@Generable 宏会自动创建一个 PartiallyGenerated 类型,其中所有属性都是可选的(随着模型生成它们而填充)。详情请参见 axiom-foundation-models-ref。
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
}
}
}
}
}
}
对于数组至关重要:
// ✅ 正确 - 稳定的标识
ForEach(days, id: \.id) { day in
DayView(day: day)
}
// ❌ 错误 - 标识改变,动画中断
ForEach(days.indices, id: \.self) { index in
DayView(day: days[index])
}
✅ 用于:
❌ 跳过:
优雅地处理流式传输期间的错误 —— 部分结果可能已经显示:
do {
for try await partial in stream {
self.itinerary = partial
}
} catch LanguageModelSession.GenerationError.guardrailViolation {
// 部分内容可能可见 —— 显示非破坏性错误
self.errorMessage = "Generation stopped by content policy"
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// 上下文过多 —— 创建新会话并重试
session = LanguageModelSession()
}
使用时机:模型需要外部数据(天气、位置、联系人)来生成响应。
// ❌ 错误 - 模型会产生幻觉
let response = try await session.respond(
to: "What's the temperature in Cupertino?"
)
// 输出:"It's about 72°F"(完全是编造的!)
原因:30 亿参数的模型没有实时天气数据。
让模型自主调用你的代码来获取外部数据。
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!"
模型自主:
GetWeatherToolname、description、@Generable Arguments 和 call() 方法String(自然语言)或 GeneratedContent(结构化)class(而非 struct)Tool 协议参考、ToolOutput 形式、有状态工具模式以及其他示例,请参见 axiom-foundation-models-ref。
1. 使用工具初始化会话
2. 用户提示:"What's Tokyo's weather?"
3. 模型分析:"Need weather data"
4. 模型生成工具调用:getWeather(city: "Tokyo")
5. 框架调用你的工具的 call() 方法
6. 你的工具从 API 获取真实数据
7. 工具输出插入到记录中
8. 模型使用工具输出生成最终响应
"模型自主决定何时以及多久调用一次工具。每个请求可以调用多个工具,甚至可以并行调用。"
✅ 保证:
❌ 不保证:
✅ 用于:
❌ 不用于:
使用时机:可能超过 4096 令牌限制的多轮对话。
// 长对话...
for i in 1...100 {
let response = try await session.respond(to: "Question \(i)")
// 最终...
// Error: exceededContextWindowSize
}
上下文窗口:4096 个令牌(输入 + 输出合计) 平均值:英语中每个令牌约 3 个字符
粗略计算:
长对话或冗长的提示/响应 → 超出限制
var session = LanguageModelSession()
do {
let response = try await session.respond(to: prompt)
print(response.content)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// 新会话,无历史记录
session = LanguageModelSession()
}
问题:丢失整个对话历史。
var session = LanguageModelSession()
do {
let response = try await session.respond(to: prompt)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
// 带有压缩历史的新会话
session = condensedSession(from: session)
}
func condensedSession(from previous: LanguageModelSession) -> LanguageModelSession {
let allEntries = previous.transcript.entries
var condensedEntries = [Transcript.Entry]()
// 始终包含第一个条目(指令)
if let first = allEntries.first {
condensedEntries.append(first)
// 包含最后一个条目(最近的上下文)
if allEntries.count > 1, let last = allEntries.last {
condensedEntries.append(last)
}
}
let condensedTranscript = Transcript(entries: condensedEntries)
return LanguageModelSession(transcript: condensedTranscript)
}
为何有效:
高级策略(使用 Foundation Models 本身总结中间条目),请参见 axiom-foundation-models-ref。
1. 保持提示简洁:
// ❌ 错误
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...
"""
// ✅ 正确
let prompt = "Summarize this article's key points"
2. 使用工具获取数据:不要将整个数据集放入提示中,而是使用工具按需获取。
3. 将复杂任务分解为步骤:
// ❌ 错误 - 一次大规模生成
let response = try await session.respond(
to: "Create 7-day itinerary with hotels, restaurants, activities..."
)
// ✅ 正确 - 多次较小的生成
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)")
}
使用时机:你需要控制输出的随机性/确定性。
| 目标 | 设置 | 用例 |
|---|---|---|
| 确定性 | GenerationOptions(sampling: .greedy) | 单元测试、演示、一致性要求高 |
| 聚焦 | GenerationOptions(temperature: 0.5) | 事实提取、分类 |
| 创造性 | GenerationOptions(temperature: 2.0) | 故事生成、头脑风暴、多样化的 NPC 对话 |
默认:随机采样(温度 1.0)给出平衡的结果。
注意事项:贪婪确定性仅适用于相同的模型版本。操作系统更新可能会改变输出。
完整的 GenerationOptions API 参考请参见 axiom-foundation-models-ref。
背景:你正在实现一个新的 AI 功能。产品经理建议使用 ChatGPT API 以获取"更好的结果"。
压力信号:
合理化陷阱:
为何失败:
隐私侵犯:用户数据发送到外部服务器
成本:每次 API 调用都要花钱
离线不可用:需要互联网
延迟:网络往返增加 500-2000 毫秒
何时 ChatGPT 是合适的:
强制性回应:
"我理解 ChatGPT 在某些任务上能提供出色的结果。然而,
对于此功能,Foundation Models 是正确的选择,原因有三点至关重要:
1. **隐私**:此功能处理[医疗笔记/财务数据/个人内容]。
用户期望这些数据保留在设备上。发送到外部 API 违反了这种信任,
并可能带来合规性问题。
2. **成本**:在规模上,ChatGPT API 调用每 1000 次请求花费 $X。Foundation Models
是免费的。对于 Y 百万用户,这相当于每年可以避免 $Z 的成本。
3. **离线能力**:Foundation Models 无需互联网即可工作。处于飞行模式
或信号差的用户仍能获得完整功能。
**何时使用 ChatGPT**:如果此功能需要世界知识或复杂推理,
ChatGPT 将是正确的选择。但这是[摘要/提取/分类],
而这正是 Foundation Models 优化的方向。
**时间估算**:Foundation Models 实现:15-20 分钟。
ChatGPT 的隐私合规审查:2-4 周。"
节省的时间:隐私合规审查与正确实现:2-4 周 vs 20 分钟
背景:队友建议提示生成 JSON,并用 JSONDecoder 解析。声称这"简单且熟悉"。
压力信号:
合理化陷阱:
为何失败:
幻觉键:模型输出 {firstName: "John"},而你期望的是 {name: "John"}
keyNotFound无效 JSON:模型可能输出:
Here's the person: {name: "John", age: 30}
没有类型安全:手动字符串解析,容易出错
真实示例:
// ❌ 错误 - 会失败
let prompt = "Generate a person with name and age as JSON"
let response = try await session.respond(to: prompt)
// 模型输出:{"firstName": "John Smith", "years": 30}
// 你的代码期望:{"name": ..., "age": ...}
// 崩溃:keyNotFound(name)
调试时间:2-4 小时寻找边界情况,编写解析技巧
正确方法:
// ✅ 正确 - 15 分钟,保证有效
@Generable
struct Person {
let name: String
let age: Int
}
let response = try await session.respond(
to: "Generate a person",
generating: Person.self
)
// response.content 是类型安全的 Person,始终有效
强制性回应:
"我理解 JSON 解析感觉更熟悉,但对于 LLM 输出,@Generable 在技术上
更优越,原因有三点:
1. **约束解码保证结构**:模型只能生成有效的 Person
实例。不可能得到错误的键、无效的 JSON 或缺失的字段。
2. **无需解析代码**:框架自动处理解析。解析错误的可能性为零。
3. **编译时安全性**:如果我们更改 Person 结构体,编译器会捕获所有问题。
手动 JSON 解析 = 运行时崩溃。
**实际成本**:手动 JSON 方法会遇到边界情况。调试 'keyNotFound' 崩溃
需要 2-4 小时。@Generable 实现需要 15 分钟,并且没有解析错误。
**类比**:这就像为新代码选择 Swift 而非 Objective-C。两者都有效,但
Swift 的类型安全性防止了整类错误。"
节省的时间:4-8 小时调试 vs 15 分钟正确实现
背景:功能需要从发票中提取名称、日期、金额、类别。队友建议一个提示:"提取所有信息。"
压力信号:
合理化陷阱:
为何失败:
更好的方法:分解为任务 + 使用工具
// ❌ 错误 - 一个巨大的提示
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 秒,质量差,可能超出上下文
// ✅ 正确 - 使用聚焦提示的结构化提取
@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 秒,axiom-高质量
@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 秒,axiom-高质量
// 总计:1.5 秒,质量更好,优雅的部分失败处理
强制性回应:
"我理解一个简单 API 调用的吸引力。然而,这个特定任务需要
不同的方法:
1. **上下文限制**:发票 + 复杂的提取提示很可能超过 4096 令牌
限制。多个聚焦的提示则远低于限制。
2. **更好的质量**:模型在聚焦任务上表现更好。'提取供应商名称'
能达到 95%+ 的准确率。'提取所有内容'只有 60-70%。
3. **更快的感知性能**:多个提示配合流式传输可以逐步显示
结果。用户在 0.5 秒内看到供应商名称,而不是等待 5 秒才看到所有内容。
4. **优雅降级**:如果行项目失败,我们仍然有基本信息。全有或全无
的方法意味着完全失败。
**实现**:分解为 3-4 个聚焦提取需要 30 分钟。一个大提示
需要 2-3 小时来调试为什么它会触及上下文限制并产生差的结果。"
节省的时间:2-3 小时调试 vs 30 分钟正确设计
预热会话:在初始化时创建 LanguageModelSession,而不是在用户点击按钮时。节省首次生成的 1-2 秒。
includeSchemaInPrompt: false:对于使用相同 @Generable 类型的后续请求,在 GenerationOptions 中设置此项,以减少 10-20% 的令牌数。
流式传输的属性顺序:将最重要的属性放在 @Generable 结构体的前面。用户在 0.2 秒内看到标题,而不是等待 2.5 秒的完整生成。
Foundation Models 仪器:使用 Instruments > Foundation Models 模板来分析延迟、查看令牌计数并识别优化机会。
每个优化的代码示例请参见 axiom-foundation-models-ref。
在发布 Foundation Models 功能之前:
exceededContextWindowSize)guardrailViolation)unsupportedLanguageOrLocale)Task {} 进行异步操作)Use when:
axiom-foundation-models-diag for systematic troubleshooting (context exceeded, guardrail violations, availability problems)axiom-foundation-models-ref for complete API reference with all WWDC code examplesWhy it fails : The on-device model is 3 billion parameters, optimized for summarization, extraction, classification — NOT world knowledge or complex reasoning.
Example of wrong use :
// ❌ BAD - Asking for world knowledge
let session = LanguageModelSession()
let response = try await session.respond(to: "What's the capital of France?")
Why : Model will hallucinate or give low-quality answers. It's trained for content generation, not encyclopedic knowledge.
Correct approach : Use server LLMs (ChatGPT, Claude) for world knowledge, or provide factual data through Tool calling.
Why it fails : session.respond() is async but if called synchronously on main thread, freezes UI for seconds.
Example of wrong use :
// ❌ BAD - Blocking main thread
Button("Generate") {
let response = try await session.respond(to: prompt) // UI frozen!
}
Why : Generation takes 1-5 seconds. User sees frozen app, bad reviews follow.
Correct approach :
// ✅ GOOD - Async on background
Button("Generate") {
Task {
let response = try await session.respond(to: prompt)
// Update UI with response
}
}
Why it fails : Prompting for JSON and parsing with JSONDecoder leads to hallucinated keys, invalid JSON, no type safety.
Example of wrong use :
// ❌ 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!
Why : Model might output {firstName: "John"} when you expect {name: "John"}. Or invalid JSON entirely.
Correct approach :
// ✅ 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
Why it fails : Foundation Models only runs on Apple Intelligence devices in supported regions. App crashes or shows errors without check.
Example of wrong use :
// ❌ BAD - No availability check
let session = LanguageModelSession() // Might fail!
Correct approach :
// ✅ 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"
}
Why it fails : 4096 token context window (input + output). One massive prompt hits limit, gives poor results.
Example of wrong use :
// ❌ 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
Correct approach : Break into smaller tasks, use tools for external data, multi-turn conversation.
Why it fails : Three errors MUST be handled or your app will crash in production.
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
}
Before writing any Foundation Models code, complete these steps:
See "Ignoring Availability Check" in Red Flags above for the required pattern. Foundation Models requires Apple Intelligence-enabled device, supported region, and user opt-in.
Ask yourself : What is my primary goal?
| Use Case | Foundation Models? | Alternative |
|---|---|---|
| Summarization | ✅ YES | |
| Extraction (key info from text) | ✅ YES | |
| Classification (categorize content) | ✅ YES | |
| Content tagging | ✅ YES (built-in adapter!) | |
| World knowledge | ❌ NO | ChatGPT, Claude, Gemini |
| Complex reasoning | ❌ NO | Server LLMs |
| Mathematical computation | ❌ NO | Calculator, symbolic math |
Critical : If your use case requires world knowledge or advanced reasoning, stop. Foundation Models is the wrong tool.
If you need structured output (not just plain text):
Bad approach : Prompt for "JSON" and parse manually Good approach : Define @Generable type
@Generable
struct SearchSuggestions {
@Guide(description: "Suggested search terms", .count(4))
var searchTerms: [String]
}
Why : Constrained decoding guarantees structure. No parsing errors, no hallucinated keys.
If your feature needs external information:
Don't try to get this information from the model (it will hallucinate). Do define Tool protocol implementations.
If generation takes >1 second, use streaming:
let stream = session.streamResponse(
to: prompt,
generating: Itinerary.self
)
for try await partial in stream {
// Update UI incrementally
self.itinerary = partial
}
Why : Users see progress immediately, perceived latency drops dramatically.
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
Use when : Simple text generation, summarization, or content analysis.
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
}
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)
Why this works : Session retains transcript automatically. Model uses context from previous turns.
✅ Good for :
❌ Not good for :
Use when : You need structured data from model, not just plain text.
Without @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
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 macro generates schema at compile-time"Constrained decoding masks out invalid tokens. Model can only pick tokens valid according to schema."
Supports String, Int, Float, Double, Bool, arrays, nested @Generable types, enums with associated values, and recursive types. See axiom-foundation-models-ref for complete list with examples.
Control generated values with @Guide. Supports descriptions, numeric ranges, array counts, and regex patterns:
@Generable
struct NPC {
@Guide(description: "A full name")
let name: String
@Guide(.range(1...10))
let level: Int
@Guide(.count(3))
let attributes: [String]
}
Runtime validation : @Guide constraints are enforced during generation via constrained decoding — the model cannot produce out-of-range values. However, always validate business logic on the result since the model may produce semantically wrong but structurally valid output.
See axiom-foundation-models-ref for complete @Guide reference (ranges, regex, maximum counts).
Properties generated in declaration order :
@Generable
struct Itinerary {
var destination: String // Generated first
var days: [DayPlan] // Generated second
var summary: String // Generated last
}
"You may find model produces best summaries when they're last property."
Why : Later properties can reference earlier ones. Put most important properties first for streaming.
Use when : Generation takes >1 second and you want progressive UI updates.
Without streaming:
// User waits 3-5 seconds seeing nothing
let response = try await session.respond(to: prompt, generating: Itinerary.self)
// Then entire result appears at once
User experience : Feels slow, frozen 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
}
@Generable macro automatically creates a PartiallyGenerated type where all properties are optional (they fill in as the model generates them). See axiom-foundation-models-ref for details.
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
}
}
}
}
}
}
Critical for arrays :
// ✅ 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])
}
✅ Use for :
❌ Skip for :
Handle errors during streaming gracefully — partial results may already be displayed:
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()
}
Use when : Model needs external data (weather, locations, contacts) to generate response.
// ❌ 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!)
Why : 3B parameter model doesn't have real-time weather data.
Let model autonomously call your code to fetch external data.
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!"
Model autonomously :
GetWeatherToolname, description, @Generable Arguments, and call() methodString (natural language) or GeneratedContent (structured)class (not struct) when tools need to maintain state across callsSee axiom-foundation-models-ref for Tool protocol reference, ToolOutput forms, stateful tool patterns, and additional examples.
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
"Model decides autonomously when and how often to call tools. Can call multiple tools per request, even in parallel."
✅ Guaranteed :
❌ Not guaranteed :
✅ Use for :
❌ Don't use for :
Use when : Multi-turn conversations that might exceed 4096 token limit.
// Long conversation...
for i in 1...100 {
let response = try await session.respond(to: "Question \(i)")
// Eventually...
// Error: exceededContextWindowSize
}
Context window : 4096 tokens (input + output combined) Average : ~3 characters per token in English
Rough calculation :
Long conversation or verbose prompts/responses → Exceed limit
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()
}
Problem : Loses entire conversation history.
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)
}
Why this works :
For advanced strategies (summarizing middle entries with Foundation Models itself), see axiom-foundation-models-ref.
1. Keep prompts concise :
// ❌ 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. Use tools for data : Instead of putting entire dataset in prompt, use tools to fetch on-demand.
3. Break complex tasks into steps :
// ❌ 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)")
}
Use when : You need control over output randomness/determinism.
| Goal | Setting | Use Cases |
|---|---|---|
| Deterministic | GenerationOptions(sampling: .greedy) | Unit tests, demos, consistency-critical |
| Focused | GenerationOptions(temperature: 0.5) | Fact extraction, classification |
| Creative | GenerationOptions(temperature: 2.0) | Story generation, brainstorming, varied NPC dialog |
Default : Random sampling (temperature 1.0) gives balanced results.
Caveat : Greedy determinism only holds for same model version. OS updates may change output.
See axiom-foundation-models-ref for complete GenerationOptions API reference.
Context : You're implementing a new AI feature. PM suggests using ChatGPT API for "better results."
Pressure signals :
Rationalization traps :
Why this fails :
Privacy violation : User data sent to external server
Cost : Every API call costs money
Offline unavailable : Requires internet
Latency : Network round-trip adds 500-2000ms
When ChatGPT IS appropriate :
Mandatory response :
"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."
Time saved : Privacy compliance review vs correct implementation: 2-4 weeks vs 20 minutes
Context : Teammate suggests prompting for JSON, parsing with JSONDecoder. Claims it's "simple and familiar."
Pressure signals :
Rationalization traps :
Why this fails :
Hallucinated keys : Model outputs {firstName: "John"} when you expect {name: "John"}
keyNotFoundInvalid JSON : Model might output:
Here's the person: {name: "John", age: 30}
No type safety : Manual string parsing, prone to errors
Real-world example :
// ❌ 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)
Debugging time : 2-4 hours finding edge cases, writing parsing hacks
Correct approach :
// ✅ 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
Mandatory response :
"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."
Time saved : 4-8 hours debugging vs 15 minutes correct implementation
Context : Feature requires extracting name, date, amount, category from invoice. Teammate suggests one prompt: "Extract all information."
Pressure signals :
Rationalization traps :
Why this fails :
Better approach : Break into tasks + use tools
// ❌ 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
Mandatory response :
"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."
Time saved : 2-3 hours debugging vs 30 minutes proper design
Prewarm session : Create LanguageModelSession at init, not when user taps button. Saves 1-2 seconds off first generation.
includeSchemaInPrompt: false : For subsequent requests with the same @Generable type, set this in GenerationOptions to reduce token count by 10-20%.
Property order for streaming : Put most important properties first in @Generable structs. User sees title in 0.2s instead of waiting 2.5s for full generation.
Foundation Models Instrument : Use Instruments > Foundation Models template to profile latency, see token counts, and identify optimization opportunities.
See axiom-foundation-models-ref for code examples of each optimization.
Before shipping Foundation Models features:
exceededContextWindowSize)guardrailViolation)unsupportedLanguageOrLocale)Task {} for async)WWDC : 286, 259, 301
Skills : axiom-foundation-models-diag, axiom-foundation-models-ref
Last Updated : 2025-12-03 Version : 1.0.0 Target : iOS 26+, macOS 26+, iPadOS 26+, axiom-visionOS 26+
Weekly Installs
145
Repository
GitHub Stars
674
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode129
codex124
gemini-cli120
claude-code119
cursor116
github-copilot113
AI 代码实施计划编写技能 | 自动化开发任务分解与 TDD 流程规划工具
50,900 周安装
SwiftData 教程:iOS 17+ 原生持久化框架,与 SwiftUI 集成和 CloudKit 同步
275 周安装
阿里云AI语音TTS测试技能 - 最小可行测试验证与安装指南
274 周安装
阿里云DNS CLI测试指南:alicloud-network-dns-cli-test 安装与使用教程
273 周安装
网络小说大纲规划工具 - 从总纲到卷章细纲的AI辅助写作技能
71 周安装
阿里云 ECS 最小可行性测试 Skill - 自动化云服务 API 连通性验证工具
275 周安装
阿里云AI语音实时TTS测试技能:最小可行性验证与兼容性探测指南
273 周安装