View in English

  • Global Nav Open Menu Global Nav Close Menu
  • Apple Developer
Search
Cancel
  • Apple Developer
  • News
  • Discover
  • Design
  • Develop
  • Distribute
  • Support
  • Account
Only search within “”

Quick Links

5 Quick Links

Vidéos

Ouvrir le menu Fermer le menu
  • Collections
  • Sujets
  • Toutes les vidéos
  • À propos

Plus de vidéos

  • À propos
  • Résumé
  • Transcription
  • Code
  • Présentation approfondie du framework de Foundation Models

    Mettez-vous à niveau avec le framework de Foundation Models. Découvrez comment fonctionne la génération guidée et utilisez des guides, des expressions régulières et des schémas de génération pour obtenir des réponses structurées personnalisées. Nous vous montrerons comment utiliser l'appel d'outil pour permettre au modèle d'accéder de manière autonome à des informations externes et d'effectuer des actions, pour une expérience personnalisée. Pour tirer le meilleur parti de cette vidéo, nous vous recommandons de regarder d'abord « Voici le framework de Foundation Models ».

    Chapitres

    • 0:00 - Introduction
    • 0:49 - Séances
    • 7:57 - Generable
    • 14:29 - Schémas dynamiques
    • 18:10 - Appel d’outils

    Ressources

    • Generate dynamic game content with guided generation and tools
    • Human Interface Guidelines: Generative AI
      • Vidéo HD
      • Vidéo SD

    Vidéos connexes

    WWDC25

    • Code-along : Intégrez l’IA contenue sur l’appareil à votre app à l’aide du framework de Foundation Models
    • Découvrir les frameworks d’apprentissage automatique et d’IA sur les plates-formes Apple
    • Voici le framework Foundation Models
  • Rechercher dans cette vidéo…

    Bonjour, je m’appelle Louis. Aujourd’hui, nous allons voir comment profiter au mieux du framework de Foundation Models.

    Comme vous le savez, le framework de Foundation Models permet d’accéder directement au grand modèle de langage embarqué, via une API Swift pratique. Il est disponible sur macOS, iPadOS, iOS et visionOS. Comme il fonctionne en local, il suffit de l’importer dans votre projet pour commencer à l’utiliser. Nous allons voir comment fonctionnent les sessions avec Foundation Models. Comment utiliser Generable pour obtenir des sorties structurées ? Comment obtenir des sorties structurées avec des schémas dynamiques définis à l’exécution et utiliser l’appel d’outils pour permettre au modèle d’invoquer vos fonctions personnalisées ? Commençons simplement par générer du texte avec une session.

    Je travaille sur un jeu en pixel art autour d’un café, et je pense que cela pourrait être amusant d’utiliser Foundation Models pour générer des dialogues ou d’autres contenus immersifs !

    Nous pouvons demander au modèle de répondre à la question d’un joueur, et ainsi permettre à notre barista d’avoir un dialogue personnalisé. Nous allons créer une LanguageModelSession avec des instructions personnalisées. Cela permet de définir le rôle du modèle pour la session, et comme invite, on utilise l’entrée de l’utilisateur. Et c’est tout ce qu’il faut pour créer un élément de jeu amusant. Demandons au barista « Depuis combien de temps travaillez-vous ici ? », et voyons sa réponse.

    Cette réponse est générée entièrement en local. Plutôt impressionnant. Comment cela fonctionne-t-il réellement ? Voyons comment Foundation Models génère du texte et quels sont les points à surveiller. Quand vous invoquez respond(to:), la session prend les instructions et l’invite. Ici, l’entrée utilisateur, et convertit tout cela en jetons. Les jetons sont de petites sous-chaînes, parfois des mots, mais souvent quelques caractères. Un grand modèle de langage prend une séquence de jetons en entrée et génère une nouvelle séquence de jetons en sortie. Vous n’avez pas besoin de vous soucier des jetons avec lesquels Foundation Models fonctionne : l’API s’en charge pour vous. Mais il est important de comprendre que les jetons ont un coût. Chaque jeton de vos instructions et invite ajoute de la latence. Avant de générer une réponse, le modèle doit d’abord traiter tous les jetons d’entrée. Et la génération elle-même a un coût computationnel. C’est pourquoi des sorties longues prennent plus de temps.

    Une LanguageModelSession est avec état. Chaque appel respond(to:) est enregistré dans la transcription.

    La transcription comprend toutes les invites et réponses d’une session donnée.

    Cela peut être utile pour du débogage, ou pour l’afficher dans votre interface utilisateur.

    Mais une session a une taille limite. Si vous faites trop de demandes, ou utilisez des invites/réponses volumineuses, vous pouvez atteindre cette limite de contexte.

    Si vous dépassez cette limite, une erreur est levée, et il faut pouvoir la gérer. Dans mon jeu, si une erreur survient en plein dialogue, la conversation se coupe... dommage, je commençais à bien connaître le personnage ! Heureusement, on peut gérer cette erreur.

    Il faut intercepter l’erreur exceededContextWindowSize.

    On peut alors démarrer une nouvelle session, sans historique. Mais dans mon jeu, cela voudrait dire que le personnage oublie toute la conversation.

    On peut aussi conserver une partie de la transcription et la réinjecter dans la nouvelle session.

    Il suffit de créer un array condensé à partir de l’ancienne transcription.

    Pour notre jeu, on peut récupérer la première entrée, les instructions. Et la dernière réponse générée. En les injectant dans une nouvelle session, notre personnage peut poursuivre la conversation. Attention : la première entrée de la transcription est toujours celle contenant les instructions. Si on recrée une transcription, il faut s’assurer d’inclure ces instructions.

    Réutiliser quelques éléments pertinents de la transcription est une solution simple et efficace. Mais cela n’est pas toujours aussi facile. Imaginons une transcription avec beaucoup d’entrées. Il faut toujours commencer par conserver les instructions. Mais de nombreuses entrées intermédiaires peuvent être utiles, donc dans ce cas, on peut envisager de résumer la transcription.

    Soit avec une bibliothèque externe, soit en utilisant Foundation Models pour résumer certaines parties.

    Voilà donc ce que vous pouvez faire avec la transcription d’une session. À présent, regardons rapidement comment les réponses sont réellement générées. Dans notre jeu, quand le joueur interagit avec le barista, il peut poser n’importe quelle question. Mais si vous démarrez deux nouvelles parties et que vous posez exactement la même question, vous obtiendrez probablement deux réponses différentes. Comment est-ce possible ? Cela vient du mécanisme d’échantillonnage.

    Lorsqu’il génère une réponse, le modèle procède jeton par jeton. Il génère une distribution de probabilité sur les jetons possibles. Par défaut, Foundation Models choisit une plage de probabilité dans cette distribution. Parfois, il commencera par « Ah », parfois par « Eh bien ». Cela se répète à chaque jeton généré. C’est ce que nous appelons l’échantillonnage. Et par défaut, Foundation Models utilise un échantillonnage aléatoire. Ce qui est super pour les jeux. Mais dans d’autres cas, on peut vouloir un résultat déterministe, par exemple, pour une démo répétable. L’API GenerationOptions permet de contrôler cela. En choisissant greedy, on obtient une réponse déterministe. À condition que l’état de la session soit identique, le modèle générera la même sortie pour la même invite. Mais attention : cela dépend aussi de la version du modèle embarqué. Lors d’une mise à jour du système d’exploitation, votre invite peut générer une sortie différente, même en utilisant l’échantillonnage greedy. Vous pouvez également régler la température pour l’échantillonnage aléatoire. Par exemple, réglez la température sur 0,5 pour limiter les variations, ou sur une valeur plus élevée pour des sorties plus diversifiées. Plus la température est élevée, plus les sorties seront variées. pour la même invite. Si vous utilisez l’entrée utilisateur comme invite, la langue peut ne pas être prise en charge.

    Dans ce cas, l’erreur unsupportedLanguageOrLocale dédiée sera détectée. Cela vous permet d’afficher un message d’erreur personnalisé dans la UI. Une API permet aussi de tester si une langue est prise en charge par le modèle. Par exemple, pour vérifier la langue actuelle de l’utilisateur et afficher un avertissement si besoin. Voilà pour l’aperçu des sessions. Vous pouvez soumettre des invites, l’historique est stocké dans la transcription. Et vous pouvez définir le paramètre d’échantillonnage pour contrôler le caractère aléatoire de la sortie de la session. Maintenant, allons plus loin ! Quand le joueur se déplace, on peut générer des PNJ (personnages non-jouables) avec Foundation Models. Mais cette fois, nous voulons une sortie plus complexe. Nous voulons un nom et une commande de café de la part du PNJ, pas juste du texte brut. C’est là que Generable entre en scène. Obtenir une sortie structurée depuis un grand modèle de langage peut devenir un véritable défi. Vous pourriez demander explicitement certains champs et utiliser un code d’analyse pour les extraire. Mais c’est fragile et difficile à maintenir si le code ne retourne pas de clé valide, ce qui ferait échouer toute la méthode. Heureusement, Foundation Models propose une meilleure API appelée Generable. Il suffit d’annoter votre structure avec la macro @Generable. Que veut dire Generable et est-ce que ce mot existe vraiment ? Et bien, oui, il existe vraiment.

    Generable est une façon simple de demander au modèle de générer des données structurées, à partir de types Swift. Lors de la compilation, la macro génère un schéma que le modèle peut utiliser pour produire la structure attendue. La macro génère aussi un initialiseur, qui est automatiquement appelé lors de l’envoi d’une requête à une session.

    Nous pouvons donc générer des instances de notre structure. Comme précédemment, nous appellerons la méthode respond sur notre session. Mais cette fois, passez l’argument générateur indiquant au modèle le type à générer. Foundation Models ajoutera automatiquement les détails de votre type Generable à l’invite, dans un format spécifique sur lequel le modèle a été entraîné. Pas besoin de lui expliquer les champs de votre type Generable. Dans notre jeu, nous aurons désormais des interactions de PNJ générées de façon réaliste !

    Generable est en réalité plus puissant qu’il n’y paraît. À un bas niveau, il utilise le décodage contraint, une technique qui permet au modèle de générer du texte respectant un schéma précis. Ce fameux schéma généré par la macro. Comme nous l’avons vu précédemment, un LLM génère des jetons qui sont ensuite transformés en texte. Avec Generable, ce texte est automatiquement analysé en toute sécurité. Les jetons sont générés dans une boucle appelée boucle de décodage. Sans décodage contraint, le modèle pourrait halluciner un nom de champ invalide. Comme « firstName » au lieu d’un nom. Ce qui empêcherait l’analyse correcte dans le type PNJ.

    Mais avec le décodage contraint, le modèle est empêché de produire ce genre d’erreur structurelle. À chaque jeton généré, il y a une distribution de tous les jetons du vocabulaire du modèle. Le décodage contraint masque les jetons non valides. Donc, au lieu de choisir n’importe quel jeton, le modèle ne peut choisir que parmi les jetons valides selon le schéma.

    Et tout cela sans devoir analyser manuellement la sortie du modèle. Cela signifie que vous pouvez vous concentrer sur l’essentiel, comme discuter avec des clients virtuels dans votre café virtuel ! Generable est vraiment le meilleur moyen d’obtenir une sortie depuis le LLM embarqué. Et il peut faire bien plus encore. Vous pouvez non seulement l’utiliser sur des structs, mais aussi sur des enums ! Alors, utilisons-cela pour rendre nos rencontres plus dynamiques ! Ici, j’ai ajouté un enum Encounter, avec deux cas. L’enum peut même contenir des valeurs associées. Utilisons-la pour générer une commande de café ou une interaction avec un client demandant à parler au responsable.

    Voyons maintenant ce que nous rencontrons dans notre jeu !

    Génial, quelqu’un a vraiment besoin d’un café.

    À l’évidence, tous les clients ne sont pas faciles à gérer, donc ajoutons des niveaux à nos PNJ. Generable prend en charge par défaut les types Swift les plus courants, y compris Int. Ajoutons donc une propriété de niveau. Mais nous ne voulons pas générer n’importe quel entier. Si nous voulons que le niveau soit dans une plage spécifique, nous pouvons utiliser un Guide. Nous pouvons utiliser la macro Guide sur notre propriété et passer une plage. Le modèle utilisera à nouveau le décodage contraint pour garantir une valeur dans cette plage.

    Profitons-en pour ajouter un array d’attributs à notre PNJ.

    Nous pouvons à nouveau utiliser un guide pour spécifier que nous voulons exactement trois attributs dans cet array. Gardez à l’esprit que les propriétés de votre type Generable sont générées dans l’ordre où elles apparaissent dans le code source. Ici, le nom sera généré en premier, suivi du niveau, puis des attributs et de la rencontre en dernier.

    Cet ordre peut être crucial si la valeur d’une propriété dépend d’une autre. Et vous pouvez même demander une génération propriété par propriété au lieu d’attendre la sortie complète. Le jeu devient vraiment amusant. Je vais bientôt pouvoir le partager avec mes amis. Mais je remarque que les noms des PNJ ne sont pas exactement ceux que j’avais en tête. Je préférerais avoir un prénom et un nom de famille.

    Nous pouvons utiliser un guide pour cela, mais cette fois-ci, avec une description en langage naturel.

    Nous pouvons dire que notre nom doit être un « nom complet ». Et c’est effectivement une autre façon d’écrire une invite. Au lieu de décrire les différentes propriétés dans votre invite, vous pouvez le faire directement dans le type Generable. Cela donne au modèle un lien plus fort entre les champs et leurs descriptions. Si nous nous promenons dans notre jeu, nous verrons ces nouveaux noms s’afficher. Voici un aperçu de tous les guides que vous pouvez appliquer aux différents types.

    Pour les types numériques, tels que Int, on peut spécifier un minimum, un maximum ou une plage. Pour array, on peut contrôler le nombre d’éléments ou des guides spécifiques pour chaque élément.

    Pour String, vous pouvez utiliser anyOf pour choisir dans un array ou même contraindre avec une expression régulière.

    Les guides utilisant les expressions régulières sont particulièrement puissants. Vous connaissez peut-être les expressions régulières pour vérifier du texte. Avec Foundation Models, vous pouvez utiliser une expression régulière pour définir la structure d’une chaîne à générer. Par exemple, vous pouvez restreindre les noms à certains préfixes. Vous pouvez même utiliser la syntaxe de création de l’expression régulière !

    Si cela vous a donné envie de découvrir les expressions régulières, regardez la vidéo « Meet Swift Regex » qui date d’il y a quelques années. Pour récapituler, Generable est une macro que vous appliquez à des structs et des enums pour obtenir des sorties structurées fiables du modèle. Vous n’avez pas à vous soucier de l’analyse, et pour obtenir une sortie encore plus spécifique, vous pouvez appliquer des guides à vos propriétés. Generable est donc parfait quand on connaît la structure au moment de la compilation. La macro génère le schéma et vous obtenez une instance du type sortie. Mais parfois, on ne connaît une structure qu’à l’exécution. C’est là que les schémas dynamiques peuvent vous aider. J’ajoute un créateur de niveau à mon jeu, dans lequel les joueurs peuvent définir dynamiquement des entités à rencontrer pendant l’exploration. Par exemple, un joueur peut créer une structure d’énigme. Dans laquelle une énigme renferme une question et des réponses à choix multiples. Si on connaissait cette structure au moment de la compilation, on pourrait simplement définir une struct Generable pour elle. Mais notre créateur de niveau permet de créer n’importe quelle structure que le joueur imagine.

    Nous pouvons utiliser « DynamicGenerationSchema » pour générer un schéma à l’exécution. À l’instar d’une structure définie au moment de la compilation, un schéma dynamique dispose d’une liste de propriétés. Nous pouvons ajouter un créateur de niveau, capable de prendre les entrées d’un joueur.

    Chaque propriété dispose d’un nom et de son propre schéma, qui définit son type. Vous pouvez utiliser le schéma pour tout type Generable, y compris les types intégrés comme String.

    Un schéma dynamique peut contenir un array, dans lequel vous spécifiez ensuite un schéma pour ses éléments. Et surtout, un schéma dynamique peut référencer d’autres schémas dynamiques. Ici, notre array peut référencer un schéma personnalisé également défini à l’exécution.

    À partir des entrées de l’utilisateur, nous pouvons créer un schéma d’énigme avec deux propriétés. La première est la question, une propriété chaîne. Et la seconde, une propriété array d’un type personnalisé nommé Answer. Ensuite, nous créerons la réponse. Il comprend une propriété chaîne et booléenne. Notez que la propriété Réponse de l’énigme fait référence au schéma Réponse par son nom. Nous pouvons créer ensuite les instances DynamicGenerationSchema. Chaque schéma dynamique est indépendant. Autrement dit, le schéma dynamique de l’énigme ne contient pas réellement celui de la réponse. Avant d’inférer du contenu, il faut d’abord convertir nos schémas dynamiques en un schéma validé. Cela peut générer des erreurs en cas d’incohérences dans les schémas dynamiques, tels que des types référencés non définis.

    Une fois le schéma validé, nous pouvons lancer une session comme d’habitude. Mais cette fois, le type de sortie est une instance GeneratedContent. Elle contient les valeurs dynamiques générées. Vous pouvez les interroger avec les noms de propriété définis dans vos schémas dynamiques. Encore une fois, Foundation Models utilise une génération guidée pour garantir que la sortie respecte votre schéma. Le modèle ne générera jamais un champ inattendu ! Donc même avec du contenu dynamique, pas besoin d’analyser la sortie manuellement.

    Ainsi, lorsque le joueur rencontre un PNJ, le modèle peut générer ce contenu dynamique. Que nous afficherons dans une interface utilisateur dynamique. Voyons ce que nous allons rencontrer. Je suis noir ou au lait, amer ou sucré, je te réveille et je te donne chaud, qui suis-je ? Café ou chocolat chaud. Je pense que la bonne réponse est le café. C’est exact ! Je pense que mes joueurs vont bien s’amuser à créer toutes sortes de niveaux amusants. Pour récapituler, avec la macro Generable, nous pouvons facilement générer une sortie structurée à partir d’un type Swift défini au moment de la compilation. Et en coulisses, Foundation Models gère le schéma et la conversion du GeneratedContent en une instance de votre type. Les schémas dynamiques fonctionnent de manière très similaire, mais vous donnent bien plus de contrôle. Vous contrôlez entièrement le schéma à l’exécution et accédez directement au GeneratedContent. Passons maintenant à l’appel d’outils, qui permet au modèle d’invoquer vos propres fonctions. Je pense créer un DLC, du contenu téléchargeable, pour rendre mon jeu plus personnel. En utilisant l’appel d’outils, je peux laisser le modèle récupérer des informations de manière autonome. Je me dis qu’intégrer les contacts et le calendrier du joueur pourrait être très amusant. Je ne ferais pas ça normalement avec un modèle côté serveur ; mes joueurs n’apprécieraient pas que le jeu charge de telles données personnelles. Mais comme tout est exécuté en local avec Foundation Models, nous pouvons le faire en préservant la confidentialité.

    Définir un outil est très simple avec le protocole Tool. Vous commencez par lui donner un nom et une description. C’est ce que l’API inclura automatiquement dans l’invite pour que le modèle décide quand et comment appeler votre outil.

    Il est préférable de choisir un nom d’outil court, mais lisible en anglais. Évitez les abréviations et ne faites pas une description trop longue ou contenant les implémentations. Souvenez-vous : ces chaînes sont injectées telles quelles dans l’invite. Donc, plus elles sont longues, plus il y a de jetons, ce qui peut augmenter la latence. À la place, utilisez un verbe dans le nom, tel que findContact. Et votre description devrait tenir en une phrase. Comme toujours, il est important de tester différentes variantes pour voir ce qui fonctionne le mieux pour votre outil spécifique.

    Ensuite, nous pouvons définir l’entrée de notre outil. Je souhaite que l’outil récupère les contacts d’une certaine génération, comme les millennials. Le modèle pourra choisir un cas amusant selon l’état du jeu, et je peux ajouter la struct Arguments que je rends Generable. Quand le modèle décide d’invoquer cet outil, il génère les arguments d’entrée. Grâce à Generable, on s’assure que les arguments d’entrée sont toujours valides. Donc, il ne va pas inventer une génération, comme la génération alpha, que nous ne prenons pas en charge dans notre jeu.

    Ensuite, je peux implémenter la fonction d’appel. Le modèle appellera cette méthode lorsqu’il décide d’invoquer l’outil. Dans cet exemple, nous faisons appel à Contacts API. Et renvoyons le nom d’un contact correspondant à cette requête.

    Pour utiliser notre outil, nous le passons dans l’initialiseur de session. Le modèle invoquera alors notre outil lorsqu’il aura besoin de cette information supplémentaire. C’est plus puissant que de récupérer le contact nous-mêmes, car le modèle ne fera appel à l’outil que lorsqu’il en a besoin pour un PNJ donné, et il peut choisir des arguments d’entrée amusants selon l’état du jeu. Comme la génération d’âge du PNJ.

    Notez que nous utilisons l’API Contacts standard, que vous connaissez peut-être déjà. Lorsque notre outil est invoqué pour la première fois, il demande la permission habituelle au joueur. Même si le joueur refuse l’accès à ses contacts, Foundation Models peut quand même générer du contenu comme avant, mais s’il accepte, on le rend plus personnel.

    Promenons-nous un peu dans notre jeu jusqu’à ce que nous rencontrions un autre PNJ. Et cette fois, je vais obtenir un nom à partir de mes contacts ! Oh bonjour Naomy ! Voyons ce qu’elle a à nous dire. Je ne savais pas que tu aimais le café. Notez que LanguageModelSession prend une instance d’un outil. Cela signifie que vous contrôlez le cycle de vie de l’outil. L’instance de cet outil reste la même pendant toute la session. Dans cet exemple, comme on récupère un personnage aléatoire avec FindContactTool, il se peut qu’on obtienne parfois le même contact. Il y a maintenant plusieurs Naomy dans notre jeu. Et ce n’est pas acceptable, il ne peut y en avoir qu’une ! Pour corriger cela, on peut suivre les contacts déjà utilisés dans le jeu. Nous pouvons ajouter un état dans notre FindContactTool. Pour cela, nous convertissons d’abord notre FindContactTool en classe. Ainsi, elle peut modifier son état depuis la méthode d’appel. Ensuite, on garde une trace des contacts sélectionnés, et on évite de les reprendre.

    Les noms des PNJ sont maintenant basés sur mes contacts ! Mais discuter avec eux ne semble pas encore naturel. Finissons par un autre outil, cette fois pour accéder à mon calendrier.

    Pour cet outil, nous transmettons le nom du contact issu du dialogue de notre jeu. Et quand le modèle invoque cet outil, on le laisse générer un jour, un mois et une année pour récupérer les évènements liés à ce contact. Nous passons ensuite cet outil dans la session liée au dialogue avec le PNJ.

    Si je demande à « Quoi de neuf ? », elle peut répondre avec de vrais évènements que nous avons planifiés ensemble.

    Waouh, c’est comme parler à la vraie Naomy.

    Examinons de plus près le fonctionnement de l’appel d’outils. Nous commençons par passer l’outil au lancement de la session, avec les instructions. Pour cet exemple, nous incluons des informations telles que la date du jour. Ensuite, quand l’utilisateur envoie une invite, le modèle l’analyse. Dans cet exemple, le modèle comprend que l’invite porte sur des évènements, il est donc logique d’invoquer le calendrier.

    Pour invoquer l’outil, le modèle commence par générer les arguments d’entrée. Ici, il doit générer une date pour laquelle récupérer les évènements. Le modèle peut relier les informations des instructions et de l’invite, et comprendre comment remplir les arguments de l’outil à partir de cela. Dans cet exemple, il peut déduire ce que signifie « demain » en se basant sur la date d’aujourd’hui fournie dans les instructions. Une fois les entrées générées pour votre outil, la méthode d’appel est invoquée. C’est le moment de briller : votre outil peut faire ce qu’il veut. Mais attention, la session attend que votre outil renvoie une réponse avant de générer la suite.

    La sortie de votre outil est ensuite ajoutée à la transcription, comme celle du modèle. Et à partir de cette sortie, le modèle peut générer une réponse à l’invite. Notez qu’un outil peut être appelé plusieurs fois pour une seule requête. Et dans ce cas, votre outil est invoqué en parallèle. Gardez cela en tête si vous accédez à des données dans la méthode d’appel de votre outil. Bon, c’était plutôt amusant ! Notre jeu génère maintenant du contenu aléatoire à partir de mes contacts et de mon calendrier personnels. Sans que mes données ne quittent jamais mon appareil. Pour résumer, l’appel d’outils permet au modèle d’invoquer votre code pour accéder à des données externes pendant une requête. Il peut s’agir d’informations privées, comme les contacts, ou des données externes issues du Web. Gardez à l’esprit qu’un outil peut être invoqué plusieurs fois dans une même requête. Le modèle détermine cela en fonction du contexte. Les outils peuvent aussi être invoqués en parallèle, et ils peuvent stocker un état. Cela fait beaucoup d’infos. Prenez peut-être un café avant de reprendre vos activités. Pour en savoir plus, vous pouvez consulter la vidéo dédiée à l’ingénierie de l’invite, avec des conseils de conception et de sécurité. Et si vous souhaitez rencontrer la vraie Naomy, regardez la vidéo de code-along. J’espère que vous vous amuserez autant avec Foundation Models que moi. Merci de votre attention !

    • 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 - Introduction
    • Découvrez le framework de Foundation Models pour les appareils Apple, qui fournit un grand modèle de langage accessible via l’API Swift. Il explique comment utiliser Generable pour obtenir des résultats structurés, des schémas dynamiques et appeler des outils pour des fonctions personnalisées.

    • 0:49 - Séances
    • Dans cet exemple, Foundation Models améliore un jeu de café en style pixel art en générant des dialogues et du contenu dynamiques. Grâce à la création d’une « LanguageModelSession », des instructions personnalisées sont fournies au modèle, ce qui lui permet de répondre aux questions des joueurs. Le modèle traite les entrées de l’utilisateur et les instructions de la séance en jetons, de petites sous-chaînes, qu’il utilise ensuite pour générer de nouvelles séquences de jetons en sortie. La « LanguageModelSession » est une session avec état qui enregistre toutes les invites et toutes les réponses dans une transcription. Vous pouvez utiliser cette transcription pour déboguer et afficher l’historique des conversations dans l’interface utilisateur du jeu. Cependant, la taille de la session est limitée, cette limite étant appelée « limite de contexte ». La génération des réponses n’est pas déterministe par défaut. Le modèle utilise l’échantillonnage, ce qui crée une distribution de probabilités pour chaque jeton et introduit ainsi un élément aléatoire. Ce caractère aléatoire peut être contrôlé à l’aide de l’API GenerationOptions, qui vous permet d’ajuster la méthode d’échantillonnage, la température ou même de la définir sur greedy pour obtenir un résultat déterministe. Foundation Models va bien plus loin que le simple dialogue : il peut créer des contenus élaborés, comme imaginer les goûts et les commandes de café des personnages non-joueurs (PNJ). Cette dimension enrichit considérablement l’univers du jeu. Elle lui donne vie et le rend plus immersif. Vous devez également tenir compte des problèmes potentiels, tels que les langues non prises en charge et les gérer de façon appropriée afin d’offrir une expérience utilisateur conviviale.

    • 7:57 - Generable
    • L’API Generable de Foundation Models est un outil puissant qui simplifie l’obtention de données structurées à partir de grands modèles de langage. En ajoutant l’annotation @Generable aux structures et énumérations Swift, un schéma est automatiquement créé lors de la compilation, ce qui permet de définir la structure de sortie du modèle. Generable génère automatiquement un initialiseur et analyse le texte généré par le modèle pour le convertir en objets Swift de type sécurisé à l’aide d’un décodage limité. Cette méthode assure que les résultats générés par le modèle suivent parfaitement le schéma spécifié, ce qui permet d’éviter toute hallucination ou erreur dans la structure des données. Vous pouvez personnaliser la génération de données grâce aux « Guides », qui vous permettent d’établir des critères précis, des intervalles ou des descriptions en langage courant pour chaque propriété. Cette fonctionnalité vous offre un meilleur contrôle sur vos données, que ce soit pour définir des formats de noms, choisir le nombre de tableaux ou fixer des plages de valeurs. Grâce à Generable, générer des données devient un jeu d’enfant. Les développeurs peuvent ainsi se concentrer sur les éléments essentiels de leurs apps.

    • 14:29 - Schémas dynamiques
    • Dans le créateur de niveaux du jeu, les schémas dynamiques permettent aux joueurs de définir des entités personnalisées lors de l’exécution. Ces schémas, similaires aux structures de compilation, possèdent des propriétés avec des noms et des types, ce qui permet d’utiliser des tableaux et des références à d’autres schémas dynamiques. À partir des informations fournies par le joueur, un schéma de devinette est créé avec une question (chaîne de caractères) et un ensemble de réponses (type personnalisé avec propriétés de chaîne de caractères et booléennes). Ces schémas dynamiques sont validés, puis utilisés pour générer du contenu par Foundation Models, ce qui garantit que le résultat correspond à la structure définie. Cette méthode interactive permet aux joueurs de partager leurs propres énigmes qui s’affichent de manière fluide dans le jeu. Le système offre ainsi une grande liberté créative tout en gardant une organisation claire des informations.

    • 18:10 - Appel d’outils
    • Grâce à Foundation Models, les développeurs de jeux peuvent créer des DLC personnalisés à l’aide d’appels d’outils. Le modèle peut ainsi récupérer de manière autonome des informations sur l’appareil du joueur, telles que ses contacts et son calendrier, tout en préservant sa confidentialité, car les données ne quittent jamais l’appareil. La configuration d’un outil nécessite trois éléments essentiels : son nom, sa description et ses paramètres d’entrée. Ces informations permettent au modèle de comprendre quand il doit faire appel à l’outil et de quelle manière. Une fois activé, l’outil peut alors communiquer avec différentes API externes, comme Contacts API, pour extraire les informations souhaitées.

Developer Footer

  • Vidéos
  • WWDC25
  • Présentation approfondie du framework de Foundation Models
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • App Store
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Fonts
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Open Source
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Sample Code
    • Tutorials
    • Downloads
    • Forums
    • Videos
    Open Menu Close Menu
    • Support Articles
    • Contact Us
    • Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • 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
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Get the Apple Developer app.
    Copyright © 2025 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines