Ship 26 NYC - Atelier - Mini-travailleurs
VVercel
Computing/SoftwareSmall Business/StartupsInternet Technology
Transcript
00:00:00Bonjour à tous. Je m'appelle Jonathan Clem, ou vous pouvez m'appeler Jay Clem, et je suis ingénieur logiciel chez
00:00:11Notion où je travaille sur notre plateforme développeur, mais je me concentre particulièrement sur un produit plus récent
00:00:17dont vous avez peut-être entendu parler, appelé Notion Workers. Dans cet atelier aujourd'hui, je vais
00:00:23vous donner un aperçu de ce que sont les Notion Workers et pourquoi nous les avons conçus avec Vercel
00:00:28Sandbox. Ensuite, je vais vous guider à travers une version réduite des Workers,
00:00:36afin que vous puissiez au moins comprendre comment un tel produit est créé avec Vercel Sandbox.
00:00:43Si vous n'êtes pas familiarisé avec les Workers, il s'agit d'un SDK et d'un environnement d'exécution où vous pouvez écrire
00:00:50du code personnalisé pour étendre Notion. Vous pouvez ainsi synchroniser des données tierces dans Notion,
00:00:57ou écrire des appels d'outils personnalisés pour vos agents. Nous avons vu des gens faire des choses folles comme commander
00:01:04leurs courses avec Notion Workers ou contrôler leur maison connectée. Nous avons aussi vu des flux de travail très complexes,
00:01:11notamment dans les domaines de l'IT et de la sécurité. L'avantage des Workers, c'est qu'il n'y a aucune
00:01:17infrastructure à gérer. Vous écrivez simplement du code ou laissez un agent le faire pour vous, et Notion
00:01:23s'assure qu'il soit toujours opérationnel et exécuté. Cela a été un énorme bénéfice pour les utilisateurs de Notion,
00:01:30en particulier les développeurs. Ils n'ont plus à attendre que Notion crée des intégrations natives
00:01:36pour eux. Tout ce qu'ils aimeraient que Notion fasse mais qu'il ne fait pas, ils peuvent l'écrire
00:01:42eux-mêmes. Alors, quand nous avons commencé à concevoir Notion Workers, ma préoccupation principale était
00:01:53la sécurité. J'ai un peu d'expérience dans la création de plateformes qui exécutent du code utilisateur non approuvé.
00:01:59J'ai travaillé sur GitHub Actions, par exemple, pendant longtemps. Et je m'inquiétais surtout de la
00:02:04difficulté à bâtir l'infrastructure pour un produit comme celui-ci, et en particulier la sécurité. Il y a beaucoup
00:02:09de choses dont il faut se soucier. Par exemple, vous voulez vous assurer que le code écrit par les utilisateurs
00:02:14ne puisse pas toucher aux bases de données Notion, ni aux services Notion auxquels ils ne devraient normalement pas
00:02:20avoir accès. Vous voulez aussi vous assurer que les utilisateurs ne s'impactent pas entre eux. Vous ne voulez pas que le code
00:02:26d'un utilisateur puisse accéder au code d'un autre utilisateur ou, évidemment, à ses secrets ou à quoi que ce soit
00:02:31de ce genre. Ce n'est pas seulement une question de sécurité. C'est aussi une question d'équité et de partage des ressources. Si un utilisateur
00:02:37fait quelque chose qui consomme énormément de processeur et de mémoire, vous voulez vous assurer que cela n'impacte pas
00:02:42injustement d'autres utilisateurs qui essaient de faire des choses en même temps. Je vais prendre une minute
00:02:48pour m'écarter un peu du sujet et raconter une histoire. Le partage des ressources est une chose particulièrement
00:02:54délicate. Cela va probablement empiéter sur mon temps, mais j'aime bien cette histoire. Quand nous construisions
00:02:58GitHub Actions, et c'est une chose à laquelle nous avons aussi pensé avec les workers, dès que vous avez une
00:03:02plateforme d'exécution de code arbitraire, des gens vont immédiatement tenter d'y miner de la crypto.
00:03:07Et j'ai eu une conversation avec quelqu'un il y a quelques semaines qui me disait : “Mais
00:03:10comment détectes-tu cela ? Il suffit de voir si un processeur tourne à 100 %, non ?”
00:03:14Eh bien, pas vraiment, parce que dès que votre plateforme a du succès, les mineurs de crypto vont
00:03:20immédiatement commencer à se partager des scripts entre eux pour modifier la façon dont
00:03:26ils exécutent les instructions du processeur, afin que cela ressemble à une activité totalement inoffensive, tout en utilisant
00:03:31le maximum absolu de ressources possible sans se faire signaler par votre système.
00:03:37C'est donc extrêmement difficile, et cela demande des années et plusieurs couches de sécurité et
00:03:42d'observabilité pour y parvenir. Une autre chose dont il faut se soucier avec une
00:03:48plateforme d'exécution de code, c'est le risque que des gens l'utilisent pour lancer des attaques par déni de service
00:03:54ou pour contrôler des réseaux de botnets. Et nous ne voulions pas avoir à nous soucier de tout
00:04:00cela dès le départ avec Notion Workers. Nous voulions nous concentrer sur ce que nous aimons faire, à savoir
00:04:07fournir un produit que nos utilisateurs aiment utiliser. C'est pourquoi nous avons décidé de construire avec Vercel Sandbox.
00:04:14Vercel Sandbox proposait d'emblée une infrastructure très solide qui résolvait une grande partie de ces
00:04:18problèmes pour nous. Je vais donc vous montrer une très courte démo de ce que sont les Notion Workers.
00:04:25J'ai ici un agent personnalisé, et la tâche de cet agent est de me dire si cet atelier est maudit ou non.
00:04:33J'ai écrit un worker appelé Mercure Rétrograde. Je ne sais pas si vous connaissez le principe, mais quand Mercure
00:04:40est aligné de manière à être en rétrograde, c'est généralement un mauvais présage.
00:04:47Ce worker possède un seul outil personnalisé qui utilise une API trouvée en ligne dont le seul rôle
00:04:54est de dire si Mercure est en rétrograde. Je vais donc demander à mon agent : “Est-ce que mon
00:05:01atelier est maudit ?” Il va réfléchir un instant et ensuite, tant que la connexion internet fonctionne,
00:05:14nous devrions le voir effectuer un appel d'outil. Il utilise mon worker Mercure Rétrograde, appelle le
00:05:22seul outil qu'il propose pour vérifier si Mercure est en rétrograde, puis l'agent
00:05:27va nous répondre. Je crois que je l'ai connecté à GPT-54 nano, donc c'est généralement beaucoup
00:05:37plus rapide. Je pense que c'est un problème d'internet, malheureusement. Si quelqu'un a vérifié au préalable si Mercure était en
00:05:46rétrograde, je sais déjà d'avance que oui. C'est sûrement pour cela que ça bloque. Je vais simplement
00:05:53passer cette étape. Nous y reviendrons dans une minute pour voir si nous avons fini par avoir une réponse. Mais je crois qu'on a déjà
00:05:59la réponse. Bien, vous avez maintenant une idée de ce que sont les Notion Workers. Je vais vous montrer
00:06:05un petit script que j'ai écrit qui exécute la plupart des tâches de base consistant à prendre le code utilisateur,
00:06:13le déployer, l'exécuter en toute sécurité et le présenter à un agent afin qu'il puisse
00:06:20effectuer des appels d'outils. Est-ce que ça s'est terminé ? Ah oui, voilà. Ah, je crois que...
00:06:28J'ai dû désactiver l'API Mercure Rétrograde parce qu'apparemment elle n'a pas répondu.
00:06:35Bref, il faudra que j'ouvre un ticket quelque part. Super. J'ai donc un script de base. Je n'attends pas
00:06:40de vous que vous me suiviez pas à pas en écrivant du code. Je vais plutôt survoler les étapes
00:06:44pour vous donner une idée des problèmes que nous avons résolus lors de la création d'un tel
00:06:49produit. Mais si vous voulez, il y a un dépôt make-notion/vercel-ship-2026-workers
00:06:55qui contient tout le code que je vais exécuter ici.
00:07:01Parfait. Laissez-moi ouvrir mes autres diapositives.
00:07:08Alors, quel est notre objectif ? Nous avons un agent de chat en streaming. Nous allons lui permettre d'appeler des outils
00:07:16définis par du code utilisateur personnalisé non vérifié ou dont nous ignorons le contenu.
00:07:22Et nous allons construire cela avec Vercel Sandbox et le service de stockage blob de Vercel.
00:07:26Je vais faire une rapide démonstration, en espérant que nous ne sommes pas vraiment maudits.
00:07:31On peut simplement lui envoyer un seul message. Je vais dire “salut” et j'espère que nous aurons une réponse.
00:07:36Ce sera un peu lent car, comme vous le voyez, il fait des déploiements en arrière-plan.
00:07:40Il a appelé un outil cette fois. Il a appelé l'outil “say hello” et a décidé de me saluer.
00:07:47Je vais lui envoyer un message normal pour lui demander combien font un plus un, afin de le voir
00:07:52générer une réponse classique en streaming.
00:07:58Si vous avez utilisé le SDK IA de Vercel, cela utilise simplement la boucle de l'agent d'outils. Super. Nous avons donc obtenu une réponse
00:08:04en streaming. Vous pouvez aussi appeler des workers beaucoup plus complexes. En voici un autre que je vais vous montrer.
00:08:14Avec celui-ci, nous lui indiquons que je me trouve à l'adresse de ce bâtiment.
00:08:17J'ai 90 minutes devant moi, je veux visiter un site historique et je ne veux pas marcher plus de 15 minutes.
00:08:24Cela illustre pourquoi cette approche est parfois préférable aux serveurs MCP. Avec un serveur
00:08:29MCP, vous avez plein d'outils distincts utilisables. Mais pour accomplir quelque chose
00:08:33de complexe, il faut décrire chaque étape à l'agent, qui va exécuter une étape, consommer des jetons
00:08:38de réflexion, puis passer à la suivante. Ici, l'agent appelle un worker contenant un algorithme de recherche
00:08:45très vaste et complexe qui utilise des API de géolocalisation, de temps de transport et d'itinéraires à New York.
00:08:52Il s'appelle “plan outing”.
00:08:56Et ce worker va renvoyer une proposition de site historique à visiter.
00:09:00Il semble qu'il ait trouvé le Freedom Tree Marker, un mémorial situé près
00:09:07de City Hall Park. Regardons maintenant comment cela fonctionne. Qu'est-ce qu'un worker ?
00:09:14C'est du code utilisateur qui est simplement défini dans un fichier. Normalement, il serait défini
00:09:20par vos utilisateurs ou sur GitHub, puis déployé via une interface CLI. Mais nous avons quelques exemples intégrés
00:09:26dans le dépôt ici. Chacun de ces workers doit exposer le nom de l'outil, une description
00:09:34pour que l'agent choisisse le bon outil, un schéma d'entrée pour que l'agent sache quelles données fournir
00:09:38à chaque appel, ainsi qu'une fonction d'exécution indiquant le code à exécuter
00:09:44lorsque l'agent appelle l'outil. Prenons un exemple avec l'outil d'accueil vu
00:09:51précédemment. C'est très simple. Ce worker est un seul module JavaScript dans le fichier index.ts. Il exporte un seul
00:09:59worker nommé “say hello”. Vous allez voir le fonctionnement du processus de build, mais dans cet exemple,
00:10:04toutes nos clés d'exportation de modules correspondent aux noms de nos outils. Cet outil s'appellera donc
00:10:09“say hello”. Il contient une description simple et un schéma d'entrée. Ici, j'utilise Zod pour définir
00:10:16le schéma, puis je le convertis au format JSON Schema, attendu par les agents.
00:10:22Il y a ensuite une fonction d'exécution simple. Mais on peut en créer de beaucoup plus complexes. Si j'ouvre
00:10:27le flux de travail de “plan outing”, vous verrez une description bien plus longue expliquant à l'agent
00:10:34le fonctionnement de l'outil, quand l'appeler et ce qu'il fait. Le script associé est lui aussi beaucoup
00:10:39plus long et complexe. Je ne vais pas tout détailler, et je n'en ai moi-même lu que de petites parties,
00:10:45mais cela fonctionne très bien. C'est notre réalité aujourd'hui. Ces workers vont donc être
00:10:54compilés et déployés sur le stockage blob de Vercel. Ensuite, nous allons extraire le contenu des
00:10:59workers d'une manière ou d'une autre, puis les exposer à un agent. Enfin, ces outils seront
00:11:03exécutés en toute sécurité dans un sandbox. Pour la phase de build, nous allons procéder par étapes. Avec Notion Workers, nous avons un
00:11:09processus de déploiement et de build basé sur le cloud. Dans ce cas précis, j'ai pré-compilé les workers sur le disque. Chaque
00:11:15worker dispose d'une archive tarball contenant tout le code TypeScript compilé, les dépendances, etc.
00:11:22Ce n'est pas la partie la plus intéressante, donc je vais simplement la passer.
00:11:26La question du jour est : comment passer d'un code utilisateur inconnu et non vérifié
00:11:34à des outils exposés et exécutés en toute sécurité par l'agent ? Cela se décompose en deux parties.
00:11:39La première partie consiste à déterminer, une fois le code utilisateur stocké dans le blob,
00:11:43comment savoir ce qu'il contient. Il faut en effet pouvoir indiquer à l'agent, avant même
00:11:48qu'il n'appelle l'outil, son nom, son schéma d'entrée et sa description.
00:11:53La seconde question est : quand l'agent décide d'appeler cet outil,
00:11:58comment l'exécuter en toute sécurité ? Ce que j'adore dans la conception du SDK Notion
00:12:04Workers et ce que nous avons reproduit ici, c'est que le code s'auto-décrit. Nous ne voulions pas,
00:12:11en concevant Notion Workers, que les utilisateurs doivent écrire du code TypeScript pour définir leur outil,
00:12:16puis créer un fichier de manifeste statique pour décrire leurs workers en réécrivant les mêmes informations.
00:12:21Nous ne voulions pas d'un processus de build local lourd où ils devaient lancer un script, analyser le code,
00:12:27le compiler sur disque puis le déployer. Nous voulions offrir l'expérience développeur idéale :
00:12:34écrire simplement l'outil, et nous laisser gérer la tâche complexe d'en extraire
00:12:40automatiquement les informations nécessaires.
00:12:46Voici un schéma très simple, mais avant de regarder le code, je vais vous expliquer
00:12:52comment nous allons résoudre ce problème. Le code compilé de l'utilisateur se trouve sur le stockage blob.
00:12:59Nous allons utiliser ce code pour créer un sandbox. Lorsque nous exécutons des commandes dans le sandbox,
00:13:04le code utilisateur se trouve à la racine de l'espace de travail. Nous importons le fichier index.js écrit par l'utilisateur,
00:13:11ce qui nous donne les noms des exportations, leurs descriptions et leurs schémas d'entrée.
00:13:19C'est ici que ça devient un peu étrange, mais cela fonctionne très bien. Nous appelons
00:13:23JSON.stringify sur ce module, puis nous l'envoyons dans la sortie standard. Tout cela se déroule dans
00:13:29le sandbox, et notre script de déploiement récupère cette sortie standard, l'analyse et identifie
00:13:34le nom de tous les outils du worker, leurs entrées et leur description. Dans cet exemple,
00:13:40nous les transmettons directement à l'agent. Mais pour Notion Workers, cela fait partie intégrante
00:13:44de notre pipeline de déploiement. Nous enregistrons ces informations en base de données pour pouvoir,
00:13:49à chaque exécution d'un agent personnalisé, récupérer les descriptions d'outils depuis
00:13:53la base de données. Est-ce que le fonctionnement est clair pour l'instant ? Très bien. Regardons maintenant le script
00:14:02de déploiement. Je dois revenir à mon premier commit. Ce script intègre tout notre processus de déploiement,
00:14:12puis l'appel à l'agent. C'est très simple. Ici, nous parcourons simplement tous les répertoires
00:14:18et les workers. Chaque sous-répertoire contient le code d'un worker, comme vu précédemment.
00:14:23Notre rôle est d'extraire les informations sur les outils de chaque worker pour remplir
00:14:29cet objet d'outils. Il est ensuite transmis à un agent à boucle d'outils fourni par le SDK AI
00:14:35de Vercel. Nous envoyons le message de l'utilisateur à cet agent, diffusons le résultat en streaming
00:14:42et l'écrivons sur la sortie standard du script. La première étape consiste à téléverser le code
00:14:48source. C'est facile et rapide. Plutôt que de coder en direct, je vais passer
00:14:53directement d'un bloc de code à l'autre pour vous éviter de me voir saisir du texte. Je vous assure que je sais
00:14:59coder, mais personne n'a envie de regarder ça. En avançant un peu, nous trouvons la fonction
00:15:06uploadSource que je vais détailler. J'ai importé le SDK Vercel Blob. C'est assez
00:15:13simple : dans cette fonction uploadSource, nous appelons la fonction put
00:15:20en indiquant que nous voulons stocker le bundle sous nom_du_worker/bundle.tar.gzip.
00:15:29Nous lisons le fichier sur le disque en streaming et le stockons dans le stockage blob. Une fois cela fait,
00:15:35nous pouvons créer des sandboxes à partir de ce blob. La partie téléversement est donc résolue.
00:15:43L'étape suivante consiste à prendre ce code source regroupé pour créer
00:15:49un sandbox. Pour cela, j'ai importé le SDK Vercel Sandbox. Il y a une autre fonction d'aide
00:15:56plus bas, appelée createSandbox. Je vais passer sur quelques détails, mais pour l'essentiel,
00:16:03une fois l'objet dans le stockage blob, nous générons des URL pré-signées que nous transmettons
00:16:10au service de sandbox. Ainsi, le service peut, durant le délai d'expiration fixé je crois à 10 minutes,
00:16:16récupérer le blob depuis le stockage et remplir un sandbox. C'est l'une des
00:16:23fonctionnalités que j'apprécie vraiment dans Vercel Sandbox : la prise en charge native des archives tarball. Il est facile
00:16:28de créer une archive du code utilisateur, et lorsque le service de sandbox doit en créer un instance,
00:16:35il la récupère depuis le stockage ou l'URL fournie, puis l'extrait automatiquement
00:16:40dans le répertoire racine. Tous les fichiers sont immédiatement prêts à l'emploi. Il suffit d'appeler
00:16:46sandbox.create. Nous n'avons pas encore abordé la partie complexe, mais nous y arrivons.
00:16:52Pour des raisons de concision dans cet exemple, je crée un nouveau sandbox à chaque
00:16:57exécution. En production, il vaut mieux utiliser un système d'instantanés (snapshots) pour éviter
00:17:02de redéployer le sandbox ou de télécharger à nouveau le blob depuis Vercel
00:17:09Blob Storage à chaque appel d'outil. Un mécanisme de mise en cache est directement
00:17:15intégré à la plateforme. Je l'ai omis ici par souci de simplicité.
00:17:19Voilà pour la création du sandbox, et c'est là que cela devient intéressant.
00:17:23La tâche suivante consiste à extraire les informations relatives aux outils
00:17:31définis dans ce worker depuis le sandbox fraîchement créé.
00:17:36Je fais une courte pause pour partager quelques conseils utiles. Si vous développez un service en production
00:17:40de ce type, veillez à toujours faire votre possible pour fermer vos sandboxes. Dans le code,
00:17:44vous verrez le rôle d'extractTools : une fois son travail terminé, nous supprimons explicitement le sandbox.
00:17:51En pratique, il est aussi conseillé de planifier une tâche de nettoyage asynchrone
00:17:56afin de ne pas laisser ces instances ouvertes indéfiniment.
00:18:00Examinons maintenant le fonctionnement de la fonction extractTools. Avec notre sandbox,
00:18:06j'aime beaucoup cet exemple car la solution semble presque trop simple, mais elle s'avère redoutablement efficace. Nous exécutons
00:18:15une commande Node à l'aide de son binaire directement dans le sandbox. Dans le script, nous importons le module
00:18:23écrit par l'utilisateur, à savoir index.js. Nous sérialisons cet objet en chaîne de caractères, puis nous appelons
00:18:30console.log. Gardez à l'esprit qu'un sandbox n'est pas un serveur web classique capable
00:18:36de recevoir une requête et de renvoyer une réponse directes. Les entrées et sorties passent par l'exécution de commandes.
00:18:42Vous pouvez envoyer la réponse à un service externe interrogé à intervalles réguliers, ou bien
00:18:48simplement l'afficher sur la sortie standard. Quelques recommandations toutefois :
00:18:52ne faites pas aveuglément confiance au contenu d'un console.log issu du sandbox. Des paquets tierce partie
00:19:00installés par l'utilisateur ou d'autres pans de son code pourraient écrire simultanément dans cette même sortie.
00:19:07Une bonne pratique consiste à encadrer le résultat par des balises spécifiques facilement analysables,
00:19:12comme des balises de type XML. Je ne l'ai pas inclus ici pour simplifier l'exemple.
00:19:18Veillez également à limiter la taille des journaux récupérés. Dans cet exemple,
00:19:23je rassemble l'intégralité du flux de sortie en mémoire. En environnement de production,
00:19:29cette méthode est déconseillée : un flux de plusieurs gigaoctets risquerait d'épuiser la mémoire de vos serveurs.
00:19:34L'API de sandbox propose des commandes dédiées à la lecture continue des journaux. C'est le choix
00:19:39fait pour Notion Workers : nous lisons le flux jusqu'à détecter la balise de début
00:19:45recherchée, puis nous enregistrons la description voulue en mémoire tampon.
00:19:53Dès l'apparition de la balise de fin, le traitement du flux s'arrête. Cette méthode protège
00:19:58nos serveurs si le sandbox génère un volume anormal de données :
00:20:04nous ignorons le surplus pour ne conserver que les données utiles.
00:20:08Une fois la commande exécutée, nous attendons sa fin pour en lire la sortie standard.
00:20:14Si vous avez déjà utilisé Zod, la suite vous sera familière : nous analysons la chaîne en JSON,
00:20:22Et si vous avez déjà utilisé Zod, cela devrait vous sembler familier. On analyse cette chaîne en JSON.
00:20:27Ensuite, nous avons ici un type Zod qui
00:20:31valide que la structure correspond bien à ce qu'on attend.
00:20:34C'est vraiment important de ne pas faire confiance à ces données car c'est du code utilisateur non sécurisé.
00:20:39Vous n'avez aucune idée de ce que les gens pourraient envoyer. Il faut donc s'assurer de
00:20:43valider la taille et la structure globale. Dans ce cas précis, ce qu'on attend,
00:20:50c'est un enregistrement dont les clés, les chaînes, seront le nom de chacun de nos outils.
00:20:57Et les objets seront la description puis le schéma d'entrée, c'est-à-dire ce
00:21:02schéma JSON. Vous remarquerez que la fonction execute n'est pas là.
00:21:06Elle n'est tout simplement pas renvoyée, ce qui est pratique car JSON.stringify ignore ce qui n'est pas
00:21:11sérialisable. C'est donc ignoré. On en extrait uniquement la description et le schéma d'entrée.
00:21:18De retour ici, nous avons nos outils de worker qui représentent cet
00:21:23enregistrement de tous les outils exposés dans ce worker.
00:21:28L'étape suivante consiste à mettre ces données au format attendu par l'AI SDK.
00:21:36Et nous devons attacher une fonction d'exécution à chacun de ces outils.
00:21:42Évidemment, nous n'avions pas de fonction d'exécution en envoyant juste les journaux sur la sortie standard.
00:21:46Maintenant que nous avons cette description de l'outil, la question est : comment fournir
00:21:51une fonction que le SDK peut appeler à chaque fois qu'il veut faire appel à ce worker ou à cet outil
00:21:58exposé par ce worker ? J'ai donc ce petit wrapper appelé execute tool. Voyons ce
00:22:04qu'il fait. Cela devrait vous sembler familier. Il appelle cette même fonction create sandbox.
00:22:10Il crée un bac à sable tout neuf. Même en utilisant du cache, les bacs à sable Vercel proposent
00:22:16une fonctionnalité de persistance : à chaque fois que le bac à sable s'arrête, il met en cache l'état du disque.
00:22:23Pour une fonctionnalité comme celle-ci, ce n'est pas ce que l'on souhaite. On veut capturer l'état initial
00:22:28contenant le code utilisateur. Mais en général, après cela, on veut s'assurer qu'à chaque
00:22:33exécution de l'outil, on dispose d'une instance totalement propre. Ainsi, si une exécution
00:22:38échoue, elle ne va pas polluer l'environnement de l'outil suivant.
00:22:43On crée donc un nouveau bac à sable et on y exécute un autre script Node. C'est très similaire,
00:22:49mais avec une légère différence. On importe le module écrit par l'utilisateur. On récupère
00:22:56l'outil de ce module, qui correspond au nom de l'outil obtenu lors de la création du wrapper execute tool.
00:23:03On appelle sa fonction execute, en lui passant l'entrée qui a été
00:23:09fournie par le modèle. Ici, je la typera en unknown. C'est assez sûr ici car
00:23:19nous avons fourni un... est-ce que je ne l'ai pas fait ici ? Je crois que j'ai sauté cela dans cet exemple. Mais ce
00:23:27que l'on fait d'habitude... ah non, je l'ai fait. Laissez-moi remonter ici. Oui. Quand on manipule
00:23:34nos outils pour les envoyer à l'agent, on prend ce schéma JSON et on le transforme
00:23:39à nouveau en type Zod. Pas besoin de faire l'analyse nous-mêmes. L'AI SDK et le code de la boucle d'outils
00:23:46vont valider pour nous l'entrée provenant de l'agent à chaque appel d'outil.
00:23:52On peut donc être à peu près sûr que cette valeur est conforme. On la passe
00:23:56dans la fonction execute ici. Une fois la fonction asynchrone terminée, on va la
00:24:03sérialiser en chaîne. On l'écrit dans la sortie standard. Et ensuite on attend... je reviendrai là-dessus
00:24:10dans un instant. On attend que la commande se termine. Et à nouveau, on
00:24:14supprime notre bac à sable. On analyse ensuite ce JSON, qui est la valeur de retour
00:24:21de la fonction d'exécution écrite par l'utilisateur. Et on renvoie cela à l'agent de la boucle d'outils.
00:24:26En gros, le flux est le suivant : l'agent demande à appeler l'outil de planification de sortie
00:24:35qu'on a vu plus tôt et qui utilise les API de transport. Cela finit par appeler cette fonction
00:24:40avec les arguments choisis par l'agent. On crée un bac à sable à partir du blob téléversé
00:24:48contenant le code compilé de l'utilisateur. On lance une commande dans ce bac à sable en appelant
00:24:55la fonction execute de l'utilisateur. On attend le retour de cette fonction. Puis on enregistre cette valeur
00:25:00dans la sortie standard, on la lit et on la renvoie à l'agent, qui va poursuivre la boucle d'outils
00:25:05en effectuant un autre appel ou en répondant à l'utilisateur. Quelques conseils rapides : les mêmes
00:25:13règles de vérification de la sortie s'appliquent en production. Il faut s'assurer que les utilisateurs
00:25:19ne renvoient pas un objet de deux gigaoctets à analyser. Il est aussi fortement conseillé
00:25:28de fournir un SDK. Nous ne le faisons pas dans cet exemple, mais dans le SDK Notion Workers,
00:25:35les types sont définis pour que la valeur de retour de la fonction d'exécution soit sérialisable en JSON.
00:25:41C'est un piège très classique pour les utilisateurs : si la fonction peut renvoyer
00:25:47n'importe quoi, ils finiront par renvoyer des éléments non sérialisables en JSON qui ne pourront être envoyés
00:25:52sur le réseau via stdout ni analysés. Ils seront alors très confus et ne comprendront pas pourquoi le
00:25:57worker ne tourne pas. Voici une petite anecdote vécue avec Notion Workers. Il faut
00:26:05toujours veiller à ce que le processus Node lancé
00:26:11se termine quoi qu'il arrive. Nous avons eu un bug chez Notion : le code utilisateur s'exécutait correctement,
00:26:18mais pour une raison inconnue, le processus Node ne se terminait pas. Il restait bloqué jusqu'à
00:26:25la fin du cycle de vie du bac à sable, soit environ cinq minutes. Ce n'était pas catastrophique,
00:26:30mais cela gaspillait des ressources. Nous avons réalisé — j'avais complètement oublié cette
00:26:38particularité de l'environnement Node — que les utilisateurs exécutaient du code configurant des minuteurs avec
00:26:44setInterval ou setTimeout. Surtout les intervalles, ou même les promesses non résolues.
00:26:52Si le script s'exécute et que des intervalles restent actifs, le processus Node ne
00:26:58s'arrêtera jamais de lui-même tant que ces minuteurs n'auront pas expiré. Dans le cas d'un
00:27:03intervalle, il tournera indéfiniment jusqu'à l'arrêt du bac à sable. Il faut donc s'assurer
00:27:08qu'une fois le code ou la fonction exécutée avec succès, vous ordonniez explicitement
00:27:14au processus de s'arrêter. Cela stoppe le bac à sable et le processus rend la main juste après.
00:27:23Voilà pour le flux global. Je vais le relancer une fois de plus en activant le mode
00:27:29débogage pour que vous puissiez voir ce qu'il se passe.
00:27:42On commence par déployer le worker de départs. Je vais tout détailler. On déploie le
00:27:48worker de départs en téléversant d'abord ce bundle. On crée le bac à sable qui servira à extraire
00:27:54les informations du worker. On a extrait ces outils. Voici ce qu'on obtient en affichant
00:28:01le contenu des modules dans stdout : les clés de chaque outil, la description et le schéma d'entrée.
00:28:08On fait la même chose pour ce simple worker greeter. On passe ensuite tout cela à l'agent pour qu'il
00:28:13puisse exécuter les outils et répondre à l'utilisateur. Voilà l'essentiel. C'est un exemple très simple
00:28:19montrant comment prendre du code utilisateur non sécurisé, le stocker, l'analyser en toute sécurité
00:28:26pour le sauvegarder ou l'envoyer à des agents, puis le faire exécuter sans risque
00:28:31par vos agents. Il nous reste environ 10 minutes. Si vous avez des questions sur
00:28:39Notion Workers, sur les bacs à sable Vercel ou l'exécution de code externe,
00:28:46je serai ravi d'en discuter. Merci.
00:28:56Ah oui ! Si vous voulez échanger sur la plateforme développeur Notion de manière générale,
00:29:01vous pouvez venir me voir ou parler à ma collègue MJ ici présente. Elle est responsable produit de la plateforme développeur chez Notion.
00:29:08Bonjour. C'est une question d'ordre opérationnel : comment limitez-vous, sachant que les utilisateurs peuvent tout faire...
00:29:15Oui. Plusieurs utilisateurs pourraient créer un outil similaire.
00:29:20Plusieurs utilisateurs qui font quoi ? Qui écrivent un outil similaire. Par exemple, pour planifier un voyage à New York.
00:29:24Oui, vous pourriez avoir 10 utilisateurs différents avec 10 codes différents qui font la même chose.
00:29:29Oui, est-ce que vous faites quelque chose pour bloquer ça ou c'est entièrement géré par l'agent ?
00:29:33Non. Si plusieurs utilisateurs veulent faire la même chose, nous les laissons faire.
00:29:37C'est en partie une question de produit. Si ce sont plusieurs utilisateurs au sein d'une même organisation,
00:29:42c'est en effet une question opérationnelle et produit à la fois.
00:29:46Il faut proposer de bonnes fonctionnalités de partage. Ainsi, je peux
00:29:50chercher s'il existe déjà un worker effectuant cette tâche pour ne pas
00:29:55avoir à le réécrire. Mais au niveau de la plateforme globale, nous ne faisons rien : il est très rare
00:30:02que les utilisateurs déploient un code identique. Cela ne vaut pas la peine d'essayer de dédupliquer.
00:30:19D'accord. Je crois qu'il y a d'autres questions. Je ne sais pas qui a le micro.
00:30:24Je n'ai pas entendu. Ah, oui ! Je suis désolé.
00:30:29Je pensais que le micro le capterait.
00:30:33La question était : si de nombreux utilisateurs déploient le même code,
00:30:40faites-vous quelque chose pour rationaliser cela ? Et la réponse est non. C'est un sujet produit.
00:30:45Nous voulons éviter que les utilisateurs ne refassent le travail déjà fait. Nous développons donc de bonnes options de partage
00:30:50sur Notion Workers. Mais d'un point de vue purement infrastructure,
00:30:54si les gens déploient 50 fois le même code, cela nous importe peu.
00:30:56Avez-vous des problèmes de temps d'attente dépassés avec les workers parce que le bac à sable
00:31:04s'arrête trop rapidement pendant l'exécution ?
00:31:07Quelle était la question exacte concernant les timeouts ?
00:31:10Rencontrez-vous des problèmes de timeouts entre Vercel (fonctions, workflows) et
00:31:15votre bac à sable, ou tout fonctionne-t-il parfaitement ?
00:31:20Nous n'avons eu aucun problème avec cela. La plateforme est très stable
00:31:25jusqu'à présent. Ce n'est pas de la promo, c'est très sincère : cela fonctionne très bien.
00:31:32Nos soucis viennent surtout des erreurs de manipulation des utilisateurs. Avec le temps,
00:31:36notre objectif est d'éliminer ces pièges et de rendre la plateforme toujours plus simple d'accès
00:31:42pour les développeurs comme pour les non-développeurs. Super, merci. Je voulais poser une question sur...
00:31:48la sérialisation du code utilisateur initial. J'imagine que c'est pour garantir
00:31:52la sécurité ou la possibilité de l'exécuter. Je souhaitais en savoir un peu plus.
00:31:57Vous avez dit que vous sérialisez le code pour obtenir les tags de worker afin de définir les entrées attendues
00:32:04pour le code utilisateur, ainsi qu'une description de l'outil. Est-ce destiné
00:32:10à votre agent pour exécutez le code ? J'ai remarqué que vous lancez déjà la fonction
00:32:15d'exécution de l'utilisateur. Je suis curieux de savoir pourquoi vous effectuez cette
00:32:21étape supplémentaire pour récupérer les entrées et la description de l'outil.
00:32:24Excellente question. C'est plus clair dans le vrai produit, mais j'ai simplifié ici.
00:32:29Vous demandez pourquoi exécuter le code utilisateur une première fois pour obtenir des informations sur le
00:32:34worker, puis de nouveau lors de l'appel de l'outil ? La raison est qu'avant que l'agent
00:32:39puisse appeler l'outil ou même savoir qu'il existe, nous devons découvrir ce
00:32:45qu'il contient pour le fournir à l'agent via le SDK. Dans Notion
00:32:50Workers, par exemple, lorsqu'on lance NTN Workers Deploy, on passe par le pipeline de build,
00:32:55le bac à sable exécutant le build prend l'archive et la stocke. Ensuite,
00:33:02dans un nouveau worker, nous extrayons le nom des outils, les descriptions et les
00:33:09schémas pour les enregistrer dans DynamoDB. Ainsi, par la suite, nous ne lançons
00:33:14aucun bac à sable avant l'appel effectif de l'outil. Vous avez mentionné le système d'archives. C'est
00:33:21ce qui m'intrigue le plus. Comment fonctionne exactement la distribution avec ces archives ?
00:33:26Avez-vous un catalogue public actuellement ou comment gérez-vous la distribution ?
00:33:31Vous voulez savoir ce que contient l'archive ? Oui, c'est ça.
00:33:33Concernant le fonctionnement technique de ce qui va dans l'archive,
00:33:38nous utilisons esbuild. Le pipeline de déploiement réel fonctionne en exécutant
00:33:47NTN workers deploy, qui appelle une API. Votre machine reçoit une URL pré-signée, regroupe
00:33:52votre code source dans une archive, puis un processus de build esbuild tourne dans le
00:33:58bac à sable. Le résultat est remis sur le stockage blob pour pouvoir être exécuté
00:34:04directement depuis là. Nous n'avons pas encore de marketplace pour les workers. Nous travaillons sur le partage
00:34:11au sein d'un espace de travail Notion d'abord, mais nous prévoyons de créer un catalogue de workers à l'avenir.
00:34:18En attendant, vous pouvez les partager sur GitHub et cela fonctionne très bien. C'est ce que font les gens aujourd'hui.
00:34:23Il suffit de publier un dépôt, quelqu'un peut le cloner, lancer NTN workers deploy et l'utiliser.
00:34:29Tout à fait.
00:34:34Je crois qu'il y a une question au fond.
00:34:37Oui, j'ai une question concernant la facturation.
00:34:40Sur la facturation ?
00:34:40Oui. En cherchant rapidement sur Google — sans trahir de secrets bien sûr —
00:34:44il semble que les agents personnalisés fonctionnent avec un système de crédits. J'imagine
00:34:48que c'est lié à la consommation de ressources.
00:34:51Oui, exactement.
00:34:51Comment cela s'articule-t-il avec la plateforme Vercel dans les grandes lignes ?
00:34:54La question est : comment fonctionne la facturation de ces éléments avec la plateforme Vercel ?
00:35:02L'un des avantages des workers est qu'ils permettent d'éviter d'avoir un énorme ensemble d'instructions
00:35:08via des serveurs MCP fournis aux agents. À chaque appel de tâche,
00:35:16l'agent consomme énormément de jetons de réflexion pour refaire encore et encore la même chose.
00:35:21En prenant ces tâches répétitives de l'agent pour les déployer sous forme de worker,
00:35:33le temps d'exécution vous est toujours facturé, mais cela coûte bien moins cher que des jetons de calcul IA.
00:35:40Lorsqu'un agent personnalisé réfléchit, exécute un outil, puis réfléchit à nouveau,
00:35:48vous payez pour la consommation de jetons de l'agent personnalisée avant l'appel de l'outil.
00:35:53Ainsi, lorsque l'outil s'exécute, vous êtes facturé à un tarif différent qui consomme vos crédits, comme vos crédits Notion AI, à un rythme beaucoup, beaucoup plus bas.
00:36:01Puis les crédits IA habituels s'appliquent à nouveau lorsque l'agent formule sa réponse finale.
00:36:05Si vous utilisez les agents personnalisés Notion, c'est un excellent moyen de réduire les coûts sur les tâches répétitives.
00:36:16C'est exact. L'accès à Notion AI n'est pas obligatoire : nous présentons ici des appels d'outils intégrés à Notion AI,
00:36:24mais les workers gèrent aussi la synchronisation tierce dans Notion, ce qui ne nécessite aucune fonction d'IA.
00:36:31Ce n'est donc pas uniquement dédié à l'IA, les gens s'en servent pour synchroniser des données.
00:36:36J'ai par exemple des workers qui synchronisent mon flux Letterboxd dans Notion. C'est mon préféré.
00:36:46Très bien. D'autres questions ?
00:36:52Une dernière question ?
00:37:06Vous voulez parler d'exposer les agents personnalisés Notion via votre propre interface utilisateur ?
00:37:13Ce n'est pas possible pour le moment.
00:37:16Pardon, merci MJ. La question était : si on a nos propres workers et agents personnalisés,
00:37:22peut-on les intégrer dans notre propre application pour nos utilisateurs ?
00:37:28Aujourd'hui, ce n'est pas possible. Nous avons une version alpha d'une API d'agents personnalisés qui le permettra.
00:37:35En définissant des agents personnalisés et des workers dans votre espace de travail,
00:37:40vous pourrez utiliser une API pour les appeler et recevoir une réponse en streaming. C'est donc envisageable.
00:37:46Peu de gens l'utilisent encore, car c'est une fonctionnalité publique mais en version alpha.
00:37:57Y a-t-il d'autres questions ?
00:38:00Parfait. Merci à tous d'avoir pris le temps
00:38:04d'assister à cette présentation. Si vous avez des questions sur Notion, Notion Workers
00:38:08ou Vercel Sandbox, n'hésitez pas à venir nous voir, MJ et moi. Merci !