博客 • Foundation Models
Apple Intelligence 2026 开发者生态变化:Foundation Models、App Intents 与 JSON 数据交换机制详解
2026 年,同一个 App 会同时接到两套入口:设备上的 LanguageModelSession 按类型解码出一份内容,系统则调用 App Intent 的 perform()。要落盘的不是这两份里的任何一份原样,而是你自己的交换信封。
产品叙事见 iPhone 18 为什么不只是硬件。Siri 会不会变成 Agent,见 Siri AI 会成为 AI Agent 吗。本篇只写信封:哪些字段要落盘,以及什么情况下不能执行。
- content 是模型解码出来的那份对象
- parameters 是交给 perform() 的那份对象
- complete 为 false 时,parameters 写成 null
- schemaVersion 变了,旧信封不能直接重放
先记住这一点:框架把 Generable 收成 schema,再解码成值。这只发生在模型会话里。你要保存、对账、再决定要不要执行,靠的是另一份信封。下面按「两份合同 → 信封字段 → 一份 JSON → 四种停」拆开。
两份合同不是一次调用
设备上的模型怎样按类型填值,见 Generable。类型会收成 schema,模型填完,框架再解码。静态类型用 @Generable。没有静态类型时,会话可以按 GenerationSchema 返回 GeneratedContent。两种结果写进信封时都叫 content。
App Intent 是另一条调用链。参数写在 @Parameter 上,说明见 为 App Intent 添加参数。系统调用 perform() 时没有 LanguageModelSession,也就没有 Generable 的 content。
- 1会话产出内容
respond(to:generating:) 返回 Response。生成还没结束时,对应的是 PartiallyGenerated,字段可以仍为空。这时还没有一份能执行的参数。
- 2投影出参数
只把 Intent 接受的字段抄进 parameters。模型多写的 rationale 留在 content。两份对象可以同名,但不能是同一个对象。
- 3先写信封再执行
source、sessionId、schemaVersion、complete、content、intent、parameters 落成一份 JSON。complete 不是 true,就不调用 perform()。
信封里有哪些字段
下面这些键是你自己的信封,不是 Apple 解码结果里的字段。
| 字段 | 谁来写 | 作用 |
|---|---|---|
source | 你的 App | foundation-models 或 app-intent。标明这次值从哪条入口来。 |
sessionId | 你的 App | 只有模型会话才有,例如 lm_01。系统调用时写成 null。 |
schemaVersion | 你的 App | 你给 Generable 字段表标的代际。这份示例是 3。字段变了就加一,旧信封不能直接重放。 |
generable | 你的 App | 类型名,例如 OrderDraft。系统直接调用时写成 null。 |
complete | 你从生成状态抄下 | false 表示还在生成,字段还会变。true 才允许往下投影并考虑执行。 |
content | 模型解码结果 | 模型解码出的对象,可以比动作多字段,例如 rationale。系统调用时写成 null。 |
intent | 你的 App | 将要调用或系统正在调用的 App Intent 标识,例如 PlaceOrderIntent。 |
parameters | 你的投影,或系统入参 | 交给 perform() 的对象。未完成时写成 null。系统调用时就是系统传入的参数。 |
content 和 parameters 可以有相同的字段名,但它们是两份对象。把 content 原样送进 perform(),模型多写的 rationale 会进动作。
一份能落盘的交换 JSON
下面这份信封和 Schema 是你的契约,不是 Apple 的线上格式。JSONNote 的 JSON Schema 会检查类型、枚举和必填。complete 为 true 才允许执行,这层 Schema 表达不了,要在 App 里判断。
{
"source": "foundation-models",
"sessionId": "lm_01",
"schemaVersion": 3,
"generable": "OrderDraft",
"complete": true,
"content": {
"itemId": "sku_42",
"quantity": 1,
"fulfillment": "pickup",
"rationale": "User asked for pickup."
},
"intent": "PlaceOrderIntent",
"parameters": {
"itemId": "sku_42",
"quantity": 1,
"fulfillment": "pickup"
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["source", "sessionId", "schemaVersion", "generable", "complete", "content", "intent", "parameters"],
"properties": {
"source": { "enum": ["foundation-models", "app-intent"] },
"sessionId": { "type": ["string", "null"] },
"schemaVersion": { "type": "integer" },
"generable": { "type": ["string", "null"] },
"complete": { "type": "boolean" },
"content": { "type": ["object", "null"] },
"intent": { "type": "string" },
"parameters": { "type": ["object", "null"] }
}
}
rationale 只留在 content。动作参数的类型对照见 App 动作字段与 JSON Schema。本篇不重复那张表。
没完成就不要 perform
{
"source": "foundation-models",
"sessionId": "lm_01",
"schemaVersion": 3,
"generable": "OrderDraft",
"complete": false,
"content": {
"itemId": "sku_42"
},
"intent": "PlaceOrderIntent",
"parameters": null
}
这封信可以通过上面的 Schema,因为 parameters 允许 null。形状通过也不执行。PartiallyGenerated 表示后面的 token 还会改已有字段。等 complete 变成 true,再从完整的 content 投影 parameters。未完成时就算把 parameters 写成对象,Schema 同样不会拦住。
| 信封 | 为什么停 |
|---|---|
| complete 为 false | 后面的 token 还会改已有字段。 |
| schemaVersion 不是当前字段表 | 旧 content 对不上现在的 Intent。 |
| foundation-models 的 parameters 多出 content 没有的键 | 投影不能发明模型没写过的值。 |
| app-intent 的 content 不是 null | 系统入参不是模型输出。不要把它写回 content,伪装成生成结果。 |
多出来的键用 JSON 对比看。这个检查只用于 source 为 foundation-models 的信封。系统调用时 content 是 null,不能要求 parameters 的每个键都在 content 里出现过。
Tool 和 App Intent 怎么交接
Foundation Models 的 Tool 由你的会话调用。说明见 用工具调用扩展生成。call(arguments:) 收到的 Arguments 也是 Generable。先写入 content,投影成 parameters 之后,才谈得上 perform()。
系统直接调用 Intent 时,source 写成 app-intent,sessionId、generable、content 写成 null,parameters 用系统已经传入的值。不要编一个 sessionId,把这次调用伪装成模型生成。
| Foundation Models Tool | App Intent | |
|---|---|---|
| 谁调用 | 你的 LanguageModelSession | Siri、快捷指令、Spotlight |
| 入参 | Generable 的 Arguments | @Parameter 上的参数 |
| 你实现的方法 | call(arguments:) | perform() |
| 信封里的 source | foundation-models | app-intent |
和另外几篇怎么分工
| 产品与 Siri 两篇 | 动作字段那篇 | 本篇 | |
|---|---|---|---|
| 回答 | 为什么不只是硬件;Siri 会不会变成 Agent | 哪些字段能进这个动作 | 模型值和动作参数之间落哪一份 JSON |
| 不要在这里找 | 又一篇发布稿 | AppEnum 和实体 id 的类型对照 | 把 content 原样当成 perform() 的入参 |
在 JSONNote 里对上这封信
- 1选对操作
打开 JSON Schema。操作选「用 Schema 校验」。第一栏贴信封,第二栏贴上面的 Schema。source 只能是 foundation-models 或 app-intent。complete 必须是布尔。parameters 必须是对象或 null。
- 2撞三条坏数据
source 写成 siri,或 complete 写成字符串 true,或删掉 parameters。这三条都会失败。未完成那封信(complete 为 false,parameters 为 null)会通过。通过也不要调用 perform()。
- 3对比两份对象
打开 JSON 对比 Diff。第一栏贴 content,第二栏贴 parameters。rationale 只该出现在第一栏。第二栏多出来的键是投影写的,不是模型写的。这个对比只用于 source 为 foundation-models 的信封。
常见问题
Generable 的输出能不能直接交给 perform()?
不能。输出写进 content。perform() 只接收投影后的 parameters。信封把两份都留下,用来对账。
Schema 通过了,就可以执行吗?
还不行。这份 Schema 只检查类型、枚举和必填。complete 为 false、parameters 为 null 时形状仍然合法。执行前要看到 complete 为 true,并且在模型这条入口上,parameters 没有 content 里不存在的键。
Tool 和 App Intent 是同一次调用吗?
不是。Tool 由 LanguageModelSession 调用。App Intent 由 Siri、快捷指令或 Spotlight 调用。同一件业务可以两边都做,入参都先落进这份信封。
schemaVersion 变了,旧日志还能重放吗?
不能直接重放。版本号是你给 Generable 字段表标的代际。字段变了,旧 content 对不上现在的 Intent。先停住,做一次明确的映射,再决定要不要执行。
和动作字段与 JSON Schema 那篇是同一篇吗?
不是。那篇把参数类型对齐成一张字段表。本篇写模型值和那张表之间要落盘的信封:入口、完成状态、版本,以及 content 和 parameters 分开。
总结
Foundation Models 和 App Intents 是两套入口。交换信封把入口、完成状态、schema 版本、content 和 parameters 放在同一份 JSON 里,是为了在 perform() 之前能停下来。
直接结论:content 留在信封里;perform() 只接收 parameters;complete 为 false 不执行;schemaVersion 对不上不重放;parameters 不能出现 content 里没有的键。
下一步:先让这封交换 JSON 通过 Schema,再用对比看 content 和 parameters。