使用 App Schemas 打造智能 Siri 体验
Build intelligent Siri experiences with App Schemas
2026年6月10日
一句话判断
这场 Session 最值得关注的一件事是:Siri 终于长出了“眼睛”,通过 .appEntityIdentifier 视图修饰符,Siri 现在能直接理解屏幕上的 UI 元素并执行跨 App 操作,这彻底改变了人机交互的范式。
这场 Session 讲了什么
以前做 Siri 集成,基本就是写几个 AppIntent,用户像念咒一样喊句特定指令,App 在后台跑个逻辑,体验非常生硬。今年 Apple Intelligence 把 Siri 重构了,它不再只是个语音遥控器,而是一个能看懂屏幕、理解数据关系的系统级 Agent。
为了实现这个目标,Apple 强推 App Schemas(应用模式)。你不仅要告诉系统你的 App 能做什么(Intents),还得把 App 里的数据(Entities)按系统预定义的 Schema(比如 Message、Contact)进行标准化建模。更狠的是,Apple 把 Entity 绑定到了 SwiftUI 的视图层,让 Siri 能结合屏幕当前显示的内容(Onscreen context)来理解用户说的“这个”和“那个”。这对所有做内容驱动和效率工具的 App 影响最大,不接入这套标准,你的 App 在系统级 AI 面前就是个孤岛。
值得深挖的点
屏幕感知 (Onscreen Awareness):把 AI 的“眼睛”长在 SwiftUI 视图上
Apple 这次让 Siri 理解屏幕上下文,没有完全依赖传统的 Accessibility(辅助功能)树,而是推出了 .appEntityIdentifier 这个视图修饰符。为什么这么设计?Accessibility 树是为了视障用户设计的,包含了大量 UI 结构噪音(比如各种 Container、Button 标签),大模型去解析这棵树不仅 Token 消耗大,而且很难准确映射到业务数据模型上。
通过 .appEntityIdentifier,开发者直接告诉系统:“这个 List Row 对应的是 MessageEntity 的 ID 123”。这种设计把 UI 渲染和业务数据在系统层做了解耦。Siri 截屏或读取视图树时,直接拿到的是干净的结构化 Entity 引用。代价是,你需要手动给关键视图打上标记,如果列表是懒加载的(LazyVStack),屏幕外的 Entity Siri 是看不到的。这要求我们在设计 UI 时,必须考虑 AI 的“可视范围”。
语义索引 (IndexedEntity):Apple Intelligence 的本地 RAG 机制
Session 里花了很大篇幅讲 IndexedEntity。当你给 Entity 加上 indexingKey 时,系统会在设备端建立语义索引(Semantic Index)。这意味着用户问“Flare 上次说关于电影的事是什么”时,Siri 不是在做简单的字符串 contains 匹配,而是在做向量检索。
这其实是 Apple 在系统层做了一个本地的 RAG(检索增强生成)。对于开发者来说,Trade-off 很明显:如果你的数据在本地且相对静态,无脑上 IndexedEntity,让系统帮你做语义搜索;但如果你的数据在云端、数据量极大或者实时性要求极高(比如股票、实时聊天记录),提前建索引就不现实了。这时候就得退回 EntityStringQuery,把 Siri 解析出的自然语言字符串拿过来,自己在服务端做检索。Apple 把选择权交给了开发者,但也意味着你需要自己评估端侧算力和数据隐私的边界。
代码片段
1. 视图级 Entity 绑定
场景:让 Siri 知道屏幕上显示的列表项具体是哪个业务实体,支持“把这个发给 Mary”这种指令。
List {
ForEach(messages) { message in
MessageRow(message: message)
// 将视图与具体的 App Entity 标识符绑定
// Siri 看到屏幕时,能直接解析出这是哪个 Message
.appEntityIdentifier(
EntityIdentifier(
for: MessageEntity.self,
identifier: message.id
)
)
}
}
坑:如果你用的是 LazyVStack,只有当前渲染在屏幕上的 Row 才会被 Siri 感知到,滑出屏幕的 Entity 会丢失上下文。
2. 本地语义索引
场景:让 Siri 能够基于语义(而不是精确关键字)搜索 App 内的聊天记录或文档。
// 遵循 IndexedEntity 让系统建立本地语义索引
@AppEntity(schema: .messages.message)
struct MessageEntity: IndexedEntity {
// 指定需要被语义索引的文本属性
// 系统会在设备端对 body 内容进行向量化处理
@Property(indexingKey: \.textContent)
var body: AttributedString?
}
坑:indexingKey 指向的属性必须是系统能理解的文本类型(如 String 或 AttributedString),且数据量不能太大,否则会导致设备端索引重建时耗电和卡顿。
3. 跨 App 实体流转
场景:允许用户把你的 App 里的联系人或消息,直接拖拽或通过 Siri 发送给其他支持该 Schema 的 App。
extension ContactEntity: Transferable {
static var transferRepresentation: some TransferRepresentation {
// 定义导出和导入的转换逻辑
IntentValueRepresentation(
exporting: \.person, // 导出时转为系统标准的 IntentPerson
importing: { intentPerson in
// 导入时,从系统标准类型创建本地 Entity
let contact = Contact(importing: intentPerson)
ContactManager.shared.contacts.append(contact)
return contact.entity
}
)
}
}
坑:importing 闭包是在后台线程执行的,如果你的 ContactManager 绑定了 MainActor,这里直接调用会引发并发报错,需要做好 Actor 隔离。
最佳实践
- 老项目迁移:别想着一次性把全量数据接入。先挑出 App 里最核心的名词(比如电商 App 的 Product,社交 App 的 Post),让它们遵循对应的 App Schema。Xcode 现在很聪明,如果你接了
sendMessage,它会在编译时通过 Fix-It 提示你缺少draftMessage等关联 Schema,顺着提示补齐即可。 - 新项目建议:在架构设计阶段就把 App Intents 和 Entity 作为一等公民。不要等 UI 写完了再去硬凑数据模型。把 Entity 当作你 App 的领域模型(Domain Model),UI 和 Intent 都去消费它。
- 测试策略:千万别直接对着 Siri 喊话来调试,效率极低。遵循测试金字塔:先用 App Intents Testing 框架跑单元测试验证业务逻辑,再去 Shortcuts App 里看 Intent 的参数映射对不对,接着用 Spotlight 搜一下确认
IndexedEntity索引生效没,最后再上真机测 Siri 端到端。
还有什么值得关注
- Xcode Fix-Its 补全 Schema Domain:Apple 把 App Schema 做成了类似协议簇的东西,少实现一个关联动作,编译器直接报错并提供一键补全,这极大降低了接入系统级 AI 的心智负担。
EntityStringQuery的兜底机制:对于无法本地索引的云端数据,Siri 会把用户的自然语言原封不动地传给这个 Query,这意味着你的后端需要具备接收模糊自然语言并转换为结构化数据库查询的能力。- 屏幕感知的
NSUserActivity降级方案:如果你还没全面拥抱 SwiftUI,依然可以用NSUserActivity来标记当前屏幕的单一主要 Entity,虽然不如.appEntityIdentifier支持多元素,但能保住基础的上下文感知。