ブログ • App Intents
iOS 27 の Agent は第三者 App をどう呼ぶか:API の前に App Intent の JSON を検証する
システムが第三者 App を呼ぶとき、入口は宣言した AppIntent だ。パラメータが perform() に届いてから API の前に、JSON として残す。型、列挙、必須が合わなければ失敗の記録を返し、呼び出しは App の中で止める。
一つの指示が API に届く道は App Actions の JSON の経路。Siri が Agent になるかは Siri AI は AI Agent になるか。ここでは App 側だけを書く。どのパラメータを宣言するか、Schema が何を止めるか、失敗のときどの JSON を返すか。
- Intent のパラメータは App 自身の契約
- Schema は型、列挙、必須を見る
- 失敗は needs_value か rejected と記録する
- この二つの記録は API に進まない
先にこれだけ:呼び出しが App に届いても、パラメータが使えるとは限らない。以下は、契約の宣言、一つの Schema、三つの失敗、形が通っても止める場合、失敗の返し方の順だ。
先にパラメータ契約を宣言する
第三者 App の入口は AppIntent だ。タイトルを付け、perform() を実装する。システムがこの動作を見つけられるのは、宣言したからだ。宣言は、今回のパラメータで API を呼んでよい証明にはならない。
各パラメータは @Parameter で示す。説明は App Intent にパラメータを追加する。この例が受け取るのは三つだけだ。商品 id、数量、受け取り方。名称、説明文、モデルが足したキーは契約に入らない。
- 1Intent 名を固定する
候補 JSON の intent は PlaceOrderIntent だけだ。ほかの名前は、別の動作に届いたことを意味する。
- 2三つのパラメータを並べる
itemId は文字列、quantity は整数、fulfillment は pickup か delivery だけだ。一つでも欠ければ、候補は未完成だ。
- 3perform() の最初に検証する
検証に失敗したら、下の失敗記録を返す。通り、id が解決でき、数量が 1 以上のときだけ API に進む。
同じフィールドを Schema にする
下の候補と Schema は自分の契約で、Apple の通信形式ではない。JSONNote の JSON Schema は型、列挙、必須を見る。数量が 1 以上であること、id が存在することは、この Schema では表せない。判断は App 側で行う。
{
"intent": "PlaceOrderIntent",
"parameters": {
"itemId": "sku_42",
"quantity": 1,
"fulfillment": "pickup"
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["intent", "parameters"],
"properties": {
"intent": { "enum": ["PlaceOrderIntent"] },
"parameters": {
"type": "object",
"required": ["itemId", "quantity", "fulfillment"],
"properties": {
"itemId": { "type": "string" },
"quantity": { "type": "integer" },
"fulfillment": { "enum": ["pickup", "delivery"] }
}
}
}
}
パラメータ型の対応は App の動作フィールドと JSON Schema。ここではその表を繰り返さない。検証が失敗したとき App が何を返すかだけを書く。
失敗する候補三つ
| 候補で変えたこと | なぜ失敗するか |
|---|---|
| fulfillment を ship にする | pickup でも delivery でもない。 |
| quantity を文字列 1 にする | 契約が求めるのは整数だ。 |
| itemId を消す | 必須が欠け、候補は未完成だ。 |
quantity を 0 にしても型は整数のままで、この Schema は通る。通ったことは注文ではない。0 個は App が拒否する。
形が通っても止める
{
"intent": "PlaceOrderIntent",
"status": "rejected",
"reason": "invalid_parameter",
"field": "quantity",
"dialog": "Quantity must be at least 1."
}
itemId が空でない文字列でも Schema は通る。次に自分のデータでこの id を解決する。解決できなければ、field を itemId にした rejected をもう一つ書く。商品名から id を作らず、API も呼ばない。
失敗をどう返すか
必須が欠けているときは、そのパラメータの requestValue でもう一度尋ね、空の値で API を呼ばない。値が不正なときは perform() を止め、IntentDialog でユーザーが読める一文を書く。上の JSON は、この決定のために残す記録だ。
| 場合 | 記録の status | API |
|---|---|---|
| fulfillment が無い | needs_value | もう一度尋ね、API は呼ばない |
| fulfillment が ship | rejected | pickup か delivery だけだと伝える |
| quantity が 0 | rejected | 形は合法でも止める |
| itemId が解決できない | rejected | 商品名で id を代用しない |
needs_value は、まだ尋ねる必要があるという意味だ。rejected は、この候補を実行できないという意味だ。どちらも API には進まない。ユーザーが見るのは dialog の一文だ。ログには JSON 全体を残し、後からどのフィールドが止めたかを合わせられるようにする。
ほかの記事との分担
| 指示の経路の記事 | 交換用の封筒の記事 | 本記事 | |
|---|---|---|---|
| 答えること | フレーズがどう API に届くか | モデルの値とパラメータの間にどの JSON を残すか | App がどう宣言し、検証し、失敗を返すか |
| ここで探さない | 音声から API までの手順をもう一度 | content と parameters という二つのオブジェクト | 商品名でエンティティ id を代用すること |
JSONNote でこの呼び出しを止める
- 1操作を選ぶ
JSON Schema を開く。操作する を Schemaで確認してください にする。1つ目の欄に候補、2つ目の欄に上の Schema を貼る。intent は PlaceOrderIntent だけ。fulfillment は pickup か delivery だけ。quantity は整数。
- 2壊れたデータを三つぶつける
fulfillment を ship にする、quantity を文字列 1 にする、または itemId を消す。この三つは失敗する。quantity を 0 にすると通る。通ったあとでも rejected を書き、API は呼ばない。
- 31 と 0 を比べる
JSON 比較Diff。1つ目の欄に quantity が 1 の parameters、2つ目の欄は quantity を 0 にした同じオブジェクトを貼る。差はこの数だ。0 でも Schema は通る。API を止めるのは失敗記録で、この形の確認ではない。
よくある質問
システムがパラメータを App に渡したあとでも、まだ検証するか?
検証する。システムが渡したのは一回の呼び出しで、検証済みの API リクエストではない。perform() の最初にこの JSON を見る。通らなければ続けない。
Schema を通ったら API を呼んでよいか?
まだだ。この Schema は型、列挙、必須だけを見る。quantity が 0 でも通る。itemId が文字列でも通る。数量が 1 以上で、id が解決できたときだけ API を呼ぶ。
パラメータが欠けているとき、エラーを返すか、もう一度尋ねるか?
もう一度尋ねる。status は needs_value にし、requestValue でその値をユーザーに求める。合法な値が来るまで API は呼ばない。値が来ても不正なら rejected にする。
一つの指示の JSON 経路の記事と同じか?
同じ記事ではない。あちらは一つの指示をフレーズから API まで追う。こちらは App の中で止まる。パラメータ契約、Schema、失敗のときに返す JSON。API が走るのは、rejected も needs_value も書いていないときだけだ。
失敗記録は Apple の戻り値の形式か?
Apple の形式ではない。Apple は requestValue と IntentDialog で結果をシステムとユーザーに渡す。この JSON は自分のログだ。status、reason、field、dialog。残しておけば、なぜ API を呼ばなかったかを JSONNote で後から合わせられる。
まとめ
第三者 App が呼ばれるとき、先にあるのはパラメータ契約で、次にあるのは失敗できる JSON だ。失敗記録はこの呼び出しを API の前で止め、ユーザーが読める dialog を一文残す。
結論:先に Intent のパラメータを宣言する。Schema で型、列挙、必須を止める。値が無ければ needs_value を返す。値が不正なら rejected を返す。どちらも API は呼ばない。数量が 1 未満、または id が無いときは、形が通っても止める。
次:候補のパラメータを Schema に通し、この呼び出しを続けるか失敗を返すかを決める。