App Schemas:アプリをSiriから使えるようにする
WWDC 2026で、あるAppleのエンジニアが、タップにしか反応しなかったSwiftUIのカレンダーアプリを、Siriがイベントを検索し、名前やメモの内容について質問に答え、音声で作成・更新し、カスタムの結果カードを表示できるアプリへと変えてみせました。そのために書いたのは3つのstructと、いくつかのコードスニペットを埋めるだけです。1 この変化を支える仕組みが App Schemas です。これは、アプリのコンテンツとアクションを、Siriがすでに理解している形で記述する方法であり、トレーニングフレーズも、開発者側での自然言語処理も必要ありません。1 このセッションは、CometCalというサンプルプロジェクトを題材にしたコードアロング形式で、宇宙をテーマにした見た目の裏にある教えは構造的なものです。Siriに自分の語彙を教えるのではありません。自分のデータとアクションを、Siriがすでに知っている形に対して宣言すれば、あとは自然に付いてくるのです。
この記事では、その成果を支える3つの柱を順に見ていきます。スキーマ・ドメインモデル、IndexedEntity を通じたSpotlightへのセマンティックな寄贈(ドネーション)、そして画面認識と、音声駆動の更新を安全にする valueState の区別です。以下の内容はすべてセッションから直接得たものです。本記事の主題は、App Intentsのバックグラウンド実行とは異なります。あちらはUIを起動せずに処理を実行する話ですが、ここでの焦点は、Siriがどのようにコンテンツを推論し、それを操作するかにあります。
TL;DR
App Schemasは、アプリのエンティティ、アクションのパラメータ、出力を、Siriがすでに理解している形で記述し、App Schema Domainsに整理します。calendarドメインはイベント、カレンダー、参加者、およびそれらに対するアクションを網羅し、トレーニングフレーズも開発者側でのNLPも不要です。1- スキーマ化されたエンティティはXcodeのコードスニペットから得られます。
calendar_のようなドメインのプレフィックスを入力してスニペット(たとえばcalendar_calendar)を選ぶと、エンティティのマクロ、プロパティ、表示表現、クエリのスタブが足場として生成されます。1 - エンティティを
IndexedEntityに準拠させ、indexAppEntitiesを使ってCSSearchableIndexに寄贈する(そしてdeleteAppEntitiesで削除する)ことで、カスタムのプロパティクエリなしに、Siriが名前、プロパティ、文脈からそれを解決できるようになります。1 - 2つのview modifier、リストに付ける
.appEntityIdentifierと詳細ビューに付ける.userActivity(それぞれEntityIdentifierを持ちます)が、Siriに画面認識を与えます。これにより「このイベントの参加者にメールを送って」といったリクエストが、イベント名を指定しなくても解決されます。1 - 更新インテントは
IntentParameter.valueStateを公開します。値を伴う.set、nilを伴う.set、.unsetが、それぞれ新しい値、明示的なクリア、パラメータの不在を区別するため、Siri駆動の編集があいまいになりません。1
App Schemasとは実際に何なのか(セッション344)
Swift Intelligence FrameworksチームのJustinが、Xcodeを開く前にApp Schemasを解説します。3:12から。
Siriは、App Intentsフレームワークを通じてアプリに到達し、その上での推論はApple Intelligenceが担います。1 CometCalにおける出発点の問題は明快です。「今のところ、Siriは、CometCalの中でカレンダーやイベントが何を意味するのかをまったく分かっていません」1。App Schemas はそのギャップを埋めます。セッションの言葉を借りれば、これらは「アプリのコンテンツとアクションを、Siriがすでに理解できる形で記述します。エンティティの構造、アクションのパラメータ、そして出力を定義します。トレーニングフレーズもなく、私の側での自然言語処理もありません」1。
整理の単位となるのがApp Schema Domainです。calendarドメインは「スケジューリングに関するすべて、つまりイベント、カレンダー、参加者、そしてそれらを操作するアクションを網羅します」1。形が事前に定義されているため、カタログ化はエディタが行ってくれます。エンジニアは CalendarEntity ファイルを作成し、AppIntentsをimportして calendar_ と入力します。すると「Xcodeがオートコンプリートのその場で、Calendarドメインのすべてのスキーマを提示してくれます」1。calendar_calendar を選ぶと、構造が埋め込まれます。エンティティのマクロ、プロパティ、表示表現、クエリのスタブが揃い、セッションが言うところの「スキーマ化されたエンティティ、つまりSiriが推論できる型」が生まれます。1
命名規則については、慎重に注意しておく価値があります。スキーマスニペットの名前は、エディタ上では小文字でアンダースコアを前置した識別子(calendar_calendar、calendar_attendee、calendar_event、calendar_createEvent、calendar_updateEvent、さらに calendar_attendeeStatus や calendar_attendeeType といったenumのスニペット)として現れ、音声の文字起こしでもそのように表記されます。それらが足場として生成するSwiftの型(@AppEntity マクロ、DisplayRepresentation、クエリプロトコルへの準拠)は、通常のSwiftの大文字小文字の慣習に従います。各シンボルの正確なスペルと大文字小文字は、ビルドに使う前に、AppleのApp Intentsドキュメントとダウンロード可能なCometCalサンプルプロジェクトで確認してください。音声によるコードアロングは、大文字小文字の正確なリファレンスにはならないからです。
スキーマモデルの見返りは、ごくわずかなコードで得られる到達範囲です。セッションはコンテンツ層全体を「3つのstructと、いくつかのコードスニペットを埋めること」と表現します。1 CometCalは、豊かさが増していく3つのエンティティを構築します。カレンダー、参加者、そしてその2つをまとめ上げるイベントです。イベントは「先に構築した他のエンティティと組み合わさります」。そのカレンダーは CalendarEntity であり、参加者は AttendeeEntity の配列であって、「SiriはApp Schemasによってこれらの関係を理解します」1。スキーマはまた、何が必須で何がオプションかも決めます。タイトルや開始日時のような必須項目は直接つながり、アプリが使わないオプションのスキーマプロパティ(セッションでは移動時間と仮想の場所を挙げています)は未設定のままにできます。そして、データモデルには存在するがスキーマには存在しないプロパティ、たとえば isFavorite も、エンティティに追加することができます。1
イベントにはさらに2つのスキーマの仕組みが現れます。Union値を使うと、1つのプロパティが複数の型のいずれかを保持できます。場所は「GeoToolbox フレームワークの PlaceDescriptor か、Stringのどちらか」になり得て、アラームは Duration か Date のどちらかになり得ます。1 繰り返しのプロパティはFoundationの Calendar.RecurrenceRule を使い、毎日・毎週・毎月・毎年のケースについて、CometCal独自の頻度enumとの間で相互変換します。1 スキーマ化されたenum(セッションでは EventEntityStatus と呼ぶイベントステータスのenum、および前述の参加者のenumを指しています)は、スニペットから完全な形で届き、アプリは該当するケースを採用します。もしアプリが異なる用語を使っているなら、既存のモデルをスキーマのケースに対応づけて「Siriがその形を認識できるように」します。1
IndexedEntityを通じたセマンティックな寄贈
スキーマはSiriに語彙を与えます。寄贈は、推論の対象となる実際のデータをSiriに与えます。この2つは別々のステップであり、セッションは2つ目を見落としがちだと明言しています。「IndexedEntity はインデックス化されるコンテンツの形を定義しますが、エンティティはやはり寄贈される必要があります」1。
エンティティを IndexedEntity プロトコルに準拠させることが、テキストだけでなく意味によるマッチングを可能にします。1 その理由は検索インデックスにあります。準拠することは「アプリがSpotlightインデックスを使ってエンティティを寄贈し、セマンティックな理解の恩恵を受けられるようにします」。そしてエンティティがいったん寄贈されると、「Siriは、カスタムのプロパティクエリを必要とせず、名前、プロパティ、または文脈からそれを解決できます」1。この最後の一節こそが要点です。「クルーのランチ」や「酸素に言及しているイベント」のための専用マッチャーを書く必要はありません。Siriは寄贈されたタイトルとメモの内容を直接検索し、「アプリのコンテンツを使ってあらゆる質問に答えます。カスタムの自然言語処理は不要……エンティティとスキーマだけです」1。
寄贈は CSSearchableIndex を通じて行われます。CometCalは、アプリ固有の名前のもとで CalendarManager のイニシャライザ内で作成された CSSearchableIndex のインスタンスを保持しています。1 セッションが述べるルールは、「カレンダー、というよりインデックス化されたあらゆるエンティティが変更されたときはいつでも、インデックスを更新する必要がある」というものです。1 そのためデータ層は書き込み時に寄贈します。作成のパスは、戻る前に検索インデックスを渡して indexAppEntities を呼び、更新のパスは変更されたエンティティを再インデックスし、削除のパスは「エンティティのidと型を渡して」 deleteAppEntities を呼びます。1 カレンダーエンティティを組み込んだあと、エンジニアは「Lunar Orbit Log」という名前のカレンダーを作成し、スワイプして検索すると、アイコンとタイトルとともにそれが見つかりました。寄贈がうまくいった証拠です。1
すべてのエンティティをインデックス化すべきというわけではなく、参加者がそのルールを教える反例です。AttendeeEntity は IndexedEntity ではなく TransientAppEntity に準拠します。これは「一意の識別子を必要とせず、クエリの対象になることを意図していない一時的なエンティティ」です。1 その理由はモデリングの規律にあります。CometCalにおいて参加者は「人そのものではなく、特定のイベントへの人の参加」を表します。同じ人が多くのイベントに参加し得るので、「参加のたびに個別にインデックス化すると、Spotlightに重複した結果が生まれてしまいます」1。参加者は常にそのイベントを通じて到達されるため、維持すべき独立した検索パスは存在せず、TransientAppEntity は「それを明示的にします……書くべきクエリも、維持すべきインデックスもありません」1。参加者はまた IntentPerson も導入します。これは「名前と連絡先情報を持つ人を表す、システム標準の方法」であり、参加者のメールアドレスをMailに渡してメッセージを下書きさせるのに役立ちます。1
インデックス化されたエンティティには、それでもクエリの配線が必要です。クエリは @Dependency プロパティラッパーを通じてデータ層を保持します。これは「App Intentsが、共有リソースをインテントやクエリに注入する方法」であり、そのためクエリは新しいインスタンスではなく、登録済みの唯一の CalendarManager を使います。マネージャがmain-actorなので、クエリもmain-actorとしてマークされます。1 必須の EntityQuery メソッドは、システムがすでにIDを知っているケースのためにIDで取得します。そして allEntities メソッドを持つ EnumerableEntityQuery に準拠させると、イベントの作成中にSiriが選択肢として提示する必要があるときに、システムが後から利用可能なカレンダーを一覧できるようになります。1 DisplayRepresentation(タイトルとシステムのカレンダー画像)が、エンティティをどう表示するかをSiriとSpotlightに伝えます。1
ここで名前を付けておく価値のあるナビゲーションの継ぎ目があります。寄贈だけでは、ユーザーはアプリのメイン画面に着地してしまうからです。system.open スキーマに準拠し、EventEntity をターゲットとして受け取り、ナビゲーション層にそこへ遷移するよう指示する OpenEventIntent が、そのギャップを埋めます。システムはこれを「誰かがSpotlightやSiriでイベントの結果をタップしたとき、あるいはSiriにイベントを開くよう頼んだときはいつでも」呼び出すので、タップされた結果はそのイベントの詳細ビューへとまっすぐ開きます。1
画面認識とvalueStateの区別
最初の2つの柱は、Siriが名前によってコンテンツを見つけられるようにします。3つ目の柱は、すでにユーザーの目の前にあるものをSiriに使わせ、そのうえであいまいさなく操作させます。
画面認識のコストは「たった2つのview modifier」です。1 リストビューでは、.appEntityIdentifier がリストに付き、「各イベントエンティティの EntityIdentifier を渡し」ます。これが「リストをそのエンティティに結びつけるので、誰かがリストを閲覧しているとき、システムはどのイベントが画面上にあるかを把握します」1。詳細ビューでは、.userActivity がフォーカス中の単一のイベントの EntityIdentifier を持ち、「その特定の1つのイベントがまさに中心にあること」をシステムに伝え、「Siriがこのイベントを、まさに今表示されているものへと解決できるように」します。1 両方が揃えば、イベントの詳細ビューを見ているユーザーは「このイベントの参加者にメールを送って、誰かにチョコレートとマシュマロを持ってきてもらうよう頼んで」と言えて、Siriは画面上のイベントについての理解を使って参加者を見つけ出し、Mailに渡します。タイトルは不要です。1
コンテンツを操作することは、それを読むのと同じパターンを逆向きに走らせたものです。インテントもスニペットから生まれます。calendar_createEvent スニペットは、マクロ、スキーマ、スキーマが要求するパラメータ、そしてperformのスタブとともに、インテントの足場を生成します。1 performのロジックは、セッションが平明に述べる3段階の形をしています。「インテントのパラメータを、データ層が理解できるものへと解決し、アクションを実行し、その結果をエンティティとして返す」1。作成の場合、それはunion値から場所を取り出し、繰り返しが指定されていれば変換し、マネージャの作成メソッドを呼び、EventEntity を返すことを意味します。1 インテントがスキーマに準拠しているため、「Siriが面倒な作業をすべて引き受けてくれます。言語の解釈、確認の要求、詳細の確定です」。だから開発者がそのやり取りを書くことは決してありません。1
更新は、音声駆動の編集を信頼できるものにする微妙な点を浮かび上がらせます。calendar_updateEvent のほとんどのパラメータはオプションです。ユーザーは普通、1つか2つのことを変えるだけだからで、「イベントのパラメータはSiriが解決するもので、それ以外はすべてオプション」です。1 単純なnilチェックでは、本当の問いに答えられません。セッションの表現を借りれば、「recurrenceがnilのとき、それは『変更するな』を意味するのか、それとも『削除しろ』を意味するのか? 単純なnilチェックでは、自分がどちらのケースを扱っているのか分かりません」1。その答えが IntentParameter.valueState です。これは、インテントのマクロが各プロパティを IntentParameter で包むことによって公開されます。3つの状態は、それぞれ明確な意味を持ちます。「実際の値を伴う .set は、新しい値が提供されたことを意味します。nil値を伴う .set は、それが明示的にクリアされたことを意味します。.unset は、そのパラメータがリクエストに含まれていないことを意味します」1。この区別は「値をクリアすることが意味のあるアクションとなる、あらゆるオプションのパラメータに当てはまります」。だからこそ「このイベントを繰り返さないで」が、recurrenceを手つかずのまま残すのではなく、確実にクリアするのです。1
2つの仕上げが、アクション層を締めくくります。カスタムの結果カードは、Siriのデフォルトの表示表現カードを置き換えます。performメソッドの戻り値の型に ShowsSnippetView を追加し、用意したSwiftUIビュー(セッションのものは EventEntity を受け取ります)を渡すと、Siriの中にアプリ独自のスタイルが描画されます。これは「結果を返す他のどんなインテントにも使える」手法です。1 そして DeleteEventIntent は「3つの中で最もシンプル」で、イベントと、繰り返しイベントのためのオプションのspanだけを受け取ります。Siriは「何かが削除される前の確認ダイアログを自動的に処理し」、複数のイベントが一致するときには区別を求めます。1
重要なポイント
App Intentsを採用するiOS開発者へ:
- まずスキーマに手を伸ばしましょう。Xcodeで
calendar_のようなドメインのプレフィックスを入力し、オートコンプリートに利用可能なスニペットを一覧させます。スニペットがマクロ、プロパティ、表示表現、クエリのスタブを足場として生成するので、構造を考案するのではなく、型と対応づけを埋めるだけで済みます。1 - インデックスに値するかどうかをエンティティごとに判断しましょう。永続的でクエリ可能なコンテンツは
IndexedEntityに準拠させて寄贈します。常に親を通じて到達され、インデックス化するとSpotlightを汚すだけになる参加形式のレコード(CometCalの参加者)にはTransientAppEntityを使います。1 - ビルドの前に、正確なシンボルのスペルと大文字小文字を、AppleのApp IntentsドキュメントとCometCalサンプルで確認しましょう。コードアロングの名前は音声の文字起こしから来ているからです。
音声とApple Intelligenceのフローを設計するチームへ:
- 寄贈を書き込みパスの責務として扱いましょう。作成と更新で
indexAppEntitiesを、削除でdeleteAppEntitiesを、エンティティのidと型をキーにして呼び出すことで、Siriのインデックスがデータからずれることがなくなります。1 - 画面認識は早めに加えましょう。リストには
.appEntityIdentifier、詳細ビューには.userActivity(それぞれEntityIdentifierを持ちます)を付ければ、ユーザーはタイトルの代わりに「このイベント」と言えるようになります。1 - 更新インテントでは
valueStateを明示的に扱いましょう。値を伴う.set、nilを伴う.set、.unsetで分岐させることで、明示的なクリアが「変更しないまま」と読み取られることが決してなくなります。1
FAQ
App IntentsにおけるApp Schemasとは何ですか?
App Schemas は、アプリのコンテンツとアクションを、Siriがすでに理解している形で記述します。アプリのエンティティの構造、アクションのパラメータ、そして出力を定義し、トレーニングフレーズも開発者側での自然言語処理も必要ありません。これらはApp Schema Domainsに整理されます。calendarドメインはイベント、カレンダー、参加者、そしてそれらに対するアクションを網羅します。Xcodeでは、calendar_ のようなドメインのプレフィックスを入力し、calendar_calendar のようなコードスニペットを選ぶことでスキーマを採用します。これがエンティティの足場を生成します。1
Siriはどうやって名前や文脈からアプリのコンテンツを解決するのですか?
エンティティを IndexedEntity プロトコルに準拠させ、作成と更新で indexAppEntities を、削除でエンティティのidと型を渡して deleteAppEntities を呼び出すことで、CSSearchableIndex(Spotlightインデックス)に寄贈します。寄贈はSiriに「セマンティックな理解」を与え、カスタムのプロパティクエリなしに、名前、プロパティ、または文脈からエンティティを解決できるようにします。これには「酸素に言及しているイベントは?」のような質問に対してメモの内容を検索することも含まれます。1
IndexedEntityではなくTransientAppEntityを使うべきなのはどんなときですか?
一意の識別子を必要とせず、クエリの対象になることを意図していない一時的なエンティティには TransientAppEntity を使います。CometCalの参加者がこれに当てはまります。参加者は人そのものではなく、特定のイベントへの人の参加を表すからです。同じ人が多くのイベントに参加し、参加のたびに個別にインデックス化するとSpotlightに重複した結果が生まれてしまいます。参加者はそのイベントを通じてのみ到達されるので、独立した検索パスは存在せず、この一時的なエンティティにはクエリもインデックスも不要です。1
valueStateとは何で、なぜ更新インテントにとって重要なのですか?
更新インテントでは、App Intentsのマクロが各プロパティを IntentParameter で包み、それが valueState を公開します。これは、nilチェックでは区別できない3つのケースを区別します。値を伴う .set は新しい値を意味し、nilを伴う .set はその値が明示的にクリアされたことを意味し、.unset はそのパラメータがリクエストに含まれていなかったことを意味します。この区別により、Siri駆動の編集がプロパティをクリアできて(たとえば「このイベントを繰り返さないで」)、それが「変更しないまま」と取り違えられることがなくなります。1
Siriにアプリの画面認識を持たせるにはどうすればよいですか?
2つのview modifierを加えます。リストビューには .appEntityIdentifier を付け、各イベントエンティティの EntityIdentifier を渡すことで、閲覧中にどのイベントが画面上にあるかをシステムが把握します。詳細ビューには EntityIdentifier を伴う .userActivity を付けることで、特定の1つのイベントがフォーカスされていることをシステムが把握します。両者が揃えば、ユーザーは「このイベントの参加者にメールを送って」と言うことができ、Siriは「このイベント」を、まさに今表示されているものへと解決します。1
この記事は、Appleのintelligence frameworksに関するクラスターの一部です。App Schemasが土台とするフレームワークについては、まずApp Intents:アプリへのAppleの新しいAPIから始めてください。UIを起動せずにインテントの処理を実行することについては、ここで扱うコンテンツの推論とは別の関心事ですが、App Intentsのバックグラウンド実行をお読みください。セマンティックな解決の背後にある、より広範なSpotlight寄贈の物語については、オンデバイスAIとSpotlightメディアのインデックス化をご覧ください。シリーズ全体のハブはApple Ecosystemシリーズです。
参考文献
-
Apple, WWDC 2026 session 344, Code-along: Make your app available to Siri. 出典:App SchemasとApp Schema Domains(calendarドメイン。トレーニングフレーズなし、NLPなし)、Xcodeスニペットによるスキーマ化されたエンティティ(
calendar_calendar、calendar_attendee、calendar_event、calendar_createEvent、calendar_updateEvent、calendar_attendeeStatus、calendar_attendeeType)、IndexedEntityとindexAppEntities/deleteAppEntitiesを介したCSSearchableIndexへのSpotlight寄贈、名前・プロパティ・文脈による解決、TransientAppEntityと参加者のモデリングの根拠、IntentPerson、union値(GeoToolboxのPlaceDescriptor、String、DurationまたはDateのアラーム)とCalendar.RecurrenceRule、@Dependencyラッパー、EntityQuery、EnumerableEntityQuery、DisplayRepresentation、system.openのOpenEventIntent、.appEntityIdentifierとEntityIdentifierを持つ.userActivityによる画面認識、IntentParameter.valueState(.set/.unset)、ShowsSnippetViewのカスタム結果カード、DeleteEventIntentの確認と区別、そして自動テストのために言及されたAppIntentsTestingフレームワーク。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩