mcpField
A
axiom-foundation-models

axiom-foundation-models

github.com/charleswiltgen开发1.2k188 次安装今天更新
代码仓库

关于 axiom-foundation-models

面向 Apple 平台开发者的 Foundation Models 实现指南,适用于端侧文本生成与分析功能。内容涵盖结构化输出、流式生成、工具调用以及可用性和错误处理。

主要功能

  1. 01端侧生成与文本分析
  2. 02@Generable 结构化输出
  3. 03流式生成与渐进更新
  4. 04通过工具接入外部数据
  5. 05可用性检查与错误处理

适用场景

  1. 01为文本摘要和分类加入端侧模型
  2. 02生成具备类型约束的结构化数据
  3. 03逐步展示较长生成结果
  4. 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
}
要点
  1. 指令可选 — 省略时会使用合理的默认值
  2. 绝不要将用户输入插入指令中 — 存在安全风险(提示词注入)
  3. 指令应简洁 — 每个 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!
工作原理(受约束解码)
  1. @Generable 宏在编译时生成架构
  2. 架构会自动传递给模型
  3. 模型生成的 token 受架构约束
  4. 框架将输出解析为 Swift 类型
  5. 保证结构正确 — 不会产生幻觉键,也不会出现解析错误

“受约束解码会屏蔽无效 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!"

模型会自主:

  1. 识别出需要天气数据
  2. 调用 GetWeatherTool
  3. 获取实际温度
  4. 将温度融入自然的响应中
关键概念
  • 工具协议:需要 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 的回答更好”
  • “使用现有代码,实施起来更快”

这样做为什么行不通:

  1. 侵犯隐私:用户数据会发送到外部服务

    • 医疗记录、财务文件、私人消息
    • 违背用户对设备端隐私的预期
    • 可能违反 GDPR 或隐私法规
  2. 成本:每次 API 调用都要花钱

    • Foundation Models 免费
    • 扩展到数百万用户时,成本会非常高
  3. 无法离线使用:需要网络连接

    • 飞行模式、信号差 → 功能无法使用
    • Foundation Models 可离线运行
  4. 延迟:网络往返会增加 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 更快”

这样做为什么行不通:

  1. 生成不存在的键:你期望 {name: "John"},模型却输出 {firstName: "John"}

    • JSONDecoder 崩溃:keyNotFound
    • 没有编译时安全保障
  2. 无效 JSON:模型可能输出:

    Here's the person: {name: "John", age: 30}
    
    • 不是有效的 JSON(包含前言文本)
    • 解析失败
  3. 没有类型安全保障:手动解析字符串,容易出错

实际示例:

// ❌ 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 更简单”
  • ⏰ 速度:“何必把事情搞复杂?”
  • 📉 复杂度:“提示词越多,代码越多”

合理化陷阱:

  • “越简单越好”
  • “一个提示词意味着代码更少”
  • “模型足够聪明”

这样做为什么行不通:

  1. 上下文溢出:复杂提示词 + 大型发票 → 超过 4096 个 token
  2. 结果不佳:模型试图一次处理太多内容,质量会下降
  3. 生成缓慢:一次生成大量内容需要 5-8 秒
  4. 成败与共:一个字段失败,整个生成过程都会失败

更好的做法:拆分任务 + 使用工具

// ❌ 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 分钟


性能优化

关键优化
  1. 预热会话:在初始化时创建 LanguageModelSession,而不是等用户点击按钮时再创建。首次生成可缩短 1–2 秒。

  2. includeSchemaInPrompt: false:对于后续使用相同 @Generable 类型的请求,在 GenerationOptions 中设置此项,可减少 10–20% 的 token 数量。

  3. 流式生成的属性顺序:在 @Generable 结构体中将最重要的属性放在前面。这样用户可在 0.2 秒内看到标题,而不必等待 2.5 秒,直到完整生成结束。

  4. 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+

评论

支持 Markdown