블로그 • Foundation Models
Apple Intelligence 2026: Foundation Models와 App Intents 사이에서 교환하는 JSON
2026년, 같은 앱에는 입구가 둘이다. 기기의 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 | 당신의 앱 | 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
아래 봉투와 Schema는 자신의 계약이지 Apple의 전송 형식이 아니다. JSONNote의 JSON Schema는 타입, 열거, 필수 키를 본다. complete가 true일 때만 실행할 수 있다는 규칙은 이 Schema로 표현하지 못한다. 판단은 앱에서 한다.
{
"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
}
이 봉투는 위 Schema를 통과한다. parameters가 null을 허용하기 때문이다. 모양이 맞아도 실행하지 않는다. PartiallyGenerated는 뒤의 토큰이 이미 있는 필드를 아직 바꿀 수 있다는 뜻이다. complete가 true가 된 뒤, 완성된 content에서 parameters를 투영한다. 끝나기 전에 parameters를 객체로 써도 Schema는 막지 않는다.
| 봉투 | 왜 멈추나 |
|---|---|
| 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를 어떻게 넘기나
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를 비교한다.