Блог • Foundation Models
Apple Intelligence 2026: какой JSON передаётся между Foundation Models и App Intents
В 2026 году у одного приложения два входа. LanguageModelSession на устройстве декодирует типизированное значение, а система вызывает perform() у App Intent. Сохраняете вы не эти значения как есть, а собственный конверт обмена.
Продуктовая история — в статье почему iPhone 18 — это больше, чем железо. Станет ли Siri агентом — в статье Станет ли Siri AI агентом. Здесь только конверт: какие поля сохранять и когда действие запускать нельзя.
- content — объект, который декодировала модель
- parameters — объект, который вы передаёте в perform()
- Когда complete равен false, parameters остаётся null
- Если schemaVersion изменилась, старый конверт нельзя проиграть как есть
Сначала это: Фреймворк превращает тип Generable в схему и декодирует значение. Это остаётся внутри сессии модели. Конверт нужен, чтобы сохранить и сверить данные до запуска действия. Дальше: два контракта, поля, один JSON и четыре причины остановиться.
Два контракта — два вызова
Как модель на устройстве заполняет типизированное значение, описано в Generable. Тип становится схемой, модель её заполняет, фреймворк декодирует значение. Статический тип помечают @Generable. Без него сессия может вернуть GeneratedContent по GenerationSchema. В конверте оба результата называются content.
App Intent — другой вызов. Параметры описывают через @Parameter. См. Добавление параметров в App Intent. Когда система вызывает perform(), нет ни LanguageModelSession, ни content типа Generable.
- 1Сессия выдаёт содержимое
respond(to:generating:) возвращает Response. Пока генерация не закончена, значение — PartiallyGenerated, поля могут быть пустыми. Исполняемых параметров ещё нет.
- 2Спроецировать параметры
В parameters копируйте только поля, которые принимает Intent. rationale, который добавила модель, остаётся в content. Имена могут совпадать. Это не один и тот же объект.
- 3Сначала записать конверт
Сохраните source, sessionId, schemaVersion, complete, content, intent и parameters одним JSON. Если complete не true, не вызывайте perform().
Поля конверта
Эти ключи принадлежат вашему конверту. Их нет в значении, которое декодирует Apple.
| Поле | Кто пишет | Зачем |
|---|---|---|
source | Ваше приложение | foundation-models или app-intent. Показывает, какой вход дал значение. |
sessionId | Ваше приложение | Есть только у сессии модели, например lm_01. Системный вызов пишет null. |
schemaVersion | Ваше приложение | Поколение вашего списка полей Generable. В примере это 3. Увеличивайте, когда поля меняются. Старый конверт в новый Intent не проигрывайте. |
generable | Ваше приложение | Имя типа, например OrderDraft. Прямой системный вызов пишет null. |
complete | Вы копируете состояние генерации | false значит, что генерация ещё идёт и поля могут измениться. Только true позволяет спроецировать параметры и думать о запуске. |
content | Декодированное значение модели | Декодированный объект. В нём может быть больше полей, чем у действия, например rationale. Системный вызов пишет null. |
intent | Ваше приложение | Идентификатор App Intent, который вы вызовете или который вызывает система, например PlaceOrderIntent. |
parameters | Ваша проекция или аргументы системы | Объект для perform(). Пока генерация не закончена, пишите null. При системном вызове это уже переданные аргументы. |
У content и parameters могут быть одинаковые имена полей. Это всё равно два объекта. Если передать content прямо в perform(), rationale модели попадёт в действие.
Один JSON, который можно сохранить
Конверт и схема ниже — ваш контракт, не сетевой формат Apple. JSON Schema в JSONNote проверяет типы, enum и обязательные ключи. Правило «выполнять только при complete true» эта схема не выражает. Это решает приложение.
{
"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. Соответствие типов параметров действия — в статье поля действия приложения и JSON Schema. Эту таблицу статья не повторяет.
Не вызывать perform, пока не закончено
{
"source": "foundation-models",
"sessionId": "lm_01",
"schemaVersion": 3,
"generable": "OrderDraft",
"complete": false,
"content": {
"itemId": "sku_42"
},
"intent": "PlaceOrderIntent",
"parameters": null
}
Этот конверт проходит схему выше, потому что parameters может быть null. Верная форма — ещё не повод выполнять. PartiallyGenerated значит, что следующие токены могут изменить уже появившиеся поля. Дождитесь complete true и только тогда спроецируйте parameters из полного content. Если записать в parameters объект до конца генерации, схема это тоже пропустит.
| Конверт | Почему остановиться |
|---|---|
| complete равен false | Следующие токены могут изменить уже появившиеся поля. |
| schemaVersion не совпадает с текущим списком полей | Старый content не совпадает с текущим Intent. |
| При foundation-models в parameters есть ключ, которого нет в content | Проекция не должна выдумывать значение, которого модель не писала. |
| При app-intent content не null | Аргументы системы — не вывод модели. Не копируйте их в content, изображая результат генерации. |
Лишний ключ виден в сравнении JSON. Эта проверка только для source со значением foundation-models. При системном вызове content равен null, и нельзя требовать, чтобы каждый ключ parameters уже был в content.
Как Tool передаёт работу App Intent
Ваша сессия вызывает Tool из Foundation Models. См. Расширение генерации вызовом инструментов. Arguments, которые получает call(arguments:), тоже Generable. Сначала запишите их в content. О perform() речь только после проекции в parameters.
Когда система вызывает Intent напрямую, source равен app-intent, а sessionId, generable и content — null. parameters — уже переданные значения. Не выдумывайте sessionId, чтобы выдать вызов за вывод модели.
| Foundation Models Tool | App Intent | |
|---|---|---|
| Кто вызывает | Ваша LanguageModelSession | Siri, Команды, Spotlight |
| Вход | Arguments типа Generable | Значения на @Parameter |
| Метод, который вы реализуете | call(arguments:) | perform() |
| source в конверте | foundation-models | app-intent |
Чем эта статья отличается от других
| Статьи о продукте и Siri | Статья о полях действия | Эта статья | |
|---|---|---|---|
| Отвечает | Почему это больше, чем железо, и станет ли Siri агентом | Какие поля могут войти в действие | Какой JSON лежит между значением модели и параметрами действия |
| Здесь не ищите | Ещё один пересказ анонса | Соответствие AppEnum и id сущности | Передачу content без изменений во вход perform() |
Проверить конверт в JSONNote
- 1Выбрать нужную операцию
Откройте JSON 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. Конверт хранит оба, чтобы их сверить.
Если схема прошла, можно выполнять действие?
Ещё нет. Эта схема проверяет только типы, enum и обязательные ключи. complete false и parameters null — всё ещё допустимая форма. Перед запуском complete должен быть true, а на входе модели в parameters не должно быть ключа, которого нет в content.
Tool и App Intent — это один и тот же вызов?
Нет. LanguageModelSession вызывает Tool. App Intent вызывают Siri, Команды или Spotlight. Одну задачу можно выставить с обеих сторон. Оба входа сначала попадают в этот конверт.
Если schemaVersion изменилась, можно ли проиграть старый журнал?
Не как есть. Версия — это поколение вашего списка полей Generable. После смены полей старый content не совпадает с текущим Intent. Остановитесь, напишите явное соответствие и только потом решайте, выполнять ли.
Это та же статья, что про поля действия и JSON Schema?
Нет. Та статья сводит типы параметров в список полей. Эта — про конверт между значением модели и этим списком: вход, завершённость, версия и content отдельно от parameters.
Итог
Foundation Models и App Intents — два входа. Конверт кладёт вход, завершённость, версию схемы, content и parameters в один JSON, чтобы можно было остановиться до perform().
Решение: content остаётся в конверте; perform() получает только parameters; при complete false не выполнять; при несовпадении schemaVersion не проигрывать; в parameters не должно быть ключа, которого нет в content.
Дальше: прогоните этот JSON через схему, затем сравните content и parameters.