WWDC Quick Look 💓 By SwiftGGTeam
App Schemas でインテリジェントな Siri 体験を構築する

App Schemas でインテリジェントな Siri 体験を構築する

元の動画を見る

ハイライト

iOS 27 の Siri は App Schemas によって、App の内容を理解し、App をまたいだ操作を実行し、画面上の文脈を認識できるようになりました。開発者は既存データを AppEntity としてモデル化し、システム定義の Schema に接続し、ビューに注釈を付けるだけで、Siri が自然言語で App 内の内容を検索、操作、受け渡しできるようになります。

主要内容

これまで Siri に App を操作させるには、開発者が自然言語解析を自分で処理し、多数のカスタム Intent を書き、ユーザーにも決まった言い方を覚えてもらう必要がありました。体験はぎこちなく、対応できる場面も限られていました。

今年の Apple Intelligence によって、Siri は App を本当に「理解する」アシスタントになりました。Siri は App をブラックボックスとして扱うのではなく、App 内の Entity が何で、どんな操作ができ、画面に何が表示されているかを直接理解します。開発者が言語理解を自分で実装する必要はありません。App のデータ構造と機能を明確に記述すれば、Siri がユーザーの言葉を具体的な操作に変換します。

この変化の中心にあるのが App Schemas です。App Schemas は、メッセージ、連絡先、ドキュメントなど、システムがあらかじめ定義した概念の集合です。開発者の AppEntity が特定の Schema に準拠すると、Siri はその概念の意味、属性、よく使われる操作をすでに理解しています。ゼロから学習する必要はありません。

たとえば、ユーザーが「Glow に映画のおすすめを聞くメッセージを送って」と言ったとします。Siri は「メッセージを送る」が messages domain の sendMessage schema に対応すること、「Glow」が連絡先エンティティであること、「映画のおすすめを聞く」がメッセージ本文であることを理解します。エンティティ解決、パラメータ入力、Intent 実行を自動で行い、App は実際にメッセージを送るだけです。

さらに強力なのが App をまたぐ操作です。ユーザーが「この返信メールを妻に送って」と言うと、Siri はまず画面上の「この返信」がどの message entity を指すのか理解し、それをシステムが理解できる形式で export して Mail App に渡し、送信させます。全体の流れには画面認識、コンテンツの export、App 間の受け渡しが含まれますが、開発者が必要なのはビューへの注釈と Transferable プロトコルの実装です。

Siri の 3 つの新機能

App の内容を理解する01:57):Siri は App のエンティティに直接アクセスできます。ユーザーが「次の会議はいつ、どこ?」と聞くと、Siri は「会議」というエンティティ型を識別し、最も関連するものを見つけて、時間と場所の属性を返せます。

App のアクションを実行する02:19):ユーザーが「最新のレポートを Mary に送って」と言うと、Siri は「送る」アクション、「最新のレポート」エンティティ、「Mary」受信者を解析し、App の sendMessage intent を呼び出して操作を完了します。

画面の文脈を認識する02:44):ユーザーが画面を指して「この文章を説明して」または「この商品のレビューを調べて」と言うと、Siri はビュー注釈から画面に表示されている内容を理解し、「この文章」や「この商品」を具体的なエンティティに解決します。

セマンティック検索と文字列クエリ

多くの場面では、IndexedEntity が第一候補になります。これはエンティティの属性をシステムのセマンティックインデックスに登録し、Siri が文字列の完全一致ではなく意味でマッチングできるようにします。ユーザーが「Flare が前に映画について言っていたこと」と言った場合、メッセージ内に「映画」という単語がなくても、映画に言及したメッセージを見つけられます。

ただし IndexedEntity はすべての場面に向くわけではありません。データ量が多い、サーバー側にある、頻繁に変化するデータには、EntityStringQuery の方が適しています。Siri はユーザー入力の文字列を App に渡し、App が検索とマッチングを自分で決めます。意味理解は失われますが、完全な制御を得られます。

画面認識の 2 つの API

1 つ目は、画面上に主要コンテンツが 1 つだけある場合です。文書を読む、メールを書くといった場面では、NSUserActivity で現在の活動をマークします。Siri は「これ」が何を指すのか理解できます。

2 つ目は、チャット一覧や商品一覧のように、画面に複数の項目が同時に表示される場合です。.appEntityIdentifier 修飾子を使い、各リスト項目に対応するエンティティ識別子を結び付けます。Siri は「このメッセージ」と「あのメッセージ」を区別し、「最後のメッセージを転送して」といった指示に対応できます。

コンテンツ受け渡しの 2 つのモード

他の App から自分の App にコンテンツが入ってくるとき、処理方法は 2 つあります。コンテンツが App 内の既存データに対応する場合は、IntentValueQuery で既存エンティティに解決します。新しいデータを作るべき場合は、Transferableimporting クロージャで受け取った値を新しいエンティティに変換します。多くの App は具体的な場面に応じて両方を使います。

詳細

AppEntity でデータをモデル化する

AppEntity は新しいデータモデルを置き換えるものではなく、既存データを記述するものです。これは「それが何か」「どう識別するか」「どの属性が重要か」という 3 つの問いに答えます。

UnicornChat を例にすると、App には ContactConversationMessage の 3 種類のエンティティがあります。それぞれが対応する App Schema に準拠します。

@AppEntity(schema: .messages.message)
struct MessageEntity: IndexedEntity {

    // Spotlight に、どの属性をセマンティック検索に使うか伝える
    @Property(indexingKey: \.textContent)
    var body: AttributedString?
}

要点:

  • @AppEntity(schema: .messages.message)MessageEntity を messages domain の message schema に結び付け、Siri はすぐにこれが「メッセージ」概念だと理解する
  • IndexedEntity プロトコルにより、システムは body 属性のセマンティックインデックスを作成する
  • indexingKey はセマンティック検索に参加する属性を指定し、Siri は文字通りの一致ではなく意味に基づいて内容を見つけられる

文字列クエリのフォールバック

データをインデックスできない場合は、EntityStringQuery を実装します。

struct ContactQuery: EntityStringQuery {
    func entities(matching string: String) async throws -> [ContactEntity] {
        let predicate = #Predicate<Person> { person in
            person.name.localizedStandardContains(string)
        }
        let descriptor = FetchDescriptor<Person>(predicate: predicate)
        let matches = try modelContext.fetch(descriptor)
        return matches.map(\.entity)
    }
}

要点:

  • EntityStringQuery プロトコルは entities(matching:) メソッドの実装を要求する
  • Siri はユーザーの自然言語入力をそのまま渡し、App が一致するエンティティを探す
  • 例では SwiftData の FetchDescriptor#Predicate を使ってローカル検索している
  • [ContactEntity] を返すことで、Siri は後続の操作を続けられる

画面コンテンツに注釈を付ける

リストの各項目がどのエンティティに対応するかを Siri に理解させます。

List {
    ForEach(messages) { message in
        MessageRow(message: message)
            .appEntityIdentifier(
                EntityIdentifier(
                    for: MessageEntity.self,
                    identifier: message.id
                )
            )
    }
}

要点:

  • .appEntityIdentifier は UI 要素と AppEntity を関連付ける SwiftUI ビュー修飾子
  • EntityIdentifier にはエンティティ型と一意識別子を指定する必要がある
  • ユーザーが「このメッセージを編集して」と言うと、Siri はビュー注釈から具体的な MessageEntity を解決する
  • 現在画面に描画されているリスト項目だけが注釈される。LazyVStack の画面外項目は Siri から見えない

他の App が使えるようにエンティティを export する

他の App があなたのコンテンツを操作できるようにします。

extension ContactEntity: Transferable {

    static var transferRepresentation: some TransferRepresentation {
        IntentValueRepresentation(
            exporting: \.person
        )
    }
}

要点:

  • Transferable プロトコルにより、エンティティは App 間を移動できる
  • IntentValueRepresentation は export 形式を定義する。ここでは ContactEntity をシステム標準の IntentPerson として export している
  • export 後、他の App の Intent は「この連絡先に電話して」のように、この連絡先を受け取れる

コンテンツを import して新しいエンティティを作る

他の App から受け取ったコンテンツで新しいデータを作成します。

extension ContactEntity: Transferable {

    static var transferRepresentation: some TransferRepresentation {
        IntentValueRepresentation(
            exporting: \.person,
            importing: { intentPerson in
                let contact = Contact(importing: intentPerson)
                ContactManager.shared.contacts.append(contact)
                return contact.entity
            }
        )
    }
}

要点:

  • importing クロージャは外部コンテンツを受け取ったときに実行される
  • システム標準の IntentPerson を App 内部の Contact モデルへ変換する
  • ContactManager に追加したあと、対応する ContactEntity を返す
  • スレッド安全性に注意する。ContactManagerMainActor に結び付いている場合、importing クロージャでは Actor 分離を正しく扱う必要がある

受け取った Intent 値を解決する

他の App から渡されたコンテンツが既存データに一致すべき場合です。

struct ContactEntityQuery: IntentValueQuery {

    func values(for input: [IntentPerson]) async throws -> [ContactEntity] {
        let names = input.map(\.displayName)
        let descriptor = FetchDescriptor<Contact>()
        let contacts = try model.mainContext.fetch(descriptor)
        let matches = contacts.filter { contact in
            names.contains(where: { name in
                contact.name.localizedStandardContains(name)
            })
        }
        return matches.map(\.entity)
    }
}

要点:

  • IntentValueQuery プロトコルは、受け取ったシステム値を App エンティティに変換するために使う
  • 例では [IntentPerson] を受け取り、displayName でローカルの Contact と照合している
  • localizedStandardContains を使ってローカライズされたあいまい一致を行う
  • 解決済みの [ContactEntity] を返し、Intent が利用できるようにする

Xcode Fix-It が Schema を補完する

Xcode は Schema の完全性をチェックします。sendMessage を実装しているのに関連する draftMessage を実装していない場合、コンパイル時にエラーを出し、補完コードを生成します。

// Xcode Fix-It が生成した draftMessage のひな形
@main
struct DraftMessageIntent: AppIntent {
    static var title: LocalizedStringResource = "メッセージの下書き"

    @Parameter
    var recipient: ContactEntity?

    @Parameter
    var content: String?

    @MainActor
    func perform() async throws -> some IntentResult {
        // メッセージ作成画面を開く
        return .result()
    }
}

要点:

  • 一部の Siri シナリオでは、複数の関連 Schema が連携して初めて完全な体験になる
  • Xcode は欠けている Schema を実行時に黙って失敗させるのではなく、コンパイル時に示す
  • Fix-It は完全な Intent 定義、パラメータ宣言、実装のひな形を生成する
  • 開発者は App 固有の業務ロジックを埋めるだけでよい

核心的な示唆

Siri が「理解できる」ノート App を作る

作るもの:ユーザーが「先週の予算に関するノートを探して」のように自然言語でノートを検索し、Siri がセマンティックインデックスに基づいて結果を返す。

作る価値:IndexedEntity によりローカルのノート内容がシステムのセマンティックインデックスに入り、ユーザーはノートのタイトルやタグを覚える必要がない。

始め方:Note モデルを AppEntity(schema: .notes.note) に準拠するエンティティでラップし、contenttitle 属性に indexingKey を付ける。クラウド上のノート向けには EntityStringQuery をフォールバックとして実装する。

App 間受け渡しに対応した To-do App を作る

作るもの:ユーザーが Mail App でタスクを見て「これを To-do リストに追加して」と言うと、Siri がメール内容を To-do 項目として解析する。

作る価値:Transferable + importing により、他の App の内容が自然に自分の App に流れ込み、ユーザーは手動でコピー&ペーストする必要がない。

始め方:TodoEntityTransferable に準拠させ、IntentValueRepresentationimporting クロージャを実装し、受け取ったテキストや URL を Todo オブジェクトに変換する。

画面認識に対応した画像編集 App を作る

作るもの:ユーザーが画像を選択して「この写真に映画っぽいフィルターをかけて」と言うと、Siri が「この写真」が画面上で選択中の画像だと理解する。

作る価値:.appEntityIdentifier により Siri は画面上の内容を正確に識別でき、「これ」「あれ」といった自然な指示に対応できる。

始め方:画像グリッドの各 Image ビューに .appEntityIdentifier を追加し、PhotoEntity に紐付ける。applyFilterAppIntent を実装する。

Schema が完全なメールクライアントを作る

作るもの:messages domain に完全対応し、送信、下書き、検索など一連の Siri 操作をサポートする。

作る価値:Xcode が domain 全体の Schema 補完を案内してくれる。実装後、Siri によるメール操作体験はシステム標準の Mail App と同じように感じられる。

始め方:sendMessage schema から始める。Xcode Fix-It が不足している関連 Schema を示すので、順に実装すればよい。

関連セッション

コメント

GitHub Issues · utterances