ハイライト
App Intents には、カスタム対話応答、インタラクションの寄付、エンティティ所有権の宣言、画面認識、システムエンティティ注釈などの API が追加され、アプリは Siri と Apple Intelligence の中でより自然でパーソナライズされた体験を提供できます。
主要内容
以前は、アプリの操作を Siri から実行できるようにしても、開発者が制御できる範囲は限られていました。Siri は応答を自動生成するため、アプリ独自の語り口や視覚スタイルには合わせられませんでした。ユーザーがアプリ内で操作しても、Apple Intelligence はその行動パターンを学習できませんでした。アプリの内容も Siri から十分に見えるわけではなく、ユーザーには画面上の内容が見えていても、Siri には「見えていない」ことがありました。
Apple は WWDC26 で、こうした制限を解決する App Intents の高度な機能を発表しました。
開発者は Siri の対話応答をカスタマイズし、アプリの言葉遣いに合った返答を返せます。アプリはインタラクション寄付 API を通じて、ユーザーが UI 上で行った操作を Apple Intelligence に伝え、システムがユーザーの好みを理解できるようにします。エンティティ所有権の宣言により、Siri はどの内容が公開または共有されているかを把握し、確認が必要かどうかを判断できます。画面認識 API は、画面上に表示されている内容を Siri が理解できるエンティティに結び付けます。システムレベルのエンティティ注釈により、通知、Now Playing、アラームなどの場面でも Siri がアプリの内容を理解できます。
これらの機能により、アプリと Siri の連携はより深くなり、ユーザー体験はより一貫したものになります。
詳細
カスタム対話応答 (02:00)
ProvidesDialog プロトコルを使うと、intent はカスタム対話を返せます。Siri は supporting テキストを表示し、音声デバイスでは full テキストを読み上げられます。
@AppIntent(schema: .audio.addToPlaylist)
struct AddToPlaylistIntent {
func perform() async throws -> some IntentResult & ProvidesDialog {
// 曲をプレイリストに追加して応答する
return .result(
dialog: IntentDialog(
full: """
\(song.title) を \
\(playlist.title) ミックステープに追加しました。
""",
supporting: "追加しました"
)
)
}
}
要点:
ProvidesDialogプロトコルは、intent がカスタム対話を提供することを宣言しますIntentDialogのfullフィールドには、音声デバイス向けの完全な説明を入れますsupportingフィールドは UI に表示できる短い版です
intent の実行中には、$label.requestValue() を使ってユーザーに質問できます。
@AppIntent(schema: .clock.createTimer)
struct CreateTimerIntent {
var duration: Duration
var label: String?
var isSleepTimer: Bool
func perform() async throws -> some ReturnsValue<TimerEntity> {
// 実行中のタイマーを確認し、label パラメータを要求する
label = try await $label.requestValue(
"""
すでにタイマーが実行中です。\
このタイマーには何という名前を付けますか?
"""
)
return .result(value: timerEntity)
}
}
要点:
$label.requestValue()は実行中にユーザー入力を要求します- 名前の重複を避けるなど、追加確認が必要な場面に適しています
intent の応答は、ShowsSnippetView プロトコルを使ってカスタム SwiftUI ビューも返せます。
@AppIntent(schema: .audio.addToPlaylist)
struct AddToPlaylistIntent {
var audioEntity: AudioEntity
var playlist: PlaylistEntity
func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView {
// プレイリストに追加し、対話とスニペットを表示する
let view = PlaylistSnippetView(
playlist: updatedEntity,
tracks: updated.tracks
)
return .result(dialog: dialog, view: view)
}
}
エンティティの表示表現 (04:26)
エンティティは DisplayRepresentation を通じて、システム内での視覚的な表示を定義できます。
@AppEntity(schema: .audio.song)
struct SongEntity {
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(artistName)",
image: artworkImage
)
}
}
要点:
DisplayRepresentationは Siri、Spotlight、Shortcuts でのエンティティ表示を定義しますtitle、subtitle、imageの 3 つの要素を含みます- エンティティ選択、確認ダイアログなどの場面で使われます
インタラクション寄付 (07:00)
ユーザーがアプリ UI で行った操作を IntentDonationManager で Apple Intelligence に伝えると、システムはユーザーの好みを学習できます。
@ModelActor
actor ModelManager {
func sendMessage(_ /* ... */, donateIntent: Bool = false) async throws -> [Message.ID] {
// Siri がユーザーの好みを学習できるよう、パラメータと結果を含む intent を寄付する
if donateIntent {
let intent = SendMessageIntent()
intent.destination = .recipients(conversation.recipients.map(\.entity))
let result = messages.map(\.entity)
Task {
try await IntentDonationManager.shared.donate(
intent: intent,
result: .result(value: result)
)
}
}
}
}
要点:
- 寄付が必要なのは UI インタラクション時だけで、Siri が呼び出した intent は自動的に記録されます
- intent のパラメータと実行結果を渡す必要があります
- システムはこの情報から、特定の連絡先にはどのアプリをよく使うか、といった好みを推定します
過剰な寄付はシステムに無視されるため、本当にユーザーの行動を表すときだけ寄付します。
エンティティ所有権の宣言 (10:03)
OwnershipProvidingEntity プロトコルは、エンティティが公開されたものか共有されたものかを Siri に伝え、確認方針に影響します。
@AppEntity(schema: .calendar.event)
struct EventEntity: OwnershipProvidingEntity {
var ownership: EntityOwnership {
// isShared を使って所有状態を計算する: .shared、.public、または .unknown
attendees.isEmpty ? .unknown : .shared
}
}
要点:
- 公開または共有エンティティへの操作は、確認を求められやすくなります
- デフォルトは
.unknownで、システムは非公開と仮定します - エンティティの状態に応じて動的に返します
セマンティックインデックスと構造化検索 (11:30)
IndexedEntity と CSSearchableIndex.indexAppEntities() を使い、エンティティを Spotlight のセマンティックインデックスに追加します。
struct EntityIndexingHelper {
// プレイリストエンティティをインデックスする
func indexPlaylist(_ playlist: Playlist) async throws {
let entity = PlaylistEntity(playlist: playlist)
try await CSSearchableIndex(name: indexName)
.indexAppEntities([entity])
}
}
要点:
- インデックス後は、Siri から名前でエンティティを検索できます
- Spotlight でもこれらのエンティティを検索できます
- 一部のドメインはキーワード一致だけでなく、セマンティック検索にも対応します
事前にインデックスしない内容には、IntentValueQuery を使って構造化検索を提供します。
struct AudioIntentValueQuery: IntentValueQuery {
func values(for input: AudioSearch) async throws -> [AudioEntity] {
switch input.criteria {
case .searchQuery(let query):
return try await searchResults(for: query)
case .unspecified:
return try await likedSongResults()
// ... .url の case もある
}
}
}
要点:
IntentValueQueryは構造化された検索入力を受け取りますUnionValueを通じて複数のエンティティ型を返せます.searchQuery、.unspecified、.urlなどの条件をサポートします
アプリ内検索 (14:49)
.system.searchInApp schema を使うと、Siri は結果を直接表示するのではなく、アプリ内で検索を実行できます。
@AppIntent(schema: .system.searchInApp)
struct SearchAudioLibraryIntent {
var criteria: StringSearchCriteria
func perform() async throws -> some IntentResult {
// Siri の検索文字列を使ってアプリ内検索を実行する
navigation.searchText = criteria.term
navigation.selectedTab = .library
return .result()
}
}
要点:
- 開発者が丁寧に設計したアプリ内検索体験に適しています
- エンティティをインデックスしていなくても機能します
- 検索文字列をアプリのナビゲーションシステムへ渡します
画面認識 (16:27)
画面認識により、Siri は画面上に表示されているエンティティを理解できます。主な API は 3 種類あります。
単一の主要エンティティには NSUserActivity を使います。
struct NowPlayingView: View {
@Environment(PlaybackController.self) private var playback
var body: some View {
VStack {
// プレーヤー UI
}
.userActivity("cosmotunes.nowPlaying", isActive: playback.currentTrack) { activity in
activity.title = playback.currentTrack?.title
activity.appEntityIdentifier = EntityIdentifier(
for: SongEntity.self,
identifier: playback.currentTrack.id
)
}
}
}
要点:
- 画面全体が 1 つのエンティティを表示する場面で使います
appEntityIdentifierが activity を具体的なエンティティに結び付けます
リスト内の複数エンティティには View Entity annotation を使います。
struct AlbumView: View {
private var header: some View {
VStack(alignment: .leading, spacing: 6) {
// ...
}
.appEntityIdentifier(
EntityIdentifier(for: AlbumEntity.self, identifier: session.id.uuidString)
)
}
}
要点:
- エンティティを表す各 view に注釈を追加します
- 画面上に少数のエンティティがある場面に適しています
大量のエンティティには Collection annotation を使います。
struct PlaylistDetailView: View {
var body: some View {
List {
ForEach(playlist.tracks) { track in
PlaylistTrackRow(track: track)
}
}
.appEntityIdentifier(forSelectionType: GeneratedTrack.ID.self) { trackID in
EntityIdentifier(for: SongEntity.self, identifier: trackID)
}
}
}
要点:
- 各リスト項目へ個別に注釈を付ける必要を避けられます
- システムは必要に応じてエンティティ識別子を取得します
- 画面外へスクロールしたエンティティにも対応します
カスタムキャンバスビューでは custom canvas view annotation を使います。詳しくはサンプルコードを参照してください。
システムエンティティ注釈 (21:07)
通知、Now Playing、アラームなどのシステム場面でエンティティに注釈を付けます。
// ユーザー通知
func scheduleNotification(message: Message, author: Contact, conversation: Conversation) {
let content = UNMutableNotificationContent()
content.title = author.name
content.body = message.body
content.appEntityIdentifiers = [
EntityIdentifier(for: MessageEntity.self, identifier: message.id)
]
}
// Now Playing
final class CosmoTunesMediaSession: MediaSessionRepresentable {
var content: (any MediaContentRepresentable)? {
var content = MusicContent(id: track.id.uuidString, songTitle: track.title /* ... */)
content.appEntityIdentifiers = [
EntityIdentifier(for: SongEntity.self, identifier: track.id),
EntityIdentifier(for: ArtistEntity.self, identifier: track.session.artistName),
EntityIdentifier(for: PlaylistEntity.self, identifier: currentPlaylist.id),
]
return content
}
}
// AlarmKit
func scheduleAlarm(_ alarm: Alarm) async throws {
let configuration = AlarmManager.AlarmConfiguration<CosmoTunesAlarmMetadata>.alarm(
schedule: schedule,
attributes: attributes,
appEntityIdentifier: EntityIdentifier(for: AlarmEntity.self, identifier: alarm.id),
stopIntent: DismissAlarmIntent(),
secondaryIntent: SnoozeAlarmIntent(),
sound: sound
)
}
要点:
- 3 つの場面はいずれも同じ注釈パターンを使います
- エンティティ識別子は具体的なものから一般的なものへ並べます
- 永続的な識別子が必要なため、
TransientAppEntityは使えません
重要な示唆
1. パーソナライズされた対話応答
何をするか: アプリの重要な Intent にカスタム対話を追加し、Siri の返答がアプリ自身の言葉に聞こえるようにします。
なぜ価値があるか: Siri の自動生成応答は均一で、アプリ固有の語り口には合いません。カスタム対話により、ユーザーは Siri がそのアプリを本当に「理解している」と感じられます。曲の追加やタイマー作成など、高頻度の操作から始めると費用対効果が高くなります。
どう始めるか: Intent の perform() が some IntentResult & ProvidesDialog を返すようにし、IntentDialog(full:supporting:) で完全版と短縮版の返答を定義します。入口: ProvidesDialog プロトコル + IntentDialog
2. UI 寄付
何をするか: メッセージ送信、ナビゲーション開始、音楽再生など、アプリの中心的な操作フローに Intent 寄付を追加します。
なぜ価値があるか: Apple Intelligence がパーソナライズされた提案を行うには、ユーザーがアプリ内で実際に何をしているかを理解する必要があります。寄付は「このユーザーはこの相手にメッセージを送るとき、このアプリをよく使う」とシステムに伝え、Siri が次回優先して提案できるようにします。
どう始めるか: 操作完了後に IntentDonationManager.shared.donate(intent:result:) を呼び、完全なパラメータを含む intent と実行結果を渡します。本当に UI インタラクションがあった場合だけ寄付し、Siri 呼び出しは自動記録に任せます。入口: IntentDonationManager.shared.donate(intent:result:)
3. エンティティ所有権の認識
何をするか: アプリに公開コンテンツや共有コンテンツがある場合は、OwnershipProvidingEntity を実装します。
なぜ価値があるか: 公開または共有エンティティへの操作は Siri の確認を求められやすくなり、ユーザーが共有データを誤って変更することを防げます。所有権の状態を明確にすると、Siri はいつ追加確認すべきかを判断でき、アプリへの信頼も高まります。
どう始めるか: Entity を OwnershipProvidingEntity に準拠させ、ownership プロパティを実装して、エンティティの状態に応じて .shared、.public、.unknown を動的に返します。入口: OwnershipProvidingEntity プロトコル + var ownership: EntityOwnership
4. セマンティックインデックス
何をするか: アプリ内の比較的安定したコンテンツエンティティを Spotlight インデックスに追加します。
なぜ価値があるか: ユーザーはコンテンツがどのアプリにあるかを忘れても、名前は覚えていることがあります。Spotlight のセマンティックインデックスに追加すると、システム検索からアプリ内の具体的な内容へ直接移動でき、発見の負担が下がります。
どう始めるか: Entity を IndexedEntity に準拠させ、CSSearchableIndex.indexAppEntities() を呼んでインデックスへ追加します。事前にインデックスしない内容には、IntentValueQuery で構造化検索を提供します。入口: IndexedEntity + CSSearchableIndex.indexAppEntities()
5. 画面認識
何をするか: アプリの詳細画面や一覧画面にエンティティ注釈を追加し、Siri が画面上の内容を理解できるようにします。
なぜ価値があるか: ユーザーは画面を見たあと、「3 つ目を再生して」「あれを共有して」と言えるようになり、完全な名前を言う必要がありません。「ユーザーには見えているが Siri には見えていない」という断絶をなくし、音声操作をより自然にします。
どう始めるか: 単一エンティティには .userActivity の appEntityIdentifier を使い、少数エンティティには .appEntityIdentifier で View に注釈を付け、大量リストには .appEntityIdentifier(forSelectionType:) で Collection 注釈を使います。入口: appEntityIdentifier + EntityIdentifier(for:identifier:)
関連セッション
- App Intents の新機能 - App Intents の基礎と新機能の概要
- Code-along: アプリを Siri から使えるようにする - App Intents を実装する実践的なデモ
- App Intents Testing - App Intents をテストするためのツールと方法
- アプリを保護する: エージェント機能のリスクを軽減する - エージェント型アプリにおけるセキュリティ上の考慮点
- Apple Intelligence と Siri AI - Apple Intelligence と Siri AI の体系の概要
コメント
GitHub Issues · utterances