チュートリアル
Apple AI Agent は App・API・ツールをどうつなぐ?JSON の役割は?
システム Agent は App Intents、App 内モデルは Foundation Models の Tool。JSON は装飾ではなく、引数契約・構造化出力・HTTP ペイロードで共有する形です。
「Apple AI Agent」はしばしば製品名のように扱われます。実際の現場では、少なくとも二つのまったく異なる呼び出しチェーンに当たります。一つはシステムレベルの Apple Intelligence がユーザーの代わりに App とアクションを選ぶ経路。もう一つは App 自身が on-device モデルを走らせ、モデルがどのツールを呼ぶか決める経路です。どちらも App・API・ツールに触れますが、セッションの所有者、引数を生成する主体、JSON が現れるレイヤーは同じではありません。この三層を分けておかないと、後の Schema・ログ・トラブルシュートに手がかりがありません。
まず誰がモデルを動かしているか
まず問いかけ:今回の推論は誰が開始したか。ユーザーが Siri やシステムレベルの Apple Intelligence に話しかけた場合、モデルはシステム側(デバイス上または Private Cloud Compute)で動き、あなたの App はルーティング先の能力提供者にすぎません。このとき公開するのは App Intents:型付きのアクション、エンティティ、引数です。システム Agent がいつ呼ぶか決め、あなたのコードが実行します。公式の入口はApp Intents ドキュメント。
ユーザーがすでに App 内にいる場合、Foundation Models でLanguageModelSessionを起動したなら、それは別のチェーンです。モデルはあなたのプロセスが駆動し、ツールはあなたが注入します。セッションはもともと App 内にあるため、システムが「どの App を開くか」を選ぶことはありません。ツールは連絡先を参照したり、カレンダーを読んだり、独自のネットワーク API を叩き、結果を transcript に書き戻せます。フレームワークの説明はFoundation Models、ツール呼び出しのセッション単位の解説は WWDC25 のMeet the Foundation Models framework。
三つ目のパターンがますます一般的です。外部ホスト(Claude、ChatGPT、自前 Agent)が MCP または通常の HTTPS でサービスを呼びます。モデルは Apple スタック上では動きませんが、ペイロードはほぼ必ず JSON です。同じドメイン関数を App Intents、Foundation Models Tool、HTTP の三つに同時に提供でき、違いはアダプタ層だけです。
| 呼び出しチェーン | 誰がモデルを動かすか | App が提供するもの |
|---|---|---|
| システム Agent | Apple Intelligence | App Intents / App Entity |
| App 内 Agent | あなたの LanguageModelSession | Tool プロトコルと @Generable 引数 |
| 外部 Agent | サードパーティホスト | JSON-RPC または REST JSON |
三つのチェーンを一枚の「万能 Agent 図」にまとめないでください。トラブルシュートではまずどのチェーンか特定します。システムルーティング失敗なら Intent 宣言と引数型を確認。App 内ツールの乱呼び出しなら name、description、Arguments の生成可能な形を確認。外部 API の不一致なら HTTP JSON と Schema を確認。混ぜると「モデルが適当に動いている」ように感じるだけです。
App Intents:システム Agent が App に届く仕組み
システム Agent にとって App は「開いて眺める」ものではなく、発見可能な能力カタログです。AppIntent でアクション名、自然言語の説明、引数型、結果を宣言します。Spotlight、Shortcuts、Siri、Apple Intelligence が同じカタログを共有します。ユーザーが「この請求書を支払済みにして」と言ったとき、システムは発話をあなたのMarkInvoicePaidにマッピングできなければなりません。モデルに UI をタップさせるのではありません。
接点は引数です。システム側はユーザーの発話を型付き値にまとめます:enum、日付、AppEntity 参照。Swift では散文ではなく構造体として見えます。プロセスやフレームワークを跨ぐときも、この構造にはシリアライズ可能な形が必要です。デバッグ、ログ、サーバー再生は最終的に JSON または同等のプロパティリストになります。Intent 引数を「JSON オブジェクトとして書ける」フィールド集合として設計すれば、後から API に接続するアダプタがかなり楽になります。
よくある判断:Intent が直接ネットワークを叩くべきか。短いアクションなら perform 内で完結できます。認証、ページング、冪等性が絡むなら、perform では「引数検証 + ドメインサービス呼び出し」だけにし、サービスが JSON リクエストを送ります。システム Agent は REST パスを知る必要はありません。Intent の成功・失敗の意味だけで十分です。パスを知る必要があるのはあなた側です。請求、監査、リトライは API レイヤーにあります。
Foundation Models:App 内モデルがツールを呼ぶ仕組み
App 内 Agent の接続は「モデルに関数仕様書を渡す」イメージに近く、Tool:name、description、さらに@GenerableのArguments。フレームワークがこれを prompt に書き込み、モデルがいつ呼ぶか決めます。呼び出し時はまず引数を生成し、フレームワークがcall(arguments:)を実行し、戻り値(通常は String または生成可能型)を transcript に戻します。モデルはそれに基づいて最終回答を書きます。モデルに URL を組み立てさせるのではなく、許可したツール集合から選ばせます。
引数生成は構造化出力の経路であり、「JSON で答えてください」ではありません。@Generableと動的 Schema がフィールド、enum、ネストオブジェクトをデコード段階で固定します。Swift では型付きインスタンスを得ます。永続化、ログ、URLSession へ渡すなら、その時点で JSON にエンコードします。Apple は language understanding、structured output、tool calling としてまとめています。Foundation Models 概要を参照してください。
struct FindOrders: Tool {
let name = "findOrders"
let description = "Find recent orders by customer email and status."
@Generable
struct Arguments {
var email: String
var status: String
var limit: Int
}
func call(arguments: Arguments) async throws -> String {
// Call your domain API, then return a compact summary.
return "3 orders, latest is paid"
}
}
この段落では、モデルはフィールド名を「勝手に発明」しません。必ずemail、status、limitを埋めます。description が選ばれるかどうかを決めます。広すぎると何事にも呼び出し、狭すぎると必要なときに呼ばれません。ツール出力も控えめに:注文 JSON 全体を transcript に流すとコンテキストウィンドウをすぐ消費します。要約を返し、詳細が必要なら二つ目のツールで ID を取る方がよいです。
ツールはチェーンできます。モデルは最初のツールの出力を二つ目の入力に使い、フレームワークが順に実行します。ドメイン層で冪等性を保証してください:同じorderIdを二度支払済みにしても、二重課金してはいけません。Agent ループはデータベース制約を見えません。JSON 引数が正しくても、業務が正しいとは限りません。
JSON が担う役割
Swift 型はコンパイル時契約、JSON は実行時契約です。Agent が現在のプロセスを出る瞬間——バックエンド呼び出し、ファイル書き込み、別モデルへの提示、テストフィクスチャ再生——形は言語非依存のテキストになる必要があります。JSON はこのチェーン上で少なくとも三つの役割を担います。二つを混ぜると「parse はできるがフィールドが全部違う」状態になります。
1. ツール引数の交換形式
Foundation Models は内部で GeneratedContent として構造化値を表します。デバッグで最も有用なのは「この Arguments を JSON にエンコードするとどう見えるか」です。フィールド名、オプション性、配列かオブジェクトかは安定している必要があります。引数ログを JSON にすれば、本番 API アクセスログと突き合わせられます:同じcustomerIdがサーバーに届いたかどうか。
{
"email": "ada@example.com",
"status": "paid",
"limit": 5
}
2. 構造化出力の Schema
ツールを使わず請求書要約だけ欲しい場合でも Schema は必要です:必須キー、enum 値、金額が number か string か。Apple 側は@Generable;クラウドモデルは JSON Schema が一般的です。同じドメインオブジェクトを二系統で宣言するときは、同じフィールド表を共有し、App 内がtotalCents、API がamount。Schema 記述の共通ルールはUnderstanding JSON Schema。
3. App から API へのペイロード
ツールのcallがネットワークに触れると、JSON が HTTP 本文になります。Agent は API 設計の代わりにはなりません。認証ヘッダ、冪等キー、エラーオブジェクトは依然としてあなたが定義します。モデルが埋めるのは「業務パラメータ」だけです。トランスポート、ページング、レート制限は普通のバックエンド問題です。エラーも安定した JSON(code、message、retryable)に設計すれば、モデルはツールを替えるか、ユーザーに失敗を説明するか決められます。
三層は同じ Schema 文書を共有可能です:Intent 引数 ⊂ ツール Arguments ⊂ HTTP body。部分集合関係はテストを簡単にします:同じ有効な JSON フィクスチャで、まず API、次に Tool、最後に Intent アダプタをテスト。上位集合(HTTP がツールより内部フィールド三つ多い)も可能ですが、モデルに内部フィールドを見せないでください。公開したくないキーを「親切に」埋め始めます。
HTTP API と MCP への接続
App 内ツールが REST を叩くときは、明示的にエンコードしてください。GeneratedContent をそのまま Data として送らない。まず Codable モデルにマップし、JSONEncoder。enum の raw 値、日付形式、キー戦略(snake_case)はあなたが制御します。モデルが生成するのはドメイン値、エンコーダがプロトコル詳細を担当します。
{
"tool": "markInvoicePaid",
"arguments": {
"invoiceId": "inv_9f2",
"paidAt": "2026-08-20T09:00:00Z"
}
}
MCP は「ツール名 + 引数オブジェクト」を JSON-RPC にします。Apple 開発者にとってこれは三つ目のアダプタにすぎません:同じmarkInvoicePaid(invoiceId:paidAt:)、App Intent は perform、Foundation Models は Tool.call、MCP は tools/call。MCP のために別の業務ロジックを書かないでください。外部 Agent は型を間違えやすい(数値が文字列になる)ので、サーバー側は依然として検証が必要です。「すでに JSON だから」信頼してはいけません。
プライバシー境界もツール説明に書いてください。on-device モデルがカレンダーを読めるからといって、カレンダーイベントを自分のサーバーに POST すべきではありません。モデル向け出力はローカル要約でよい。ユーザーが明示的に同期を望むときだけ、アップロード用 JSON を組み立ててください。システム Agent と App 内 Agent のデータ出境ポリシーは別々にレビューし、ログでも出所を区別できるようにしてください。
リリース時に JSON をどう見るか
Agent を連調するとき、もう一句プロンプトを足すより、三つのスナップショットを並べて見る方が効果的です:モデルが生成した引数 JSON、送信した HTTP JSON、サーバーが返した JSON。形が一致しないなら、問題はほぼマッピング層にあり、「モデルが賢くない」わけではありません。ローカルで整形・検証・差分比較する方が、Xcode コンソールで Optional を凝視するより速いです。
サンプル引数をまずブラウザで一通り:JSON フォーマット で parse できることを確認;JSON 検証 で末尾カンマと型エラーを捕捉し、Intent / Tool / API の共通フィールドを JSON Schema で固定し、次に JSON Diff で「モデル出力」と「実際のリクエスト本体」を比較。クラウドモデルの Structured Output は API が異なりますが、「契約を先に、解析は後」は同じです。サイト内のGemini API で JSON を出力する方法。
三つのフィクスチャファイルを固定することをおすすめします:tool-args.valid.json、http-body.valid.json、http-error.json。CI では同じ Schema で最初の二つを検証。エラーフィクスチャで、Agent がユーザーに実行可能な失敗理由を伝えるか、retryable: true。
よくある質問 FAQ
App Intents と Foundation Models の Tool で同じ引数セットを共有可能か?
ドメインモデルは共有可能ですが、システムが Tool プロトコルを直接実行できると仮定しないでください。Intent はシステム発見と権限向け、Tool は現在セッションの prompt 向けです。間にマッピング層を置き、JSON フィクスチャでテストするのはドメインモデルであり、特定フレームワーク型ではありません。
モデルに完全な HTTP リクエストを直接出力させないのはなぜ?
URL、Header、署名はモデルが捏造すべきではありません。モデルは業務フィールドだけを埋め、クライアントが固定テンプレートで送信します。そうでなければ一度の幻覚で誤った環境に叩いたり、誤った認証を付けたりします。
JSON と @Generable は衝突する?
しません。@Generable は Swift 側の生成とデコード、JSON は言語横断・ネットワーク横断の形です。まず安定したフィールド表を作り、そこから Swift マクロと JSON Schema をそれぞれ生成します。
ツール戻り値は必ず JSON か?
必ずしもそうではありません。モデル向けは短いテキスト要約、サーバー向けは JSON であるべきです。二つの出力を一つの文字列に混ぜて無理やり parse しないでください。
まとめと次の一歩
Apple スタック上の Agent は一つのソケットではなく、三つの組み合わせ可能な線です:システム Agent が App Intents で App を発見・呼び出し、App 内モデルが Tool でコードを呼び、外部ホストが JSON で同じドメインサービスを呼びます。JSON は引数、Schema、API ペイロードを同じ形で話させます。型安全がコンパイル時、Schema が実行時、業務検証が正否を担当します。
次のステップ:三到五つのドメインアクションを列挙し、各アクションに最小 JSON オブジェクトと Schema を書き、Intent・Tool・HTTP のどれに載せるか決めます。形が安定してから、プロンプトとマルチツール編成を足してください。