ブログ • Foundation Models
Apple Intelligence 2026:Foundation Models と App Intents の間で交換する JSON
2026 年、同じ App には入口が二つある。端末上の LanguageModelSession は型どおりに値をデコードし、システムは App Intent の perform() を呼ぶ。残すのはどちらの値でもそのままではなく、自分で定義した交換用の封筒だ。
製品の話は iPhone 18 がハードウェア以上である理由。Siri が Agent になるかは Siri AI は AI Agent になるか。ここでは封筒だけを書く。どのフィールドを残すか、いつ実行しないか。
- content はモデルがデコードしたオブジェクト
- parameters は perform() に渡すオブジェクト
- complete が false のとき、parameters は null
- schemaVersion が変わったら、古い封筒はそのまま再実行できない
先にこれだけ:フレームワークは Generable を schema にして値をデコードする。それはモデルセッションの中の話だ。保存し、突き合わせ、実行するかを決めるために残すのが封筒だ。以下は、二つの契約、フィールド、一つの JSON、止める四つの場合の順だ。
二つの契約は一回の呼び出しではない
端末上のモデルが型どおりに値を埋める仕組みは Generable に書いてある。型は schema になり、モデルが埋め、フレームワークがデコードする。静的な型は @Generable。静的な型がなければ、セッションは GenerationSchema から GeneratedContent を返せる。封筒に書くときはどちらも content だ。
App Intent は別の呼び出しだ。パラメータは @Parameter に書く。説明は App Intent にパラメータを追加する。システムが perform() を呼ぶとき、LanguageModelSession はなく、Generable の content もない。
- 1セッションが内容を出す
respond(to:generating:) は Response を返す。生成が終わる前は PartiallyGenerated で、フィールドは空のままであり得る。まだ実行できるパラメータはない。
- 2パラメータを射影する
Intent が受け取るフィールドだけを parameters に写す。モデルが足した rationale は content に残す。名前は同じでも、同じオブジェクトではない。
- 3実行の前に封筒を書く
source、sessionId、schemaVersion、complete、content、intent、parameters を一つの JSON にする。complete が true でなければ perform() を呼ばない。
封筒のフィールド
次のキーは自分の封筒のもので、Apple のデコード結果のフィールドではない。
| フィールド | 誰が書くか | 役割 |
|---|---|---|
source | あなたの App | foundation-models か app-intent。値がどちらの入口から来たかを示す。 |
sessionId | あなたの App | モデルセッションだけが持つ。例は lm_01。システム呼び出しでは null。 |
schemaVersion | あなたの App | Generable のフィールド表につける世代。この例は 3。フィールドが変わったら一つ増やし、古い封筒をそのまま再実行しない。 |
generable | あなたの App | 型名。例は OrderDraft。システムが直接呼ぶときは null。 |
complete | 生成状態から写す | false はまだ生成中で、フィールドはまだ変わり得る。true になってから射影し、実行を検討する。 |
content | モデルのデコード結果 | モデルがデコードしたオブジェクト。動作よりフィールドが多くてよい。例は rationale。システム呼び出しでは null。 |
intent | あなたの App | これから呼ぶ、またはシステムが呼んでいる App Intent の識別子。例は PlaceOrderIntent。 |
parameters | あなたの射影、またはシステムの引数 | perform() に渡すオブジェクト。未完了のときは null。システム呼び出しでは、システムがすでに渡した引数。 |
content と parameters は同じフィールド名を持てるが、別のオブジェクトだ。content をそのまま perform() に渡すと、モデルが足した rationale が動作に入る。
残せる交換 JSON
次の封筒と Schema は自分の契約で、Apple の通信形式ではない。JSONNote の JSON Schema は型、列挙、必須を見る。complete が true のときだけ実行できる、という規則はこの Schema では表せない。判断は App 側で行う。
{
"source": "foundation-models",
"sessionId": "lm_01",
"schemaVersion": 3,
"generable": "OrderDraft",
"complete": true,
"content": {
"itemId": "sku_42",
"quantity": 1,
"fulfillment": "pickup",
"rationale": "User asked for pickup."
},
"intent": "PlaceOrderIntent",
"parameters": {
"itemId": "sku_42",
"quantity": 1,
"fulfillment": "pickup"
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["source", "sessionId", "schemaVersion", "generable", "complete", "content", "intent", "parameters"],
"properties": {
"source": { "enum": ["foundation-models", "app-intent"] },
"sessionId": { "type": ["string", "null"] },
"schemaVersion": { "type": "integer" },
"generable": { "type": ["string", "null"] },
"complete": { "type": "boolean" },
"content": { "type": ["object", "null"] },
"intent": { "type": "string" },
"parameters": { "type": ["object", "null"] }
}
}
rationale は content だけに残す。動作パラメータの型の対応は App の動作フィールドと JSON Schema。ここではその表を繰り返さない。
未完了なら perform しない
{
"source": "foundation-models",
"sessionId": "lm_01",
"schemaVersion": 3,
"generable": "OrderDraft",
"complete": false,
"content": {
"itemId": "sku_42"
},
"intent": "PlaceOrderIntent",
"parameters": null
}
この封筒は上の Schema を通る。parameters が null を許すからだ。形が通っても実行しない。PartiallyGenerated は、後続のトークンが既にあるフィールドをまだ変え得る、という意味だ。complete が true になってから、揃った content を parameters に射影する。未完了のまま parameters をオブジェクトにしても、Schema は止めない。
| 封筒 | なぜ止めるか |
|---|---|
| complete が false | 後続のトークンが既にあるフィールドをまだ変え得る。 |
| schemaVersion が今のフィールド表ではない | 古い content は今の Intent に合わない。 |
| foundation-models で、parameters に content にないキーがある | 射影は、モデルが書いていない値を作ってはいけない。 |
| app-intent なのに content が null ではない | システムの引数はモデル出力ではない。content に書き戻して生成結果のふりをしない。 |
余分なキーは JSON の比較で見る。この確認は source が foundation-models の封筒だけに使う。システム呼び出しでは content が null なので、parameters の各キーが content に出ていた、とは要求できない。
Tool と App Intent の受け渡し
Foundation Models の Tool は自分のセッションが呼ぶ。説明は ツール呼び出しで生成を広げる。call(arguments:) が受け取る Arguments も Generable だ。まず content に書き、parameters に射影してから perform() を検討する。
システムが Intent を直接呼ぶときは、source を app-intent にし、sessionId、generable、content を null にする。parameters はシステムがすでに渡した値だ。sessionId を作って、モデル生成のふりをしない。
| Foundation Models Tool | App Intent | |
|---|---|---|
| 誰が呼ぶか | あなたの LanguageModelSession | Siri、ショートカット、Spotlight |
| 入力 | Generable の Arguments | @Parameter の値 |
| 実装するメソッド | call(arguments:) | perform() |
| 封筒の source | foundation-models | app-intent |
ほかの記事との分担
| 製品と Siri の二本 | 動作フィールドの記事 | 本記事 | |
|---|---|---|---|
| 答えること | なぜハードウェア以上か。Siri は Agent になるか | どのフィールドがこの動作に入れるか | モデルの値と動作パラメータの間にどの JSON を残すか |
| ここで探さない | もう一本の発表まとめ | AppEnum とエンティティ id の型対応 | content をそのまま perform() の引数にすること |
JSONNote でこの封筒を合わせる
- 1操作を選ぶ
JSON Schema を開く。操作する を Schemaで確認してください にする。1つ目の欄に封筒、2つ目の欄に上の Schema を貼る。source は foundation-models か app-intent。complete はブール。parameters はオブジェクトか null。
- 2壊れたデータを三つぶつける
source を siri にする、complete を文字列 true にする、または parameters を消す。この三つは失敗する。未完了の封筒(complete が false、parameters が null)は通る。通っても perform() は呼ばない。
- 3二つのオブジェクトを比べる
JSON 比較Diff を開く。1つ目の欄に content、2つ目の欄に parameters を貼る。rationale は1つ目の欄だけにあるべきだ。2つ目の欄だけにあるキーは射影が書いたもので、モデルが書いたものではない。この比較は source が foundation-models のときだけ使う。
よくある質問
Generable の出力をそのまま perform() に渡せるか?
渡せない。出力は content に書く。perform() が受け取るのは射影した parameters だけだ。封筒は両方を残し、突き合わせに使う。
Schema を通ったら実行してよいか?
まだだ。この Schema は型、列挙、必須だけを見る。complete が false で parameters が null でも形は合法だ。実行前に complete が true であること、そしてモデル入口では parameters に content にないキーがないことを確認する。
Tool と App Intent は同じ一回の呼び出しか?
同じ呼び出しではない。Tool は LanguageModelSession が呼ぶ。App Intent は Siri、ショートカット、Spotlight が呼ぶ。同じ仕事を両方に出せる。入力は先にこの封筒へ書く。
schemaVersion が変わったら、古いログを再実行できるか?
そのままは再実行できない。版は Generable のフィールド表の世代だ。フィールドが変わると、古い content は今の Intent に合わない。一度止め、明示的な対応を書いてから実行するかを決める。
動作フィールドと JSON Schema の記事と同じか?
同じ記事ではない。あちらはパラメータ型をフィールド表に揃える。こちらはモデルの値とその表の間に残す封筒だ。入口、完了状態、版、そして content と parameters を分ける。
まとめ
Foundation Models と App Intents は二つの入口だ。交換用の封筒は、入口、完了状態、schema の版、content、parameters を一つの JSON に置き、perform() の前に止まれるようにする。
結論:content は封筒に残す。perform() が受け取るのは parameters だけ。complete が false なら実行しない。schemaVersion が合わなければ再実行しない。parameters に、content にないキーを置かない。
次:この交換 JSON を Schema に通し、それから content と parameters を比べる。