블로그 • AI / App Intents

iOS 27에서 Siri App Actions가 늘면 JSON은 어떻게 흐르나? 음성 지시에서 API 실행까지

Siri 지시가 실행되는 이유는 먼저 App Intent 파라미터가 되고, AppIntent 의 perform() 에서 기기에 남을지 자신의 API를 호출할지 정하기 때문이다. 시스템은 음성을 그대로 서버에 보내지 않는다.

Siri가 Agent인지는 이전 글 에 있다. 이 글은 지시 하나만 따라간다. 문구가 Intent에 맞는 과정, 파라미터가 JSON이 되는 과정, 검증 뒤에 API를 호출하는 과정.

먼저 기억할 점: 음성은 요청 본문이 아니다. 채워진 Intent 파라미터가 본문이다. 아래는 문구, 파라미터 타입, 엔티티 id, 자신의 JSON, API 순서다.

지시 하나가 거치는 다섯 단계

공식 입구는 App Intents다. 설명은 App Intents 시작하기. Siri, 단축어, 앱 안 버튼은 같은 Intent를 쓴다. 다른 점은 누가 파라미터를 채우느냐다.

  1. 1
    문구가 맞는다

    음성 문구는 AppShortcutsProvider 에 등록한다. 시스템은 그 문구로 Intent를 고른다. 원문을 모델이 자유롭게 실행하게 두지 않는다.

  2. 2
    파라미터를 채운다

    Intent의 @Parameter 는 필요한 값과 필수 여부를 시스템에 알린다. 없으면 사용자에게 다시 묻는다. 짐작해서 보내지 않는다.

  3. 3
    엔티티를 해석한다

    사용자 데이터는 EntityQuery 가 id 로 해석한다. 파라미터에 들어가는 것은 엔티티이지, 그것을 설명하는 문장이 아니다.

  4. 4
    실행

    이미 해석된 값을 읽는 곳은 perform()이다. 기기에서 끝낼 수 있으면 거기서 끝낸다.

  5. 5
    Siri에 돌려준다

    결과는 IntentResult 또는 대화로 반환한다. 실패에도 말로 할 수 있는 이유가 있어야 한다. 서버 로그만 남기지 않는다.

파라미터는 세 종류

타입 설명은 App Intent에 파라미터 추가 에 있다. 타입을 잘못 고르면 뒤의 JSON이 안정되지 않는다.

타입쓰는 곳JSON에 넣을 때
문자열, 숫자, 날짜수량이나 메모 같은 단순 입력parameters에 그대로 넣는다
AppEnum컴파일 때 고정된 선택. 매장 수령인지 배송인지열거 값을 넣는다. 표시 문장을 기본 키로 쓰지 않는다
AppEntity바뀌는 사용자 데이터. 어떤 상품해석된 id만 넣는다

고정 집합은 AppEnum이다. 사용자가 늘리고 지우는 목록은 AppEntity와 쿼리다. 바뀌는 목록을 열거로 만들면 선택이 오래되는 순간 문구가 맞지 않는다.

엔티티가 남기는 것은 id

시스템이 필요로 하는 것은 보여주고 조회할 수 있는 엔티티다. 데이터의 원본은 자신의 데이터베이스다. 엔티티에는 최소한 안정된 id와 사람이 읽는 이름이 있어야 한다.

해석 뒤에 기록할 수 있는 엔티티 필드. 이후 API의 키는 id다.
{
  "entity": "OrderItem",
  "id": "sku_42",
  "display": "Oat milk"
}

entities(for identifiers:) 를 구현하고 id로 대상을 찾는다. 없으면 id를 만들어 계속하지 않는다.

직접 남기는 파라미터 JSON

아래 문서는 perform() 안에서 저장하고 API 호출 여부를 정하는 계약이다. Apple 요청 형식이 아니고, 시스템도 이 파일을 읽지 않는다.

해석이 끝난 주문 지시. resolved가 true인 것은 엔티티 쿼리가 성공했을 때뿐이다.
{
  "intent": "StartOrder",
  "source": "siri",
  "parameters": {
    "item_id": "sku_42",
    "quantity": 1
  },
  "resolved": true
}

언제 API를 호출하는가

  1. 1
    기기에서 끝낸다

    기기 상태만 바꾸는 동작은 perform()에서 끝낸다. 네트워크는 필요 없다.

  2. 2
    먼저 검증한다

    서버가 필요하면 먼저 Schema로 JSON을 검증한다. item_id가 없거나 quantity가 1보다 작으면 기기에서 멈춘다.

  3. 3
    그다음 네트워크로 나간다

    검증을 통과하고 resolved가 true일 때만 parameters를 자신의 API로 보낸다. 서버는 id로 자기 기록을 찾는다. 표시 이름은 믿지 않는다.

  4. 4
    실패는 말로 돌려준다

    API가 거절하거나 시간이 초과하면 Intent 대화로 끝나지 않았다고 알린다. Siri가 성공했다고 말하게 두지 않는다.

서드파티 앱이 Intent를 어떻게 선언하고 실패를 어떻게 돌려주는지는 다음 글이다. 이 글은 데이터 경로에서 멈춘다.

이전 글과의 분담

Siri는 Agent인가이 글
답하는 질문시스템이 스스로 계획하고 도구를 호출하는가지시 하나가 API까지 어떤 JSON을 거치는가
여기서 찾지 말 것제품 발표 글을 한 편 더음성 원문을 저장할 수 있는 요청 본문으로 보는 일

하드웨어와 시스템 기능의 제품 이야기는 iPhone 18이 하드웨어 업그레이드만이 아닌 이유 에 있다. 그 글은 파라미터 필드를 나누지 않는다.

JSONNote에서 이 JSON 검사

  1. 1
    포맷

    JSON 포맷 으로 파라미터 기록을 열고 끝의 쉼표가 없는지 확인한다.

  2. 2
    Schema로 빠진 필드를 막는다

    아래 Schema를 JSON Schema 에 붙인다. intent나 item_id가 없거나 quantity가 양의 정수가 아니면 API로 보내지 않는다.

  3. 3
    샘플 하나를 검증한다

    JSON 검증 에 실제 샘플을 붙인다. item_id 가 빈 문자열인지 본다.

파라미터 기록의 JSON Schema. source는 siri, shortcut, app만 허용한다.
{
  "$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()에 연결한다.

블로그로 돌아가기