튜토리얼
Apple AI Agent는 App, API, 도구를 어떻게 연결하나요? JSON의 역할은?
시스템 Agent는 App Intents, App 내 모델은 Foundation Models의 Tool. JSON은 장식이 아니라 인수 계약, 구조화 출력, HTTP 페이로드가 공유하는 형태입니다.
「Apple AI Agent」는 종종 제품명처럼 쓰입니다. 실제로는 최소 두 가지 완전히 다른 호출 체인을 만납니다. 하나는 시스템 수준 Apple Intelligence가 사용자 대신 App과 동작을 고르는 경로, 다른 하나는 App이 on-device 모델을 돌리고 모델이 어떤 도구를 부를지 정하는 경로입니다. 둘 다 App, API, 도구를 만나지만 세션 소유, 인수 생성 주체, JSON이 나타나는 계층은 다릅니다. 이 세 층을 나눠야 Schema, 로그, 트러블슈팅에실마리이 생깁니다.
먼저 누가 모델을 돌리는지
먼저 물어보세요. 이번 추론은 누가 시작했나요? 사용자가 Siri나 시스템 수준 Apple Intelligence에 말하면 모델은 시스템 쪽(기기 또는 Private Cloud Compute)에서 돌고, App은 라우팅된 기능 제공자일 뿐입니다. 이때 노출하는 것은 App Intents: 형식화된 동작, 엔터티, 인수. 시스템 Agent가 호출 시점을 정하고 코드가 perform합니다. 공식 입문:App Intents 문서.
사용자가 이미 App 안에 있고 Foundation Models로LanguageModelSession를 시작했다면 다른 체인입니다. 모델은 프로세스가 구동하고 도구는 주입합니다. 세션이 이미 App 안에 있으므로 시스템이 「어떤 App을 열지」 고르지 않습니다. 도구는 연락처, 캘린더, 자체 네트워크 API를 호출하고 결과를 transcript에 씁니다. 프레임워크 설명:Foundation Models. 세션 단위 도구 호출은 WWDC25Meet the Foundation Models framework.
세 번째 패턴이 늘고 있습니다. 외부 호스트(Claude, ChatGPT, 자체 Agent)가 MCP 또는 HTTPS로 서비스를 호출합니다. 모델은 Apple 스택에서 돌지 않지만 페이로드는 거의 항상 JSON입니다. 같은 도메인 함수가 App Intents, Foundation Models Tool, HTTP에 동시에 서비스할 수 있고 차이는 어댑터뿐입니다.
| 호출 체인 | 모델 실행 주체 | App이 제공하는 것 |
|---|---|---|
| 시스템 Agent | Apple Intelligence | App Intents / App Entity |
| App 내 Agent | LanguageModelSession | Tool 프로토콜과 @Generable 인수 |
| 외부 Agent | 서드파티 호스트 | JSON-RPC 또는 REST JSON |
세 체인을 하나의 「만능 Agent」 그림으로 그리지 마세요. 디버깅 때 먼저 어느 체인인지 찾으세요. 시스템 라우팅 실패는 Intent 선언과 인수 타입, App 내 도구 무작위 호출은 name, description, Arguments 생성 가능 형태, 외부 API 불일치는 HTTP JSON과 Schema. 섞으면 「모델이 막 돌아다닌다」고만 느껴집니다.
App Intents: 시스템 Agent가 App에 닿는 방법
시스템 Agent에게 App은 「열어 구경」이 아니라 발견 가능한 기능 카탈로그입니다. AppIntent로 동작 이름, 자연어 설명, 인수 타입, 결과를 선언합니다. Spotlight, Shortcuts, Siri, Apple Intelligence가 같은 카탈로그를 씁니다. 사용자가 「이 송장을 지급 완료로 표시」라고 하면 시스템은 발화를MarkInvoicePaid로 매핑해야 합니다. 모델이 UI를 탭하게 두면 안 됩니다.
연결점은 인수입니다. 시스템은 발화를 형식화된 값으로 모읍니다: enum, 날짜, AppEntity 참조. Swift에서는 산문이 아니라 구조체입니다. 프로세스나 프레임워크를 넘을 때도 직렬화 가능한 형태가 필요합니다. 디버깅, 로그, 서버 재생은 결국 JSON이나 속성 목록이 됩니다. Intent 인수를 「JSON 객체로 쓸 수 있는」 필드 집합으로 설계하면 API 어댑터가 훨씬 쉬워집니다.
흔한 선택: Intent가 네트워크을 직접 칠까? 짧은 동작은 perform 안에서 끝낼 수 있습니다. 인증, 페이징, 멱등이 필요하면 perform은 「인수 검증 + 도메인 서비스 호출」만 하고 서비스가 JSON 요청을 보냅니다. 시스템 Agent는 REST 경로를 알 필요 없고 Intent 성공/실패 의미만 필요합니다. 경로는 청구, 감사, 재시도가 API 계층에 있으므로 개발자가 알아야 합니다.
Foundation Models: App 내 모델이 도구 호출
App 내 Agent 연결은 「모델에 함수 설명서를 주는」 느낌에 가깝습니다. Tool: name, description, 그리고 @Generable Arguments. 프레임워크가 prompt에 넣고 모델이 호출 시점을 정합니다. 호출 시 먼저 인수를 생성하고 프레임워크가 call(arguments:)를 실행해 반환값(보통 String 또는 생성 가능 타입)을 transcript에 넣습니다. 모델이 최종 답을 씁니다. URL을 조립하게 하는 게 아니라 허용한 도구 집합에서 고르게 합니다.
인수 생성은 구조화 출력 경로이지 「JSON으로 답하세요」가 아닙니다. @Generable와 동적 Schema가 필드, enum, 중첩 객체를 디코드 단계에서 고정합니다. Swift에서는 형식 인스턴스를 얻습니다. 저장, 로그, URLSession 전달 시 JSON으로 인코딩하세요. Apple은 language understanding, structured output, tool calling으로 정리합니다. Foundation Models 개요 참고.
struct FindOrders: Tool {
let name = "findOrders"
let description = "Find recent orders by customer email and status."
@Generable
struct Arguments {
var email: String
var status: String
var limit: Int
}
func call(arguments: Arguments) async throws -> String {
// Call your domain API, then return a compact summary.
return "3 orders, latest is paid"
}
}
이 구간에서 모델은 필드명을 「지어내지」 않습니다. 반드시 email, status, limit를 채웁니다. description이 선택 여부를 결정합니다. 너무 넓으면 모든 일에 호출, 너무 좁으면 필요할 때 안 불립니다. 도구 출력도 절제: 주문 JSON 전체를 transcript에 넣으면 컨텍스트가 금방 찹니다. 요약을 반환하고 세부는 두 번째 도구로 ID를 가져오세요.
도구는 체인할 수 있습니다. 첫 도구 출력을 둘째 입력으로 쓰고 프레임워크가 순서대로 실행합니다. 도메인에서 멱등을 보장하세요: 같은 orderId를 두 번 지급 완료 처리해도 이중 청구되면 안 됩니다. Agent 루프는 DB 제약을 못 봅니다. JSON 인수가 맞아도 비즈니스가 맞다는 뜻은 아닙니다.
JSON이 하는 역할
Swift 타입은 컴파일 타임 계약, JSON은 런타임 계약입니다. Agent가 프로세스를 벗어나면——백엔드, 파일, 다른 모델, 테스트 픽스처——형태는 언어 중립 텍스트가 되어야 합니다. JSON은 이 체인에서 최소 세 역할을 합니다. 두 가지를 섞으면 「parse는 되는데 필드가 전부 틀림」이 됩니다.
1. 도구 인수 교환 형식
Foundation Models는 내부적으로 GeneratedContent로 구조화 값을 표현합니다. 디버깅에 가장 유용한 것은 「이 Arguments를 JSON으로 인코딩하면 어떻게 보이나」입니다. 이름, 옵션, 배열/객체는 안정적이어야 합니다. 인수 로그를 JSON으로 남겨야 프로덕션 API 로그와 맞출 수 있습니다: 같은 customerId가 서버에 실제로 갔는지.
{
"email": "ada@example.com",
"status": "paid",
"limit": 5
}
2. 구조화 출력 Schema
도구 없이 송장 요약만 원해도 Schema가 필요합니다: 필수 키, enum, 금액이 number인지 string인지. Apple 쪽은 @Generable; 클라우드 모델은 보통 JSON Schema. 같은 도메인 객체를 두 선언이 다루면 필드 표를 공유해 App은 totalCents, API는 amount. Schema 작성 공통 규칙: Understanding JSON Schema.
3. App에서 API로의 페이로드
도구의 call가 네트워크에 닿으면 JSON이 HTTP 본문입니다. Agent는 API 설계를 대체하지 않습니다. 인증 헤더, 멱등 키, 오류 객체는 여전히 정의해야 합니다. 모델은 「업무 매개변수」만 채웁니다. 전송, 페이징, 속도 제한은 일반 백엔드 문제입니다. 오류도 안정 JSON(code, message, retryable)으로 설계해야 모델이 도구를 바꿀지 사용자에게 설명할지 결정합니다.
세 계층은 하나의 Schema 문서를 공유할 수 있습니다: Intent 인수 ⊂ Tool Arguments ⊂ HTTP body. 부분집합이면 테스트가 쉽습니다: 같은유효한 JSON 픽스처로 API, Tool, Intent 어댑터 순. 상위집합(HTTP에 내부 필드 세 개 더)도 되지만 모델에 내부 필드를 보이지 마세요. 공개하지 않을 키를 「친절히」 채우기 시작합니다.
HTTP API와 MCP 연결
App 내 도구가 REST를 칠 때는 명시적으로 인코딩하세요. GeneratedContent를 Data로 바로 보내지 마세요. Codable 모델로 매핑한 뒤 JSONEncoder. enum raw 값, 날짜 형식, 키 전략(snake_case)은 개발자가 통제합니다. 모델은 도메인 값, 인코더는 프로토콜 세부.
{
"tool": "markInvoicePaid",
"arguments": {
"invoiceId": "inv_9f2",
"paidAt": "2026-08-20T09:00:00Z"
}
}
MCP는 「도구명 + 인수 객체」를 JSON-RPC로 만듭니다. Apple 개발자에게는 세 번째 어댑터일 뿐: 같은 markInvoicePaid(invoiceId:paidAt:), App Intent는 perform, Foundation Models는 Tool.call, MCP는 tools/call. MCP용 비즈니스 로직을 따로 쓰지 마세요. 외부 Agent는 타입을 더 자주 틀립니다(숫자→문자열). 서버는 여전히 검증해야 합니다. 「이미 JSON」이라 믿으면 안 됩니다.
프라이버시 경계도 도구 설명에 쓰세요. on-device 모델이 캘린더를 읽는다고 이벤트를 서버에 POST하면 안 됩니다. 모델용 출력은 로컬 요약, 업로드 JSON은 사용자가 명확히 동기화할 때만. 시스템 Agent와 App 내 Agent 데이터 반출 정책은 따로 검토하고 로그에서 출처를 구분하세요.
출시 후 JSON 확인 방법
Agent 연동 때 또 한 줄 prompt보다 세 스냅샷을 나란히 보는 게 효과적입니다: 모델이 생성한 인수 JSON, 보낸 HTTP JSON, 서버가 돌려준 JSON. 형태가 다르면 문제는 거의 매핑 계층이지 「모델이 멍청해서」가 아닙니다. 로컬에서 포맷, 검증, diff가 Xcode 콘솔의 Optional보다 빠릅니다.
샘플 인수를 브라우저에서 먼저: JSON 포맷 parse 확인; JSON 검증 후행 쉼표와 잘못된 타입을 잡고 Intent / Tool / API 공통 필드를 JSON Schema 로 고정한 뒤 JSON Diff 「모델 출력」과 「실제 요청 본문」 diff. 클라우드 Structured Output API는 다르지만 「계약 먼저, 파싱 나중」은 같습니다. 사이트 Gemini API에서 JSON 출력하기.
픽스처 파일 세 개를 고정하세요: tool-args.valid.json, http-body.valid.json, http-error.json. CI에서 같은 Schema로 앞 두 개 검증. 오류 픽스처로 Agent가 실행 가능한 실패 이유를 말하는지, retryable: true.
자주 묻는 질문 FAQ
App Intents와 Foundation Models Tool이 같은 인수를 공유할 수 있나요?
도메인 모델은 공유 가능하지만 시스템이 Tool 프로토콜을 직접 실행한다고 가정하지 마세요. Intent는 시스템 발견과 권한, Tool은 현재 세션 prompt. 중간에 매핑을 두고 JSON 픽스처는 특정 프레임워크 타입이 아니라 도메인 모델을 검증합니다.
모델이 완전한 HTTP 요청을 직접 출력하지 않는 이유?
URL, Header, 서명은 모델이 지어내면 안 됩니다. 업무 필드만 채우고 클라이언트가 고정 템플릿으로 보냅니다. 한 번의 환각으로 잘못된 환경이나 인증이 갈 수 있습니다.
JSON과 @Generable이 충돌하나요?
아닙니다. @Generable은 Swift 생성/디코드, JSON은 언어·네트워크 공통 형태. 안정 필드 표를 먼저 만들고 Swift 매크로와 JSON Schema를 각각 생성하세요.
도구 반환값이 반드시 JSON인가요?
꼭 그렇지 않습니다. 모델용은 짧은 텍스트 요약, 서버용은 JSON. 두 출력을 한 문자열에 섞어 억지 parse하지 마세요.
요약과 다음 단계
Apple 스택의 Agent는 하나의 소켓이 아니라 세 갈래 선입니다: 시스템 Agent는 App Intents로 App 발견·호출, App 내 모델은 Tool로 코드 호출, 외부 호스트는 JSON으로 같은 도메인 서비스. JSON은 인수, Schema, API 페이로드가 같은 형태로 말하게 합니다. 타입 안전은 컴파일, Schema는 런타임, 비즈니스 검증이 옳고 그름.
다음: 도메인 동작 3~5개를 나열하고 각각 최소 JSON 객체와 Schema를 쓴 뒤 Intent, Tool, HTTP 중 어디에 둘지 정하세요. 형태가 안정된 뒤 prompt와 멀티 도구 오케스트레이션을 추가하세요.