블로그 • AI / App Intents
iOS 27에서 Siri App Actions가 늘면 JSON은 어떻게 흐르나? 음성 지시에서 API 실행까지
Siri 지시가 실행되는 이유는 먼저 App Intent 파라미터가 되고, AppIntent 의 perform() 에서 기기에 남을지 자신의 API를 호출할지 정하기 때문이다. 시스템은 음성을 그대로 서버에 보내지 않는다.
Siri가 Agent인지는 이전 글 에 있다. 이 글은 지시 하나만 따라간다. 문구가 Intent에 맞는 과정, 파라미터가 JSON이 되는 과정, 검증 뒤에 API를 호출하는 과정.
- 문구는 App Shortcut에 맞는다. 음성을 자유 텍스트로 실행하지 않는다
- 파라미터는 세 종류다. 내장 타입, 컴파일 때 고정된 AppEnum, 쿼리가 필요한 AppEntity
- 엔티티가 넘기는 것은 안정된 id다. 쿼리는 그 id로 데이터를 찾는다
- 파라미터 JSON은 직접 남긴다. Apple의 통신 형식이 아니다
- resolved가 false이거나 Schema가 실패하면 API를 호출하지 않는다. 대화는 Siri로 돌려준다
먼저 기억할 점: 음성은 요청 본문이 아니다. 채워진 Intent 파라미터가 본문이다. 아래는 문구, 파라미터 타입, 엔티티 id, 자신의 JSON, API 순서다.
지시 하나가 거치는 다섯 단계
공식 입구는 App Intents다. 설명은 App Intents 시작하기. Siri, 단축어, 앱 안 버튼은 같은 Intent를 쓴다. 다른 점은 누가 파라미터를 채우느냐다.
- 1문구가 맞는다
음성 문구는
AppShortcutsProvider에 등록한다. 시스템은 그 문구로 Intent를 고른다. 원문을 모델이 자유롭게 실행하게 두지 않는다. - 2파라미터를 채운다
Intent의
@Parameter는 필요한 값과 필수 여부를 시스템에 알린다. 없으면 사용자에게 다시 묻는다. 짐작해서 보내지 않는다. - 3엔티티를 해석한다
사용자 데이터는
EntityQuery가id로 해석한다. 파라미터에 들어가는 것은 엔티티이지, 그것을 설명하는 문장이 아니다. - 4실행
이미 해석된 값을 읽는 곳은 perform()이다. 기기에서 끝낼 수 있으면 거기서 끝낸다.
- 5Siri에 돌려준다
결과는
IntentResult또는 대화로 반환한다. 실패에도 말로 할 수 있는 이유가 있어야 한다. 서버 로그만 남기지 않는다.
파라미터는 세 종류
타입 설명은 App Intent에 파라미터 추가 에 있다. 타입을 잘못 고르면 뒤의 JSON이 안정되지 않는다.
| 타입 | 쓰는 곳 | JSON에 넣을 때 |
|---|---|---|
| 문자열, 숫자, 날짜 | 수량이나 메모 같은 단순 입력 | parameters에 그대로 넣는다 |
AppEnum | 컴파일 때 고정된 선택. 매장 수령인지 배송인지 | 열거 값을 넣는다. 표시 문장을 기본 키로 쓰지 않는다 |
AppEntity | 바뀌는 사용자 데이터. 어떤 상품 | 해석된 id만 넣는다 |
고정 집합은 AppEnum이다. 사용자가 늘리고 지우는 목록은 AppEntity와 쿼리다. 바뀌는 목록을 열거로 만들면 선택이 오래되는 순간 문구가 맞지 않는다.
엔티티가 남기는 것은 id
시스템이 필요로 하는 것은 보여주고 조회할 수 있는 엔티티다. 데이터의 원본은 자신의 데이터베이스다. 엔티티에는 최소한 안정된 id와 사람이 읽는 이름이 있어야 한다.
{
"entity": "OrderItem",
"id": "sku_42",
"display": "Oat milk"
}
entities(for identifiers:) 를 구현하고 id로 대상을 찾는다. 없으면 id를 만들어 계속하지 않는다.
직접 남기는 파라미터 JSON
아래 문서는 perform() 안에서 저장하고 API 호출 여부를 정하는 계약이다. Apple 요청 형식이 아니고, 시스템도 이 파일을 읽지 않는다.
{
"intent": "StartOrder",
"source": "siri",
"parameters": {
"item_id": "sku_42",
"quantity": 1
},
"resolved": true
}
intent는 Intent 타입 이름이다. 단축어 동작과 맞추기 쉽다.item_id는 엔티티 id다. 말로 한 표현이 아니다. 말은 바뀐다. id는 바뀌지 않아야 한다.resolved가 false면 아직 해석되지 않았거나 사용자가 확인을 취소한 것이다. API를 호출하지 않는다.- 원본 음성, 위치 궤적, 토큰은 이 JSON에 넣지 않는다.
언제 API를 호출하는가
- 1기기에서 끝낸다
기기 상태만 바꾸는 동작은 perform()에서 끝낸다. 네트워크는 필요 없다.
- 2먼저 검증한다
서버가 필요하면 먼저 Schema로 JSON을 검증한다. item_id가 없거나 quantity가 1보다 작으면 기기에서 멈춘다.
- 3그다음 네트워크로 나간다
검증을 통과하고 resolved가 true일 때만 parameters를 자신의 API로 보낸다. 서버는 id로 자기 기록을 찾는다. 표시 이름은 믿지 않는다.
- 4실패는 말로 돌려준다
API가 거절하거나 시간이 초과하면 Intent 대화로 끝나지 않았다고 알린다. Siri가 성공했다고 말하게 두지 않는다.
서드파티 앱이 Intent를 어떻게 선언하고 실패를 어떻게 돌려주는지는 다음 글이다. 이 글은 데이터 경로에서 멈춘다.
이전 글과의 분담
| Siri는 Agent인가 | 이 글 | |
|---|---|---|
| 답하는 질문 | 시스템이 스스로 계획하고 도구를 호출하는가 | 지시 하나가 API까지 어떤 JSON을 거치는가 |
| 여기서 찾지 말 것 | 제품 발표 글을 한 편 더 | 음성 원문을 저장할 수 있는 요청 본문으로 보는 일 |
하드웨어와 시스템 기능의 제품 이야기는 iPhone 18이 하드웨어 업그레이드만이 아닌 이유 에 있다. 그 글은 파라미터 필드를 나누지 않는다.
JSONNote에서 이 JSON 검사
- 1포맷
JSON 포맷 으로 파라미터 기록을 열고 끝의 쉼표가 없는지 확인한다.
- 2Schema로 빠진 필드를 막는다
아래 Schema를 JSON Schema 에 붙인다. intent나 item_id가 없거나 quantity가 양의 정수가 아니면 API로 보내지 않는다.
- 3샘플 하나를 검증한다
JSON 검증 에 실제 샘플을 붙인다.
item_id가 빈 문자열인지 본다.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["intent", "source", "parameters", "resolved"],
"properties": {
"intent": { "type": "string", "minLength": 1 },
"source": { "enum": ["siri", "shortcut", "app"] },
"resolved": { "type": "boolean" },
"parameters": {
"type": "object",
"required": ["item_id", "quantity"],
"properties": {
"item_id": { "type": "string", "minLength": 1 },
"quantity": { "type": "integer", "minimum": 1 }
}
}
}
}
자주 묻는 질문
시스템이 음성 전체를 내 서버로 보내나요?
이 경로에서는 보내지 않는다. 서버가 받는 것은 perform() 안에서 조립하고 검증한 파라미터 JSON이다. 음성은 기기 쪽 인식에 남는다.
파라미터에 상품 이름을 직접 쓰지 않는 이유는?
이름은 바뀌고 겹친다. AppEntity는 안정된 id를 쓰고, 쿼리는 그 id로 대상을 찾는다. API도 id만 본다.
Schema를 통과하면 API를 호출해도 되나요?
resolved도 본다. 모양은 맞지만 엔티티가 풀리지 않았다면 사용자가 아직 대상을 고르지 않은 것이다. 이때 API를 호출하면 확인 질문이 잘못된 주문이 된다.
기기 안 동작과 네트워크 동작은 어떻게 나누나요?
기기 데이터만 바꾸는 일은 perform()에서 끝낸다. 백엔드가 필요하면 파라미터 JSON을 쓰고 검증한 다음 요청한다. 둘 다 IntentResult로 결과를 돌려준다.
단축어에서 직접 채운 파라미터도 같은 경로인가요?
같은 AppIntent다. 다른 점은 누가 파라미터를 채우느냐다. Siri는 문구로, 단축어는 사용자나 이전 동작으로 채운다. 엔티티 해석, JSON, API 문은 같다.
정리
지시 하나의 데이터 경로는 이렇다. 문구가 Intent를 고르고, 파라미터가 타입별로 채워지고, 엔티티가 id가 되고, 자신이 JSON을 쓴 다음, 검증을 통과해야 API를 호출하고, 대화로 결과를 Siri에 돌려준다.
결론: 원본 음성을 올리지 않는다. id가 해석되기 전에는 요청하지 않는다. 실패는 서버 로그만 남기지 말고 사용자에게 말로 돌려준다.
다음: 이 파라미터 JSON을 Schema로 막은 다음 perform()에 연결한다.