Ship 26 NYC - Workshop - Mini-workers
VVercel
Computing/SoftwareSmall Business/StartupsInternet Technology
Transcript
00:00:00Halo semuanya. Nama saya Jonathan Clem, atau Anda bisa memanggil saya Jay Clem, dan saya adalah seorang insinyur perangkat lunak di
00:00:11Notion tempat saya bekerja pada platform pengembang kami, tetapi saya khususnya berfokus pada produk yang lebih baru
00:00:17yang mungkin pernah Anda dengar yang disebut Notion Workers. Dalam lokakarya hari ini, saya akan memberikan
00:00:23sedikit gambaran umum tentang apa itu Notion Workers dan mengapa kami membangunnya menggunakan Vercel
00:00:28Sandbox. Lalu saya akan mengajak Anda melihat versi mini dari Workers,
00:00:36sehingga Anda setidaknya bisa memahami bagaimana produk seperti itu dibangun menggunakan Vercel Sandbox.
00:00:43Jadi jika Anda belum familiar dengan apa itu Workers, mereka adalah SDK dan runtime tempat Anda dapat menulis
00:00:50kode kustom dan memperluas Notion dengannya. Jadi Anda bisa melakukan hal-hal seperti menyinkronkan data pihak ketiga ke Notion,
00:00:57Anda bisa menulis panggilan alat kustom untuk agen Anda. Kami telah melihat orang-orang melakukan hal-hal seru yang gila seperti memesan
00:01:04bahan makanan mereka dengan Notion Workers atau mengontrol rumah pintar mereka. Kami juga melihat alur kerja yang sangat kompleks
00:01:11khususnya dari bidang IT dan keamanan. Hal yang menyenangkan dari Workers adalah tidak ada
00:01:17infrastruktur yang harus Anda kelola. Anda hanya perlu menulis kode atau meminta agen koding menulisnya untuk Anda, dan Notion
00:01:23yang menangani agar kode tersebut selalu aktif, tersedia, dan berjalan. Ini adalah manfaat yang sangat besar bagi pengguna Notion,
00:01:30terutama para pengembang. Mereka tidak perlu lagi menunggu Notion membuat integrasi
00:01:36pihak pertama untuk mereka. Apa pun yang mereka harapkan bisa dilakukan oleh Notion namun belum ada, mereka bisa langsung menulisnya
00:01:42sendiri. Jadi ketika kami pertama kali mulai membangun Notion Workers, perhatian utama saya adalah
00:01:53keamanan. Saya memiliki sedikit pengalaman dalam membangun platform tempat Anda menjalankan kode pengguna yang tidak terpercaya.
00:01:59Saya pernah mengerjakan GitHub Actions, misalnya, untuk waktu yang lama. Dan saya terutama khawatir tentang
00:02:04kesulitan dalam membangun infrastruktur untuk produk seperti ini dan khususnya masalah keamanan. Ada banyak
00:02:09hal yang harus Anda khawatirkan. Misalnya, Anda ingin memastikan bahwa kode yang ditulis oleh pengguna
00:02:14tidak dapat menyentuh basis data Notion, tentu saja, atau tidak dapat menyentuh layanan Notion yang biasanya tidak boleh
00:02:20mereka akses. Anda juga ingin memastikan pengguna tidak dapat berdampak negatif pada pengguna lain. Anda tidak ingin kode milik satu pengguna
00:02:26dapat mengakses kode pengguna lain, atau tentunya rahasia mereka atau hal semacamnya.
00:02:31Ini bukan sekadar tentang keamanan. Ini juga tentang keadilan dan pembagian sumber daya. Jika ada pengguna
00:02:37yang melakukan sesuatu yang menghabiskan banyak CPU dan memori, Anda ingin memastikan hal itu tidak
00:02:42secara tidak adil berdampak pada pengguna lain yang mencoba melakukan sesuatu di waktu yang sama. Saya akan meluangkan sebentar
00:02:48dan sedikit melenceng untuk menceritakan sebuah kisah di sini. Soal pembagian sumber daya ini terutama merupakan hal yang sangat
00:02:54rumit. Ini mungkin akan memakan waktu saya, tetapi saya suka cerita ini. Ketika kami sedang membangun
00:02:58GitHub Actions, dan ini adalah sesuatu yang kami pikirkan juga untuk Workers, begitu Anda memiliki
00:03:02platform eksekusi kode sembarang, orang-orang akan langsung mencoba melakukan penambangan kripto di sana.
00:03:07Dan saya sempat mengobrol dengan seseorang beberapa minggu lalu di mana mereka berkata, “Lalu,
00:03:10bagaimana cara Anda mendeteksinya? Anda kan bisa langsung tahu jika CPU melonjak di angka seratus persen?”
00:03:14Sebenarnya tidak juga, karena begitu platform Anda sukses, penambang kripto akan langsung
00:03:20mulai melakukan hal-hal seperti saling berbagi skrip yang memodifikasi cara mereka
00:03:26menjalankan instruksi CPU sehingga terlihat seperti aktivitas yang sama sekali tidak berbahaya, tetapi mereka menggunakannya
00:03:31hingga jumlah sumber daya maksimum yang dimungkinkan tanpa terdeteksi oleh sistem Anda.
00:03:37Jadi ini luar biasa sulit, dan membutuhkan waktu bertahun-tahun serta banyak lapisan keamanan dan
00:03:42observabilitas untuk melakukannya dengan benar. Hal lain yang harus Anda khawatirkan adalah ketika Anda memiliki
00:03:48platform eksekusi kode, Anda harus khawatir tentang orang-orang yang melakukan serangan denial of service menggunakannya,
00:03:54menggunakannya untuk jaringan command and control botnet. Dan kami tidak ingin pusing memikirkan semua
00:04:00hal ini sejak awal pembuatan Notion Workers. Kami ingin berfokus pada hal yang kami sukai,
00:04:07yaitu menghadirkan produk yang senang digunakan oleh pengguna kami. Itulah mengapa kami memutuskan untuk membangun dengan Vercel Sandbox.
00:04:14Vercel Sandbox, dapat kami lihat sejak awal, adalah infrastruktur yang sangat solid yang menyelesaikan banyak dari
00:04:18masalah ini untuk kami secara langsung. Jadi saya akan memperlihatkan demo singkat
00:04:25tentang apa itu Notion Workers. Saya memiliki agen kustom di sini dan tugas agen kustom ini adalah memberi tahu saya
00:04:33Apakah lokakarya ini terkutuk atau tidak. Saya menulis worker bernama Mercury Retrograde. Saya tidak tahu apakah ada yang
00:04:40familiar dengan ini, tetapi ketika Merkurius sejajar sedemikian rupa sehingga mengalami retrograde, itu biasanya
00:04:47pertanda buruk. Worker ini memiliki satu panggilan alat kustom yang menggunakan API yang saya temukan yang tidak melakukan hal lain
00:04:54selain memberi tahu Anda apakah Merkurius sedang dalam retrograde atau tidak. Jadi saya akan memberi perintah pada agen saya dan berkata, “Apakah
00:05:01lokakarya saya terkutuk?” dan dia akan berpikir sejenak dan kemudian kita seharusnya, selama koneksi internet lancar,
00:05:14kita akan mulai melihatnya melakukan panggilan alat. Agen ini menggunakan worker Mercury in retrograde saya, memanggil
00:05:22satu alat yang disediakan oleh worker tersebut dan mencari tahu apakah Merkurius sedang dalam retrograde atau tidak, lalu
00:05:27agen tersebut akan merespons kita. Sepertinya saya menghubungkannya ke sesuatu seperti GPT-54 nano. Jadi biasanya jauh
00:05:37lebih cepat. Sayangnya sepertinya ini masalah internet. Jika ada yang kebetulan memeriksa apakah Merkurius sedang dalam
00:05:46retrograde, saya sudah tahu sebelumnya bahwa memang benar demikian. Dan mungkin itulah mengapa hal ini terjadi. Saya akan
00:05:53melewatinya saja. Kita akan kembali ke sini sebentar lagi untuk melihat apakah kita akhirnya mendapatkan respons. Tapi sepertinya kita sudah
00:05:59tahu jawabannya. Oke, sekarang Anda sudah memiliki gambaran tentang apa itu Notion Workers. Saya akan memperlihatkan
00:06:05skrip kecil yang saya tulis yang melakukan banyak tugas dasar yang terlibat dalam mengambil
00:06:13kode pengguna, mengelolanya, mengeksekusinya secara aman, dan memberi tahu agen tentang kode tersebut sehingga agen dapat
00:06:20melakukan panggilan alat ke kode tersebut. Apakah ini sudah selesai? Oh ya, ini dia. Oh, sepertinya,
00:06:28mungkin saya telah menonaktifkan API Mercury in retrograde karena ternyata API tersebut tidak merespons.
00:06:35Bagaimanapun, saya harus membuka tiket di suatu tempat. Bagus. Saya memiliki skrip dasar. Saya tidak berharap
00:06:40Anda untuk mengikuti sepenuhnya dan menulis kode atau semacamnya. Saya akan melompat-lompat
00:06:44dan memberi Anda gambaran tentang beberapa masalah yang kami selesaikan dan harus Anda selesaikan saat membangun
00:06:49produk seperti ini. Tetapi jika mau, ada repositori make notion slash for cell ship 2026 workers
00:06:55yang berisi semua kode yang akan saya jalankan di sini.
00:07:01Bagus. Jadi mari saya buka slide saya yang lain di sini.
00:07:08Jadi apa yang sedang kita tuju? Kita memiliki agen obrolan streaming. Kita akan mengizinkannya memanggil alat-alat yang
00:07:16didefinisikan oleh kode kustom pengguna yang tidak kita percayai atau kita tidak tahu isi dari kode tersebut.
00:07:22Dan kita akan membangun ini dengan Vercel Sandbox dan layanan penyimpanan blob milik Vercel.
00:07:26Jadi saya akan menjalankan demo singkat di sini dan mudah-mudahan kita tidak benar-benar terkutuk.
00:07:31Anda pada dasarnya hanya bisa mengirim satu pesan saja. Saya akan menyapa “hai” dan mudah-mudahan kita mendapat balasan
00:07:36kembali. Ini akan sedikit lambat karena seperti yang terlihat, proses ini melakukan beberapa penyebaran di latar belakang.
00:07:40Jadi kali ini sebuah alat dipanggil. Alat bernama say hello dipanggil dan ia memutuskan untuk menyapa saya.
00:07:47Saya akan mengirim pesan biasa di sini, hanya menanyakan berapa satu tambah satu agar kita bisa melihatnya secara langsung
00:07:52menampilkan respons biasa.
00:07:58Jika Anda pernah menggunakan Vercel AI SDK, ini hanya menggunakan loop agen alat. Bagus. Jadi kita mendapat respons
00:08:04streaming kembali. Anda juga bisa memanggil worker yang jauh lebih kompleks. Saya punya satu lagi di sini yang akan saya perlihatkan kepada Anda.
00:08:14Dengan yang satu ini, kita memberi tahu bahwa saya berada di alamat gedung ini.
00:08:17Saya punya waktu 90 menit dan ingin melihat beberapa situs bersejarah, serta tidak ingin berjalan kaki lebih dari 15 menit.
00:08:24Jadi ini memperlihatkan mengapa solusi ini terkadang lebih disukai daripada server MCP. Dengan server MCP
00:08:29Anda memiliki banyak panggilan alat terpisah yang bisa dilakukan. Dan jika Anda mencoba melakukan sesuatu
00:08:33yang kompleks, Anda harus menjelaskan langkah-langkah tersebut ke agen, dan agen akan melakukan satu langkah, menggunakan beberapa
00:08:38token berpikir, lalu melakukan langkah berikutnya. Jadi ini akan memanggil worker yang memiliki semacam algoritma pencarian
00:08:45yang sangat besar dan kompleks yang menggunakan banyak waktu transit geolokasi dan API perencanaan rute di New York City.
00:08:52Alat ini bernama plan outing.
00:08:56Dan worker tersebut akan merespons dengan rekomendasi situs bersejarah yang dapat kita kunjungi.
00:09:00Dan sepertinya ia menemukan penanda Freedom Tree yang merupakan monumen MIPOW di dekat
00:09:07City Hall Park. Bagus. Mari kita lihat bagaimana cara kerjanya. Jadi, apa itu worker?
00:09:14Worker adalah kode pengguna yang dalam kasus ini didefinisikan dalam sebuah berkas. Biasanya ini didefinisikan
00:09:20oleh pengguna Anda atau di suatu tempat di GitHub dan disebarkan menggunakan CLI. Tetapi kita hanya memiliki beberapa contoh yang telah diperiksa
00:09:26ke dalam repositori di sini. Dan setiap worker ini perlu menyediakan nama alat, deskripsi alat sehingga
00:09:34agen dapat mengarahkan ke alat yang tepat, skema input agar agen tahu input apa yang perlu
00:09:38diberikan setiap kali memanggil alat tersebut, serta fungsi eksekusi yang menentukan ketika agen memanggil alat ini,
00:09:44kode apa yang sebenarnya dijalankan. Mari kita lihat sebuah contoh. Ini adalah alat penyapa yang Anda lihat
00:09:51sebelumnya. Sangat sederhana. Worker ini adalah satu modul JavaScript di berkas index.ts ini. Ia mengekspor satu
00:09:59worker bernama say hello. Jadi Anda akan melihat bagaimana proses kompilasi ini bekerja. Namun dalam contoh ini,
00:10:04semua kunci ekspor modul kami dipetakan ke nama alat kami. Jadi alat ini akan dinamai
00:10:09say hello. Dan ia memiliki deskripsi sederhana, sebuah skema input. Dalam kasus ini, saya hanya menggunakan Zod untuk mendefinisikan
00:10:16skema tersebut. Dan kemudian saya mengonversinya menjadi skema JSON. Itu adalah format yang diharapkan oleh agen-agen ini.
00:10:22Lalu ada fungsi eksekusi sederhana. Namun, Anda bisa membuat fungsi yang jauh lebih kompleks. Jadi jika saya
00:10:27membuka alur kerja plan outing, Anda dapat melihat deskripsinya jauh lebih panjang untuk memberi tahu agen
00:10:34segalanya tentang cara kerja alat ini, kapan harus memanggilnya, dan apa fungsinya. Dan skrip untuk hal ini jauh,
00:10:39jauh lebih panjang dan kompleks. Saya tidak akan membahas semua ini. Dan saya hanya membaca sebagian kecil
00:10:45dari skrip ini, tetapi fungsinya cukup baik. Itulah dunia yang kita jalani sekarang. Jadi worker-worker ini akan
00:10:54dibangun dan disebarkan ke penyimpanan blob Vercel. Kemudian kita akan mempelajari isi dari
00:10:59worker tersebut dengan suatu cara. Dan kita akan menyediakannya untuk agen. Lalu alat-alat itu akan
00:11:03diekskusi secara aman di dalam sandbox. Bagian kompilasi akan kita lewati sedikit. Pada Notion workers, kami memiliki
00:11:09proses penyebaran dan kompilasi berbasis awan. Dalam kasus ini, saya baru saja melakukan kompilasi awal worker ini di disk. Jadi setiap
00:11:15worker memiliki tarball yang berisi semua kode TypeScript yang dikompilasi, dependensi, dan hal-hal seperti itu.
00:11:22Ini bukan bagian yang sangat menarik, jadi saya akan melewatinya saja.
00:11:26Jadi pertanyaan utamanya adalah, bagaimana kita beralih dari kode pengguna yang belum pernah kita lihat dan tidak kita percayai
00:11:34menjadi alat yang dapat diakses dan dijalankan secara aman oleh agen? Ada dua bagian dari proses ini.
00:11:39Bagian pertama adalah, setelah kita memiliki kode pengguna di penyimpanan blob, katakanlah,
00:11:43bagaimana cara kita mengetahui apa yang ada di dalam kode tersebut? Karena kita harus bisa memberi tahu agen sebelum
00:11:48agen memanggil alat tersebut, apa nama alatnya, apa skema inputnya, dan apa deskripsi
00:11:53dari alat itu. Dan pertanyaan kedua adalah, ketika agen memutuskan untuk memanggil alat tersebut,
00:11:58bagaimana cara kita mengeksekusinya secara aman? Salah satu hal yang saya sukai dari apa yang kami lakukan dengan
00:12:04SDK Notion Workers dan yang telah kami lakukan di sini adalah bahwa kodenya mendeskripsikan dirinya sendiri. Jadi hal yang tidak
00:12:11kami inginkan saat merancang Notion Workers adalah pengguna harus menulis kode TypeScript
00:12:16untuk mendefinisikan alat mereka dan kemudian harus membuat berkas manifest statis yang mendeskripsikan
00:12:21worker mereka dan pada dasarnya menulis ulang hal yang sama. Kami tidak menginginkan proses kompilasi lokal yang canggung di mana
00:12:27mereka harus menjalankan skrip, melakukan analisis, mengompilasinya di disk, lalu menyebarkannya. Jadi kami ingin memulai
00:12:34dengan apa yang terasa seperti pengalaman pengembang yang optimal di mana Anda hanya perlu menulis alat tersebut lalu menjadi
00:12:40tugas kami untuk menyelesaikan hal rumit yaitu mencari tahu cara mendapatkan informasi dari kode itu.
00:12:46Jadi ini adalah diagram yang sangat sederhana, tetapi sebelum kita masuk ke kodenya, saya akan memberi tahu Anda tentang
00:12:52bagaimana cara kita akan menyelesaikan masalah ini. Jadi kita memiliki kode pengguna yang sudah dikompilasi di penyimpanan blob.
00:12:59Kita akan menggunakan kode tersebut untuk membuat sandbox. Jadi ketika kita menjalankan perintah di sandbox, kode pengguna
00:13:04tersebut akan berada di direktori utama ruang kerja. Kita akan mengimpor berkas index.js pengguna yang telah mereka tulis.
00:13:11Langkah itu akan memberi kita semua nama ekspor, deskripsi, dan skema inputnya.
00:13:19Di sinilah hal-hal menjadi sedikit aneh, tetapi cara ini bekerja dengan sangat baik. Kita akan memanggil
00:13:23json.stringify pada modul tersebut dan kemudian kita akan mencatatnya ke keluaran standar. Jadi semua itu terjadi di dalam
00:13:29sandbox, lalu skrip penyebaran kita mengambil keluaran standar tersebut, menguraikannya, dan membaca, “Oke,
00:13:34sekarang saya tahu nama semua alat pada worker ini, inputnya, dan deskripsinya.” Dan dalam
00:13:40kasus ini, kita akan meneruskannya langsung ke agen. Namun dalam kasus Notion Workers, misalnya,
00:13:44hal itu merupakan bagian dari alur penyebaran kami. Jadi kami mengambil semua informasi itu dan menyimpannya di basis data sehingga
00:13:49setiap kali Anda menjalankan agen kustom, kami mengambil deskripsi alat tersebut dari
00:13:53basis data. Apakah cara kerjanya cukup masuk akal sejauh ini? Oke. Jadi mari kita lihat skrip
00:14:02penyebaran. Saya harus kembali ke komit pertama saya di sini. Skrip ini mencakup seluruh proses penyebaran dan pemanggilan
00:14:12agen di dalamnya. Ini sangat sederhana. Dalam kasus ini, kita hanya melakukan iterasi pada semua direktori
00:14:18dan worker. Masing-masing subdirektori tersebut berisi kode worker seperti yang Anda lihat semenit yang lalu.
00:14:23Tugas kita adalah mencari tahu cara mengekstrak semua informasi alat dari setiap worker dan mengisi
00:14:29objek alat ini. Objek itu diteruskan ke agen loop alat. Ini hanyalah bagian dari AI SDK yang dibuat
00:14:35oleh Vercel. Lalu kita akan mengirim pesan pengguna ke agen tersebut, melakukan streaming keluaran, dan menulisnya
00:14:42ke keluaran standar dari skrip. Jadi hal pertama yang perlu kita cari tahu adalah bagaimana cara mengunggah
00:14:48kode sumbernya. Bagian itu cukup mudah dan cepat. Alih-alih melakukan koding langsung, saya akan
00:14:53melompati setiap bagian ini agar Anda tidak perlu menonton saya mengetik. Saya berjanji saya bisa menulis
00:14:59kode. Hanya saja saya pikir tidak ada yang ingin menonton saya melakukan itu. Melompat sedikit ke depan, kita memiliki fungsi
00:15:06bernama upload source yang akan saya jelaskan. Namun Anda dapat melihat bahwa saya telah mengimpor Vercel blob SDK. Ini cukup
00:15:13sederhana di sini. Jika kita melihat fungsi upload source ini, kita pada dasarnya hanya memanggil fungsi put
00:15:20ini. Dan kita menentukan bahwa kita ingin menyimpan bundel pengguna ini di bawah nama worker slash bundle.tar.gzip.
00:15:29Kita akan mengalirkan berkas dari disk dan menyimpannya di penyimpanan blob. Setelah itu selesai,
00:15:35kita dapat membuat sandbox dari blob tersebut. Jadi masalah mengunggah sudah teratasi.
00:15:43Hal berikutnya yang perlu kita lakukan adalah mengambil kode sumber yang sudah dibundel itu dan membuat
00:15:49sandbox darinya. Untuk melakukan itu, saya telah mengimpor Vercel sandbox SDK. Ada fungsi pembantu lainnya
00:15:56di bawah sini yang bernama create sandbox. Saya akan melewatinya beberapa bagian. Namun pada dasarnya apa yang
00:16:03kita lakukan adalah ketika kita memiliki objek tersebut di penyimpanan blob, kita menggunakan URL pra-tanda tangan yang akan
00:16:10kita teruskan ke layanan sandbox. Sehingga layanan sandbox dapat, katakanlah, saya pikir ini diatur ke kadaluwarsa 10 menit,
00:16:16ia dapat menarik blob tersebut dari penyimpanan blob dan menggunakannya untuk mengisi sandbox. Ini adalah salah satu
00:16:23fitur yang sangat saya sukai dari Vercel sandbox karena dapat bekerja dengan tarball seperti ini. Sangat mudah
00:16:28untuk membuat tarball dari kode pengguna, dan jika layanan sandbox ingin membuat sandbox dari sebuah tarball,
00:16:35layanan tersebut mengambil tarball dari penyimpanan atau URL apa pun yang Anda berikan dan secara otomatis mengekstrak tarball tersebut di
00:16:40direktori utama ruang kerja. Jadi semua berkas sudah siap untuk Anda gunakan. Hal itu dilakukan hanya dengan memanggil
00:16:46sandbox.create. Kita belum sampai di bagian yang sangat rumit, tetapi kita akan sampai di sana sebentar lagi.
00:16:52Hal lainnya adalah dalam contoh ini, demi kerapian, saya membuat sandbox baru setiap
00:16:57kali menjalankannya. Kenyataannya, Anda tentu ingin menggunakan pembuatan snapshot agar tidak secara terus-menerus
00:17:02menyebarkan ulang sandbox setiap kali sebuah alat dipanggil, atau mengalirkannya ulang dari penyimpanan blob Vercel
00:17:09atau penyimpanan blob awan lain yang Anda gunakan. Pada dasarnya ada mekanisme tembolok yang dibangun
00:17:15ke dalam platform. Saya menyederhanakannya di sini demi kemudahan.
00:17:19Jadi itulah proses pembuatan sandbox dan di sinilah hal menjadi menarik.
00:17:23Hal berikutnya yang perlu kita lakukan adalah mengekstrak informasi tentang alat-alat
00:17:31yang didefinisikan dalam worker tersebut dari sandbox yang baru saja kita buat.
00:17:36Saya akan berhenti sejenak di sini dan ada beberapa tips yang telah saya sebarkan
00:17:40di sepanjang sesi. Secara umum, jika Anda membangun layanan tingkat produksi seperti
00:17:44ini, Anda harus selalu berusaha sebaik mungkin untuk menghentikan sandbox Anda. Jadi dalam kasus ini, Anda akan melihat
00:17:51apa yang dilakukan extract tools, tetapi setelah selesai menggunakannya, kita memastikan bahwa kita menghapus sandbox secara eksplisit.
00:17:56Kenyataannya, Anda juga ingin memastikan bahwa Anda mungkin memasukkan tugas pembersihan asinkron ke dalam antrean
00:18:00atau semacamnya. Anda tidak ingin sandbox ini terus berjalan tanpa batas waktu.
00:18:06Jadi mari kita lihat apa yang dilakukan fungsi extract tools. Kita memiliki sandbox kita dan saya sangat menyukai contoh-contoh ini
00:18:15karena kelihatannya sangat konyol betapa sederhananya fungsi ini, tetapi ia bekerja dengan sangat, sangat baik. Jadi kita menjalankan
00:18:23perintah node hanya menggunakan biner node pada sandbox dan di dalam skrip, kita mengimpor modul yang
00:18:30ditulis pengguna, yaitu index.js. Kita mengubah objek tersebut menjadi string lalu memanggil
00:18:36console.log. Hal yang perlu diingat adalah bahwa sandbox tidak seperti web server tempat Anda bisa
00:18:42mengirim permintaan dan mendapatkan balasan kembali. Semua input dan output dilakukan melalui eksekusi perintah dan kemudian
00:18:48Anda dapat membuatnya mengirim respons ke suatu layanan yang Anda ambil datanya, atau dalam kasus ini, Anda dapat langsung membuatnya
00:18:52mencatatnya ke keluaran standar. Beberapa tips di sini. Biasanya Anda tidak ingin hanya melakukan console.log dan mempercayai semua
00:19:00keluaran yang Anda dapatkan dari sandbox. Boleh jadi ada paket lain yang telah dipasang pengguna atau
00:19:07kode lain yang mereka jalankan yang dapat ikut mencatat hal-hal pada saat yang sama ke alir keluaran standar.
00:19:12Jadi satu hal yang baik untuk dilakukan adalah membungkus keluaran Anda dalam semacam tag yang dapat Anda uraikan.
00:19:18Ini seperti tag mirip XML yang akan kita lakukan di sini. Saya hanya tidak melakukannya dalam contoh ini.
00:19:23Anda juga harus memastikan bahwa Anda membatasi ukuran log yang Anda konsumsi. Saya hanya
00:19:29mengumpulkan semua keluaran dalam contoh ini ke dalam sebuah stream. Dalam aplikasi tingkat produksi, Anda tidak
00:19:34ingin melakukan itu karena ukurannya bisa mencapai satu gigabyte stream dan akan menyebabkan server Anda kehabisan memori.
00:19:39Jadi ada juga perintah yang dimiliki sandbox API untuk mengalirkan log secara langsung. Dan itulah yang kami lakukan
00:19:45pada Notion workers, yaitu kami mengonsumsi stream tersebut sampai kami melihat token awal keluaran
00:19:53yang kita butuhkan. Lalu kita mulai menampung deskripsi tersebut, yaitu hal yang sengaja kita catat.
00:19:58Dan kita berhenti memproses stream keluaran segera setelah mencapai tag penutup. Begitulah cara jika terjadi hal yang
00:20:04aneh dan salah pada sandbox serta ada sejumlah besar data yang dicatat,
00:20:08semua data itu tidak akan terkumpul di memori. Kita dapat membuangnya dan menunggu sampai kita mendapatkan hal yang kita butuhkan.
00:20:14Jadi setelah perintah itu berjalan, kita menunggu sampai selesai dan mengambil keluaran standarnya.
00:20:22Dan jika Anda pernah menggunakan Zod sebelumnya, ini akan terlihat cukup familier. Kami hanya mengurai string itu sebagai JSON.
00:20:27Lalu kita punya tipe Zod di sini yang
00:20:31mervalidasinya bahwa bentuknya sesuai dengan yang kita inginkan.
00:20:34Sangat penting untuk tidak langsung memercayai data ini karena ini adalah kode pengguna yang tidak terpercaya. Anda tidak tahu
00:20:39apa yang mungkin dicatat orang. Jadi, Anda harus memastikan bahwa Anda
00:20:43mervalidasi ukuran dan mervalidasi bentuk umumnya. Jadi dalam kasus ini, yang kita harapkan
00:20:50adalah sebuah record yang kunci-kuncinya, string tersebut, akan menjadi nama dari masing-masing alat kita.
00:20:57Dan objeknya akan berupa deskripsi lalu skema input, yaitu
00:21:02skema JSON. Anda akan melihat bahwa fungsi execute tidak ada di sini. Itu hanya
00:21:06tidak dikembalikan, yang mana bagus karena JSON.stringify akan melewati hal-hal yang tidak bisa
00:21:11diserialisasi. Jadi itu diabaikan begitu saja. Kita hanya mendapatkan deskripsi dan skema input darinya.
00:21:18Kembali ke atas sini, kita memiliki alat worker kita yang merupakan
00:21:23record dari setiap alat yang diekspos di worker tersebut.
00:21:28Hal berikutnya yang kita lakukan adalah mengubah data itu ke bentuk yang diharapkan oleh AI SDK.
00:21:36Dan kita perlu melampirkan fungsi eksekusi ke masing-masing alat tersebut.
00:21:42Tentu saja kita tidak mendapatkan fungsi eksekusi saat hanya mencatat hal-hal ke stdout.
00:21:46Jadi pertanyaannya sekarang, setelah kita memiliki deskripsi alat ini, bagaimana cara kita menyediakan
00:21:51fungsi yang dapat dipanggil SDK setiap kali ia ingin memanggil worker ini atau alat yang diekspos oleh
00:21:58worker ini? Saya punya pembungkus kecil di sini yang disebut execute tool. Mari kita lihat apa
00:22:04yang dilakukannya. Ini akan terlihat sangat familier sekarang. Ia memanggil fungsi create sandbox yang sama. Jadi ia
00:22:10membuat sandbox baru. Bahkan jika Anda menggunakan pembolosan (caching), ada fitur Vercel Sandbox yang disebut
00:22:16persistensi di mana setiap kali sandbox ditangguhkan atau berhenti, ia menyimpan status seperti segala sesuatu yang
00:22:23ada di disk. Untuk fitur seperti ini, Anda sebenarnya tidak menginginkan hal itu. Anda ingin mengambil cuplikan (snapshot) dari status awal Anda
00:22:28yang berisi semua kode pengguna di dalamnya. Namun secara umum, setelah itu, Anda ingin memastikan setiap kali
00:22:33alat itu dieksekusi, Anda mungkin menginginkan instans yang benar-benar baru. Dengan begitu, dalam satu eksekusi alat,
00:22:38jika terjadi kesalahan, itu tidak akan mencemari lingkungan alat yang berjalan setelahnya.
00:22:43Jadi kita membuat sandbox baru, dan menjalankan skrip Node lainnya di sana. Ini seharusnya terlihat familier,
00:22:49tetapi sedikit berbeda. Kita mengimpor modul yang ditulis pengguna. Kita mengambil
00:22:56alat dari modul tersebut, yang merupakan nama alat yang kita dapatkan saat membuat pembungkus execute tool ini.
00:23:03Kita memanggil fungsi execute padanya. Dan kita meneruskan ke fungsi execute tersebut input yang
00:23:09diberikan oleh model. Di sini saya mengetikkannya sebagai unknown. Ini cukup aman di sini, karena
00:23:19kita menyediakan-- apakah saya tidak melakukannya di sini? Sepertinya saya melewatinya di contoh ini. Tetapi apa
00:23:27yang biasanya Anda lakukan adalah-- oh, tidak, sepertinya saya melakukannya. Mari saya melompat kembali ke atas. Ya. Jadi saat kita
00:23:34mermanipulasi alat-alat kita untuk dikirim ke agen, kita mengambil skema JSON tersebut, dan mengubahnya
00:23:39kembali menjadi tipe Zod. Jadi kita tidak perlu melakukan penguraian sendiri. AI SDK, kode agen tool-loop,
00:23:46setiap kali alat itu dipanggil, ia akan mervalidasi input yang berasal dari agen untuk kita.
00:23:52Jadi kurang lebih kita bisa percaya bahwa nilai ini sesuai dengan yang kita harapkan. Kita akan meneruskannya
00:23:56ke fungsi execute di sini. Kemudian ketika fungsi asinkron tersebut selesai, kita akan
00:24:03mengubahnya menjadi string. Kita akan mencatatnya ke stdout. Lalu kita akan menunggu-- saya akan membahas
00:24:10hal ini sebentar lagi. Kita akan menunggu perintah tersebut selesai. Dan sekali lagi, kita akan
00:24:14menghapus sandbox kita setelah selesai. Lalu kita akan mengurai JSON tersebut, yang merupakan nilai kembalian
00:24:21dari fungsi eksekusi yang ditulis oleh pengguna. Dan kita akan mengirimkannya kembali ke agen tool-loop.
00:24:26Jadi pada dasarnya alurnya di sini adalah agen tool-loop berkata, Saya ingin memanggil alat plan outing
00:24:35yang kita lihat sebelumnya di mana ia menggunakan semua API transit tersebut. Itu akhirnya memanggil fungsi ini di sini dengan
00:24:40input apa pun yang ditentukan oleh agen. Kita membuat sandbox menggunakan blob yang kita unggah dari
00:24:48kode pengguna yang dikompilasi sebelumnya. Kemudian kita menjalankan perintah pada sandbox itu di mana kita memanggil fungsi execute
00:24:55milik pengguna. Kita menunggu fungsi tersebut mengembalikan nilai. Lalu kita mencatat nilai kembalian itu ke
00:25:00stdout, menguraikannya, lalu mengirimkannya kembali ke agen, yang kemudian akan melanjutkan tool loop
00:25:05dan membuat panggilan alat lain atau merespons pengguna. Beberapa tips cepat di sini. Aturan
00:25:13verifikasi output yang sama berlaku di sistem produksi. Sekali lagi, Anda harus memastikan bahwa pengguna tidak dapat
00:25:19mengembalikan objek yang membutuhkan waktu dua gigabita atau semacamnya untuk diurai. Anda juga mungkin ingin
00:25:28menyediakan SDK. Kami tidak melakukannya di contoh ini, tetapi di SDK Notion Workers,
00:25:35tipe-tipenya diatur sedemikian rupa sehingga nilai kembalian dari fungsi eksekusi tersebut harus dapat diserialisasi ke JSON.
00:25:41Itu adalah hal sepele yang rawan membuat pengguna terkecoh. Jika Anda membuatnya sehingga fungsi tersebut
00:25:47dapat mengembalikan apa saja, mereka akan mengembalikan hal-hal yang tidak dapat diserialisasi sebagai JSON dan dikirimkan
00:25:52melalui jaringan via stdout lalu diurai. Dan mereka akan sangat bingung serta tidak mengerti mengapa
00:25:57worker tidak berjalan. Lalu ada sedikit cerita yang terjadi dengan Notion Workers. Anda juga
00:26:05harus selalu sangat berhati-hati untuk memastikan bahwa prosesnya, proses Node yang Anda jalankan
00:26:11di sini, berhenti apa pun yang terjadi. Kami memiliki bug pada Notion di mana kode pengguna berjalan dan kurang lebih berjalan
00:26:18hingga selesai. Namun entah mengapa, proses Node ini tidak berhenti. Proses tersebut menggantung sampai
00:26:25siklus hidup sandbox habis, sekitar lima menit atau semacamnya. Jadi itu tidak katastropik,
00:26:30tetapi membuang-buang sumber daya. Dan apa yang kami temukan atau ingat, saya benar-benar lupa bahwa ini
00:26:38adalah sifat dari runtime Node, yaitu pengguna menjalankan kode yang menyetel pengukur waktu (timer) seperti
00:26:44setInterval atau setTimeout. Terutama dengan interval atau saya pikir janji (promise) yang tidak terselesaikan akan melakukan hal yang sama.
00:26:52Jika skrip berjalan dan ada interval atau semacamnya yang berjalan, proses Node tidak akan pernah
00:26:58kembali. Proses itu tidak akan berhenti sendiri sampai semua pengukur waktu tersebut habis. Jadi jika itu adalah
00:27:03interval, ia akan terus berjalan selamanya sampai sandbox mati. Jadi Anda harus selalu memastikan bahwa
00:27:08ketika kode atau fungsi yang Anda coba eksekusi benar-benar selesai, Anda secara eksplisit memberi tahu
00:27:14proses untuk keluar (exit). Dengan begitu ia menghentikan sandbox, dan proses kembali setelahnya.
00:27:23Jadi itulah kurang lebih seluruh alur kerjanya. Saya akan menjalankannya sekali lagi dan saya akan menyalakan
00:27:29penelusuran kesalahan (debugging) agar Anda dapat melihat apa yang terjadi.
00:27:42Jadi kita mengagih (deploy) worker departures kita terlebih dahulu. Saya akan menjelaskannya sebentar lagi. Kita mengagih
00:27:48worker departures kita dengan mengunggah bundel itu terlebih dahulu. Kita membuat sandbox yang akan kita gunakan untuk mengekstrak
00:27:54informasi tentang worker tersebut. Kita mengekstrak alat-alat ini. Jadi inilah yang kita dapatkan saat mencatat
00:28:01konten modul ke stdout. Kita mendapatkan kunci untuk setiap alat, deskripsi, dan skema inputnya.
00:28:08Kita melakukan hal yang sama untuk worker greeter sederhana itu. Lalu kita mengambil semua itu dan meneruskannya ke agen agar
00:28:13ia dapat membuat panggilan alat dan merespons pengguna. Kurang lebih seperti itu. Itu adalah contoh yang cukup sederhana
00:28:19tentang bagaimana Anda dapat mengambil kode pengguna yang tidak terpercaya, menyimpannya di suatu tempat, mempelajarinya dengan cara yang aman sehingga
00:28:26Anda dapat menyimpannya di penyimpanan tahan lama atau mengirimkannya langsung ke agen, lalu meminta agen Anda mengeksekusi
00:28:31kode itu secara aman. Jadi kita memiliki waktu tersisa sekitar 10 menit. Jika ada yang punya pertanyaan, jika ingin bertanya tentang
00:28:39Notion workers atau bekerja dengan hal-hal Vercel Sandbox secara umum atau eksekusi kode yang tidak terpercaya,
00:28:46saya senang untuk berbincang tentang hal itu. Terima kasih.
00:28:56Oh ya. Dan setelah ini, jika Anda ingin berbicara tentang platform pengembang Notion secara umum,
00:29:01Anda dapat berbicara dengan saya atau rekan saya MJ di sebelah sini. Dia adalah manajer produk untuk platform pengembang di Notion.
00:29:08Halo. Ini pertanyaan operasional, tetapi bagaimana Anda membatasi, karena seperti pengguna bisa melakukan apa saja.
00:29:15Ya. Anda mungkin memiliki beberapa pengguna yang menulis alat yang serupa.
00:29:20Banyak pengguna apa? Membuat alat yang serupa. Contohnya, seperti merencanakan perjalanan ke New York City.
00:29:24Ya. Anda bisa memiliki 10 pengguna berbeda yang memiliki 10 kode berbeda. Ya. Yang melakukan hal yang sama.
00:29:29Ya. Apakah ada sesuatu yang Anda lakukan untuk memblokir itu atau itu terserah agen saja?
00:29:33Tidak. Kami membiarkan mereka, jika beberapa pengguna ingin melakukan hal yang sama, kami biarkan saja.
00:29:37Ini sebagian adalah pertanyaan produk. Seperti jika ada beberapa pengguna dalam organisasi yang sama,
00:29:42Anda ingin memastikan bahwa, jadi ini pertanyaan operasional sekaligus pertanyaan produk juga.
00:29:46Anda ingin memastikan bahwa Anda memiliki primitif berbagi yang baik dan sejenisnya. Sehingga saya bisa
00:29:50melihat dan mencari lalu berkata, apakah sudah ada worker yang melakukan tugas ini? Dengan begitu mereka tidak
00:29:55menulis ulang kodenya. Namun secara umum dari sudut pandang platform, kami tidak melakukan apa pun seperti, sangat kecil kemungkinan pengguna akan
00:30:02mengagih kode yang identik. Dan tidak sepadan untuk mencoba melakukan deduplikasi.
00:30:19Oke. Ya. Saya rasa kita punya beberapa pertanyaan lagi. Saya tidak yakin siapa yang memegang mikrofon.
00:30:24Saya tidak mendengarnya. Oh, ya. Maaf sekali.
00:30:29Saya kira tadi terekam di headphone.
00:30:33Oh, ya. Pertanyaan yang diajukan adalah, jika Anda memiliki banyak pengguna yang mengagih kode yang sama,
00:30:40apakah kita melakukan sesuatu untuk mengoperasionalkannya? Dan jawabannya tidak. Ini lebih ke pertanyaan produk.
00:30:45Seperti kita ingin memastikan pengguna tidak mengulangi pekerjaan yang sama. Jadi kita menginginkan primitif berbagi yang baik
00:30:50yang sedang kami kerjakan saat ini untuk Notion workers. Namun secara operasional di tingkat platform,
00:30:54jika orang mengagih kode yang sama 50 kali, kami tidak peduli.
00:30:56Apakah Anda mengalami masalah dengan worker yang kehabisan waktu (timeout) karena sandbox hanya
00:31:04melakukan tugasnya dan mati terlalu cepat?
00:31:07Jadi apa pertanyaan tepatnya mengenai timeout?
00:31:10Apakah Anda mengalami masalah dengan timeout antara Vercel, seperti produk dan alur kerja lain,
00:31:15misalnya, atau fungsi dan sandbox Anda? Atau semuanya baik-baik saja?
00:31:20Ya, kami tidak mengalami masalah apa pun sejauh ini. Platform ini sangat solid untuk kami
00:31:25sejauh ini. Saya tidak sedang promosi. Saya serius. Ini sangat bagus.
00:31:32Kami menghadapi jauh lebih banyak masalah terkait pengguna yang tidak sengaja melakukan kesalahan. Jadi seiring
00:31:36waktu, ini lebih ke bagaimana kami mengeliminasi jebakan-jebakan tersebut dan membuat platform ini semakin mudah
00:31:42digunakan baik bagi pengembang maupun non-pengembang. Keren. Itu hebat. Saya ingin bertanya tentang,
00:31:48mungkin, ketika Anda mengubah kode awal pengguna menjadi string. Saya berasumsi Anda mencoba memastikan
00:31:52apakah kodenya aman atau bisa dijalankan. Saya ingin bertanya sedikit lebih banyak.
00:31:57Anda katakan Anda mengubah kode menjadi string dan mendapatkan, kurasa, tag worker untuk mendapatkan input yang diinginkan
00:32:04untuk kode pengguna dan kemudian deskripsi dari alat itu. Apakah itu untuk seperti Notion,
00:32:10seperti untuk agen Anda mengeksekusi kode tersebut? Karena saya memperhatikan Anda sudah menjalankan kode eksekusi
00:32:15atau fungsi eksekutor pengguna. Jadi saya penasaran mengapa Anda melakukan
00:32:21Langkah tambahan ini untuk mendapatkan input dan deskripsi alat itu sendiri.
00:32:24Pertanyaan yang bagus. Ini sedikit lebih jelas pada produk yang sebenarnya, tetapi disederhanakan di sini.
00:32:29Pertanyaannya pada dasarnya adalah, mengapa saya mengeksekusi kode pengguna sekali untuk mendapatkan informasi tentang
00:32:34worker lalu melakukannya lagi saat kode alat dipanggil? Alasannya adalah, sebelum agen
00:32:39dapat memanggil alat tersebut pertama kali atau mengetahui keberadaan alat tersebut, kita harus mengetahui
00:32:45apa yang ada di dalam alat itu lalu mengeksposnya ke agen melalui SDK. Jadi apa yang terjadi di Notion
00:32:50Workers, misalnya, saat Anda menjalankan seperti NTN Workers Deploy, kita melewati alur pembuatan (build pipeline),
00:32:55sandbox yang mengeksekusi build mengambil tarball, menyimpannya di suatu tempat. Kita mulai,
00:33:02saya rasa kami melakukan ini di worker baru tempat kami mengekstrak nama alat, deskripsi, dan
00:33:09skema, lalu menyimpannya di tempat seperti DynamoDB. Dengan begitu, setelah itu, kami tidak pernah menjalankan
00:33:14sandbox sampai alat tersebut benar-benar dipanggil. Anda menyebutkan sistem tarball. Saya rasa itulah yang paling saya
00:33:21penasarankan. Dalam hal distribusi, bagaimana tepatnya cara kerjanya terkait tarball tersebut?
00:33:26Apakah Anda memiliki semacam pasar (marketplace) terbuka saat ini atau bagaimana cara kerja distribusinya?
00:33:31Maksudnya apa yang ada di dalam tarball sebenarnya? Ya, ya.
00:33:33Mengenai mekanismenya, apa yang masuk ke dalam tarball itu,
00:33:38kami hanya menggunakan ESbuild saat ini. Cara kerja seluruh alur pengagihan sebenarnya adalah Anda menjalankan
00:33:47NTN workers deploy, memanggil titik akhir API, komputer Anda mendapatkan URL yang telah ditandatangani sebelumnya, membundel semua
00:33:52kode sumber Anda, kita membuat tarball dengan kode sumber tersebut, menjalankan proses build dengan ESbuild pada
00:33:58sandbox itu, lalu hasilnya dimasukkan kembali ke penyimpanan blob sehingga kita dapat menjalankan hal yang sebenarnya
00:34:04dari sana. Kami belum memiliki marketplace yang sebenarnya untuk worker. Kami sedang mengerjakan primitif berbagi
00:34:11di dalam ruang kerja Notion terlebih dahulu, tetapi pasti ada rencana untuk semacam marketplace worker di masa mendatang.
00:34:18Sementara itu, Anda bisa mendistribusikannya di GitHub dan itu berfungsi dengan baik. Itulah yang dilakukan orang-orang saat ini.
00:34:23Ya, cukup publikasikan repositori dan seseorang dapat mengkloningnya lalu menjalankan NTN workers deploy dan menggunakannya sendiri.
00:34:29Ya.
00:34:34Saya rasa ada satu pertanyaan di belakang sana.
00:34:37Ya, saya punya pertanyaan tentang penagihan (billing).
00:34:40Tentang billing?
00:34:40Tentu saja jangan membeberkan semua rahasia Anda,
00:34:44tetapi saya mencari dengan cepat di Google. Tampaknya agen kustom bekerja dengan semacam sistem kredit. Saya
00:34:48menduga itu terikat dengan konsumsi sumber daya.
00:34:51Ya, ya.
00:34:51Bagaimana cara kerjanya dengan platform Vercel secara garis besar?
00:34:54Oke, jadi pertanyaannya adalah, bagaimana cara kerja penagihan untuk ini dengan platform Vercel?
00:35:02Salah satu keuntungan dari worker adalah Anda dapat menulis ini, banyak tim telah mampu beralih dari
00:35:08set instruksi yang sangat besar menggunakan server MCP yang mereka berikan kepada agen mereka. Dan setiap kali mereka memanggil tugas,
00:35:16agen tersebut akan menghabiskan sangat banyak token berpikir untuk melakukan hal yang sama berulang kali.
00:35:21Jadi jika Anda dapat mengambil tugas-tugas berulang yang dilakukan agen tersebut dan mengagihnya sebagai worker,
00:35:33Anda tetap ditagih untuk waktu eksekusi worker Anda, tetapi itu jauh lebih murah daripada token komputasi AI.
00:35:40Jadi jika Anda menjalankan agen kustom, ia melakukan beberapa proses berpikir, mensekusi alat, lalu melakukan beberapa proses berpikir lagi.
00:35:48Anda ditagih untuk konsumsi token dalam agen kustom yang dilakukannya sebelum memanggil alat.
00:35:53Lalu ketika alat berjalan, Anda ditagih dengan tarif berbeda yang mengonsumsi kredit Anda, seperti kredit Notion AI Anda dengan tarif yang jauh lebih rendah.
00:36:01Dan kemudian Anda ditagih kredit AI normal lagi ketika agen akhirnya merespons.
00:36:05Jadi ini adalah cara jika Anda menggunakan agen kustom Notion, Anda dapat memangkas banyak biaya jika memiliki tugas berulang.
00:36:16Itu juga benar. Anda juga tidak harus memiliki Notion AI, mereka tidak, kami menampilkan panggilan alat di sini yang terintegrasi dengan Notion AI.
00:36:24Tetapi worker juga melakukan sinkronisasi pihak ketiga ke Notion. Jadi itu tidak memerlukan fitur AI sama sekali.
00:36:31Jadi ini bukan sekadar produk AI. Orang-orang menggunakannya untuk sinkronisasi.
00:36:36Saya memiliki worker yang menyinkronkan umpapan Letterboxd saya ke Notion saya. Secara pribadi, itu yang paling penting bagi saya.
00:36:46Oke. Ada hal lain?
00:36:52Apakah ada satu pertanyaan lagi?
00:37:06Oh, seperti mengeksposnya melalui antarmuka pengguna Anda sendiri, seperti mengekspos agen kustom Notion melalui antarmuka Anda sendiri.
00:37:13Itu bukan sesuatu yang kami miliki saat ini.
00:37:16Maaf. Terima kasih, MJ. Pertanyaannya adalah, jika Anda memiliki worker dan agen kustom sendiri,
00:37:22apakah ada cara untuk mengeksposnya melalui aplikasi Anda sendiri untuk konsumen Anda sendiri?
00:37:28Dan saat ini belum ada cara untuk melakukannya. Kami memang memiliki API agen kustom versi alfa tempat Anda dapat
00:37:35melakukannya. Jika Anda mendefinisikan agen kustom dan worker atau agen kustom di ruang kerja Anda,
00:37:40Anda dapat menggunakan API untuk memanggil agen-agen tersebut lalu mendapatkan respons penyiaran (streaming). Jadi itu memungkinkan.
00:37:46Saya rasa belum banyak orang yang melakukannya. Dan itu adalah fitur publik yang masih sangat alfa.
00:37:57Ada pertanyaan lain?
00:38:00Baiklah. Keren. Terima kasih semua telah meluangkan waktu
00:38:04untuk datang mendengarkan ini hari ini. Dan ya, jika Anda punya pertanyaan tentang Notion, Notion workers,
00:38:08atau Vercel Sandbox, silakan temui MJ atau saya. Terima kasih.