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機能を実装する
  • テキストの要約、分類、抽出機能を追加する
  • LLMの応答から構造化出力を作成する
  • 外部データを連携するツール呼び出しパターンを構築する
  • UXを向上させるため、生成コンテンツをストリーミングする
  • Foundation Modelsの問題(コンテキストのオーバーフロー、生成の遅さ、出力の誤り)をデバッグする
  • Foundation ModelsとサーバーLLM(ChatGPT、Claudeなど)のどちらを使うか判断する
関連スキル
  • 体系的なトラブルシューティング(コンテキスト超過、ガードレール違反、利用可否の問題)には axiom-foundation-models-diag を使用します
  • WWDCのコード例をすべて含む完全なAPIリファレンスには axiom-foundation-models-ref を使用します

要注意事項 — 失敗につながるアンチパターン

❌ 世界知識に使用する

失敗する理由:オンデバイスモデルは30億パラメーターで、要約、抽出、分類向けに最適化されており、世界知識や複雑な推論には向いていません。

誤った使用例:

// ❌ BAD - Asking for world knowledge
let session = LanguageModelSession()
let response = try await session.respond(to: "What's the capital of France?")

理由:モデルが幻覚を起こしたり、質の低い回答を返したりする可能性があります。このモデルはコンテンツ生成向けにトレーニングされており、百科事典的な知識を備えているわけではありません。

正しい方法:世界知識にはサーバーLLM(ChatGPT、Claude)を使用するか、ツール呼び出しを通じて事実データを提供します。


❌ メインスレッドをブロックする

失敗する理由:session.respond() は async ですが、メインスレッド上で同期的に呼び出すと、UIが数秒間フリーズします。

誤った使用例:

// ❌ 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トークン(入力+出力)です。巨大なプロンプトをひとつ使うと上限に達し、結果の質が低下します。

誤った使用例:

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

正しい方法:タスクを小さく分割し、外部データにはツールを使い、複数ターンの会話にします。


❌ 生成エラーに対処しない

失敗する理由:3つのエラーに必ず対処しないと、本番環境でアプリがクラッシュします。

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
複雑な推論❌ いいえサーバーLLM
数学的な計算❌ いいえ電卓、記号計算

重要:世界知識や高度な推論が必要なユースケースの場合は、ここで中止してください。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 ツール

この情報をモデルから取得しようとしないでください(ハルシネーションが発生します)。 Tool プロトコルの実装を定義してください。


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. 指示は簡潔にしてください — トークンが増えるたびにレイテンシーが加わります
複数ターンのやり取り
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)

これが機能する理由:セッションはトランスクリプトを自動的に保持します。モデルは前のターンのコンテキストを使用します。

このパターンを使用する場面

✅ 適しています:

  • シンプルな質疑応答
  • テキストの要約
  • コンテンツ分析
  • 1 ターンの生成

❌ 適していません:

  • 構造化出力(パターン 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. モデルはスキーマによって制約されたトークンを生成します
  4. フレームワークが出力を Swift 型に解析します
  5. 構造的な正しさが保証されます — ハルシネーションによるキーや解析エラーは発生しません

「制約付きデコーディングでは、無効なトークンが除外されます。モデルはスキーマ上有効なトークンのみを選択できます。」

対応する型

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
                    }
                }
            }
        }
    }
}
ビューの ID

配列では特に重要です:

// ✅ 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])
}
ストリーミングを使用する場面

✅ 使用する場合:

  • 旅程
  • 物語
  • 長い説明
  • 複数セクションのコンテンツ

❌ 使用しない場合:

  • 簡単なQ&A(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(構造化データ)を返します
  • 複数のツール:セッションはツールの配列を受け入れ、モデルが自律的に呼び出すツールを決定します
  • ステートフルなツール:呼び出しをまたいで状態を保持する必要がある場合は、structではなくclassを使用します

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トークンの上限を超える可能性がある複数ターンの会話。

問題
// Long conversation...
for i in 1...100 {
    let response = try await session.respond(to: "Question \(i)")
    // Eventually...
    // Error: exceededContextWindowSize
}

コンテキストウィンドウ:4096トークン(入力と出力の合計) 平均:英語では1トークンあたり約3文字

概算:

  • 4096トークン ≈ 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)
}

これが有効な理由:

  • 指示が常に保持されます
  • 直近のコンテキストが維持されます
  • トークン数を大幅に削減できます

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)では、バランスの取れた結果が得られます。

注意点:貪欲法による決定性が保たれるのは、同じモデルバージョンの場合に限られます。OSのアップデートで出力が変わることがあります。

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 トークン超)

必須の対応:

"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 週間に対して 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:「大きなプロンプトを 1 つ使う」

状況:機能で請求書から名前、日付、金額、カテゴリを抽出する必要があります。チームメイトは「すべての情報を抽出する」というプロンプトを 1 つ使うことを提案します。

プレッシャーの兆候:

  • 🏗️ アーキテクチャ:「API 呼び出しが 1 回ならシンプルです」
  • ⏰ スピード:「なぜ複雑にするのですか?」
  • 📉 複雑さ:「プロンプトを増やすとコードも増えます」

正当化の落とし穴:

  • 「シンプルなほうが良いです」
  • 「プロンプトが 1 つならコードが少なくて済みます」
  • 「モデルは十分賢いです」

これが失敗する理由:

  1. コンテキストの超過:複雑なプロンプトと長い請求書により、4096 トークンを超えます
  2. 結果の悪化:モデルが一度に多くのことを処理しようとするため、品質が低下します
  3. 生成の遅延:非常に大きな応答を生成するのに 5~8 秒かかります
  4. 全か無か:1 つの項目で失敗すると、生成全体が失敗します

より良い方法:タスクを分割し、ツールを使います

// ❌ 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型を使う2回目以降のリクエストでは、GenerationOptionsにこれを設定して、トークン数を10~20%削減します。

  3. ストリーミング向けのプロパティ順序:@Generable構造体では、最も重要なプロパティを先頭に配置します。完全な生成を待たずに、ユーザーは0.2秒でタイトルを確認できます(待ち時間は2.5秒から短縮されます)。

  4. Foundation Models Instrument:Instruments > Foundation Modelsテンプレートを使ってレイテンシーをプロファイリングし、トークン数を確認して、最適化の機会を特定します。

各最適化のコード例については、axiom-foundation-models-refを参照してください。


チェックリスト

Foundation Models の機能をリリースする前に、以下を確認してください。

必須チェック
  • セッション作成前に利用可能かどうかを確認している
  • 構造化出力に**@Generableを使用している**(手動でJSONを作成していない)
  • コンテキストの超過に対応している(exceededContextWindowSize)
  • ガードレール違反に対応している(guardrailViolation)
  • 未対応の言語に対応している(unsupportedLanguageOrLocale)
  • 長い生成(1秒超)にはストリーミングを使用している
  • UIをブロックしていない(非同期処理にTask {}を使用している)
  • 外部データにはツールを使用している(天気や場所についてプロンプトで尋ねていない)
  • レイテンシーが重要な場合、セッションを事前にウォームアップしている
ベストプラクティス
  • 指示が簡潔である(冗長ではない)
  • ユーザー入力を指示に埋め込んでいない
  • ストリーミングUX向けにプロパティ順序を最適化している
  • 適切な温度/サンプリング設定を使用している
  • 実機でテストしている(シミュレーターだけで済ませていない)
  • 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+

コメント