← 所有文章

App Schemas:让你的 App 为 Siri 所用

在 WWDC 2026 上,一位 Apple 工程师拿了一款原本只会响应点按的 SwiftUI 日历 App,便让 Siri 得以搜索其中的事件、按名称与备注内容回答相关问题、用语音创建与更新事件,并呈现自定义的结果卡片——而达成这一切,只写了三个 struct、填了寥寥几段代码片段。1 这项转变背后的机制就是 App Schemas:一种以 Siri 早已理解的语汇来描述 App 内容与动作的方式,开发者端无需训练语句、也无需自然语言处理。1 这场分享是一段围绕名为 CometCal 的示例项目展开的 code-along,而宇宙主题之下的核心启示其实关乎结构。你不必教会 Siri 你的词汇,而是针对 Siri 早已熟知的形态声明你的数据与动作,其余便自然水到渠成。

本文走访支撑这项成果的三大支柱:schema 领域模型、通过 IndexedEntity 向 Spotlight 进行语义 donation,以及让语音驱动的更新得以安全进行的屏幕感知与 valueState 之别。以下内容皆直接取自该场分享。本主题与 App Intents 中的后台执行有所不同,后者谈的是不启动 UI 即执行工作;本文聚焦的是 Siri 如何对你的内容进行推理并据以行动。

TL;DR

  • App Schemas 以 Siri 早已理解的语汇描述 App 的实体、动作参数与输出,并组织成 App Schema Domains;日历领域涵盖事件、日历、参与者以及作用于其上的动作,开发者端无需训练语句、也无需 NLP。1
  • schema 化的实体源自 Xcode 代码片段:输入像 calendar_ 这样的领域前缀并挑选一个片段(例如 calendar_calendar),即会搭建出含宏、属性、display representation 与 query stub 的实体。1
  • 让实体遵循 IndexedEntity 协议,并通过 indexAppEntities 将其 donate 到 CSSearchableIndex(并通过 deleteAppEntities 移除),Siri 便能按名称、按属性或按上下文解析它,无需自定义属性 query。1
  • 两个 view modifier——列表上的 .appEntityIdentifier 与详情视图上的 .userActivity(各自带有一个 EntityIdentifier)——赋予 Siri 屏幕感知,使「给这个事件中的人发邮件」这类请求无需指名事件即可解析。1
  • 更新用的 intent 会暴露 IntentParameter.valueState,其中带值的 .set、带 nil 的 .set.unset 分别代表新值、显式清除与缺席的参数,使 Siri 驱动的编辑保持明确无歧义。1

App Schemas 究竟是什么(Session 344)

Watch on Apple Developer ↗

来自 Swift Intelligence Frameworks 团队的 Justin 在打开 Xcode 之前先讲解 App Schemas,从 3:12 开始。

Siri 通过 App Intents 框架触及 App,而 Apple Intelligence 则为其上的推理提供动力。1 CometCal 的起点问题很简单:「目前 Siri 完全不知道在 CometCal 里,日历或事件代表什么含义。」1 App Schemas 正是要弥合这道落差。如该场分享所言,它们「以 Siri 早已能理解的语汇描述我 App 的内容与动作。它们定义我实体的结构、我动作的参数以及输出。我这端无需训练语句、也无需自然语言处理。」1

其组织单位是 App Schema Domain。日历领域「涵盖与日程安排相关的一切:事件、日历、参与者,以及作用于其上的动作。」1 由于这些形态是预先定义好的,编目工作便由编辑器代劳。工程师创建一个 CalendarEntity 文件、导入 AppIntents、输入 calendar_,于是「Xcode 便在自动补全中直接列出日历领域里的每一个 schema。」1 选取 calendar_calendar 即会填入整套结构:实体宏、属性、一个 display representation 与 query stub,产生该场分享所称「一个 schema 化的实体,一个 Siri 能据以推理的类型。」1

命名约定值得审慎留意。schema 片段的名称在编辑器中以小写、下划线前缀的标识符形式出现(calendar_calendarcalendar_attendeecalendar_eventcalendar_createEventcalendar_updateEvent,外加 calendar_attendeeStatuscalendar_attendeeType 等枚举片段),而口述的逐字稿也是如此呈现。它们所搭建出的 Swift 类型(一个 @AppEntity 宏、一个 DisplayRepresentation、query 协议的遵循)则遵循一般的 Swift 大小写约定。在据以开发之前,请对照 Apple 的 App Intents 文档与可下载的 CometCal 示例项目,确认每个符号的确切拼写与大小写,因为一段口述的 code-along 并非大小写的精确参照。

schema 模型的回报,是以极少的代码换取广泛的触及范围。该场分享把整个内容层概括为「三个 struct,再填上几段代码片段。」1 CometCal 建立了三个丰富度递增的实体:一个日历、一个参与者,以及一个把前两者汇聚在一起的事件。事件「与先前建立的其他实体组合」:其日历是一个 CalendarEntity,其参与者是一个 AttendeeEntity 数组,而「Siri 通过 App Schemas 理解这些关系。」1 schema 也决定了哪些是必填、哪些是选填。标题或开始日期之类的要项可直接接上;App 未使用的选填 schema 属性(该场分享点名了行程时间与虚拟地点)可保持未设置;而存在于数据模型却不在 schema 上的属性,例如 isFavorite,仍可加进实体。1

事件上还现身了另外两项 schema 机制。union 值让单个属性能持有数种类型之一:地点可以是「来自 GeoToolbox 框架的 PlaceDescriptor,也可以是 String」,而闹钟可以是 DurationDate1 重复属性使用 Foundation 的 Calendar.RecurrenceRule,并针对每天、每周、每月与每年的情况与 CometCal 自身的频率枚举互相转换。1 schema 化的枚举(该场分享指向一个它称为 EventEntityStatus 的事件状态枚举,以及前述的参与者枚举)从片段中完整到位,App 采用适用的 case 即可;若 App 使用不同的术语,便把既有模型对应到 schema 的 case 上,「好让 Siri 能识别这个形态。」1

通过 IndexedEntity 进行语义 donation

schema 给了 Siri 一套语汇。donation 则给了 Siri 可供推理的实际数据。两者是各自独立的步骤,而该场分享明确指出,漏掉第二步是很容易发生的:「IndexedEntity 定义了我索引内容的形态,但实体仍然需要被 donate。」1

让实体遵循 IndexedEntity 协议,正是让匹配得以按含义而非仅按文本进行的关键。1 原因在于搜索索引。遵循该协议「让我的 App 能运用 Spotlight 索引来 donate 实体,从而获得语义理解的好处」,而一旦实体被 donate,「Siri 便能按名称、按属性或按上下文解析它,无需自定义属性 query。」1 最后这句话正是重点所在。你无需为「组员午餐」或「提及氧气的事件」编写任何专属的匹配器。Siri 直接搜索 donate 进来的标题与备注内容,并「用 App 的内容回答每一个问题。不需要自定义自然语言……只需要实体与 schema。」1

donation 通过 CSSearchableIndex 进行。CometCal 持有一个 CSSearchableIndex 实例,在其 CalendarManager 的初始化器中以一个 App 专属的名称创建。1 该场分享所述的规则是:「每当日历——或就此而言任何已索引的实体——发生变更时,索引就必须更新。」1 因此数据层在写入时即进行 donate:创建路径在返回前以 searchable index 调用 indexAppEntities,更新路径为变更后的实体重新索引,删除路径则调用 deleteAppEntities,「并传入该实体的 id 与类型。」1 接上日历实体后,工程师创建了一个名为「Lunar Orbit Log」的日历,滑动至搜索,便连同其图标与标题一并找到了它——这正是 donation 已生效的证明。1

并非每个实体都该被索引,而参与者正是阐明此规则的反例。AttendeeEntity 遵循的是 TransientAppEntity 而非 IndexedEntity,即「一个不需要唯一标识符、也并非用来查询的临时性实体。」1 其理由在于建模的纪律:在 CometCal 中,参与者代表的是「某人对某个特定事件的参与,而非那个人本身」,同一个人可以参加许多事件,而「把每一次参与分别索引,会在 Spotlight 中制造出重复的结果。」1 既然参与者一律是通过其事件来触及,便没有需要维护的独立查找路径,而 TransientAppEntity「把这一点讲明了……无需编写 query,也无需维护索引。」1 参与者还引入了 IntentPerson,即「系统用来表示一个具有姓名与联系信息的人的标准方式」,在把参与者的电子邮件交给「邮件」App 起草消息时相当有用。1

已索引的实体仍然需要其 query 的接管机制。query 通过 @Dependency 属性包装器持有数据层,这「正是 App Intents 将共享资源注入 intent 与 query 的方式」,因此 query 使用的是那个唯一注册过的 CalendarManager 而非一个全新的实例,且该 query 因为 manager 是 main-actor 而同样标记为 main-actor。1 必要的 EntityQuery 方法会在系统已知 ID 的情况下按 ID 提取,而遵循 EnumerableEntityQuery 并提供一个 allEntities 方法,则能让系统在 Siri 于创建事件时需要提供可用日历作为选项时,将其逐一列出。1 一个 DisplayRepresentation(标题加上一张系统日历图像)则告诉 Siri 与 Spotlight 该如何呈现该实体。1

有一道值得点名的导航接缝,因为单靠 donation 只会把用户带到 App 的主屏幕。一个遵循 system.open schema、以 EventEntity 为目标、并告知导航层路由至该处的 OpenEventIntent,便弥合了这道落差:系统会「每当有人在 Spotlight 或 Siri 中点按某个事件结果,或要求 Siri 打开某个事件时」便加以调用,于是被点按的结果便直接打开至该事件的详情视图。1

屏幕感知与 valueState 之别

前两根支柱让 Siri 能按名称找到内容。第三根则让 Siri 能运用用户眼前既有之物,继而毫无歧义地对其行动。

屏幕感知只需「两个 view modifier。」1 在列表视图中,.appEntityIdentifier 附加于列表上,「为每一个事件实体传入一个 EntityIdentifier」,这「把列表与其实体连接起来,于是当有人正在浏览列表时,系统便知道哪些事件在屏幕上。」1 在详情视图中,.userActivity 为焦点所在的单个事件带上一个 EntityIdentifier,告知系统「那一个特定事件正居于正中央,好让 Siri 能把『这个事件』精确解析为正在查看的那一个。」1 两者就位后,身处某事件详情视图的用户便可说「给这个事件中的人发邮件,并请某人带巧克力和棉花糖来」,Siri 便运用它对屏幕上事件的理解去找出参与者,并把他们交给「邮件」App——完全无需提供标题。1

对内容行动,与读取内容是同一套模式,只是反向执行。intent 同样源自片段。calendar_createEvent 片段搭建出含宏、schema、schema 所需参数与一个 perform stub 的 intent。1 perform 逻辑是该场分享直白道出的三步形态:「把 intent 的参数解析成数据层能理解的东西、执行该动作,再把结果以实体形式返回。」1 以创建为例,这意味着从 union 值中取出地点、在有提供的情况下转换重复设置、调用 manager 的创建方法,再返回一个 EventEntity1 由于该 intent 遵循一个 schema,「Siri 便能处理所有繁重工作。诠释语言、要求澄清以及确认细节」,因此开发者从不需要编写那段对话。1

更新则浮现出让语音驱动的编辑值得信赖的那份微妙之处。calendar_updateEvent 上的多数参数都是选填,因为用户通常只会更改一两项,而「事件参数是 Siri 所解析的对象;其余一切都是选填。」1 单纯的 nil 检查无法回答真正的问题。如该场分享所述:「当重复设置为 nil 时,那究竟代表『别更动它』还是『移除它』?单纯的 nil 检查并不告诉我自己面对的是哪一种情况。」1 答案是 IntentParameter.valueState,之所以得以暴露,是因为 intent 宏把每个属性都包进了一个 IntentParameter。这三种状态各带不同的含义:「带有实际值的 .set 代表提供了一个新值。带有 nil 值的 .set 代表它被显式清除。.unset 代表该参数并非请求的一部分。」1 这项区分「适用于任何『清除其值是一项有意义动作』的选填参数」,这正是为何「别重复这个事件」能可靠地清除重复设置,而非任其原封不动。1

另有两处收尾之笔让动作层更趋完整。一张自定义结果卡片取代了 Siri 默认的 display representation 卡片:在 perform 方法的返回类型上加入 ShowsSnippetView,并传入一个备妥的 SwiftUI view(该场分享的版本接受一个 EventEntity),便会在 Siri 内部渲染出 App 自身的样式——这个做法「对任何其他会返回结果的 intent 同样适用。」1DeleteEventIntent,即「三者中最简单的一个」,只需接受该事件以及一个给重复事件用的选填区间;Siri 会「在移除任何东西之前自动处理确认对话框」,并在符合的事件不止一个时加以消歧。1

要点总结

给采用 App Intents 的 iOS 开发者:

  • 先求助于 schema。在 Xcode 中输入像 calendar_ 这样的领域前缀,让自动补全列出可用的片段;片段会搭建出宏、属性、display representation 与 query stub,于是你填入的是类型与对应关系,而非从头发明结构。1
  • 逐一实体判断它是否值得一个索引。让持久、可查询的内容遵循 IndexedEntity 并加以 donate;对于那些一律通过父层触及、一旦索引只会污染 Spotlight 的参与式记录(CometCal 的参与者),则使用 TransientAppEntity1
  • 在开发之前,对照 Apple 的 App Intents 文档与 CometCal 示例确认符号的确切拼写与大小写,因为 code-along 中的名称出自一段口述逐字稿。

给设计语音与 Apple Intelligence 流程的团队:

  • 把 donation 当成写入路径的职责。在创建与更新时调用 indexAppEntities、在删除时调用 deleteAppEntities,皆以实体的 id 与类型为键,好让 Siri 的索引绝不与数据脱节。1
  • 及早加入屏幕感知:列表上的 .appEntityIdentifier 与详情视图上的 .userActivity(各自带有一个 EntityIdentifier),让用户能说「这个事件」而非其标题。1
  • 在更新 intent 中显式处理 valueState。针对带值的 .set、带 nil 的 .set.unset 分别分支,好让一次显式的清除绝不会被读作「保持不变」。1

常见问题

App Intents 中的 App Schemas 是什么?

App Schemas 以 Siri 早已理解的语汇描述 App 的内容与动作:它们定义 App 实体的结构、其动作的参数以及输出,开发者端无需训练语句、也无需自然语言处理。它们组织成 App Schema Domains;日历领域涵盖事件、日历、参与者以及作用于其上的动作。在 Xcode 中,你通过输入像 calendar_ 这样的领域前缀并挑选一个如 calendar_calendar 的代码片段来采用某个 schema,该片段便会搭建出实体。1

Siri 如何按名称或上下文解析我 App 的内容?

让实体遵循 IndexedEntity 协议,并通过在创建与更新时调用 indexAppEntities、在删除时以实体的 id 与类型调用 deleteAppEntities,将其 donate 到一个 CSSearchableIndex(即 Spotlight 索引)。donation 赋予 Siri「语义理解」,让它能按名称、按属性或按上下文解析实体而无需自定义属性 query,这也包括为「哪些事件提到氧气?」这类问题搜索备注内容。1

我何时该使用 TransientAppEntity 而非 IndexedEntity?

对于一个不需要唯一标识符、也并非用来查询的临时性实体,请使用 TransientAppEntity。CometCal 的参与者就符合,因为参与者代表的是某人对某个特定事件的参与,而非那个人;同一个人会参加许多事件,而把每一次参与分别索引会制造出重复的 Spotlight 结果。既然参与者只通过其事件触及,便没有独立的查找路径,因此这个临时性实体既无需 query、也无需索引。1

valueState 是什么,它为何对更新 intent 至关重要?

在一个更新 intent 中,App Intents 宏把每个属性都包进一个会暴露 valueStateIntentParameter。它能区分 nil 检查所无法区分的三种情况:带值的 .set 代表一个新值,带 nil 的 .set 代表该值被显式清除,而 .unset 代表该参数并非请求的一部分。这项区分让 Siri 驱动的编辑得以清除某项属性(例如「别重复这个事件」),而不至于被误认为「保持不变」。1

我该如何赋予 Siri 对我 App 的屏幕感知?

加入两个 view modifier。把 .appEntityIdentifier 放在列表视图上,为每个事件实体传入一个 EntityIdentifier,好让系统在浏览时知道哪些事件在屏幕上。把带有 EntityIdentifier.userActivity 放在详情视图上,好让系统知道某个特定事件正居于焦点。两者合起来,便让用户能说「给这个事件中的人发邮件」,并让 Siri 把「这个事件」精确解析为正在查看的那一个。1


本文隶属于一个关于 Apple intelligence 框架的文章群。关于 App Schemas 所立基的那个框架,请从 App Intents:Apple 通往你 App 的全新 API 开始。关于不启动 UI 即执行 intent 工作——这与本文所谈的内容推理是另一回事——请阅读 App Intents 中的后台执行。关于语义解析背后更宏观的 Spotlight donation 故事,请参阅 设备端 AI 与 Spotlight 媒体索引。整个系列的总览中心是 Apple Ecosystem Series

参考资料


  1. Apple, WWDC 2026 session 344, Code-along: Make your app available to Siri。App Schemas 与 App Schema Domains(日历领域;无训练语句、无 NLP);通过 Xcode 片段创建 schema 化实体(calendar_calendarcalendar_attendeecalendar_eventcalendar_createEventcalendar_updateEventcalendar_attendeeStatuscalendar_attendeeType);IndexedEntity 以及通过 CSSearchableIndexindexAppEntities / deleteAppEntities 进行的 Spotlight donation;按名称、属性或上下文的解析;TransientAppEntity 与参与者的建模理据;IntentPerson;union 值(来自 GeoToolboxPlaceDescriptor、String;DurationDate 闹钟)与 Calendar.RecurrenceRule@Dependency 包装器、EntityQueryEnumerableEntityQueryDisplayRepresentationsystem.openOpenEventIntent;通过带有 EntityIdentifier.appEntityIdentifier.userActivity 达成的屏幕感知;IntentParameter.valueState.set/.unset);ShowsSnippetView 自定义结果卡片;DeleteEventIntent 的确认与消歧;以及为自动化测试所引用的 AppIntentsTesting 框架,皆以此为来源。 

相关文章

App Intents 是 Apple 通向你应用的全新 API

2026年2月8日,我在 Water 中上线了一个 App Intent。本文讲述 Apple Intelligence 在 iOS 26 中对第三方应用的诉求,以及为什么 App Intents 才是真正重要的那份契约。

6 分钟阅读

MetricKit 重构:iOS 27 中的状态感知遥测

iOS 27 重构了 MetricKit:异步指标流与诊断流、Codable 报告,以及按应用状态拆分指标的 StateReporting 框架。

3 分钟阅读

The Robots Are Taking Exams in My Search Console

First-party GSC data: 91% of 3.8M impressions fail a human-query filter. Exam questions, pasted errors, and agent sweeps…

10 分钟阅读