Блог • Foundation Models

Apple Intelligence 2026: какой JSON передаётся между Foundation Models и App Intents

В 2026 году у одного приложения два входа. LanguageModelSession на устройстве декодирует типизированное значение, а система вызывает perform() у App Intent. Сохраняете вы не эти значения как есть, а собственный конверт обмена.

Продуктовая история — в статье почему iPhone 18 — это больше, чем железо. Станет ли Siri агентом — в статье Станет ли Siri AI агентом. Здесь только конверт: какие поля сохранять и когда действие запускать нельзя.

Сначала это: Фреймворк превращает тип Generable в схему и декодирует значение. Это остаётся внутри сессии модели. Конверт нужен, чтобы сохранить и сверить данные до запуска действия. Дальше: два контракта, поля, один JSON и четыре причины остановиться.

Два контракта — два вызова

Как модель на устройстве заполняет типизированное значение, описано в Generable. Тип становится схемой, модель её заполняет, фреймворк декодирует значение. Статический тип помечают @Generable. Без него сессия может вернуть GeneratedContent по GenerationSchema. В конверте оба результата называются content.

App Intent — другой вызов. Параметры описывают через @Parameter. См. Добавление параметров в App Intent. Когда система вызывает perform(), нет ни LanguageModelSession, ни content типа Generable.

  1. 1
    Сессия выдаёт содержимое

    respond(to:generating:) возвращает Response. Пока генерация не закончена, значение — PartiallyGenerated, поля могут быть пустыми. Исполняемых параметров ещё нет.

  2. 2
    Спроецировать параметры

    В parameters копируйте только поля, которые принимает Intent. rationale, который добавила модель, остаётся в content. Имена могут совпадать. Это не один и тот же объект.

  3. 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» эта схема не выражает. Это решает приложение.

Конверт после завершения генерации. rationale остаётся в content и отсутствует в parameters.
{
  "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.
{
  "$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, пока не закончено

Генерация не закончена. quantity ещё нет, parameters остаётся null.
{
  "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 ToolApp Intent
Кто вызываетВаша LanguageModelSessionSiri, Команды, Spotlight
ВходArguments типа GenerableЗначения на @Parameter
Метод, который вы реализуетеcall(arguments:)perform()
source в конвертеfoundation-modelsapp-intent

Чем эта статья отличается от других

Статьи о продукте и SiriСтатья о полях действияЭта статья
ОтвечаетПочему это больше, чем железо, и станет ли Siri агентомКакие поля могут войти в действиеКакой JSON лежит между значением модели и параметрами действия
Здесь не ищитеЕщё один пересказ анонсаСоответствие AppEnum и id сущностиПередачу content без изменений во вход perform()

Проверить конверт в JSONNote

  1. 1
    Выбрать нужную операцию

    Откройте JSON Schema. Переключите действовать на Проверьте с помощью Schema. Конверт — в первое поле, схему выше — во второе. source равен foundation-models или app-intent. complete — булево значение. parameters — объект или null.

  2. 2
    Прогнать три плохих документа

    Поставьте source равным siri, complete — строке true, или удалите parameters. Все три не проходят. Незаконченный конверт, где complete равен false, а parameters — null, проходит. Прохождение не значит вызывать perform().

  3. 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.

Назад в блог