블로그 • App Intents

iOS 27 Agent는 서드파티 앱을 어떻게 호출하나: API 전에 App Intent JSON을 검증한다

시스템이 서드파티 앱을 호출할 때 입구는 선언한 AppIntent다. 파라미터가 perform()에 들어온 뒤, API 전에 JSON으로 남긴다. 타입, 열거, 필수가 맞지 않으면 실패 기록을 반환하고, 호출은 앱 안에서 멈춘다.

한 지시가 API까지 가는 길은 App Actions의 JSON 경로. Siri가 Agent가 되는지는 Siri AI는 AI Agent가 될까. 이 글은 앱 쪽만 다룬다. 어떤 파라미터를 선언하는지, Schema가 무엇을 막는지, 실패할 때 어떤 JSON을 반환하는지.

먼저 이것만: 호출이 앱에 도착해도 파라미터가 쓸 수 있다는 뜻은 아니다. 아래는 계약 선언, Schema 하나, 실패 셋, 모양이 맞아도 멈추는 경우, 실패를 돌려주는 방법 순이다.

먼저 파라미터 계약을 선언한다

서드파티 앱의 입구는 AppIntent 이다. 제목을 주고 perform()을 구현한다. 시스템이 이 동작을 찾는 것은 선언했기 때문이다. 선언이 이번 파라미터로 API를 호출해도 된다는 증거는 아니다.

각 파라미터는 @Parameter로 표시한다. 설명은 App Intent에 파라미터 추가하기. 이 예는 셋만 받는다. 상품 id, 수량, 수령 방식. 이름, 설명 문장, 모델이 더한 키는 계약에 없다.

  1. 1
    Intent 이름을 고정한다

    후보 JSON의 intent는 PlaceOrderIntent만 허용한다. 다른 이름은 이번 호출이 다른 동작에 갔다는 뜻이다.

  2. 2
    파라미터 셋을 적는다

    itemId는 문자열, quantity는 정수, fulfillment는 pickup 또는 delivery만 된다. 하나라도 없으면 후보는 미완성이다.

  3. 3
    perform() 앞에서 먼저 검증한다

    검증에 실패하면 아래 실패 기록을 반환한다. 통과하고, id가 해석되며, 수량이 1 이상일 때만 API로 간다.

같은 필드를 Schema로 쓴다

아래 후보와 Schema는 자신의 계약이지 Apple의 전송 형식이 아니다. JSONNote의 JSON Schema는 타입, 열거, 필수를 본다. 수량이 1 이상이어야 한다거나 id가 존재해야 한다는 규칙은 이 Schema로 표현하지 못한다. 판단은 앱에서 한다.

다음 확인으로 갈 수 있는 후보. fulfillment는 pickup 또는 delivery.
{
  "intent": "PlaceOrderIntent",
  "parameters": {
    "itemId": "sku_42",
    "quantity": 1,
    "fulfillment": "pickup"
  }
}
첫 칸에 후보, 둘째 칸에 이 Schema. 작동하다 를 Schema로 확인하세요 로 바꾼다.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["intent", "parameters"],
  "properties": {
    "intent": { "enum": ["PlaceOrderIntent"] },
    "parameters": {
      "type": "object",
      "required": ["itemId", "quantity", "fulfillment"],
      "properties": {
        "itemId": { "type": "string" },
        "quantity": { "type": "integer" },
        "fulfillment": { "enum": ["pickup", "delivery"] }
      }
    }
  }
}

파라미터 타입을 맞추는 방법은 앱 동작 필드와 JSON Schema. 이 글은 그 표를 반복하지 않는다. 검증이 실패할 때 앱이 무엇을 반환하는지만 쓴다.

실패하는 후보 셋

후보에서 바꾼 것왜 실패하나
fulfillment를 ship으로 한다pickup도 delivery도 아니다.
quantity를 문자열 1로 한다계약이 요구하는 것은 정수다.
itemId를 지운다필수가 빠져 후보가 미완성이다.

quantity를 0으로 바꿔도 타입은 정수라 이 Schema는 통과한다. 통과가 주문은 아니다. 0개는 앱이 거부한다.

모양이 맞아도 멈춘다

수량이 1 미만일 때 남기는 실패 기록. dialog는 사용자에게 보여줄 한 문장이다.
{
  "intent": "PlaceOrderIntent",
  "status": "rejected",
  "reason": "invalid_parameter",
  "field": "quantity",
  "dialog": "Quantity must be at least 1."
}

itemId가 빈 문자열이 아니어도 Schema는 통과한다. 다음으로 자신의 데이터에서 이 id를 해석한다. 해석되지 않으면 field를 itemId로 한 rejected를 하나 더 쓴다. 상품 이름으로 id를 만들지 않고, API도 호출하지 않는다.

실패를 어떻게 돌려주나

필수가 빠졌으면 그 파라미터의 requestValue로 다시 묻고, 빈 값으로 API를 호출하지 않는다. 값이 잘못되면 perform()을 멈추고 IntentDialog 로 사용자가 읽을 수 있는 한 문장을 쓴다. 위 JSON은 이번 결정을 위해 남기는 기록이다.

경우기록의 statusAPI
fulfillment가 없다needs_value다시 묻고 API는 호출하지 않는다
fulfillment가 shiprejectedpickup 또는 delivery만 된다고 알린다
quantity가 0rejected모양이 맞아도 멈춘다
itemId가 해석되지 않는다rejected상품 이름으로 id를 대신하지 않는다

needs_value는 아직 물어야 한다는 뜻이다. rejected는 이 후보를 실행할 수 없다는 뜻이다. 둘 다 API로 가지 않는다. 사용자가 보는 것은 dialog의 한 문장이다. 로그에는 JSON 전체를 남겨, 나중에 어느 필드가 막았는지 맞출 수 있게 한다.

다른 글과의 역할 나누기

지시 경로 글교환 봉투 글이 글
답하는 것문구가 어떻게 API에 닿는지모델 값과 파라미터 사이에 어떤 JSON을 남기는지앱이 어떻게 선언하고, 검증하고, 실패를 돌려주는지
여기서 찾지 말 것음성에서 API까지의 단계를 다시content와 parameters라는 객체 둘상품 이름으로 엔티티 id를 대신하는 것

JSONNote에서 이번 호출을 막는다

  1. 1
    동작을 고른다

    JSON Schema를 연다. 작동하다 를 Schema로 확인하세요 로 바꾼다. 첫 칸에 후보, 둘째 칸에 위 Schema를 붙인다. intent는 PlaceOrderIntent만. fulfillment는 pickup 또는 delivery만. quantity는 정수.

  2. 2
    나쁜 데이터 셋을 부딪힌다

    fulfillment를 ship으로 하거나, quantity를 문자열 1로 하거나, itemId를 지운다. 이 셋은 실패한다. quantity를 0으로 바꾸면 통과한다. 통과한 뒤에도 rejected를 쓰고 API는 호출하지 않는다.

  3. 3
    1과 0을 비교한다

    JSON 비교 Diff. 첫 칸에 quantity가 1인 parameters, 둘째 칸에는 quantity를 0으로 바꾼 같은 객체를 붙인다. 차이는 이 숫자다. 0도 Schema는 통과한다. API를 막는 것은 실패 기록이지, 이번 모양 확인이 아니다.

자주 묻는 질문

시스템이 이미 파라미터를 앱에 넘겼어도 다시 검증하나?

검증한다. 시스템이 넘긴 것은 한 번의 호출이지, 이미 검증된 API 요청이 아니다. perform() 앞에서 이 JSON을 본다. 통과하지 않으면 계속하지 않는다.

Schema를 통과하면 API를 호출해도 되나?

아직이다. 이 Schema는 타입, 열거, 필수만 본다. quantity가 0이어도 통과한다. itemId가 문자열이어도 통과한다. 수량이 1 이상이고 id가 해석될 때만 API를 호출한다.

파라미터가 없으면 오류를 반환하나, 다시 묻나?

다시 묻는다. status는 needs_value로 하고, requestValue로 사용자에게 그 값을 요청한다. 올바른 값이 오기 전에는 API를 호출하지 않는다. 값이 왔지만 잘못되면 rejected로 쓴다.

한 지시의 JSON 경로 글과 같은 글인가?

같은 글이 아니다. 그 글은 한 지시를 문구에서 API까지 따라간다. 이 글은 앱 안에 머문다. 파라미터 계약, Schema, 실패할 때 반환하는 JSON. API는 rejected도 needs_value도 쓰지 않았을 때만 실행된다.

실패 기록은 Apple의 반환 형식인가?

Apple의 형식이 아니다. Apple은 requestValue와 IntentDialog로 결과를 시스템과 사용자에게 넘긴다. 이 JSON은 자신의 로그다. status, reason, field, dialog. 남겨 두면 왜 API를 호출하지 않았는지 JSONNote에서 나중에 맞출 수 있다.

정리

서드파티 앱이 호출될 때 먼저 있는 것은 파라미터 계약이고, 다음에 있는 것은 실패할 수 있는 JSON이다. 실패 기록은 이번 호출을 API 전에 멈추고, 사용자가 읽을 수 있는 dialog 한 문장을 남긴다.

결론: 먼저 Intent 파라미터를 선언한다. Schema로 타입, 열거, 필수를 막는다. 값이 없으면 needs_value를 반환한다. 값이 잘못되면 rejected를 반환한다. 둘 다 API를 호출하지 않는다. 수량이 1 미만이거나 id가 없으면 모양이 맞아도 멈춘다.

다음: 후보 파라미터가 Schema를 통과하게 한 뒤, 이번 호출을 계속할지 실패를 반환할지 정한다.

블로그로 돌아가기