ブログ • AI / App Intents
iOS 27 で Siri の App Actions が増えたあと、JSON はどう流れる?音声指示から API 実行まで
Siri の指示が実行できるのは、先に App Intent のパラメータになり、AppIntent の perform() で端末内に留めるか自分の API を呼ぶかを決めるからだ。システムは音声をそのままサーバーへ送らない。
Siri が Agent かどうかは 前回の記事 に書いた。この記事は一つの指示だけを追う。フレーズが Intent に当たるまで、パラメータが JSON になるまで、検証のあとで API を呼ぶまで。
- フレーズは App Shortcut に当たる。音声を自由文として実行しない
- パラメータは三種類。組み込み型、コンパイル時に固定の AppEnum、クエリが要る AppEntity
- エンティティが渡すのは安定した id。クエリはその id でデータを取る
- パラメータ JSON は自分で残す。Apple の通信形式ではない
- resolved が false、または Schema が失敗したら API を呼ばない。対話は Siri に戻す
先に押さえる点:音声はリクエスト本体ではない。埋まった Intent のパラメータが本体だ。以下はフレーズ、パラメータの型、エンティティ id、自分の JSON、API の順。
一つの指示が通る五段階
公式の入口は App Intents。説明は App Intents 入門。Siri、ショートカット、アプリ内ボタンは同じ Intent を使う。違うのは誰がパラメータを埋めるかだ。
- 1フレーズが当たる
口頭の句は
AppShortcutsProviderに登録する。システムはその句で Intent を選ぶ。原文をモデルに自由に扱わせない。 - 2パラメータを埋める
Intent の
@Parameterは、必要な値と必須かどうかをシステムに伝える。欠けたらユーザーに聞き返す。推測して送らない。 - 3エンティティを解決する
ユーザーデータは
EntityQueryがidで解決する。パラメータに入るのはエンティティであり、それを説明する文章ではない。 - 4実行する
解決済みの値を読むのは perform() だ。端末で終われる処理はここで終える。
- 5Siri に返す
結果は
IntentResultか対話で返す。失敗にも口に出せる理由が要る。サーバーログだけに残さない。
パラメータは三種類
型の説明は App Intent にパラメータを追加する。型を誤ると、その後の JSON が安定しない。
| 型 | 向いているもの | JSON に入れるとき |
|---|---|---|
| 文字列、数値、日付 | 数量やメモのような単純な入力 | parameters にそのまま入れる |
AppEnum | コンパイル時に固定の選択肢。店頭受取か配送かなど | 列挙値を入れる。表示文を主キーにしない |
AppEntity | 変わるユーザーデータ。ある商品など | 解決後の id だけを入れる |
固定集合は AppEnum。ユーザーが増減させる一覧は AppEntity とクエリ。変わる一覧を列挙にすると、選択肢が古くなった瞬間にフレーズが当たらなくなる。
エンティティが残すのは id
システムが要るのは表示でき、問い合わせできるエンティティだ。データの源は自分のデータベースのまま。エンティティには少なくとも安定した id と人が読める名前が要る。
{
"entity": "OrderItem",
"id": "sku_42",
"display": "Oat milk"
}
entities(for identifiers:) を実装し、id で対象を取る。見つからなければ id を作って続けない。
自分のパラメータ JSON
下の文書は、perform() の中で保存し、API を呼ぶか決める契約だ。Apple のリクエスト形式ではなく、システムもこのファイルを読まない。
{
"intent": "StartOrder",
"source": "siri",
"parameters": {
"item_id": "sku_42",
"quantity": 1
},
"resolved": true
}
intentは Intent の型名。ショートカットのアクションと揃えやすい。item_idはエンティティの id。口頭の言い方ではない。言い方は変わる。id は変えない。resolvedが false なら、まだ解決していないか、ユーザーが確認を取り消した。API は呼ばない。- 生の音声、位置の軌跡、トークンはこの JSON に入れない。
いつ API を呼ぶか
- 1端末で終える
端末の状態だけを変える処理は perform() で終える。通信は要らない。
- 2先に検証する
サーバーが要るときは、先に Schema で JSON を検証する。item_id が無い、quantity が 1 未満なら端末で止める。
- 3それから通信する
検証が通り resolved が true のときだけ parameters を自分の API に送る。サーバーは id で自分の記録を引く。表示名は信用しない。
- 4失敗は言葉で返す
API が拒否するか時間切れなら、Intent の対話で未完了だと伝える。Siri に成功したと言わせない。
サードパーティの App が Intent をどう宣言し、失敗をどう返すかは次の記事。この記事はデータ経路で止める。
以前の記事との分担
| Siri は Agent か | この記事 | |
|---|---|---|
| 答える問い | システムが自分で計画してツールを呼ぶか | 一つの指示が API までにどの JSON を通るか |
| ここで探さないもの | 製品発表の記事をもう一篇 | 音声原文を保存できるリクエスト本体とみなすこと |
ハードウェアとシステム機能の製品の話は iPhone 18 がハードウェア以上である理由。あちらはパラメータのフィールドを分けない。
JSONNote でこの JSON を確認する
- 1整形する
JSON 整形 でパラメータ記録を開き、末尾カンマが無いことを確認する。
- 2Schema で欠けたフィールドを止める
下の Schema を JSON Schema に貼る。intent や item_id が無い、quantity が正の整数でないなら API に送らない。
- 3サンプルを一つ検証する
JSON 検証 に実サンプルを貼る。見るのは
item_idが空文字でないか。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["intent", "source", "parameters", "resolved"],
"properties": {
"intent": { "type": "string", "minLength": 1 },
"source": { "enum": ["siri", "shortcut", "app"] },
"resolved": { "type": "boolean" },
"parameters": {
"type": "object",
"required": ["item_id", "quantity"],
"properties": {
"item_id": { "type": "string", "minLength": 1 },
"quantity": { "type": "integer", "minimum": 1 }
}
}
}
}
よくある質問
システムは音声全体を自分のサーバーに送りますか?
この経路では送らない。サーバーが受け取るのは、perform() の中で組み立てて検証したパラメータ JSON である。音声は端末側の認識に留まる。
パラメータに商品名を直接書かないのはなぜですか?
名前は変わり、重複する。AppEntity は安定した id を使い、クエリはその id で対象を取る。API も id だけを見る。
Schema を通れば API を呼べますか?
resolved も見る。形は正しくてもエンティティが未解決なら、ユーザーはまだ対象を選んでいない。ここで API を呼ぶと、確認の質問が誤った注文になる。
端末内の処理と通信する処理はどう分けますか?
端末内だけで終わる変更は perform() で終える。バックエンドが要るものは、パラメータ JSON を書いて検証してからリクエストする。どちらも IntentResult で結果を返す。
ショートカットで手入力したパラメータも同じ経路ですか?
同じ AppIntent だ。違うのは誰がパラメータを埋めるかだけだ。Siri はフレーズ、ショートカットはユーザーか前のアクション。エンティティ解決、JSON、API の門は同じ。
まとめ
一つの指示のデータ経路は、フレーズが Intent を選び、パラメータが型ごとに埋まり、エンティティが id になり、自分で JSON を書き、検証を通ってから API を呼び、対話で結果を Siri に返す。
結論:生の音声はアップロードしない。id が解決する前にリクエストしない。失敗はサーバーログだけでなく、ユーザーに言葉で返す。
次にすること:このパラメータ JSON を Schema で止めてから perform() に接続する。