View in English

  • 메뉴 열기 메뉴 닫기
  • Apple Developer
검색
검색 닫기
  • Apple Developer
  • 뉴스
  • 둘러보기
  • 디자인
  • 개발
  • 배포
  • 지원
  • 계정
페이지에서만 검색

빠른 링크

5 빠른 링크

비디오

메뉴 열기 메뉴 닫기
  • 컬렉션
  • 주제
  • 전체 비디오
  • 소개

더 많은 비디오

  • 소개
  • 요약
  • 자막 전문
  • 코드
  • Foundation Models 프레임워크 자세히 알아보기

    Foundation Models 프레임워크로 수준을 높여보세요. 가이드 생성이 내부에서 작동하는 방식을 알아보고 가이드, 정규 표현식 및 생성 스키마를 사용하여 구조화된 맞춤형 응답을 받아보세요. 맞춤형 경험을 위해 도구 호출을 사용하여 모델이 외부 정보에 자율적으로 액세스하고 작업을 수행할 수 있도록 하는 방법을 보여드립니다. 이 비디오를 최대한 활용하려면 먼저 ‘Foundation Models 프레임워크 만나보기'를 시청하는 것이 좋습니다.

    챕터

    • 0:00 - 서론
    • 0:49 - 세션
    • 7:57 - Generable
    • 14:29 - 동적 스키마
    • 18:10 - 도구 호출

    리소스

    • Generate dynamic game content with guided generation and tools
    • Human Interface Guidelines: Generative AI
      • HD 비디오
      • SD 비디오

    관련 비디오

    WWDC25

    • 코딩 실습: Foundation Models 프레임워크를 사용하여 앱에 온디바이스 AI 가져오기
    • Apple 플랫폼의 머신 러닝 및 AI 프레임워크 살펴보기
    • Foundation Models 프레임워크 만나보기
  • 비디오 검색…

    안녕하세요, 저는 Louis입니다 파운데이션 모델 프레임워크를 최대한 활용하는 방법을 알아볼게요

    파운데이션 모델 프레임워크가 온디바이스 대규모 언어 모델에 대한 직접적인 접근성과 편리한 Swift API를 제공한다는 점은 이미 알고 계실 것입니다 이 프레임워크는 macOS, iPadOS iOS, visionOS에서 사용할 수 있으며 온디바이스로 작동하므로 한 번만 가져오면 프로젝트에서 활용하실 수 있죠 이 비디오에서는 파운데이션 모델에서 세션이 작동하는 방법 Generable을 사용하여 구조화된 출력을 얻는 방법 런타임에 정의된 동적 스키마로 구조화된 출력을 얻는 방법 도구 호출로 모델이 맞춤 함수를 호출하도록 하는 방법을 다루겠습니다 간단한 내용부터 시작하죠 세션으로 텍스트를 생성해 볼게요

    현재 저는 카페에 관한 픽셀 아트 게임을 개발 중입니다 파운데이션 모델을 활용하여 게임 대화나 기타 내용을 생성하면 게임에 생동감이 더해져 재미있을 것 같습니다

    플레이어의 질문에 답하도록 모델에 요청해야 합니다 그렇게 하면 바리스타가 독특한 대사를 반환하게 되죠 이를 위해 맞춤형 명령어를 포함하는 LanguageModelSession을 만들겠습니다 이를 통해 세션에서 수행해야 할 일을 모델에 알릴 수 있습니다 프롬프트로는 사용자의 입력을 활용하면 되죠 이것만으로도 흥미로운 게임 요소를 만들 수 있습니다 바리스타에게 “여기서 일한 지 얼마나 됐나요”라고 물어볼게요 바리스타가 어떻게 답하는지 봅시다

    완전히 온디바이스로 생성된 답변입니다 정말로 놀랍네요 이 과정은 실제로 어떻게 진행될까요? 파운데이션 모델이 텍스트를 생성하는 방법과 주의해야 할 점을 자세히 살펴보겠습니다 세션에서 respond(to:)를 호출하면 먼저 세션의 명령어와 프롬프트를 가져옵니다 사용자의 입력이죠, 그러면 텍스트가 토큰으로 변환됩니다 토큰은 작은 서브스트링으로 때로는 단어이지만 보통 몇 개의 문자만으로 구성됩니다 대규모 언어 모델은 토큰 시퀀스를 입력으로 받고 새로운 토큰 시퀀스를 출력으로 생성합니다 파운데이션 모델이 정확히 어떤 토큰을 사용하는지 신경 쓰지 않아도 됩니다 API가 알아서 추상화하여 처리하거든요 하지만 토큰을 무작위로 사용해서는 안 됩니다 명령어와 프롬프트에서 토큰이 증가할수록 지연도 늘어납니다 응답 토큰을 생성하기 전에 모델은 모든 입력 토큰을 처리해야 합니다 또한 토큰 생성에는 계산이 필요합니다 출력이 길수록 생성하는 데 시간이 더 걸리는 이유죠

    LanguageModelSession은 스테이트풀에 속합니다 모든 respond(to:) 호출은 transcript에 기록되죠

    transcript는 특정 세션의 모든 프롬프트와 응답을 포함합니다

    이는 디버깅할 때 유용하며 해당 내용을 UI에 표시할 수도 있습니다

    그러나 세션의 최대 크기에는 한계가 있습니다 많은 요청을 생성하거나 큰 프롬프트를 입력하거나 큰 출력을 받는 경우 컨텍스트 한도에 도달할 수 있습니다

    사용 가능한 컨텍스트 크기를 세션이 초과하면 오류가 발생하죠 이 오류를 catch할 준비가 되어 있어야 합니다 게임에서 캐릭터와 대화 중이었는데 오류가 발생했네요 대화가 그냥 끝나네요 안타깝네요, 이 캐릭터와 친해지고 있었거든요 다행히 여러 방법으로 이 오류를 극복할 수 있습니다

    exceededContextWindowSize 오류를 catch할 수 있습니다

    이렇게 하면 아무 기록 없이 새로운 세션이 시작됩니다 그러나 이렇게 처리하면 게임에서 캐릭터가 갑자기 대화 전체를 잊게 됩니다

    현재 세션의 일부 transcript가 새 세션에서 이어지도록 할 수도 있습니다

    세션의 transcript에서 entry를 가져온 다음 새 entry 배열로 압축할 수 있죠

    게임 대화의 경우 세션 transcript의 첫 entry인 instruction을 가져올 수 있습니다 최종 entry인 성공적인 마지막 응답도 가져올게요 이 정보를 새 세션으로 전달하면 캐릭터는 다시 대화를 이어갈 수 있습니다 그런데 세션 transcript의 첫 entry는 초기 명령어를 포함한다는 점을 기억해 두세요 게임 캐릭터를 위한 transcript를 전달할 때 이 명령어를 포함해야 합니다

    transcript에서 관련 부분만 가져와 간단하면서도 효과적으로 문제를 해결할 수 있죠 하지만 항상 그렇게 간단하게 해결되지는 않습니다 entry가 더 많은 transcript가 있다고 가정할게요 항상 명령어를 전달하며 시작하는 것이 좋습니다 그러나 transcript의 여러 entry도 대화와 관련 있을 수 있죠 이 경우 transcript를 요약하는 것이 좋습니다

    외부 라이브러리를 사용하거나 파운데이션 모델로 transcript의 일부를 요약할 수 있습니다

    지금까지 세션의 transcript를 활용하는 방법을 다루었습니다 이제 응답이 실제로 어떻게 생성되는지 간단히 살펴보죠 이 게임에서 플레이어는 바리스타에게 다가가 다양한 질문을 던질 수 있습니다 그런데 새 게임을 시작한 후 같은 질문을 던졌는데 이전과 다른 출력이 나올 수 있습니다 왜 그럴까요? 샘플링 때문이죠

    출력을 생성할 때 모델은 토큰을 한 번에 하나씩 만듭니다 이를 위해 모델은 특정 토큰이 선택될 가능성을 뜻하는 확률 분포를 만듭니다 파운데이션 모델은 기본적으로 특정 확률 범위 내에서 토큰을 고릅니다 첫 토큰으로 ‘Ah’를 선택했다가 다른 상황에서는 ‘Well’을 선택할 수도 있습니다 이 과정은 생성되는 모든 토큰에 대해 진행됩니다 토큰을 선택하는 것을 샘플링이라고 합니다 파운데이션 모델은 기본적으로 무작위 샘플링을 채택하죠 게임과 같은 사용 사례에는 다양한 출력을 얻는 것이 좋습니다 하지만 반복이 가능한 데모를 빌드할 때처럼 결정론적 출력이 필요한 경우도 있죠 GenerationOptions API로 샘플링 메서드를 제어할 수 있습니다 greedy로 설정하면 결정론적 출력을 얻을 수 있죠 이렇게 설정하면 같은 프롬프트에 대해 동일한 출력을 얻을 수 있습니다 세션 상태도 동일하다면요 그러나 이 제어는 온디바이스 모델의 특정 버전에 대해서만 적용됩니다 OS 업데이트의 일부로 모델이 업데이트되면 greedy 샘플링을 사용해도 프롬프트에 의해 다른 출력이 반환될 수 있습니다 무작위 샘플링의 temperature도 조정할 수 있습니다 예를 들어 temperature를 0.5로 설정하면 출력이 약간만 달라집니다 더 높은 값으로 설정하면 같은 프롬프트에 대해서도 상당히 다른 출력이 생성될 수 있습니다 또한 프롬프트에서 사용자 입력을 가져올 때 해당 입력의 언어가 지원되지 않을 수 있습니다

    이때 unsupportedLanguageOrLocale 오류를 catch하면 됩니다 UI에서 맞춤형 메시지를 표시하고 싶을 때 유용할 수 있습니다 또한 모델이 특정 언어를 지원하는지 확인하는 API도 있습니다 사용자가 현재 이용 중인 언어가 지원되는지 확인할 수 있고 지원되지 않을 때 고지를 표시할 수도 있습니다 지금까지 세션을 소개해 드렸습니다 세션을 실행하면 기록이 transcript에 저장됩니다 샘플링 매개변수를 설정하여 세션 출력의 무작위성을 제어할 수 있습니다 더 근사한 기능을 만들어 볼까요? 파운데이션 모델을 통해 플레이어가 걸어 다닐 때 플레이 불가능한 캐릭터인 NPC를 생성할 수 있습니다 이번에는 더 복잡한 출력이 필요합니다 NPC에서 일반 텍스트 외에도 이름과 커피 주문도 구현해야 하거든요 이때 Generable을 사용할 수 있습니다 대규모 언어 모델에서 구조화된 출력을 얻는 것은 쉽지 않습니다 예상하는 특정 필드를 사용하여 모델을 실행한 다음 해당 필드를 추출하는 파싱 코드를 추가할 수 있습니다 하지만 이 방법은 관리하기 어렵고 매우 취약합니다 유효한 키를 반환하지 않아 메서드 전체가 실패할 수 있습니다 다행히 파운데이션 모델에는 Generable이라는 우수한 API가 있죠 struct에 @Generable 매크로를 적용할 수 있습니다 Generable은 무엇일까요? 실제로 존재하는 단어이긴 한가요? 네, 사전에도 있습니다

    Generable은 모델이 Swift 타입을 통해 구조화된 데이터를 생성하도록 하는 간단한 방법입니다 이 매크로는 컴파일 시점에 스키마를 생성합니다 모델은 예상되는 구조를 생성하기 위해 이 스키마를 사용할 수 있죠 이 매크로는 또한 세션을 대상으로 요청할 때 자동으로 호출되는 이니셜라이저를 만듭니다

    이제 struct의 인스턴스를 생성할 수 있습니다 이전과 마찬가지로 세션의 respond 메서드를 호출할게요 이번에는 생성할 타입을 모델에 알리는 generating 인수를 전달합니다 심지어 파운데이션 모델은 Generable 타입에 대한 세부 정보를 모델이 학습한 특정 형식으로 프롬프트에 자동으로 포함합니다 Generable 타입에 어떤 필드가 있는지 모델에 알려주지 않아도 됩니다 이제 게임에서 근사한 NPC를 만날 수 있습니다

    생각보다 Generable은 훨씬 강력합니다 낮은 수준에서 Generable은 제약 디코딩을 사용합니다 이는 모델이 특정 스키마를 따르는 텍스트를 생성하도록 하는 기술입니다 매크로가 생성하는 그 스키마 말이죠 앞서 본 것처럼 LLM은 토큰을 생성하며 이는 나중에 텍스트로 변환됩니다 Generable을 사용하면 이 텍스트가 type-safe한 방식으로 자동으로 파싱됩니다 토큰은 디코딩 루프라고 하는 루프에서 생성됩니다 제약 디코딩을 사용하지 않으면 모델이 유효하지 않은 필드 이름을 생성할 수 있습니다 예를 들어 name 대신 ‘firstName’을 생성할 수 있죠 이 필드는 NPC 타입으로 파싱될 수 없습니다

    하지만 제약 디코딩을 사용하면 이와 같은 모델의 구조적 실수를 방지할 수 있습니다 토큰이 생성될 때마다 모델의 어휘에 포함된 모든 토큰에 대해 확률 분포가 생성됩니다 제약 디코딩은 유효하지 않은 토큰을 마스킹합니다 따라서 모델은 아무 토큰을 선택할 수 있는 게 아니라 스키마에 따라 유효한 토큰만 선택할 수 있게 됩니다

    이 모든 과정은 모델의 출력을 수동으로 파싱할 필요 없이 진행됩니다 그러므로 정말 중요한 일에 시간을 투자할 수 있습니다 카페의 가상 손님과 대화하는 것처럼요 Generable은 온디바이스 LLM으로부터 출력을 얻는 가장 효과적인 방법이며 다른 작업도 처리할 수 있습니다 Generable은 struct뿐만 아니라 enum에서도 활용할 수 있습니다 카페 손님과의 만남이 더 생동감이 넘치도록 해볼게요 여기에 case가 2개 있는 Encounter enum을 추가했습니다 enum의 case는 연관된 값도 포함할 수 있습니다 이 점을 활용하여 커피 주문이나 매니저와 대화하고 싶어 하는 사람을 만들어 보죠

    이제 게임에서 어떤 캐릭터를 만날 수 있는지 보죠

    와, 정말 커피가 필요하신 분이네요

    상대하기 어려운 손님도 존재하기 마련이죠 그러므로 NPC에 레벨을 추가하여 난이도를 높여 보겠습니다 Generable은 Int를 포함한 대부분의 Swift 타입을 지원합니다 level 속성을 추가해 보겠습니다 하지만 정수를 생성하고 싶지는 않습니다 레벨이 특정 범위 내에 있어야 한다면 Guide를 사용해 지정할 수 있습니다 속성에 Guide 매크로를 적용하고 범위를 전달하면 됩니다 아까 말했듯이 모델은 제약 디코딩을 통해 이 범위에 해당하는 값을 출력합니다

    이참에 NPC에 속성 배열도 추가해 보겠습니다

    여기서도 Guide를 사용하여 이 NPC 배열에 정확히 3개의 속성이 포함되도록 지정할 수 있습니다 참고로 Generable 타입의 속성은 소스 코드에서 선언된 순서대로 생성됩니다 여기서는 name이 먼저 생성되고 그다음에 level과 attribute, 마지막으로 encounter가 생성되죠

    특정 속성의 값이 다른 속성의 영향을 받는 경우 이 순서가 중요할 수 있습니다 또한 전체 출력이 생성될 때까지 기다리지 않고 속성별로 스트리밍할 수도 있습니다 게임이 이제 꽤 재미있네요 곧 친구들과 공유할 수 있겠습니다 그런데 NPC의 이름이 제 마음에 들지 않습니다 NPC가 성과 이름을 갖도록 구성해 보겠습니다

    여기서 Guide를 사용할 수도 있지만 이번에는 Natural Language 설명만 제공하겠습니다

    NPC의 이름이 ‘full name’이어야 한다고 말할 수 있죠 세션을 실행하는 다른 방법이기도 합니다 프롬프트에서 여러 속성을 설명하는 대신 Generable 타입에서 바로 설명할 수 있습니다 이렇게 하면 각 설명이 어떻게 연관되어 있는지 모델이 이해할 수 있죠 게임을 확인해 보니 NPC에 새 이름이 생겼습니다 다양한 타입에 적용할 수 있는 가이드를 간략히 설명할게요

    int와 같은 일반적인 숫자 타입의 경우 최솟값, 최댓값 또는 범위를 지정할 수 있습니다 Array로는 개수를 제어하거나 Array의 element 타입에 대한 가이드를 지정할 수 있습니다

    String의 경우 anyOf를 사용하여 모델이 배열에서 선택하도록 지정할 수 있으며, 정규 표현식 패턴으로 제한할 수도 있습니다

    정규 표현식 패턴 가이드는 특히 강력합니다 대응하는 텍스트를 찾기 위해 정규 표현식을 사용해 보셨을 수 있죠 그런데 파운데이션 모델을 사용하면 생성될 문자열의 구조를 정규 표현식 패턴으로 정의할 수 있습니다 예를 들어 이름을 특정 접두사 집합으로 제한할 수 있습니다 심지어 RegexBuilder 구문도 사용할 수 있습니다

    정규 표현식에 대한 흥미가 생겼다면 몇 년 전의 ‘Swift Regex 소개’ 세션을 시청해 보세요 요약하자면, Generable은 struct와 enum에 적용할 수 있는 매크로로 이를 통해 모델로부터 구조화된 출력을 안정적으로 얻을 수 있습니다 파싱에 신경을 쓸 필요가 없으며 더 구체적인 출력을 얻기 위해 속성에 가이드를 적용할 수 있습니다 Generable은 컴파일 시점에 구조를 알고 있을 때 유용합니다 매크로가 스키마를 생성해 주며 출력으로 타입의 인스턴스를 얻게 됩니다 하지만 런타임 시점에 구조를 알게 되는 경우도 있습니다 이때 동적 스키마가 도움이 됩니다 플레이어가 게임에서 이동하며 만날 수 있는 엔티티를 동적으로 정의할 수 있는 레벨 생성기를 게임에 추가하고자 합니다 예를 들어 플레이어가 수수께끼 구조를 만들 수 있죠 수수께끼는 질문과 여러 선택지로 구성됩니다 컴파일 시점에서 이 구조를 알고 있다면 Generable struct를 정의하면 됩니다 그러나 레벨 생성기는 플레이어가 원하는 대로 구조를 만들 수 있도록 하죠

    DynamicGenerationSchema를 사용해 런타임에 스키마를 만들 수 있습니다 컴파일 시점에 정의된 struct처럼 동적 스키마에는 속성 목록이 있습니다 플레이어의 입력을 받는 레벨 생성기를 추가할 수 있습니다

    각 속성에는 name과 타입을 정의하는 자체 schema가 있습니다 이 schema는 String처럼 내장된 타입을 포함한 모든 Generable 타입에 사용할 수 있습니다

    동적 스키마는 배열을 포함할 수 있으며 이 배열의 요소에 대해 스키마를 지정할 수 있습니다 또한 동적 스키마는 다른 동적 스키마에 대한 참조를 포함할 수 있습니다 그러므로 이 배열은 런타임에도 정의되는 맞춤형 스키마를 참조할 수 있습니다

    사용자의 입력으로부터 속성이 2개인 riddle 스키마를 만들 수 있습니다 첫 속성은 string 속성인 question이고 두 번째는 customType의 array 속성인 Answer입니다 이제 정답을 만들겠습니다 정답에는 string 및 boolean 속성이 있습니다 참고로 수수께끼의 answers 속성은 이름에 따라 answer 스키마를 참조합니다 이제 DynamicGenerationSchema 인스턴스를 만들 수 있습니다 각 동적 스키마는 독립적입니다 즉, 수수께끼 동적 스키마는 실제로는 정답의 동적 스키마를 포함하지 않습니다 추론을 수행하기 전에 먼저 동적 스키마를 유효한 스키마로 변환해야 합니다 존재하지 않는 타입 참조와 같은 불일치가 동적 스키마에 있으면 오류가 발생할 수 있습니다

    유효한 스키마가 있으니 이제 평소처럼 세션을 실행하면 됩니다 그런데 여기서는 출력 타입이 GeneratedContent 인스턴스입니다 이 인스턴스는 동적 값을 포함합니다 이 값은 동적 스키마의 속성 이름으로 쿼리할 수 있습니다 다시 언급하자면 Foundation Models는 가이드 생성을 사용하여 출력이 스키마와 일치하는지 확인합니다 예상치 못한 필드는 만들지 않습니다 그러므로 동적이라 해도 개발자가 출력을 수동으로 파싱할 필요가 없죠

    이제 플레이어가 NPC를 만나면 모델이 이 동적 콘텐츠를 생성할 수 있습니다 이 콘텐츠는 동적 UI를 통해 표시되죠 어떤 수수께끼가 나오는지 보겠습니다 이것은 진하거나 연해, 쓰거나 달아 잠을 깨워주면서 열기를 가져다줘 이것은 무엇인가요? 커피일까요, 핫초코일까요? 정답은 커피 같네요 정답입니다 플레이어들이 재미있고 다양한 레벨을 만들면서 게임을 즐길 것 같습니다 다룬 내용을 요약할게요 Generable 매크로를 사용하면 컴파일 시점에 정의된 Swift 타입으로부터 구조화된 출력을 쉽게 생성할 수 있습니다 파운데이션 모델은 자율적으로 스키마를 처리하고 GeneratedContent를 자체 타입 인스턴스로 변환합니다 동적 스키마는 유사하게 작동하지만 훨씬 더 많은 제어를 제공합니다 런타임에 스키마를 완전히 제어할 수 있으며 GeneratedContent에 바로 접근할 수 있습니다 다음으로 도구 호출을 살펴보겠습니다 이 기능은 개발자가 만든 함수를 모델이 호출하도록 합니다 이번에는 다운로드 가능 콘텐츠인 DLC를 만들어 더 개인화된 게임 경험을 생성하려고 합니다 도구 호출을 사용하면 모델이 자율적으로 정보를 가져올 수 있죠 플레이어의 연락처와 캘린더를 통합하면 더 재미있는 게임이 될 것 같습니다 서버 기반 모델에서는 이렇게 할 수 없습니다 게임이 개인 데이터를 업로드하는 것을 플레이어가 좋아하지 않을 테니까요 하지만 파운데이션 모델은 완전히 온디바이스로 작동하므로 개인 정보를 보호하면서 통합할 수 있죠

    도구 정의는 간단합니다 Tool 프로토콜을 사용하면 되죠 먼저 이름과 설명을 추가합니다 이 내용은 API가 프롬프트에 자동으로 입력합니다 모델이 언제, 얼마나 자주 도구를 호출하는 것이 좋을지 판단하게 되죠

    짧으면서 읽기 쉬운 영어 이름을 도구 이름으로 사용하세요 약어는 피하고 너무 긴 설명은 쓰지 마세요 구현 방식도 설명하지 마세요 이 문자열은 프롬프트에 그대로 입력되기 때문입니다 문자열이 길수록 토큰 수가 증가해 지연이 늘어날 수 있습니다 대신 findContact처럼 이름에 동사를 사용해 보세요 설명은 한 문장으로 간결하게 작성하세요 다양한 변형을 시도하여 도구에서 가장 잘 작동하는 변형을 찾는 것도 중요합니다

    다음으로 도구의 입력을 정의합니다 도구가 밀레니얼 세대처럼 특정 연령대의 연락처를 가져오도록 할 것입니다 게임 상태에 따라 모델이 재미있는 case를 고를 수 있습니다 Arguments struct를 추가한 다음 Generable도 추가할게요 모델이 이 도구를 호출하기로 결정하면 입력 인수를 생성합니다 Generable을 사용하면 도구가 항상 유효한 입력 인수를 받게 됩니다 따라서 알파 세대처럼 이 게임에서 지원하지 않는 세대는 만들지 않습니다

    다음으로 call 함수를 만듭니다 이 도구를 호출하기로 결정할 때 모델이 이 함수를 호출합니다 이 예시에서는 Contacts API를 호출한 다음 해당 쿼리에 대한 연락처 이름을 반환합니다

    도구를 사용하기 위해 이 도구를 session 이니셜라이저로 전달하겠습니다 이 추가 정보가 필요할 때 모델이 도구를 호출하게 됩니다 이 방법은 직접 연락처를 가져오는 것보다 더 강력합니다 모델이 특정 NPC를 위해 필요할 때만 도구를 호출하며 게임 상태에 따라 재미있는 입력 인수를 선택할 수 있기 때문이죠 NPC의 연령대처럼요

    참고로 여기서는 여러분에게 이미 익숙할 수 있는 일반적인 Contacts API를 사용하고 있습니다 처음 호출될 때 도구는 평소처럼 플레이어에게 권한을 요청합니다 플레이어가 연락처 접근을 허용하지 않더라도 파운데이션 모델은 기존 방식대로 콘텐츠를 생성할 수 있습니다 대신 접근이 허용되면 더 개인화된 콘텐츠를 제공할 수 있게 되죠

    다른 NPC를 만날 때까지 게임에서 이동해 볼게요 이번에는 제 연락처의 이름이 등장할 것입니다 Naomy가 있네요 무슨 말을 하는지 볼까요?

    Naomy가 커피를 좋아한다는 것을 몰랐네요 LanguageModelSession은 도구의 인스턴스를 인수로 받습니다 즉, 개발자는 도구의 라이프사이클을 제어할 수 있죠 이 도구의 인스턴스는 세션 전체에서 동일하게 유지됩니다 현재 이 예시에서는 FindContactsTool로 캐릭터를 무작위로 생성하므로 게임에서 같은 연락처를 또 가져올 수도 있습니다 게임을 보니 Naomy가 여러 명 있네요 이 문제를 해결해야 합니다 한 명만 있어야죠 문제를 해결하기 위해 게임에서 이미 사용한 연락처를 추적할 수 있죠 FindContactTool에 상태를 추가할 수 있습니다 이를 위해 먼저 FindContactTool을 클래스로 변환합니다 이렇게 하면 call 메서드에서 상태를 바꿀 수 있습니다 다음으로, 선택된 연락처를 추적하면 됩니다 call 메서드에서 같은 연락처를 다시 선택하지 않게 되죠

    연락처에 등록된 사람들의 이름이 NPC 이름이 되었네요 하지만 대화가 아직 어색합니다 다른 도구로 이 작업을 마무리할게요 캘린더 접근을 위한 도구죠

    이 도구에서는 게임 내에서 진행 중인 대화상자에서 연락처 이름을 전달합니다 모델이 이 도구를 호출하면 도구에서 해당 연락처와 관련된 이벤트를 가져올 일, 월, 연도를 생성하도록 하죠 그런 다음 NPC 대화를 위해 이 도구를 세션에서 전달합니다

    이제 Naomy NPC에게 말을 걸어 요새 무슨 일이 있는지 물어보면 우리가 함께 계획한 실제 이벤트를 기반으로 답하죠

    와, 진짜 Naomy와 대화하는 것 같아요

    도구 호출이 어떻게 작동하는지 자세히 살펴보겠습니다 세션을 시작할 때 명령어를 포함한 도구를 전달합니다 이 예시에서는 오늘 날짜와 같은 정보가 포함되어 있습니다 사용자가 세션을 실행하면 모델이 텍스트를 분석합니다 이 예시에서 모델은 프롬프트가 이벤트를 요청하고 있음을 이해하므로 캘린더 도구를 호출하기로 결정합니다

    도구 호출을 위해 모델은 먼저 입력 인수를 생성합니다 이 경우 모델은 이벤트를 가져오기 위해 날짜를 생성해야 합니다 모델은 명령어와 프롬프트의 정보를 연결하여 도구 인수를 어떻게 채워야 하는지 이해합니다 이 예시에서는 명령어에 있는 오늘 날짜를 기반으로 ‘내일’이 무엇을 의미하는지 추론할 수 있습니다 도구의 입력값이 생성되면 call 메서드가 실행됩니다 도구가 빛을 발하는 순간입니다 모든 작업을 원하는 대로 처리할 수 있죠 하지만 세션에서는 도구가 값을 반환될 때까지 기다린 후에 추가 출력을 생성한다는 점에 유의하세요

    도구의 출력은 모델의 출력과 마찬가지로 transcript에 저장됩니다 그리고 도구 출력에 따라 모델은 프롬프트에 대한 응답을 생성할 수 있습니다 참고로 하나의 요청에서 도구를 여러 번 호출할 수도 있습니다 이 경우 도구 호출은 병렬로 실행됩니다 도구의 call 메서드를 통해 데이터에 접근할 때 이 점을 유의하세요 좋아요, 정말 재미있었네요 이제 제 게임은 개인 연락처와 캘린더를 기반으로 콘텐츠를 무작위로 생성합니다 데이터를 기기 밖으로 전송할 필요 없이 이 모든 것이 가능했습니다 요약하자면, 도구 호출은 모델이 요청 중에 외부 데이터에 접근하기 위해 코드를 호출할 수 있도록 합니다 이 데이터는 연락처와 같은 개인 정보나 웹 소스에서 가져온 외부 데이터일 수 있습니다 하나의 요청에서 도구를 여러 번 호출할 수 있다는 점을 기억하세요 모델은 컨텍스트에 따라 호출 여부를 결정합니다 도구는 병렬로 호출될 수 있으며 상태를 저장할 수 있습니다 많은 내용을 다루었습니다 다른 일을 하기 전에 커피 한 잔 드시는 게 좋을 것 같아요 더 알아보려면 프롬프트 엔지니어링에 대한 비디오를 확인해 보세요 디자인 및 안전 관련 팁도 다룹니다 실제 Naomy를 만나고 싶다면 코딩 실습 비디오를 시청해 보세요 파운데이션 모델로 재미있는 콘텐츠를 개발하시기를 바랍니다 시청해 주셔서 감사합니다

    • 1:05 - Prompting a session

      import FoundationModels
      
      func respond(userInput: String) async throws -> String {
        let session = LanguageModelSession(instructions: """
          You are a friendly barista in a world full of pixels.
          Respond to the player’s question.
          """
        )
        let response = try await session.respond(to: userInput)
        return response.content
      }
    • 3:37 - Handle context size errors

      var session = LanguageModelSession()
      
      do {
        let answer = try await session.respond(to: prompt)
        print(answer.content)
      } catch LanguageModelSession.GenerationError.exceededContextWindowSize {
        // New session, without any history from the previous session.
        session = LanguageModelSession()
      }
    • 3:55 - Handling context size errors with a new session

      var session = LanguageModelSession()
      
      do {
        let answer = try await session.respond(to: prompt)
        print(answer.content)
      } catch LanguageModelSession.GenerationError.exceededContextWindowSize {
        // New session, with some history from the previous session.
        session = newSession(previousSession: session)
      }
      
      private func newSession(previousSession: LanguageModelSession) -> LanguageModelSession {
        let allEntries = previousSession.transcript.entries
        var condensedEntries = [Transcript.Entry]()
        if let firstEntry = allEntries.first {
          condensedEntries.append(firstEntry)
          if allEntries.count > 1, let lastEntry = allEntries.last {
            condensedEntries.append(lastEntry)
          }
        }
        let condensedTranscript = Transcript(entries: condensedEntries)
        // Note: transcript includes instructions.
        return LanguageModelSession(transcript: condensedTranscript)
      }
    • 6:14 - Sampling

      // Deterministic output
      let response = try await session.respond(
        to: prompt,
        options: GenerationOptions(sampling: .greedy)
      )
                      
      // Low-variance output
      let response = try await session.respond(
        to: prompt,
        options: GenerationOptions(temperature: 0.5)
      )
                      
      // High-variance output
      let response = try await session.respond(
        to: prompt,
        options: GenerationOptions(temperature: 2.0)
      )
    • 7:06 - Handling languages

      var session = LanguageModelSession()
      
      do {
        let answer = try await session.respond(to: userInput)
        print(answer.content)
      } catch LanguageModelSession.GenerationError.unsupportedLanguageOrLocale {
        // Unsupported language in prompt.
      }
      
      let supportedLanguages = SystemLanguageModel.default.supportedLanguages
      guard supportedLanguages.contains(Locale.current.language) else {
        // Show message
        return
      }
    • 8:14 - Generable

      @Generable
      struct NPC {
        let name: String
        let coffeeOrder: String
      }
      
      func makeNPC() async throws -> NPC {
        let session = LanguageModelSession(instructions: ...)
        let response = try await session.respond(generating: NPC.self) {
          "Generate a character that orders a coffee."
        }
        return response.content
      }
    • 9:22 - NPC

      @Generable
      struct NPC {
        let name: String
        let coffeeOrder: String
      }
    • 10:49 - Generable with enum

      @Generable
      struct NPC {
        let name: String
        let encounter: Encounter
      
        @Generable
        enum Encounter {
          case orderCoffee(String)
          case wantToTalkToManager(complaint: String)
        }
      }
    • 11:20 - Generable with guides

      @Generable
      struct NPC {
        @Guide(description: "A full name")
        let name: String
        @Guide(.range(1...10))
        let level: Int
        @Guide(.count(3))
        let attributes: [Attribute]
        let encounter: Encounter
      
        @Generable
        enum Attribute {
          case sassy
          case tired
          case hungry
        }
        @Generable
        enum Encounter {
          case orderCoffee(String)
          case wantToTalkToManager(complaint: String)
        }
      }
    • 13:40 - Regex guide

      @Generable
      struct NPC {
        @Guide(Regex {
          Capture {
            ChoiceOf {
              "Mr"
              "Mrs"
            }
          }
          ". "
          OneOrMore(.word)
        })
        let name: String
      }
      
      session.respond(to: "Generate a fun NPC", generating: NPC.self)
      // > {name: "Mrs. Brewster"}
    • 14:50 - Generable riddle

      @Generable
      struct Riddle {
        let question: String
        let answers: [Answer]
      
        @Generable
        struct Answer {
          let text: String
          let isCorrect: Bool
        }
      }
    • 15:10 - Dynamic schema

      struct LevelObjectCreator {
        var properties: [DynamicGenerationSchema.Property] = []
      
        mutating func addStringProperty(name: String) {
          let property = DynamicGenerationSchema.Property(
            name: name,
            schema: DynamicGenerationSchema(type: String.self)
          )
          properties.append(property)
        }
      
        mutating func addArrayProperty(name: String, customType: String) {
          let property = DynamicGenerationSchema.Property(
            name: name,
            schema: DynamicGenerationSchema(
              arrayOf: DynamicGenerationSchema(referenceTo: customType)
            )
          )
          properties.append(property)
        }
        
        var root: DynamicGenerationSchema {
          DynamicGenerationSchema(
            name: name,
            properties: properties
          )
        }
      }
      
      var riddleBuilder = LevelObjectCreator(name: "Riddle")
      riddleBuilder.addStringProperty(name: "question")
      riddleBuilder.addArrayProperty(name: "answers", customType: "Answer")
      
      var answerBuilder = LevelObjectCreator(name: "Answer")
      answerBuilder.addStringProperty(name: "text")
      answerBuilder.addBoolProperty(name: "isCorrect")
      
      let riddleDynamicSchema = riddleBuilder.root
      let answerDynamicSchema = answerBuilder.root
      
      let schema = try GenerationSchema(
        root: riddleDynamicSchema,
        dependencies: [answerDynamicSchema]
      )
      
      let session = LanguageModelSession()
      let response = try await session.respond(
        to: "Generate a fun riddle about coffee",
        schema: schema
      )
      let generatedContent = response.content
      let question = try generatedContent.value(String.self, forProperty: "question")
      let answers = try generatedContent.value([GeneratedContent].self, forProperty: "answers")
    • 18:47 - FindContactTool

      import FoundationModels
      import Contacts
      
      struct FindContactTool: Tool {
        let name = "findContact"
        let description = "Finds a contact from a specified age generation."
          
        @Generable
        struct Arguments {
          let generation: Generation
              
          @Generable
          enum Generation {
            case babyBoomers
            case genX
            case millennial
            case genZ            
          }
        }
        
        func call(arguments: Arguments) async throws -> ToolOutput {
          let store = CNContactStore()
              
          let keysToFetch = [CNContactGivenNameKey, CNContactBirthdayKey] as [CNKeyDescriptor]
          let request = CNContactFetchRequest(keysToFetch: keysToFetch)
      
          var contacts: [CNContact] = []
          try store.enumerateContacts(with: request) { contact, stop in
            if let year = contact.birthday?.year {
              if arguments.generation.yearRange.contains(year) {
                contacts.append(contact)
              }
            }
          }
          guard let pickedContact = contacts.randomElement() else {
            return ToolOutput("Could not find a contact.")
          }
          return ToolOutput(pickedContact.givenName)
        }
      }
    • 20:26 - Call FindContactTool

      import FoundationModels
      
      let session = LanguageModelSession(
        tools: [FindContactTool()],
        instructions: "Generate fun NPCs"
      )
    • 21:55 - FindContactTool with state

      import FoundationModels
      import Contacts
      
      class FindContactTool: Tool {
        let name = "findContact"
        let description = "Finds a contact from a specified age generation."
         
        var pickedContacts = Set<String>()
          
        ...
      
        func call(arguments: Arguments) async throws -> ToolOutput {
          contacts.removeAll(where: { pickedContacts.contains($0.givenName) })
          guard let pickedContact = contacts.randomElement() else {
            return ToolOutput("Could not find a contact.")
          }
          return ToolOutput(pickedContact.givenName)
        }
      }
    • 22:27 - GetContactEventTool

      import FoundationModels
      import EventKit
      
      struct GetContactEventTool: Tool {
        let name = "getContactEvent"
        let description = "Get an event with a contact."
      
        let contactName: String
          
        @Generable
        struct Arguments {
          let day: Int
          let month: Int
          let year: Int
        }
          
        func call(arguments: Arguments) async throws -> ToolOutput { ... }
      }
    • 0:00 - 서론
    • Swift API를 통해 액세스할 수 있는 온디바이스 대규모 언어 모델을 제공하는 Apple 기기용 파운데이션 모델 프레임워크에 대해 알아보세요. 이 섹션에서는 Generable을 사용하여 구조화된 출력, 동적 스키마, 사용자 정의 함수에 대한 툴 호출을 얻는 방법을 다룹니다.

    • 0:49 - 세션
    • 이 예제에서 파운데이션 모델은 동적인 게임 대화 및 콘텐츠를 생성하여 픽셀 아트 커피숍 게임을 향상시킵니다. ‘LanguageModelSession’을 생성하면 사용자 정의 지침이 모델에 제공되어 플레이어의 질문에 응답할 수 있습니다. 이 모델은 사용자 입력과 세션 명령어를 토큰, 즉 작은 하위 문자열로 처리한 다음 이를 사용하여 출력으로 새로운 토큰 시퀀스를 생성합니다. ‘LanguageModelSession’은 스테이트풀로, 모든 프롬프트 및 응답을 전사본으로 기록합니다. 이 전사본을 사용하면 게임 사용자 인터페이스에서 대화 기록을 디버깅하고 표시할 수 있습니다. 하지만 세션의 크기에는 맥락의 제한이라는 한계가 있습니다. 응답의 생성은 기본적으로 결정적이지 않습니다. 이 모델은 샘플링을 사용하여 각 토큰에 대한 가능성의 배포를 생성하여 무작위성을 도입합니다. 이러한 무작위성은 GenerationOptions API를 사용하여 제어할 수 있기 때문에 샘플링 메서드 또는 온도를 조정하거나 심지어 결정적 출력을 위해 greedy로 설정할 수도 있습니다. 간단한 대화 외에도 파운데이션 모델은 NPC(Non-Playable Character)의 이름이나 커피 주문과 같은 더 복잡한 출력을 생성하는 데 사용될 수 있습니다. 이를 통해 게임 세계에 깊이와 다양성이 더해져 더욱 생생하고 상호작용하는 느낌을 줍니다. 지원되지 않는 언어 등의 잠재적인 문제도 고려하고 이를 원활하게 처리하여 원활한 사용자 환경을 제공해야 합니다.

    • 7:57 - Generable
    • 파운데이션 모델의 Generable API는 대규모 언어 모델에서 구조화된 데이터를 얻는 것을 단순화하는 강력한 도구입니다. @Generable 매크로를 Swift 구조체나 열거형에 적용하면 컴파일 시점에 스키마가 생성되어 모델의 출력을 안내합니다. Generable은 자동으로 초기화 프로그램을 생성하고 제한된 디코딩을 사용하여 모델의 생성된 텍스트를 타입 세이프 Swift 객체로 구문 분석합니다. 이 기술은 모델의 출력이 지정된 스키마를 준수하도록 보장하여 환각 및 구조적 실수를 방지합니다. 특정 속성에 대한 제약 조건, 범위 또는 자연어 설명을 제공하는 ‘Guides’를 사용하여 생성 프로세스를 추가로 사용자 지정할 수 있습니다. 이를 통해 이름 형식, 배열 개수 또는 숫자 범위 지정 등 생성된 데이터를 보다 효과적으로 제어할 수 있습니다. Generable은 효율적이고 안정적인 데이터 생성을 지원하여 개발자가 애플리케이션의 더 복잡한 측면에 집중할 수 있도록 해줍니다.

    • 14:29 - 동적 스키마
    • 게임의 레벨 생성기에서 동적 스키마를 사용하면 플레이어가 런타임에 사용자 정의 엔티티를 정의할 수 있습니다. 컴파일 타임 구조체와 유사한 이러한 스키마는 이름과 유형이 있는 속성을 가지기 때문에 배열 및 다른 동적 스키마에 대한 참고를 지원합니다. 플레이어의 입력을 통해 질문(문자열)과 답변 배열(문자열 및 부울 속성이 있는 사용자 지정 유형)을 포함하는 수수께끼 스키마가 생성됩니다. 이러한 동적 스키마는 검증을 거친 후 파운데이션 모델에서 콘텐츠를 생성하는 데 사용되기 때문에 출력이 정의된 구조와 일치하는지 보장합니다. 이러한 역동적인 접근 방식을 통해 게임은 플레이어가 만든 수수께끼와 기타 엔티티를 역동적인 UI로 표시할 수 있어 구조화된 데이터 처리를 유지하는 동시에 플레이어에게 높은 수준의 유연성과 창의성을 제공합니다.

    • 18:10 - 도구 호출
    • 파운데이션 모델을 사용하면 게임 개발자가 툴 호출을 사용하여 개인화된 DLC를 만들 수 있습니다. 이를 통해 모델은 플레이어의 기기에서 연락처 및 일정 등의 정보를 자율적으로 가져올 수 있으며, 데이터가 기기를 벗어나지 않기 때문에 개인 정보가 보호됩니다. 툴을 정의하려면 이름, 설명, 입력 인수를 지정해야 합니다. 모델은 이 정보를 사용하여 툴을 언제 그리고 어떻게 호출할지 결정합니다. 그런 다음 툴의 구현은 Contacts API와 같은 외부 API와 상호작용하여 데이터를 검색합니다.

Developer Footer

  • 비디오
  • WWDC25
  • Foundation Models 프레임워크 자세히 알아보기
  • 메뉴 열기 메뉴 닫기
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    메뉴 열기 메뉴 닫기
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    메뉴 열기 메뉴 닫기
    • 손쉬운 사용
    • 액세서리
    • 앱 확장 프로그램
    • App Store
    • 오디오 및 비디오(영문)
    • 증강 현실
    • 디자인
    • 배포
    • 교육
    • 서체(영문)
    • 게임
    • 건강 및 피트니스
    • 앱 내 구입
    • 현지화
    • 지도 및 위치
    • 머신 러닝 및 AI
    • 오픈 소스(영문)
    • 보안
    • Safari 및 웹(영문)
    메뉴 열기 메뉴 닫기
    • 문서(영문)
    • 튜토리얼
    • 다운로드(영문)
    • 포럼(영문)
    • 비디오
    메뉴 열기 메뉴 닫기
    • 지원 문서
    • 문의하기
    • 버그 보고
    • 시스템 상태(영문)
    메뉴 열기 메뉴 닫기
    • Apple Developer
    • App Store Connect
    • 인증서, 식별자 및 프로파일(영문)
    • 피드백 지원
    메뉴 열기 메뉴 닫기
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(영문)
    • News Partner Program(영문)
    • Video Partner Program(영문)
    • Security Bounty Program(영문)
    • Security Research Device Program(영문)
    메뉴 열기 메뉴 닫기
    • Apple과의 만남
    • Apple Developer Center
    • App Store 어워드(영문)
    • Apple 디자인 어워드
    • Apple Developer Academy(영문)
    • WWDC
    Apple Developer 앱 받기
    Copyright © 2025 Apple Inc. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침