博客 • App Intents
iOS 27 AI Agent 如何调用第三方 App?App Intents、App Actions、API 与 JSON 参数验证完整教程
系统调用第三方 App 时,入口是你声明的 AppIntent。参数进 perform() 之后、API 之前,先落成一份 JSON。类型、枚举或必填对不上,就返回失败记录,这次调用停在 App 里。
一条指令怎么走到 API,见 App Actions 的 JSON 路径。Siri 会不会变成 Agent,见 Siri AI 会成为 AI Agent 吗。本篇只写 App 侧:声明哪些参数、用 Schema 挡住什么、失败时返回哪一份 JSON。
- Intent 的参数是 App 自己的合同
- Schema 检查类型、枚举和必填
- 失败记成 needs_value 或 rejected
- 这两种记录都不进入 API
先记住这一点:系统把调用交到你的 App,不代表参数已经能用。下面按「声明合同 → 一份 Schema → 三条失败 → 形状通过仍停 → 失败怎么回」拆开。
先声明参数合同
第三方 App 对外的入口写在 AppIntent 上。你给出标题,并实现 perform()。系统能发现这个动作,是因为你声明了它。声明本身不证明这次参数可以打 API。
每个参数用 @Parameter 标出。说明见 为 App Intent 添加参数。这份示例只收三样:商品 id、数量、履约方式。名称、说明文字和模型多写的键都不在合同里。
- 1钉住 Intent 名
候选 JSON 里的 intent 只能是 PlaceOrderIntent。别的名字表示这次调用找错了动作。
- 2列出三个参数
itemId 是字符串,quantity 是整数,fulfillment 只能是 pickup 或 delivery。缺任何一项,这份候选就不完整。
- 3perform() 里先校验
校验失败就返回下面的失败记录。通过,并且 id 能解析、数量至少为 1,才进入 API。
同一组字段写成 Schema
下面的候选和 Schema 是你的契约,不是 Apple 的线上格式。JSONNote 的 JSON Schema 会检查类型、枚举和必填。数量至少为 1、id 必须存在,这层 Schema 表达不了,要在 App 里再判断。
{
"intent": "PlaceOrderIntent",
"parameters": {
"itemId": "sku_42",
"quantity": 1,
"fulfillment": "pickup"
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["intent", "parameters"],
"properties": {
"intent": { "enum": ["PlaceOrderIntent"] },
"parameters": {
"type": "object",
"required": ["itemId", "quantity", "fulfillment"],
"properties": {
"itemId": { "type": "string" },
"quantity": { "type": "integer" },
"fulfillment": { "enum": ["pickup", "delivery"] }
}
}
}
}
参数类型怎么对齐,见 App 动作字段与 JSON Schema。本篇不重复那张表,只写校验失败时 App 返回什么。
三条会失败的候选
| 候选里改了什么 | 为什么失败 |
|---|---|
| fulfillment 写成 ship | 不在 pickup 和 delivery 里。 |
| quantity 写成字符串 1 | 合同要的是整数。 |
| 删掉 itemId | 必填缺了,候选不完整。 |
把 quantity 改成 0,类型仍是整数,这份 Schema 会通过。通过不等于可以下单。0 件商品要在 App 里拒绝。
形状通过仍要停
{
"intent": "PlaceOrderIntent",
"status": "rejected",
"reason": "invalid_parameter",
"field": "quantity",
"dialog": "Quantity must be at least 1."
}
itemId 是非空字符串时,Schema 也通过。接着要在你的数据里解析这个 id。解析不到,就再写一份 rejected,field 写成 itemId。不要用商品名造一个 id,也不要打 API。
失败怎么回
缺必填时,用参数上的 requestValue 再问一次,不要拿空值去打 API。值非法时,让 perform() 停住,并用 IntentDialog 写出用户能看懂的那一句。上面的 JSON 是你为这次决定留下的记录。
| 情况 | 记录里的 status | API |
|---|---|---|
| 缺 fulfillment | needs_value | 再问一次,不打 API |
| fulfillment 是 ship | rejected | 说明只能是 pickup 或 delivery |
| quantity 是 0 | rejected | 形状合法也停 |
| itemId 解析不到 | rejected | 不用商品名顶替 id |
needs_value 表示还要问。rejected 表示这次候选不能执行。两条都不进入 API。用户看见的是 dialog 那一句,日志里留下整份 JSON,之后才能对上是哪一个字段被挡住。
和另外几篇怎么分工
| 指令路径那篇 | 交换信封那篇 | 本篇 | |
|---|---|---|---|
| 回答 | 短语怎么走到 API | 模型值和参数之间落哪一份 JSON | App 如何声明、校验、把失败回回去 |
| 不要在这里找 | 再走一遍语音到 API 的步骤 | content 和 parameters 两份对象 | 用商品名顶替实体 id |
在 JSONNote 里挡住这次调用
- 1选对操作
打开 JSON Schema。操作选「用 Schema 校验」。第一栏贴候选,第二栏贴上面的 Schema。intent 只能是 PlaceOrderIntent。fulfillment 只能是 pickup 或 delivery。quantity 必须是整数。
- 2撞三条坏数据
fulfillment 写成 ship,或 quantity 写成字符串 1,或删掉 itemId。这三条都会失败。quantity 改成 0 会通过。通过之后仍要写 rejected,不打 API。
- 3对比 1 和 0
打开 JSON 对比 Diff。第一栏贴 quantity 为 1 的 parameters,第二栏把 quantity 改成 0。差的是这个数。0 仍能通过 Schema,所以挡住 API 的是失败记录,不是这次形状检查。
常见问题
系统已经把参数交给 App,还要再校验吗?
要。系统交来的是一次调用,不是已经校验过的 API 请求。perform() 开头先看这份 JSON。不过就不继续。
Schema 通过就可以打 API 吗?
还不行。这份 Schema 只看类型、枚举和必填。quantity 为 0 会通过。itemId 是字符串也会通过。数量至少为 1,并且 id 能解析,才打 API。
缺参数时应该返回错误还是再问一次?
再问一次。status 写成 needs_value,用 requestValue 向用户要这个值。问到合法值之前不打 API。值到了但非法,才写成 rejected。
和「一条指令的 JSON 路径」是同一篇吗?
不是。那篇跟着一条指令从短语走到 API。本篇停在 App 里面:参数合同、Schema,以及失败时返回的 JSON。API 只有在 rejected 和 needs_value 都没有出现时才发生。
失败记录是 Apple 的返回格式吗?
不是。Apple 用 requestValue 和 IntentDialog 把结果交给系统和用户。这份 JSON 是你自己的日志:status、reason、field、dialog。留下它,才能在 JSONNote 里复核这次为什么没有打 API。
总结
第三方 App 被调用时,先有一份参数合同,再有一份能失败的 JSON。失败记录把这次调用停在 API 之前,并留下用户能看懂的一句 dialog。
直接结论:先声明 Intent 参数;用 Schema 卡住类型、枚举和必填;缺值返回 needs_value;非法值返回 rejected;这两种都不打 API;数量小于 1 或 id 不存在,形状通过也要停。
下一步:先让候选参数通过 Schema,再决定这次调用是继续还是返回失败。