FoundationModels: オンデバイスLLM(iOS 26)
FoundationModelsフレームワークを使用して、Appleのオンデバイス言語モデルをアプリに統合するためのパターン。テキスト生成、@Generableによる構造化出力、カスタムツール呼び出し、スナップショットストリーミングをカバー。すべてデバイス上で動作するため、プライバシーが保護されオフラインでも使用できます。
アクティベートすべき時
- Apple Intelligenceをオンデバイスで使用したAI搭載機能の構築
- クラウド依存なしでのテキスト生成または要約
- 自然言語入力からの構造化データ抽出
- ドメイン固有のAIアクションのためのカスタムツール呼び出しの実装
- リアルタイムUI更新のための構造化レスポンスのストリーミング
- プライバシーを重視したAIが必要な場合(デバイス外にデータが流出しない)
コアパターン — 利用可能性の確認
セッションを作成する前に、必ずモデルの利用可能性を確認します。
struct GenerativeView: View {
private var model = SystemLanguageModel.default
var body: some View {
switch model.availability {
case .available:
ContentView()
case .unavailable(.deviceNotEligible):
Text("このデバイスはApple Intelligenceに対応していません")
case .unavailable(.appleIntelligenceNotEnabled):
Text("設定でApple Intelligenceを有効にしてください")
case .unavailable(.modelNotReady):
Text("モデルがダウンロード中または準備ができていません")
case .unavailable(let other):
Text("モデルを利用できません: \(other)")
}
}
}
コアパターン — 基本セッション
// シングルターン:リクエストごとに新しいセッションを作成
let session = LanguageModelSession()
let response = try await session.respond(to: "パリに行くのに良い月はいつですか?")
print(response.content)
// マルチターン:会話のコンテキストを維持するためにセッションを再利用
let session = LanguageModelSession(instructions: """
あなたは料理アシスタントです。
材料に基づいてレシピの提案を提供します。
提案は簡潔かつ実用的にしてください。
""")
let first = try await session.respond(to: "鶏肉と米があります")
let followUp = try await session.respond(to: "ベジタリアン向けのオプションはありますか?")
指示(instructions)の重要なポイント:
- モデルの役割を定義する(「あなたはメンターです」)
- 何をすべきかを指定する(「カレンダーイベントの抽出を支援する」)
- スタイルの設定を行う(「できるだけ簡潔に回答する」)
- 安全対策を追加する(「危険なリクエストには「お手伝いできません」と回答する」)
コアパターン — @Generableを使ったガイド付き生成
生の文字列ではなく、構造化されたSwift型を生成します。
1. Generable型の定義
@Generable(description: "猫の基本プロフィール情報")
struct CatProfile {
var name: String
@Guide(description: "猫の年齢", .range(0...20))
var age: Int
@Guide(description: "猫の性格を一文で表すプロフィール")
var profile: String
}
2. 構造化出力のリクエスト
let response = try await session.respond(
to: "かわいい保護猫を生成してください",
generating: CatProfile.self
)
// 構造化されたフィールドに直接アクセス
print("名前: \(response.content.name)")
print("年齢: \(response.content.age)")
print("プロフィール: \(response.content.profile)")
サポートされている@Guide制約
.range(0...20)— 数値の範囲.count(3)— 配列要素数description:— 生成のための意味的なガイダンス
コアパターン — ツール呼び出し
モデルがドメイン固有のタスクのためにカスタムコードを呼び出せるようにします。
1. ツールの定義
struct RecipeSearchTool: Tool {
let name = "recipe_search"
let description = "指定された用語に一致するレシピを検索し、結果のリストを返します。"
@Generable
struct Arguments {
var searchTerm: String
var numberOfResults: Int
}
func call(arguments: Arguments) async throws -> ToolOutput {
let recipes = await searchRecipes(
term: arguments.searchTerm,
limit: arguments.numberOfResults
)
return .string(recipes.map { "- \($0.name): \($0.description)" }.joined(separator: "\n"))
}
}
2. ツールを使用したセッションの作成
let session = LanguageModelSession(tools: [RecipeSearchTool()])
let response = try await session.respond(to: "パスタのレシピをいくつか見つけてください")
3. ツールエラーの処理
do {
let answer = try await session.respond(to: "トマトスープのレシピを見つけてください。")
} catch let error as LanguageModelSession.ToolCallError {
print(error.tool.name)
if case .databaseIsEmpty = error.underlyingError as? RecipeSearchToolError {
// 特定のツールエラーを処理
}
}
コアパターン — スナップショットストリーミング
PartiallyGenerated型を使用して、構造化された応答をリアルタイムUI向けにストリーミングします。
@Generable
struct TripIdeas {
@Guide(description: "今後の旅行のアイデア")
var ideas: [String]
}
let stream = session.streamResponse(
to: "エキサイティングな旅行のアイデアをいくつか教えてください?",
generating: TripIdeas.self
)
for try await partial in stream {
// partial: TripIdeas.PartiallyGenerated(すべてのプロパティはOptional)
print(partial)
}
SwiftUI統合
@State private var partialResult: TripIdeas.PartiallyGenerated?
@State private var errorMessage: String?
var body: some View {
List {
ForEach(partialResult?.ideas ?? [], id: \.self) { idea in
Text(idea)
}
}
.overlay {
if let errorMessage { Text(errorMessage).foregroundStyle(.red) }
}
.task {
do {
let stream = session.streamResponse(to: prompt, generating: TripIdeas.self)
for try await partial in stream {
partialResult = partial
}
} catch {
errorMessage = error.localizedDescription
}
}
}
主要な設計上の決定
| 決定 | 根拠 |
|---|---|
| オンデバイス実行 | プライバシー — デバイス外にデータが流出せず、オフラインで動作 |
| 4,096トークン制限 | オンデバイスモデルの制約;大きなデータはセッション間で分割 |
| スナップショットストリーミング(差分ではない) | 構造化出力に適している;各スナップショットは完全な部分状態 |
@Generableマクロ | 構造化生成のためのコンパイル時の安全性;PartiallyGenerated型を自動生成 |
| セッションごとに単一リクエスト | isRespondingが同時リクエストを防ぐ;必要に応じて複数セッションを作成 |
response.content(.outputではなく) | 正しいAPI — 結果には常に.contentプロパティ経由でアクセス |
ベストプラクティス
model.availabilityを常に確認 する — セッション作成前にすべての利用不可ケースを処理instructionsを使用 してモデルの動作をガイドする — プロンプトより優先される- 新しいリクエストを送信する前に
isRespondingを確認 する — セッションは一度に1つのリクエストを処理 - 結果には
response.contentにアクセス する —.outputではない - 大きな入力はチャンクに分割する — 4,096トークン制限は指示 + プロンプト + 出力の合計に適用
- 構造化出力には
@Generableを使用 する — 生の文字列をパースするよりも強力な保証 GenerationOptions(temperature:)を使用 して創造性を調整する(値が高いほど創造的)- Instrumentsでモニタリングする — Xcode Instrumentsを使用してリクエストのパフォーマンスをプロファイリング
避けるべきアンチパターン
model.availabilityを確認せずにセッションを作成する- 4,096トークンのコンテキストウィンドウを超える入力を送信する
- 単一セッションで同時リクエストを試みる
- 応答データにアクセスする際に
.contentの代わりに.outputを使用する @Generableによる構造化出力が機能する場合に、生の文字列応答をパースする- 複雑なマルチステップロジックを単一のプロンプトに構築する — 複数の焦点を絞ったプロンプトに分割する
- モデルが常に利用可能であると想定する — デバイスの適合性と設定は異なる
使用すべき時
- プライバシー重視のアプリ向けオンデバイステキスト生成
- ユーザー入力からの構造化データ抽出(フォーム、自然言語コマンド)
- オフラインでも動作する必要があるAI支援機能
- 生成されたコンテンツを段階的に表示するストリーミングUI
- ツール呼び出しによるドメイン固有のAIアクション(検索、計算、ルックアップ)