
Foundation Modelsフレームワークでテキスト生成してみた
先日、Foundation Modelsのマルチモーダル機能を使った画像解析の記事を公開した。
しかし振り返ると、テキスト生成という基本的な使い方についてはまだ記事にしていなかった。マルチモーダルの前に、まずテキスト生成の基本を押さえておきたいと思い本記事を書くことにした。
本記事では、Foundation Modelsを使ったテキスト生成の基本的な機能を順を追って紹介する。同じような検証をしてみたい方の参考になれば幸いだ。
検証環境
- MacBook Pro (16インチ, 2023), Apple M2 Pro
- macOS Tahoe 26.5.1
- Xcode 27.0 Beta
- iPhone 17 Pro Simulator(iOS 27.0 Beta)
- iPhone 16e 実機(iOS 27.0 Beta)
Foundation Modelsのテキスト生成について
Foundation Modelsフレームワークは、WWDC25で登場したApple Intelligenceを搭載したデバイスでオンデバイス推論を実現するフレームワークだ。外部サーバーへの通信が発生しないため、プライバシーに配慮したアプリ開発に活用できる。
リルオッサ氏が「try! Swift Tokyo 2026」でFoundation Modelsを使って日記の内容を絵文字に置き換える方法を発表していた。
テキスト生成の主なユースケースとしては以下が考えられる。
- 文章の要約・言い換え・校正
- アプリ内チャットや質問応答
- ユーザー入力のテキスト分類・解析
- 定型コンテンツの自動生成
ただし、動作にはApple Intelligenceに対応したデバイスが必要だ。対応デバイスについてはApple公式ページを参照してほしい。
実装手順
手順1:プロジェクトの準備
Xcodeで新しいiOSプロジェクトを作成し、FoundationModelsフレームワークを使用する。追加のSPM依存関係は不要で、システムフレームワークとして利用できる。
Info.plistには特別な設定は不要だ。
まずはサンプルコードを実行するためにボタンをタップしたら処理を実行してテキストで表示する簡単な画面を追加する。action1() の部分にはこれから説明する処理を追加することを想定している。
import SwiftUI
import FoundationModels
struct ContentView: View {
@State private var text: String = ""
var body: some View {
ScrollView {
VStack(spacing: 16) {
Text(text)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Button("Run", action: action1)
}
}
}
func action1() {
// ここに Foundation Models の処理を追加する
}
}
手順2:モデルの可用性チェックとセッションの作成
SystemLanguageModel.default でデバイスのデフォルトモデルを取得する。isAvailable で利用可能かどうかを確認してから使用するようにする。以降のコードはすべて action1() 内に追記していく。
// Apple Intelligence対応デバイスかどうかを確認
guard SystemLanguageModel.default.isAvailable else {
text = "Apple Intelligenceが利用できません"
return
}
let session = LanguageModelSession()
手順3:基本的なテキスト生成
session.respond(to:) にプロンプトを渡すと、生成されたテキストを response.content で取得できる。
Task {
do {
let response = try await session.respond(to: "iOSアプリ開発の魅力を1文で教えてください")
text = response.content
print(response.content)
} catch {
text = "エラー: \(error.localizedDescription)"
print("エラー: \(error)\n\(String(reflecting: error))")
}
}
出力のばらつきを確認するため、同じプロンプトを5回実行した。カッコの中の秒数は実行前後の Date() の差分で計測した処理時間だ。
| 回答 | 処理時間 |
|---|---|
| iOSアプリ開発の魅力は、シンプルで直感的なインターフェースでユーザーの体験を豊かにできる点です。 | 3347.9 ms |
| iOSアプリ開発の魅力は、シンプルで直感的なインターフェースでユーザーの体験を向上させることができる点です。 | 2953.1 ms |
| iOSアプリ開発の魅力は、シンプルで直感的なインターフェースでユーザーの体験を豊かにできる点です。 | 3385.4 ms |
| iOSアプリ開発の魅力は、ユーザーのニーズに応じた直感的なデザインとスムーズなパフォーマンスを実現できる点です。 | 2122.7 ms |
| iOSアプリ開発の魅力は、シンプルで直感的なデザインと強力なエコシステムを活用できる点です。 | 2097.5 ms |
同一プロンプトに対して、まったく同じ表現が返る場合と異なる表現になる場合の両方が見られた。クラウドLLMと同様に確率的な挙動をしており、完全に決定論的ではないことが確認できた。
手順1〜3のソースコード全文
import SwiftUI
import FoundationModels
struct ContentView: View {
@State private var text: String = ""
var body: some View {
ScrollView {
VStack(spacing: 16) {
Text(text)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Button("Run", action: action1)
}
}
}
func action1() {
// Apple Intelligence対応デバイスかどうかを確認
guard SystemLanguageModel.default.isAvailable else {
text = "Apple Intelligenceが利用できません"
return
}
let session = LanguageModelSession()
Task {
do {
let response = try await session.respond(to: "iOSアプリ開発の魅力を1文で教えてください")
text = response.content
print(response.content)
} catch {
text = "エラー: \(error.localizedDescription)"
print("エラー: \(error)\n\(String(reflecting: error))")
}
}
}
}
手順4:ストリーミングでリアルタイム表示
respond(to:) は生成が完了してから全文を返すが、streamResponse(to:) を使うと生成中のテキストを逐次受け取ることができる。長文生成時のUX改善に特に有効だ。
ContentView に action2() を追加し、ボタンのアクションを action1 から action2 に切り替えて確認する。
func action2() {
guard SystemLanguageModel.default.isAvailable else {
text = "Apple Intelligenceが利用できません"
return
}
let session = LanguageModelSession()
Task {
do {
text = ""
let stream = session.streamResponse(to: "Swiftを学ぶメリットを3つ挙げてください")
for try await partial in stream {
text = partial.content
}
} catch {
text = "エラー: \(error.localizedDescription)"
print("エラー: \(error)\n\(String(reflecting: error))")
}
}
}
partial.content はそれまでに生成されたテキストの累積値なので、text = で上書きするだけでリアルタイム表示が実現できる。
手順5:instructionsでシステムプロンプトを設定
LanguageModelSession(instructions:) を使うと、クラウドLLMのシステムプロンプトに相当するキャラクター設定が行える。instructions を変えることで、同じプロンプトに対する回答のトーンや粒度をコントロールできる。
ContentView に action3() を追加して動作を確認する。まず instructions なしで質問した結果だ。
// instructionsなし
let session = LanguageModelSession()
let r1 = try await session.respond(to: "AutoLayoutとは何ですか?2〜3文で説明してください。")
// → AutoLayoutは、iOSやmacOSのアプリ開発において、UI要素の位置を自動的に調整するフレームワークです。
// これにより、異なるデバイスサイズやOSバージョンに対応したデザインを実現します。
// コードで手動でレイアウトを調整する手間を省けます。
次に instructions を設定して同じ質問をした結果だ。
// instructionsあり
let session = LanguageModelSession(
instructions: "あなたはiOS開発の専門家です。専門用語を使って端的に答えてください。"
)
let r2 = try await session.respond(to: "AutoLayoutとは何ですか?2〜3文で説明してください。")
// → AutoLayoutは、UI要素のレイアウトを自動的に調整するためのフレームワークです。
// Swift言語やObjective-Cで利用し、デバイス依存性を最小限に抑えます。
// Constraintsを利用して、要素間の距離や配置を制御します。
どちらも3文構成で文量は変わらなかったが、instructions ありの回答では「Constraints」「デバイス依存性」「Swift言語やObjective-C」といった専門用語が登場した。クラウドLLMほど劇的な変化はないものの、語彙レベルの差異は確認できた。
ここでは「iOS開発の専門家」というペルソナを設定した例を紹介したが、なりきりチャットアプリも同じ機能で実現できる。たとえば instructions を戦国時代の侍に設定すると、キャラクターに沿った口調で回答が返ってくる。
let session = LanguageModelSession(
instructions: "あなたは戦国時代に生きる侍です。主君に直答を許されている。端的に問いに対して回答せよ"
)
let response = try await session.respond(to: "AutoLayoutとは何ですか?2〜3文で説明してください。")
// → 拙者には、その知識は戦国時代の侍にはない。しかし、もし貴殿が知りたいならば、
// それはスマートフォンやパソコンで使うためのデザイン技術のことである。
action3() の全体像は以下の通りだ。
func action3() {
guard SystemLanguageModel.default.isAvailable else {
text = "Apple Intelligenceが利用できません"
return
}
let session = LanguageModelSession(
instructions: "あなたはiOS開発の専門家です。専門用語を使って端的に答えてください。"
)
Task {
do {
let response = try await session.respond(to: "AutoLayoutとは何ですか?2〜3文で説明してください。")
text = response.content
} catch {
text = "エラー: \(error.localizedDescription)"
print("エラー: \(error)\n\(String(reflecting: error))")
}
}
}
手順6:@Generableで構造化出力
@Generable マクロをSwiftの構造体に付与すると、モデルの出力をその型のインスタンスとして受け取ることができる。フレームワークが型情報をJSONスキーマに変換してモデルに渡す仕組みだ。
ここではアプリのレビュー文を解析してカテゴリ別に整理する例を示す。@Guide マクロはプロパティの意味をモデルに自然言語で伝えるためのもので、必須ではないが出力品質を上げたい場合に使用する。@Guide の description は公式ドキュメントのサンプルに倣って英語で記述している。
AppReviewAnalysis はファイルのトップレベル(ContentView の外)に定義する。
@Generable
struct AppReviewAnalysis {
@Guide(description: "Overall sentiment: positive, negative, or neutral")
var sentiment: String
@Guide(description: "Key positive points mentioned in the review")
var positivePoints: [String]
@Guide(description: "Issues or complaints mentioned in the review")
var issues: [String]
}
ContentView に action4() を追加して動作を確認する。
func action4() {
guard SystemLanguageModel.default.isAvailable else {
text = "Apple Intelligenceが利用できません"
return
}
let session = LanguageModelSession()
Task {
let reviewText = """
起動が速くて使いやすいです。デザインも好みです。
ただ、通知が多すぎて設定を探すのが大変でした。
"""
do {
let response = try await session.respond(
generating: AppReviewAnalysis.self
) {
reviewText
}
print(response.content.sentiment) // → positive
print(response.content.positivePoints) // → ["起動が速い", "使いやすい", "デザインが良い"]
print(response.content.issues) // → ["通知が多すぎて設定を探すのが大変"]
} catch {
text = "エラー: \(error.localizedDescription)"
print("エラー: \(error)\n\(String(reflecting: error))")
}
}
}
以下の解析結果が得られた。複数回実行しても sentiment と issues は完全に一致し、positivePoints の表現にわずかな揺れがあった程度だった。
positive
["起動が速い", "使いやすい", "デザインが好み"]
["通知が多すぎて設定を探すのが大変"]
自由形式のテキスト生成(手順3)と比べて出力が安定している。これは @Generable が型情報をJSONスキーマに変換し、ガイド付き生成(Guided Generation)でモデルの出力を型の制約内に収める仕組みによるものだ。
同じコードでネガティブなレビューを渡すと、sentiment や positivePoints の内容が変化する。
let reviewText = """
全く使い物になりません。起動するたびにクラッシュして、
入力したデータも消えてしまいます。改善を期待したいです。
"""
// → negative
// → []
// → ["起動するたびにクラッシュする", "入力したデータも消えてしまう"]
positivePoints が空配列になり、issues にクラッシュやデータ消失が列挙された。入力テキストのトーンに応じて構造化データの中身が切り替わることが確認できた。
非構造化テキストをSwiftの型として取り出せるため、その後の処理に組み込みやすい。なお、@Generable 型の情報はコンテキストウィンドウを消費する。プロパティ数が多い・@Guide の記述が長いほど消費量が増えるため、不要なプロパティは省くようにするとよい。
手順7:マルチターン会話
LanguageModelSession はセッション内の会話履歴を自動的に保持する。同一セッションに対してリクエストを送り続けることで、文脈を引き継いだ会話が実現できる。
ContentView に action5() を追加して動作を確認する。
func action5() {
guard SystemLanguageModel.default.isAvailable else {
text = "Apple Intelligenceが利用できません"
return
}
let session = LanguageModelSession()
Task {
do {
// 1回目の質問
let r1 = try await session.respond(to: "SwiftUIとUIKitの違いを教えてください")
print(r1.content)
// 2回目:前の文脈を引き継いで質問する
let r2 = try await session.respond(to: "では、新規プロジェクトではどちらを選ぶべきですか?")
print(r2.content)
text = r2.content
} catch {
text = "エラー: \(error.localizedDescription)"
print("エラー: \(error)\n\(String(reflecting: error))")
}
}
}
1回目の出力は以下の通りだ。開発者体験・柔軟性・依存関係・学習曲線の4項目にわたる比較が返ってきた。
SwiftUIとUIKitの主な違いは以下の通りです。
1. **開発者体験**
...(以下略)
2回目の出力は以下の通りだ。
新規プロジェクトでは、**SwiftUI**を選ぶことを推奨します。SwiftUIは開発効率を向上させ、直感的なUI設計を可能にします。ただし、複雑な機能やカスタマイズが必要な場合は、UIKitを検討するのも良いでしょう。プロジェクトの規模やニーズに応じて選択してください。
2回目の「では、〜」という質問に対し、1回目の比較内容を踏まえた上でSwiftUIを推奨する回答が返ってきた。セッション内の文脈が引き継がれていることが確認できた。ただし、セッションを新規作成すると文脈はリセットされる。
また、手順5で紹介した instructions によるキャラクター設定と組み合わせると、一貫したペルソナで会話が続くなりきりチャットアプリも実現しやすい。
動作確認
実機でApple Intelligenceが有効になっていることを事前に確認する。
- 設定アプリ →「Apple Intelligence & Siri」→「Apple Intelligence」をオンにする
- 言語・地域が英語(US)など対応言語になっていることを確認する
- モデルのダウンロードが完了するまで待つ
上記の準備ができていれば、ボタンをタップすると数秒後にレスポンスが返ってくる。オンデバイスで処理されるため、外部ネットワークへの通信は発生しない。
注意事項
コンテキストウィンドウ
クラウドLLMと比較してコンテキストウィンドウが小さい。長い会話を続けたり、大量のテキストを一度に渡したりするとエラーが発生する場合がある。@Generable の型定義もコンテキストを消費するため、プロパティは必要最小限に絞るとよい。
日本語プロンプト
日本語プロンプトでも日本語が返ることが多いが、言語指定が保証されるわけではない。日本語で回答させたい場合は「日本語で答えてください」と明示するのが確実だ。
トラブルシューティング
LanguageModelError が発生する
以下のようなエラーが発生する場合がある。
Error Domain=FoundationModels.LanguageModelError
主な原因と対処法は以下の通りだ。
| 原因 | 対処法 |
|---|---|
| Apple Intelligenceが無効 | 設定からApple Intelligenceをオンにする |
| モデルのダウンロード未完了 | ダウンロード完了を待ってから再試行する |
| コンテキストウィンドウ超過 | プロンプトや会話履歴を短くする |
シミュレータで SensitiveContentAnalysisML エラーが発生する
シミュレータで実行した際に、以下のようなエラーが発生することがある。
End sanitizeText with error: Error Domain=com.apple.SensitiveContentAnalysisML Code=15
└─ SafetyGuardrailTextSanitizerBackend: Resource (Local Model Asset) unavailable error.
└─ GenerativeError Code=1020000 "Resource (Local Model Asset) unavailable error."
Error Domain=FoundationModels.LanguageModelError Code=-1
"The operation couldn't be completed. (com.apple.SensitiveContentAnalysisML error 15.)"
isAvailable が true を返していてもこのエラーが発生することがある。isAvailable はメイン言語モデルの準備状態しか確認しないが、エラーログから推測される挙動として、テキスト生成はすべて安全性フィルタリング用のサブモデル(SafetyGuardrailTextSanitizerBackend)を経由するため、そのモデルアセットが見つからない場合にこのエラーが発生していると思われる。
シミュレータのモデルアセットはホストMacのものを使用する仕組みのため、Xcode・iOS Simulator・macOSのバージョンが一致していないと一部のコンポーネントが欠落した状態になる。
実機でテストすることで解消した。
まとめ
Foundation Modelsフレームワークを使って、テキスト生成の基本的な機能を確認した。
respond(to:)でシンプルなテキスト生成streamResponse(to:)でリアルタイム表示LanguageModelSession(instructions:)でシステムプロンプト設定@Generable+@Guideで構造化出力- 同一セッション内でのマルチターン会話
これらを実際に試してみた全体的な感想として、クラウドLLMと比べてモデルを選択できない・コンテキストウィンドウが小さいといった制約はあるものの、外部通信なしでこれだけの機能が使えるのは魅力的だと感じた。
なお、WWDC26ではAFM 3 Core Advancedという20Bパラメータのオンデバイスモデルと、クラウド推論のAFM 3 Cloud Proも発表された。本記事の検証はすべて3Bモデルで行ったが、それでも実用的な受け答えができており十分な手応えを感じた。20BモデルをFoundation Models APIから試せる日が楽しみだ。
次のステップとして、テキストに加えて画像も入力できるマルチモーダル機能を試してみたい方は以下の記事も参照してほしい。









