iOS 26のFoundation Modelsで効いていた制約は、モデルの質ではなく算数でした。4096トークン — 指示も、ツール定義も、会話のトランスクリプト全体も、ぜんぶ込みで。iOS 27では同じセッションに32Kと明らかに強い推論のモードが加わり、切り替えは一行です。その代償はお金でもAPIキーでもなく、そこが見慣れない部分です。
フレームワークの基礎ガイドは8月にiOS 26を対象に書きました — SystemLanguageModel、@Generable、tool calling。この記事で扱うのは差分だけ、2026年9月までに入ったものと、それがどのプロダクト判断を変えるかです。
バージョン。以下は断りのないかぎりiOS 27、iPadOS 27、macOS 27、visionOS 27です。ひとつ明記しておくと、watchOSでFoundation Modelsが使えるようになったのは27から。26の時点では存在しませんでした。
このフレームワークはもうAppleの一モデルの話ではない#
1年前のドキュメントは、これをApple Intelligenceのモデルへのアクセスと説明していました。いまの概要の一行目は違います — 任意の大規模言語モデルへのアクセス。オンデバイスでも、サーバーでも、自分で持ち込んだものでも。LanguageModelプロトコルがあり、三者とも同じAPIの一等市民です。
実務上の意味はこうです。LanguageModelSession、Instructions、Tool、@Generable、ストリーミング — これらは一度書けば済み、差し替わるのは挿し込むモデルのほうです。以下、その挿し込まれる三つを見ていきます。
Private Cloud Compute — 同じセッション、違う天井#
切り替えはこう書きます。
// サーバーモデルでセッションを作る。
let session = LanguageModelSession(model: PrivateCloudComputeLanguageModel())残りはそのまま移ります。respond、ストリーミング、ツール、指示。SystemLanguageModelもPrivateCloudComputeLanguageModelもLanguageModelプロトコルに準拠しているので、セッションのイニシャライザは同一です。
得られるもの — 32Kのコンテキストとより強い推論。長い文書や多ターンの会話向けです。失われるもの — オフライン動作。PCCはネットワークを必要とし、接続起因でリクエストが失敗したときAppleが勧めるのはオンデバイスモデルでの再試行です。つまりフォールバックは結局こちらが書きます。
利用可否は別途、別の理由で確認します。
let model = PrivateCloudComputeLanguageModel()
switch model.availability {
case .available:
// AI機能のUIを表示する。
case .unavailable(.deviceNotEligible):
// 代替のUIを表示する。
case .unavailable(.systemNotReady):
// PCCがまだリクエストを受けられない。
case .unavailable(let other):
// 不明な理由で利用できない。
}加えてiOS 27 / macOS 27 / watchOS 27 / visionOS 27の#availableチェックと、それ以前のバージョンではオンデバイスモデルへのフォールバックを。
そしてここが見慣れない支払いです。PCCで開発するにはAppleの適格要件を満たし、managed entitlement com.apple.developer.private-cloud-compute を申請する必要があります。Capabilitiesのチェックボックスではなく、申請です。PCC前提の機能を計画するなら、アクセス取得の期間をスケジュールに織り込んでください。でないとデモの日に、美しいが起動しないコードが手元に残ります。
クォータは開発者ではなく利用者のもの#
サーバーLLMの見慣れた形 — トークン代を払うのは開発者で、利用者はトークンの存在すら知りません。PCCはこれを反転させます。認証もキーもいっさいない代わりに、一人ひとりに1日あたりのリクエスト上限があり、その拡大はiCloud+のアップグレードで行われます。
アーキテクチャ上は方向転換です。利用者の代わりに枠を買うことはできませんし、画面を開いた時点で残りがどれだけあるかも予測できません。そこでフレームワークはクォータの状態を公開しており、その置き場所は閉じられるアラートではなくUIです。
let model = PrivateCloudComputeLanguageModel()
if model.quotaUsage.isLimitReached {
Text("1日の上限に達しました")
.foregroundStyle(Color.red)
} else if case .belowLimit(let info) = model.quotaUsage.status {
if info.isApproachingLimit {
Text("上限が近づいています")
.foregroundStyle(Color.orange)
}
}
if let suggestion = model.quotaUsage.limitIncreaseSuggestion {
Button("選択肢を見る") {
suggestion.show()
}
}limitIncreaseSuggestion.show()はシステムのアップグレードUIを出します。自前の価格画面は作りませんし、Appleの書きぶりからしても作るべきではありません。
クォータ切れは専用のエラーで届き、レートリミットとは別物です。レートリミットなら待てば戻りますが、クォータ切れはリセットを待つかプランを上げるかです。リセット日時もフレームワークから取れますが、空の場合があります。
実際の上限を焼かずに試せます。Product > Scheme > Edit Scheme > Run > Options > Simulated Apple Foundation Models Availability に「Approaching Quota Usage Limit」と「Quota Usage Limit Reached」があります。
推論は自分で回すつまみになった#
let response = try await session.respond(
to: "このアーキテクチャのトレードオフは何ですか?",
contextOptions: ContextOptions(reasoningLevel: .deep)
)レベルは三段階で、両端が.lightと.deepです。推論を深くするほどレイテンシは伸び、さらにモデル自身の推論テキストがコンテキストウィンドウを食います。実務では後者のほうが痛い。品質のつもりで.deepにしたら、会話の3ターン目でexceededContextWindowSizeが飛んでくる、という形で。
推論セグメント自体は応答の本文には入りません。トランスクリプトからは見えるので、モデルが妙なことを言った理由を調べるときに役立ちます。
Appleの助言は「最小のレベルから始め、評価に応じて上げる」。退屈な助言ですが、いまは裏付けがあります。プロンプト評価のツールと、レイテンシ・プロンプト・モデル出力・ツール呼び出し・トークン消費を見せるFoundation Models instrumentが加わりました。
プロンプトに画像を入れる#
マルチモーダル入力は、いつものrespondに届きました。
func compareImages(imageOne: CGImage, imageTwo: CGImage) async throws -> String {
let session = LanguageModelSession()
let response = try await session.respond {
"この2枚の画像を3つの箇条書きで比較してください:"
Attachment(imageOne)
// 回転が適用されていない画像 — 例えばAVFoundationのフレーム — では
// orientationを渡せば、フレームワークが変換してくれる。
Attachment(imageTwo, orientation: .right)
}
return response.content
}スケーリングと色変換はフレームワーク側の担当で、前処理は不要です。CGImage、データ、ファイルURL(型はUTTypeから推定)を受け取ります。
@Generableとの組み合わせは自由記述よりうまく動きます。1回の呼び出しで分類するなら、
@Generable
enum ImageLabel {
case cat
case dog
case frog
case bird
}
func classifyImage(_ image: CGImage) async throws -> ImageLabel {
let session = LanguageModelSession()
let response = try await session.respond(
generating: ImageLabel.self,
options: GenerationOptions(samplingMode: .greedy)
) {
"次の画像を最もよく表すラベルを選んでください:"
Attachment(image)
}
return response.content
}ここでの.greedyは効いています。これがないとモデルは「惜しいラベル」を選ぶことがあり、分類器としては静かなバグになります。
画像向けの既製ツールもあります。バーコード読み取り(BarcodeReaderTool)とテキスト抽出です。ツールが複数あるときは画像にラベルを付け — Attachment(image).label("barcode-image") — どのツールを何に当てるかモデルに分かるようにします。
効きが大きいプロンプトの細部をひとつ。「この写真に写っている食品をすべて挙げて」は「この画像には何が写っていますか?」よりはっきり良く動きます。
ダイナミックプロファイル — リクエストのたびに組み直される指示#
以前のセッションは、生成時に指示を固定していました。レシピを探し、材料を置き換え、在庫を確認し、調理を案内する — そんな複数ステップの流れでは、全ケースを詰め込んだ巨大な指示を書くか、セッションを作り直して履歴を捨てるかの二択でした。
いまはDynamicInstructionsのbodyが、モデルへのリクエストごとに再評価されます。
struct PresentationInstructions: DynamicInstructions {
var isEditingImage = true
var isEditingAnimation = false
var body: some DynamicInstructions {
// どの状態でも共通の部分。
Instructions {
"プレゼンテーションの改善を手伝ってください。"
}
ListPhotosTool()
AddPhotoTool()
// いまのアプリの状態が必要とするもの。
if isEditingImage {
ImageEditingInstructions()
}
if isEditingAnimation {
AnimationEditingInstructions()
}
}
}
let session = LanguageModelSession(
dynamicInstructions: PresentationInstructions()
)ひとつ上の層には、指示とセッション設定を結びつけるProfileと、プロファイルを切り替えるDynamicProfileがあります。切り替えはコンパイラが検証します。同時にアクティブなプロファイルはちょうどひとつと決まっているので、分岐は並列のifではなくif / else if / elseで書きます。
Profile {
// 創作寄りのタスク向けの指示とツール。
}
.model(pccModel)
.temperature(likesPoetry ? 0.8 : 0.1)
.reasoningLevel(likesAstronomy ? .deep : .light)モディファイアは三段階の優先度で解決されます。respond(to:options:)へ直接渡したオプションがすべてに勝ち、サブプロファイルのモディファイアがダイナミックプロファイルのものに勝ち、ダイナミックプロファイル自体は既定値を定めます。
これを外すとプロファイルが逆効果になる一点#
bodyの中の宣言順が性能に効きます。しかもかなり効きます。
セッションはトークン列として並びます。まず指示、次にツール定義、そしてトランスクリプト。プロバイダのKVキャッシュは最初に変わったトークンまでしか有効でなく、それ以降は再計算されます。したがって静的な指示とツールはbodyの上、条件付きブロックは下です。条件を先頭に置けば、そのフラグが切り替わるたびに会話全体のキャッシュが無効になります。
ここから、善意ほど破りやすい帰結が出てきます。
- ツールの集合はセッション生成時に固定する。会話の途中で足すとキャッシュが壊れるうえ、動きも悪い。モデルはすでに前のターンのパターンを覚えています。
- ツールを外すときは、そのツールの呼び出しもトランスクリプトから消す。でないとモデルは、定義にもう存在しないものへの参照を見ることになります。
- トランスクリプトの刈り込みは、まれに・まとめて。毎ターン刈るのは毎ターン無効化するのと同じで、コンテキスト上限の手前で一度まとめるほうが安くつきます。
- プロファイルの切り替えは接頭部を丸ごと書き換え、キャッシュを空にします。毎ターンではなく、流れの自然な切れ目で行ってください。
リクエストまで1〜2秒の余裕があると分かっているなら、prewarm(promptPrefix:)が到着前に接頭部をキャッシュへ計算しておきます。
以上はInstrumentsで測れます。キャッシュされた入力トークンを入力トークン総数で割ってください。ターン間でこの比が低ければ、キャッシュが捨てられ、モデルが毎回接頭部を噛み直しているということです。
で、どうするか#
いま私が保つ順序はこうです。まずオンデバイスモデルで始め、機能を数字で評価する。4096トークンの壁か推論の質で詰まったらPCCを足す — entitlementは早めに申請し、クォータの状態は設計に入れておく。プロファイルは、流れに本当に複数のモードがあるときに。APIが新しいからではなく。
三つめの選択肢はAppleのモデルですらありません。LanguageModelプロトコルは開かれていて、Core AIでエクスポートしたモデルは同じLanguageModelSessionに入ります。これはApple Intelligenceへの依存を切り、それが無いデバイスでも動きます。



