Transcript
00:00:00안녕하세요 여러분. 제 이름은 조나단 클레임이고요, 제이 클레임이라고 부르셔도 됩니다. 저는 소프트웨어 엔지니어인데
00:00:11노션에서 개발자 플랫폼 관련 업무를 맡고 있습니다. 특히 최근에 선보인 새로운 제품에
00:00:17대해 집중하고 있는데, 바로 노션 워커스(Notion Workers)라는 제품입니다. 오늘 워크숍에서는
00:00:23노션 워커스가 무엇이고, 우리가 왜 Vercel Sandbox를 이용해 이를 구축했는지
00:00:28간단히 개요를 설명해 드리겠습니다. 그런 다음 워커스의 미니 버전을 직접 살펴보면서
00:00:36Vercel Sandbox를 사용해 이와 같은 제품이 어떻게 만들어지는지 감을 잡으실 수 있도록 하겠습니다.
00:00:43워커스가 무엇인지 잘 모르시는 분들을 위해 설명하자면, 사용자가 커스텀 코드를 작성하고
00:00:50그 코드로 노션을 확장할 수 있는 SDK이자 런타임입니다. 따라서 서드파티 데이터를 노션에 동기화하거나,
00:00:57에이전트를 위한 커스텀 도구 호출 코드를 작성하는 등의 작업을 할 수 있습니다. 사람들이 노션 워커스로
00:01:04노션 워커로 식료품을 주문하거나 스마트 홈을 제어하는 등 재미있고 기발한 일들을 하는 것을 보았습니다. 또한 매우 복잡한 워크플로도 보았는데,
00:01:11특히 IT 및 보안 분야에서 그렇습니다. 워커의 좋은 점은 여러분이 직접 관리해야 할 인프라가
00:01:17없다는 것입니다. 코드를 직접 작성하거나 코딩 에이전트에게 작성하도록 시키기만 하면, 노션이
00:01:23항상 정상적으로 작동하고 이용 가능하며 실행되도록 보장해 줍니다. 이것은 노션 사용자들,
00:01:30특히 개발자들에게 엄청난 혜택이 되었습니다. 이들은 더 이상 노션이 자체적인
00:01:36기본 연동 기능을 만들어 주기를 기다릴 필요가 없습니다. 노션이 지원했으면 좋겠지만 지원하지 않는 기능이라면 무엇이든
00:01:42직접 코드를 작성해 구현할 수 있습니다. 그래서 우리가 처음 노션 워커를 개발하기 시작했을 때 가장 우려했던 점은
00:01:53사용자의 신뢰할 수 없는 코드를 실행하는 플랫폼을 구축해 본 경험이 조금 있거든요.
00:01:59예를 들어 오랫동안 GitHub Actions 개발에 참여했었습니다. 그래서 저는 이런 제품을 위한
00:02:04인프라를 구축하는 것의 어려움과, 특히 안전성에 대해 깊이 우려했습니다. 신경 써야 할 부분이
00:02:09정말 많습니다. 예를 들어, 사용자가 작성한 코드가 당연히 노션 데이터베이스에 접근하지 못하도록 해야 하고,
00:02:14평소에는 접근할 수 없어야 하는 노션 서비스들을 건드리지 못하게 확실히 통제해야 합니다.
00:02:20또한 한 사용자가 다른 사용자에게 영향을 주지 않도록 해야 합니다. 한 사용자의 코드가
00:02:26다른 사용자의 코드나 당연히 그들의 비밀 키(secrets) 같은 것에 접근할 수 있어서는 안 됩니다.
00:02:31이것은 단순히 보안만의 문제가 아닙니다. 공정성과 자원 공유의 문제이기도 합니다. 만약 어떤 사용자가
00:02:37CPU와 메모리를 엄청나게 소모하는 작업을 하고 있다면, 그것이 동시에 다른 작업을 하려는
00:02:42동시에 작업을 수행하려는 다른 사용자들에게 불공정한 영향을 미치지 않도록 해야 합니다.
00:02:48이야기를 하나 하겠습니다. 자원 공유라는 건 특히 정말 까다로운 문제입니다. 제 발표 시간을 좀 갉아먹겠지만,
00:02:54이 이야기를 좋아합니다. 우리가 GitHub Actions를 만들 때도 그랬고(이건 워커스를 만들 때도 생각했던 부분인데요),
00:02:58임의의 코드 실행 플랫폼이 생기자마자, 사람들은 즉시 그 위에서 암호, 코인 채굴을 시도합니다.
00:03:02임의의 코드 실행 플랫폼이 생기자마자, 사람들은 즉시 그 위에서 코인 채굴을 시도합니다.
00:03:07몇 주 전에 어떤 분과 이야기를 나누는데 그분이 그러더군요. “그런 건 어떻게 탐지하나요?
00:03:10CPU 사용률이 100퍼센트로 치솟으면 그냥 바로 알 수 있는 거 아닌가요?”라고요.
00:03:14글쎄요, 꼭 그렇지는 않습니다. 왜냐하면 플랫폼이 성공적으로 자리 잡자마자, 채굴자들은 즉시
00:03:20서로 스크립트를 공유하기 시작하거든요. CPU 명령어 실행 방식을 살짝 수정해서
00:03:26마치 완전히 무해한 활동처럼 보이게 만듭니다. 하지만 시스템에 탐지되지 않으면서도
00:03:31가능한 한 절대적인 최대치의 리소스를 끌어다 씁니다.
00:03:37따라서 이는 엄청나게 어렵고, 이를 제대로 해내려면 수많은 년수와 수많은 보안 및 관측 가능성 계층이 필요합니다.
00:03:42또 다른 걱정거리는 코드 실행 플랫폼이 있을 때 사람들이 이를 이용해 서비스 거부(DoS) 공격을 하거나,
00:03:48코드 실행 플랫폼이 있을 때 사람들이 이를 이용해 서비스 거부 공격을 하거나,
00:03:54봇넷 명령 및 제어 네트워크용으로 악용하는 것에 대해 걱정해야 한다는 점입니다. 우리는 노션 워커스를 시작하자마자
00:04:00이런 모든 문제를 Notion Workers에서 처음부터 전부 신경 쓰고 싶지 않았습니다. 저희가 좋아하는 일에 집중하고 싶었죠.
00:04:07즉, 사용자들이 즐겨 쓰는 제품을 제공하는 것입니다. 그래서 Vercel Sandbox로 구축하기로 결정한 것입니다.
00:04:14Vercel Sandbox는 처음부터 보았듯이 정말 탄탄한 인프라였고,
00:04:18이러한 많은 문제들을 기본적으로 해결해 주었습니다. 그럼 Notion Workers가 무엇인지 아주 간단한 데모로 보여드리겠습니다.
00:04:25여기에 커스텀 에이전트가 있는데, 이 커스텀 에이전트의 임무는 이 워크숍이 저주받았는지 아닌지를 저에게 알려주는 것입니다.
00:04:33저는 '수성 역행(Mercury Retrograde)'이라는 워커를 작성했습니다. 다들 익숙하실지 모르겠지만, 수성이 역행하도록 정렬되면 보통
00:04:40수성이 역행하는 위치로 정렬될 때는 보통 징조가 좋지 않다고 여겨집니다.
00:04:47따라서 이 워커는 수성이 역행 중인지 아닌지 알려주는 기능 외에는 아무것도 하지 않는, 제가 찾은 API를 사용하는 단일 커스텀 도구 호출을 갖고 있습니다.
00:04:54그래서 에이전트에게 “내 워크숍에 저주가 내렸나요?”라고 프롬프트를 입력하면,
00:05:01잠시 생각하다가 인터넷만 잘 작동한다면
00:05:14도구 호출을 수행하는 모습을 보게 될 것입니다. 즉, 제 수성 역행 워커를 사용해
00:05:22그 워커가 제공하는 단일 도구를 호출하고, 수성이 역행 중인지 확인한 뒤에
00:05:27에이전트가 우리에게 응답을 줄 것입니다. 이걸 GPT-54 나노 같은 모델에 연결해 둔 것 같은데, 그래서 보통은 훨씬 더 빠릅니다.
00:05:37불행히도 이건 인터넷 문제인 것 같네요. 혹시 수성이 역행 중인지 미리 확인해 보신 분이 있다면,
00:05:46실제로 역행 기간이라는 걸 제가 미리 알고 있습니다. 그래서 아마 이런 현상이 일어나나 봅니다. 그냥 넘어가겠습니다.
00:05:53잠시 후에 다시 돌아와서 응답이 결국 왔는지 확인해 보겠습니다. 하지만 이미 답은 알고 있는 것 같네요.
00:05:59좋습니다. 이제 노션 워커가 무엇인지 감을 잡으셨을 겁니다. 이제 사용자 코드를 가져와서,
00:06:05배포하고, 안전하게 실행하며, 에이전트가 해당 코드를 호출해 도구를 쓸 수 있도록 에이전트에 그 코드를 알려주는
00:06:13기본적인 작업들을 수행하는 제가 작성한 작은 스크립트를 보여드리겠습니다.
00:06:20이게 완료되었나요? 아, 네, 여기 있네요. 아, 아마도...
00:06:28수성 역행 API를 내려버렸나 봅니다. API가 응답하지 않는 것 같네요.
00:06:35어쨌든 나중에 어딘가에 티켓을 열어야겠군요. 좋습니다. 기본적인 스크립트가 있습니다. 여러분이 완전히 따라서
00:06:40코드를 작성하거나 할 거라 기대하지는 않습니다. 건너뛰면서 이런 제품을 만들 때 해결해야 하는
00:06:44몇 가지 문제들에 대한 아이디어를 드리는 방식으로 진행하겠습니다. 하지만 원하신다면 리포지토리가 있습니다.
00:06:49make notion slash for cell ship 2026 workers에
00:06:55제가 여기서 실행할 모든 코드가 들어 있습니다.
00:07:01좋습니다. 그럼 다른 슬라이드들을 열어보겠습니다.
00:07:08자, 무엇을 만들어 볼까요? 스트리밍 채팅 에이전트가 있고, 우리가 신뢰할 수 없거나 코딩 내용을 알 수 없는
00:07:16내용을 알 수 없는 커스텀 사용자 코드로 정의된 도구들을 호출할 수 있도록 허용할 것입니다.
00:07:22그리고 이를 Vercel sandbox와 Vercel의 blob 스토리지 서비스를 이용해 구축할 것입니다.
00:07:26그럼 여기서 간단한 데모를 실행해 보겠습니다. 실제로 저주받지 않았기를 바랍니다.
00:07:31기본적으로 메시지 하나만 전달하면 됩니다. “안녕”이라고 말해볼 거고, 응답이 돌아오기를 바랍니다.
00:07:36백그라운드에서 몇 가지 배포 작업을 수행하고 있기 때문에 조금 느릴 수 있습니다.
00:07:40이번에는 도구를 호출했네요. `say hello`라는 도구를 호출했고, 저에게 인사를 건네기로 결정했습니다.
00:07:47이번에는 일반적인 메시지를 보내서 1 더하기 1이 뭔지 물어보겠습니다. 그러면 일반적인 응답이
00:07:52스트리밍되는 것을 볼 수 있습니다.
00:07:58Vercel AI SDK를 사용해 보셨다면 이건 그냥 도구 에이전트 루프를 사용하는 것입니다. 좋습니다. 스트리밍 응답을 받았네요.
00:08:04훨씬 더 복잡한 워커도 호출할 수 있습니다. 보여드릴 다른 워커가 또 하나 있습니다.
00:08:14이 워커에서는 에이전트에게 제가 이 건물의 주소에 있다고 말해줍니다.
00:08:17저에게는 90분이 있고, 역사 유적지를 보고 싶으며, 15분 이상 걷고 싶지 않다고 알려줍니다.
00:08:24이것은 왜 이것들이 때때로 MCP 서버보다 유리한지를 보여줍니다. MCP 서버를 사용하면
00:08:29호출할 수 있는 여러 가지 이질적인 도구들이 있습니다. 복잡한 무언가를 하려고 할 때
00:08:33그 단계들을 에이전트에게 설명해 주어야 하고, 에이전트는 한 단계를 수행하고, 사고 토큰을 좀 쓰고, 또 한 단계를 수행하는 식입니다.
00:08:38따라서 이 방식은 매우 크고 복잡한 일종의 검색 알고리즘을 가진 워커를 호출하게 됩니다.
00:08:45이 알고리즘은 뉴욕시의 지리적 위치, 대중교통 소요 시간, 경로 계획 API들을 다수 활용합니다.
00:08:52이름은 `plan outing`입니다.
00:08:56그러면 그 워커가 우리가 방문할 수 있는 추천 역사 유적지와 함께 응답할 것입니다.
00:09:00시청 공원 근처에 있는 MIPOW 기념물인 '프리덤 트리 표지판(freedom tree marker)'을 찾은 것 같네요.
00:09:07좋습니다. 이것이 어떻게 작동하는지 살펴보겠습니다. 자, 워커란 무엇일까요?
00:09:14이 경우 파일 하나에 정의된 사용자 코드입니다. 보통은 여러분의 사용자에 의해 정의되거나
00:09:20GitHub 어딘가에 정의되어 CLI로 배포되겠지만, 여기서는 리포지토리에 체크인된 몇 가지 예제만 가지고 있습니다.
00:09:26그리고 이 각각의 워커들은 도구 이름, 에이전트가 올바른 도구를 찾아갈 수 있도록 돕는 도구 설명,
00:09:34에이전트가 해당 도구를 호출할 때마다 제공해야 하는 입력값이 무엇인지 알 수 있게 하는 입력 스키마를 노출해야 합니다.
00:09:38그리고 에이전트가 이 도구를 호출했을 때 실제로 실행될 코드가 무엇인지 정의하는 실행 함수를 노출해야 합니다.
00:09:44예시를 살펴보겠습니다. 아까 보셨던 그리터(greeter) 도구입니다. 아주 간단합니다.
00:09:51이 워커는 `index.ts` 파일에 있는 하나의 자바스크립트 모듈입니다. `say hello`라는 단일 워커를 내보냅니다.
00:09:59say hello라는 워커가 있습니다. 이 빌드 프로세스가 어떻게 작동하는지 보실 텐데요. 이 예시에서는,
00:10:04모든 모듈 내보내기 키가 도구 이름에 매핑됩니다. 따라서 이 도구의 이름은
00:10:09그리고 간단한 설명과 입력 스키마를 가집니다. 이 경우 스키마를 정의하기 위해 Zod를 사용하고 있습니다.
00:10:16그런 다음 이를 JSON 스키마로 변환합니다. 그것이 바로 이 에이전트들이 기대하는 형식입니다.
00:10:22그리고 간단한 실행 함수가 있습니다. 하지만 훨씬 더 복잡한 것들도 만들 수 있습니다. 예를 들어
00:10:27`plan outing` 워크플로를 열어보면, 이 도구가 어떻게 작동하는지, 언제 호출해야 하는지,
00:10:34무엇을 하는지 에이전트에게 알려주는 훨씬 더 긴 설명이 포함되어 있습니다. 그리고 이것을 위한 스크립트는 훨씬 더 길고 복잡합니다.
00:10:39이것을 전부 다 살펴보지는 않겠고, 일부 작은 부분만 읽어보았지만 아주 잘 작동합니다.
00:10:45이것이 우리가 지금 살고 있는 세상입니다. 자, 이제 이 워커들이 빌드되어 Vercel blob 스토리지에 배포될 것입니다.
00:10:54그다음 어떤 방식으로든 워커의 내용에 대해 학습한 뒤,
00:10:59에이전트에게 그것들을 노출할 것입니다. 그리고 그 도구들은 샌드박스 안에서 안전하게 실행될 것입니다.
00:11:03빌드 부분은 살짝 넘어갈게요. 노션 워커의 경우, 클라우드 기반의 배포 및 빌드 프로세스를 가지고 있습니다.
00:11:09여기서는 디스크상에 미리 이 워커들을 빌드해 두었습니다. 따라서 각 워커에는
00:11:15각 워커에는 컴파일된 TypeScript 코드 종속성 및 기타 항목이 포함된 tarball이 있습니다.
00:11:22아주 흥미로운 부분은 아니니 그냥 넘어가겠습니다.
00:11:26오늘의 핵심 질문은, 우리가 본 적 없고 신뢰할 수 없는 사용자 코드를 어떻게
00:11:34에이전트가 노출하고 안전하게 실행할 수 있는 도구로 전환하느냐 하는 것입니다. 여기엔 두 가지 측면이 있습니다.
00:11:39첫 번째 부분은, 예를 들어 사용자 코드가 블롭 스토리지에 있을 때
00:11:43그 코드의 내용을 어떻게 파악하느냐 하는 것입니다. 에이전트가 도구를 호출하기 전에
00:11:48도구의 이름이 무엇이고, 입력 스키마는 무엇이며, 도구에 대한 설명은 무엇인지 알려주어야 하기 때문입니다.
00:11:53그리고 두 번째 질문은, 에이전트가 해당 도구를 호출하기로 결정했을 때
00:11:58어떻게 실제로 안전하게 실행할 것인가 하는 점입니다. Notion Workers SDK에서
00:12:04구현한 방식 중 제가 정말 좋아하는 점이자 여기서 해낸 점은 코드가 스스로를 설명한다는 것입니다. 우리가 원치 않았던 점은
00:12:11Notion Workers를 설계할 때 사용자가 도구를 정의하는 TypeScript 코드를 작성한 다음
00:12:16정적 매니페스트 파일을 따로 만들어야 하고 기본적으로 똑같은 내용을 다시 작성해야 해”라고 말하게 만드는 것이었습니다.
00:12:21우리는 사용자가 스크립트를 실행하고, 분석을 거치고, 디스크에 컴파일한 다음 배포해야 하는 어색한 로컬 빌드 프로세스를 원하지 않았습니다.
00:12:27우리는 사용자가 스크립트를 실행하고, 분석을 거치고, 디스크에 컴파일한 다음 배포해야 하는 어색한 로컬 빌드 프로세스를 원하지 않았습니다.
00:12:34도구를 그냥 작성하기만 하면 거기서 정보를 뽑아내는 어려운 문제는 우리가 해결해야 할 몫인,
00:12:40최적의 개발자 경험처럼 느껴지는 방식으로 시작하고 싶었습니다.
00:12:46이것은 아주 간단한 다이어그램이지만, 코드로 들어가기 전에 이것을 어떻게 해결할 것인지
00:12:52설명해 드리겠습니다. 블롭 스토리지에 사용자의 컴파일된 코드가 있습니다.
00:12:59우리는 그 코드를 사용해 샌드박스를 만들 것입니다. 샌드박스 안에서 명령어를 실행할 때, 그 사용자의
00:13:04코드는 작업 공간 루트(workspace root)에 위치하게 됩니다. 사용자가 작성한 `index.js` 파일을 가져올 것입니다.
00:13:11그렇게 하면 모든 내보내기 항목의 이름, 설명, 입력 스키마들을 얻을 수 있습니다.
00:13:19여기서 약간 이상해지지만 아주 잘 작동하는 부분이 나옵니다. 해당 모듈에 대해
00:13:23`json.stringify`를 호출하고 표준 출력(standard out)으로 로그를 남길 것입니다. 이 모든 과정이
00:13:29샌드박스 내에서 일어납니다. 그런 다음 배포 스크립트가 그 표준 출력을 받아와 파싱한 뒤,
00:13:34“좋아, 이제 이 워커에 있는 모든 도구의 이름과 입력값, 설명을 알게 되었어”라고 판단합니다. 이 경우
00:13:40우리는 그것을 에이전트에 직접 전달합니다. 하지만 노션 워커의 경우 예를 들어,
00:13:44그것이 배포 파이프라인의 일부가 됩니다. 그래서 모든 정보를 가져와 데이터베이스에 저장하므로
00:13:49커스텀 에이전트를 실행할 때마다 데이터베이스에서 해당 도구 설명들을 가져오게 됩니다.
00:13:53작동 방식이 대충 이해되시나요? 좋습니다. 그럼 배포 스크립트를 살펴보겠습니다.
00:14:02첫 번째 커밋으로 돌아가야겠네요. 이 스크립트에는 배포 기능과 에이전트 호출 기능이 모두 내장되어 있습니다. 아주 간단합니다.
00:14:12여기서는 디렉토리와 워커들을 순회하기만 합니다. 각 하위 디렉토리에는 아까 보셨던 것 같은 워커 코드가 들어 있습니다.
00:14:18우리의 임무는 각각의 워커에서 모든 도구 정보를 추출해 내고
00:14:23이 tools 객체를 채울 방법을 알아내는 것입니다. 그것이 도구 루프 에이전트(tool loop agent)에 전달됩니다.
00:14:29이것은 Vercel이 만든 AI SDK의 일부일 뿐입니다. 그런 다음 사용자의 메시지를 그 에이전트에 보내고,
00:14:35출력을 스트리밍한 뒤, 스크립트에서 표준 출력으로 기록합니다. 따라서 우리가 가장 먼저 파악해야 할 것은
00:14:42소스 코드를 어떻게 업로드할 것인가 하는 점입니다. 그 부분은 꽤 쉽고 빠릅니다.
00:14:48라이브 코딩을 하는 대신, 타이핑하는 모습을 보시지 않도록 이 조각들 사이를 건너뛰겠습니다.
00:14:53코딩은 할 줄 안다고 장담합니다. 다만 아무도 그걸 지켜보고 싶어 하지 않을 것 같아서요.
00:14:59조금 앞으로 건너뛰어, 살펴보게 될 `upload source`라는 함수가 있습니다.
00:15:06보시다시피 Vercel blob SDK를 임포트했습니다. 여기서 이 부분은 꽤 간단합니다.
00:15:13이 `upload source` 함수를 보면, 기본적으로 이 `put` 함수를 호출하고 있습니다.
00:15:20그리고 사용자의 번들을 `worker name / bundle.tar.gzip` 경로 아래에 저장하겠다고 명시하고 있습니다.
00:15:29디스크에서 파일을 스트리밍하여 블롭 스토리지에 저장할 것입니다. 이 작업이 끝나면,
00:15:35해당 블롭으로부터 샌드박스를 생성할 수 있게 됩니다. 이것으로 업로드 파트는 해결되었습니다.
00:15:43그 다음 해야 할 일은 번들로 묶인 소스 코드를 가져와서
00:15:49그것으로부터 샌드박스를 생성하는 것입니다. 이를 위해 Vercel sandbox SDK를 임포트했습니다.
00:15:56아래쪽에 `create sandbox`라는 또 다른 헬퍼 함수가 있습니다. 이 함수의 몇 가지 부분은 건너뛰겠습니다. 하지만 기본적으로 우리가 하는 일은
00:16:03블롭 스토리지에 해당 객체가 있을 때, 샌드박스 서비스에 전달할 사전 서명된 URL(pre-signed URLs)을 사용하는 것입니다.
00:16:10샌드박스 서비스가 (이건 10분 만료로 설정되어 있는 것 같군요) 블롭 스토리지에서 해당 블롭을 끌어와
00:16:16샌드박스를 채우는 데 사용할 수 있도록 말이죠. 이것이 Vercel sandbox에서 제가 정말 마음에 드는 기능 중 하나입니다.
00:16:23이런 tarball과 잘 작동한다는 점입니다. 사용자 코드의 tarball을 빌드하기 쉽고,
00:16:28tarball로부터 샌드박스를 만들고 싶어 하는 샌드박스 서비스는 스토리지나 제공된 URL에서 그것을 가져와
00:16:35작업 공간 루트에 자동으로 그 tarball을 추출합니다.
00:16:40따라서 모든 파일이 바로 생성되어 작업할 준비가 됩니다. 이것은 단순히 `sandbox.create`를 호출하여 완료됩니다.
00:16:46아직 엄청나게 복잡한 부분에 도달하지는 않았지만, 곧 다다를 것입니다.
00:16:52또 한 가지는, 이 예제에서는 간결함을 위해 실행할 때마다 새로운 샌드박스를 매번 생성하고 있다는 점입니다.
00:16:57실제 상황에서는 도구가 호출될 때마다 매번 샌드박스를 재배포하거나 Vercel blob 스토리지
00:17:02또는 사용 중인 다른 클라우드 블롭 스토리지로부터 스트리밍해 오지 않도록 스냅샷 기능을 사용해야 합니다.
00:17:09플랫폼에 기본적으로 구축된 캐싱 메커니즘이 존재합니다. 단순함을 위해 여기서는 건너뛰었습니다.
00:17:15플랫폼에 기본적으로 구축된 캐싱 메커니즘이 존재합니다. 단순함을 위해 여기서는 건너뛰었습니다.
00:17:19이것으로 샌드박스 생성이 끝났고, 이제 흥미로워지는 부분입니다.
00:17:23다음으로 우리가 해야 할 일은 우리가 방금 생성한 샌드박스에서
00:17:31해당 워커에 정의된 도구들에 대한 정보를 추출하는 것입니다.
00:17:36여기서 아주 잠깐 멈추고, 제가 중간중간 흩어 놓은 몇 가지 팁을 말씀드리겠습니다.
00:17:40이와 같은 프로덕션 수준의 서비스를 구축하는 경우, 일반적으로 샌드박스를 중지하기 위해 최선의 노력을 다해야 합니다.
00:17:44따라서 `extract tools`가 무엇을 하는지 보시겠지만, 작업이 끝나면 샌드박스를 명시적으로 삭제하도록 합니다.
00:17:51따라서 `extract tools`가 무엇을 하는지 보시겠지만, 작업이 끝나면 샌드박스를 명시적으로 삭제하도록 합니다.
00:17:56실제 환경에서는 비동기 정리 작업을 큐에 넣는 등의 조치도 취해야 합니다.
00:18:00이러한 샌드박스들이 무기한 방치되도록 내버려 두어선 안 됩니다.
00:18:06자, `extract tools` 함수가 무엇을 하는지 살펴보겠습니다. 우리에게는 샌드박스가 있고, 저는 이 예제들이 정말 좋습니다.
00:18:15얼마나 단순한지 좀 우스꽝스러워 보이지만, 정말 너무나도 잘 작동하거든요.
00:18:23샌드박스 위에서 노드(node) 바이어너리를 사용해 노드 명령어를 실행하고, 스크립트 내부에서
00:18:30사용자가 작성한 모듈(`index.js`)을 임포트합니다. 해당 객체를 문자열화(stringify)한 뒤
00:18:36`console.log`를 호출합니다. 명심해야 할 점은 샌드박스는 요청을 보내고 응답을 받아올 수 있는
00:18:42웹 서버 같은 것이 아니라는 것입니다. 모든 입력과 출력은 명령어를 실행하는 방식으로 이루어집니다.
00:18:48그런 다음 수신하는 서비스에 응답을 보내게 하거나, 이 경우처럼 단순히
00:18:52표준 출력으로 로그를 남기게 할 수 있습니다. 몇 가지 팁을 드리자면, 보통은 그냥 `console.log`를 사용하고 샌드박스로부터 얻는
00:19:00모든 출력을 그대로 신뢰해서는 안 됩니다. 사용자가 설치한 다른 패키지들이 있을 수 있고,
00:19:07표준 출력 스트림에 동시에 다른 로그를 출력하는 실행 중인 다른 코드가 있을 수도 있습니다.
00:19:12따라서 파싱할 수 있는 어떤 종류의 태그로 출력을 감싸는 것이 좋습니다.
00:19:18이것은 여기서 우리가 하게 될 XML 유사 태그 같은 것입니다. 이 예제에서는 그냥 생략하고 있습니다.
00:19:23또한 소비하는 로그의 크기를 제한해야 합니다. 이 예제에서는 모든 출력을
00:19:29하나의 스트림으로 수집하고 있습니다. 프로덕션 수준의 애플리케이션에서는 그렇게 하면 안 됩니다.
00:19:34스트림 데이터가 1기가바이트에 달해 서버가 OOM(메모리 부족)에 빠질 수 있기 때문입니다.
00:19:39따라서 샌드박스 API에는 로그를 스트리밍하기 위한 명령어들도 존재합니다. 그것이 바로
00:19:45노션 워커에서 하는 방식입니다. 우리가 관심을 가지는 출력 토큰의 시작 지점을 볼 때까지 스트림을 소비합니다.
00:19:53그다음에 우리가 의도적으로 로그에 남긴 설명이나 내용들을 버퍼링하기 시작합니다.
00:19:58그리고 닫는 태그에 도달하자마자 출력 스트림 처리를 중단합니다. 이렇게 하면 샌드박스에서 무언가
00:20:04이상하고 잘못되어 엄청난 양의 데이터가 로깅되더라도
00:20:08그것들이 전부 메모리에 쌓이지 않습니다. 그냥 무시하고 우리가 신경 쓰는 데이터가 들어올 때까지 기다리면 됩니다.
00:20:14따라서 해당 명령어가 실행되도록 둔 후, 완료되기를 기다려 표준 출력을 가져옵니다.
00:20:22Zod 같은 라이브러리를 써보셨다면 꽤 익숙하실 겁니다. 문자열을 JSON으로 파싱하고 있죠.
00:20:27그리고 여기에 Zod 타입이 있어서
00:20:31원하는 형태의 데이터인지 검증합니다.
00:20:34이 데이터는 신뢰할 수 없는 사용자 코기 때문에 절대 그대로 믿으면 안 됩니다. 어떤 내용을
00:20:39로그로 남길지 알 수 없기 때문이죠. 따라서 반드시
00:20:43크기를 확인하고 전반적인 데이터 형태를 검증해야 합니다. 이 경우에 우리가 예상하는 것은
00:20:50문자열 키가 각 도구의 이름이 되는 레코드 형태입니다.
00:20:57그리고 객체는 설명과 입력 스키마를 담게 되는데, 바로 그
00:21:02JSON 스키마입니다. execute 함수는 여기에 없다는 걸 눈치채셨을 겁니다.
00:21:06애초에 반환되지 않기 때문이죠. 다행히도 JSON.stringify는 직렬화할 수 없는 항목을
00:21:11그냥 건너뜁니다. 그래서 무시되는 셈이죠. 결국 설명과 입력 스키마만 가져오게 됩니다.
00:21:18다시 위로 올라가서, 워커 도구들이 있습니다.
00:21:23해당 워커에서 노출된 모든 도구의 레코드인 셈이죠.
00:21:28다음으로 할 일은 해당 데이터를 AI SDK가 기대하는 형태로 맞추는 것입니다.
00:21:36그리고 각각의 도구에 실행 함수를 연결해야 합니다.
00:21:42표준 출력에 로그를 남길 때에는 실행 함수를 당연히 가져오지 않았으니까요.
00:21:46그럼 이제 이 도구에 대한 설명이 주어졌을 때, 어떻게 함수를 제공할 수 있을까요?
00:21:51SDK가 이 워커나 이 워커가 노출하는 도구를 호출하고 싶을 때마다 호출할 수 있는 함수 말입니다.
00:21:58그래서 execute tool이라는 작은 래퍼를 만들었습니다. 어떤 일을 하는지 살펴볼까요?
00:22:04이제 꽤 익숙해 보일 겁니다. 앞서 본 것과 동일한 create sandbox 함수를 호출하니까요.
00:22:10새로운 샌드박스를 만드는 거죠. 캐싱을 사용하더라도 Vercel 샌드박스에는
00:22:16영속성(persistence)이라는 기능이 있어서, 샌드박스가 일시 중지되거나 멈출 때마다 디스크에 있는 모든 상태를 캐시합니다.
00:22:23하지만 이 같은 기능에서는 실제로 그럴 필요가 없습니다. 모든 사용자 코드가 포함된 초기 상태를 스냅샷으로 찍고 싶겠지만,
00:22:28일반적으로는 그 이후에 도구가 실행될 때마다 매번 완전히 새로운 인스턴스를 보장하고 싶을 겁니다.
00:22:33그래야 한 번의 도구 실행에서 무언가 잘못되더라도, 다음에 실행되는 도구의 환경을 오염시키지 않으니까요.
00:22:38그래서 새로운 샌드박스를 만들고, 그 위에 다른 노드 스크립트를 실행합니다.
00:22:43이것도 꽤 익숙해 보이겠지만 약간의 차이가 있습니다. 사용자가 작성한 모듈을 가져오고 있죠.
00:22:49해당 모듈에서 도구를 가져오는데, 이는 execute tool 래퍼를 만들 때 얻었던 도구 이름입니다.
00:22:56거기에 있는 execute 함수를 호출하고요.
00:23:03그 execute 함수에 모델이 제공한 입력을 전달합니다.
00:23:09여기서는 타입을 unknown으로 지정하고 있습니다. 하지만 여기서는 꽤 안전한 편입니다.
00:23:19왜냐하면 우리가 제공했기 때문이죠. 여기서 안 했었나요? 이 예제에서는 건너뛰었을지도 모르겠네요. 하지만 평소에
00:23:27하는 일은... 아, 아니요, 한 것 같네요. 다시 위로 올라가 보겠습니다. 네. 도구를 조작해서
00:23:34에이전트에 보낼 때, JSON 스키마를 가져와서 다시
00:23:39Zod 타입으로 변환하고 있습니다. 그래서 우리가 직접 파싱할 필요가 없습니다. AI SDK와 도구 루프 에이전트 코드가
00:23:46해당 도구가 호출될 때마다 에이전트로부터 온 입력을 알아서 검증해 주니까요.
00:23:52따라서 이 값이 우리가 예상한 바와 같다고 어느 정도 신뢰할 수 있습니다. 그래서 이를
00:23:56여기 있는 execute 함수에 전달할 것입니다. 그런 다음 비동기 함수가 완료되면
00:24:03문자열화(stringify)하고, 표준 출력에 로그로 남깁니다. 그리고 나서—잠시 후에 이 부분에 대해 다루겠습니다.
00:24:10명령어가 완료되기를 기다립니다. 그리고 다시 샌드박스를
00:24:14삭제하고 마무리합니다. 그런 다음 사용자가 작성한 실행 함수의 반환값인 JSON을 파싱하고,
00:24:21이를 도구 루프 에이전트에 다시 보냅니다.
00:24:26결과적으로 흐름은 이렇습니다. 도구 루프 에이전트가 앞서 보았던 대중교통 API를 사용하는 일정 계획 도구를 호출하고 싶다고 말합니다.
00:24:35그러면 에이전트가 결정한 입력값들과 함께 이 함수가 호출됩니다.
00:24:40우리는 사용자의 컴파일된 코드에서 이전에 업로드한 블롭을 사용해 샌드박스를 생성합니다.
00:24:48그런 다음 사용자의 execute 함수를 호출하는 명령어를 해당 샌드박스에서 실행합니다. 함수가 반환될 때까지 기다린 후,
00:24:55그 반환값을 표준 출력에 기록하고, 파싱한 뒤 에이전트에게 다시 보냅니다.
00:25:00그러면 에이전트는 도구 루프를 계속 진행하여 추가 도구를 호출하거나 사용자에게 응답하게 됩니다.
00:25:05몇 가지 빠른 팁을 드리자면, 프로덕션 시스템에서도 동일한 출력 검증 규칙이 적용됩니다. 사용자가
00:25:13파싱하는 데 2GB나 걸리는 객체를 반환하지 못하도록 막아야 합니다. 또한
00:25:19아마 SDK를 제공하는 편이 좋을 것입니다. 이 예제에서는 다루지 않지만, Notion Workers SDK에서는
00:25:28해당 실행 함수의 반환값이 반드시 JSON 직렬화 가능하도록 타입이 설정되어 있습니다.
00:25:35이것은 사용자가 흔히 저지르기 쉬운 실수인데, 함수가 어떤 값이든 반환할 수 있게 만들면
00:25:41JSON으로 직렬화할 수 없는 값을 반환하게 되기 쉽습니다.
00:25:47그 상태로 표준 출력을 통해 전송되어 파싱되려고 하면
00:25:52사용자는 매우 혼란스러워하며 워커가 왜 작동하지 않는지 이해하지 못할 것입니다.
00:25:57그리고 이건 Notion Workers와 관련해서 발생했던 일화인데, 여기서 생성하는 노드 프로세스가
00:26:05무조건 종료되도록 항상 각별히 주의해야 합니다. Notion에서 버그가 있었는데, 사용자 코드가 실행되어 완료된 것처럼 보이다가도
00:26:11어떤 이유에서인지 이 노드 프로세스가 종료되지 않는 문제였습니다.
00:26:18샌드박스 수명이 다할 때까지, 대략 5분 정도 동안 그냥 멈춰 있었습니다. 치명적이진 않았지만
00:26:25자원을 낭비하고 있었죠. 그리고 알게 되었거나 기억해 낸 사실은—이건 노드 런타임의 특성이기도 한데—
00:26:30사용자들이 set interval이나 set timeout 같은 타이머를 설정하는 코드를 실행하고 있었다는 점입니다.
00:26:38특히 인터벌(interval)이나 대기 중인 프로미스(dangling promise) 같은 것들이 있으면 프로세스가 종료되지 않습니다.
00:26:44setInterval이나 setTimeout 같은 타이머를 설정하는 코드를 실행하고 있었습니다. 특히 인터벌이나, 제 생각엔 대기 중인 프로미스도 같은 문제를 일으킬 겁니다.
00:26:52스크립트가 실행될 때 인터벌이나 그와 유사한 무언가가 계속 실행 중이라면, Node 프로세스는
00:26:58절대 종료되지 않습니다. 그 타이머들이 전부 끝날 때까지 혼자서는 종료되지 않죠. 만약 이게
00:27:03인터벌이라면 샌드박스가 종료될 때까지 영원히 실행될 겁니다. 따라서 여러분이 항상 확인해야 할 것은
00:27:08실행하려는 코드나 함수가 실제로 완료되었을 때, 프로세스에 명시적으로 종료하라고 지시하는 것입니다.
00:27:14그래야 샌드박스가 멈추고, 그후에 프로세스가 반환됩니다.
00:27:23자, 대략 전체 워크플로는 이렇습니다. 이걸 한 번 더 실행해 볼 건데, 이번에는
00:27:29디버깅을 켜서 어떤 일이 일어나는지 직접 보실 수 있게 하겠습니다.
00:27:42먼저 departures 워커를 배포합니다. 이건 잠시 후에 자세히 살펴볼게요. 먼저 번들을 업로드하여
00:27:48departures 워커를 배포하죠. 그리고 그 워커에 대한 정보를 추출하는 데 사용할 샌드박스를 만듭니다.
00:27:54이 도구들을 추출했습니다. 모듈의 내용을 표준 출력으로
00:28:01로그에 남기면 이런 결과가 나옵니다. 각 도구의 키, 설명, 입력 스키마를 얻게 되죠.
00:28:08간단한 greeter 워커에 대해서도 똑같이 작업합니다. 그런 다음 이 모든 것을 가져와서 에이전트에 전달하므로
00:28:13에이전트가 툴 호출을 하고 사용자에게 응답할 수 있습니다. 대략 이런 식이죠. 이것이 바로 신뢰할 수 없는 사용자 코드를 가져와서
00:28:19어딘가에 저장하고, 안전한 방식으로 분석하여 영구 스토리지에 넣거나 에이전트에게 직접 보낸 다음,
00:28:26에이전트가 해당 코드를 안전하게 실행할 수 있도록 만드는 아주 간단한 예시입니다. 자, 시간이 10분 정도 남았네요.
00:28:31혹시 질문 있으신 분 계신가요? Notion 워커나 Vercel 샌드박스 활용법 전반에 대해,
00:28:39또는 신뢰할 수 없는 코드 실행에 대해 질문하고 싶으시다면
00:28:46기꺼이 답변해 드리겠습니다. 감사합니다.
00:28:56아 맞다. 그리고 나중에 Notion 개발자 플랫폼 전반에 대해 이야기 나누고 싶으시다면
00:29:01저나 저기 있는 제 동료 MJ에게 말씀해 주세요. Notion 개발자 플랫폼의 프로덕트 매니저입니다.
00:29:08안녕하세요. 운영 관련 질문인데요, 사용자가 무엇이든 할 수 있으니까, 이걸 어떻게 제한하시나요?
00:29:15네. 여러 사용자가 비슷한 도구를 각각 작성할 수도 있잖아요.
00:29:20여러 사용자가 뭐라고요? 비슷한 도구를 작성하는 거요. 예를 들어 뉴욕 여행 일정을 짜는 것 같은 거요.
00:29:24네. 10명의 서로 다른 사용자가 각각 다른 10개의 코드를 가질 수 있죠. 네. 똑같은 일을 하는 코드를요.
00:29:29네. 이걸 차단하기 위해 따로 조치를 취하고 계신가요, 아니면 그냥 에이전트에게 맡기시나요?
00:29:33아니요. 여러 사용자가 모두 똑같은 작업을 하려고 한다면, 그냥 그렇게 하도록 둡니다.
00:29:37부분적으로는 제품 관련 질문이네요. 예를 들어 동일한 조직 내의 여러 사용자가 있다면,
00:29:42운영적인 측면이기도 하고 제품적인 측면이기도 한 그런 부분을 확실히 하고 싶을 겁니다.
00:29:46좋은 공유 기본 요소 같은 기능들을 확실히 갖추고 싶어 하죠. 그래서 제가
00:29:50이 작업을 수행하는 워커가 이미 있는지 살펴보고 검색할 수 있도록 말입니다. 그렇게 하면
00:29:55코드를 다시 작성하지 않아도 됩니다. 하지만 플랫폼 전반적인 관점에서는, 사용자들이 동일한 코드를
00:30:02배포할 가능성은 매우 낮기 때문에 이를 굳이 중복 제거하려고 애쓰는 것은 가치가 없습니다.
00:30:19좋습니다. 질문이 몇 개 더 있는 것 같은데, 마이크를 누가 가지고 계신지 모르겠네요.
00:30:24못 들었어요. 아, 아, 네. 정말 죄송합니다.
00:30:29헤드폰으로 들어오는 줄 알았어요.
00:30:33아, 네. 하신 질문은 만약 수많은 사용자가 동일한 코드를 배포한다면
00:30:40이를 운영 관리에 반영하기 위해 무언가를 하느냐는 것이었는데, 따로 하지 않습니다. 제품 관련 질문에 더 가깝죠.
00:30:45사용자들이 같은 작업을 반복하지 않기를 바라기 때문에, 현재 노션 워커를 위해
00:30:50개발 중인 좋은 공유 기본 요소들을 제공하고자 합니다. 하지만 플랫폼 수준의 운영 관점에서는
00:30:54사람들이 동일한 코드를 50번 배포하더라도 신경 쓰지 않습니다.
00:30:56샌드박스 환경에서 워커가 작업을 수행하다가 너무 일찍 종료되어
00:31:04타임아웃 문제가 발생하는 경우는 없나요?
00:31:07타임아웃에 대한 질문이 정확히 어떤 내용이었죠?
00:31:10Vercel이나 다른 제품 및 워크플로우, 예를 들어 함수들과 샌드박스 간의
00:31:15타임아웃으로 인해 겪는 문제가 있으신가요? 아니면 그냥 모든 게 원활한가요?
00:31:20네, 그와 관련해서는 아무런 문제가 없었습니다. 저희에게는 플랫폼이 지금까지
00:31:25정말 견고했습니다. 빈말이 아니라 진심으로 아주 좋았습니다.
00:31:32오히려 사용자들이 실수로 잘못된 작업을 수행하는 문제들을 훨씬 더 많이 겪고 있습니다. 그래서 시간이 지날수록
00:31:36시간이 지날수록 그런 잠재적 위험 요소들을 없애고, 개발자와 비개발자 모두
00:31:42플랫폼을 훨씬 더 쉽게 사용할 수 있도록 만드는 게 핵심입니다. 좋습니다, 아주 명쾌하네요. 제가 여쭤보고 싶었던 건,
00:31:48초기 사용자 코드를 문자열화(stringify)할 때인데요. 아마도 코드가 안전한지 확인하거나
00:31:52실행 가능하게 하려는 목적이겠죠. 이 부분에 대해 조금 더 자세히 여쭤보고 싶었습니다.
00:31:57코드를 문자열화하고 워커 태그를 가져와서 사용자 코드에 필요한 입력값을 얻고,
00:32:04해당 도구에 대한 설명을 얻는다고 하셨는데요. 그게 노션, 그러니까 예를 들어서
00:32:10에이전트가 코드를 실행할 수 있게 하기 위한 건가요? 사용자 실행 코드나
00:32:15실행자 함수는 이미 실행하고 계신 걸로 알고 있어서요. 입력값과 도구 자체의 설명을 얻기 위해
00:32:21왜 이런 추가 단계를 거치시는지 궁금했습니다.
00:32:24좋은 질문이네요. 실제 제품에서는 조금 더 명확하지만, 여기서는 간소화되어 있습니다.
00:32:29질문의 요지는 '왜 워커에 대한 정보를 얻기 위해 사용자 코드를 한 번 실행하고,
00:32:34도구 코드가 호출될 때 또다시 실행하느냐'는 것일 텐데요. 그 이유는 에이전트가
00:32:39애초에 도구를 호출하거나 그 도구의 존재를 알기 전에, 우리는 먼저 그 도구의 내용을 파악한 뒤에
00:32:45SDK를 통해 에이전트에 노출해야 하기 때문입니다. 예를 들어 노션 워커에서
00:32:50NTN Workers Deploy 같은 명령을 실행하면, 빌드 파이프라인을 거치게 되는데요.
00:32:55빌드를 실행하는 샌드박스가 tarball(압축 파일)을 가져와 어딘가의 스토리지에 저장합니다. 그리고 나서,
00:33:02새로운 워커 안에서 도구의 이름과 설명, 스키마 등을 추출한 다음
00:33:09DynamoDB 같은 곳에 저장하는 방식으로 처리합니다. 그렇게 하면 그 이후에는
00:33:14실제로 도구가 호출될 때까지는 샌드박스를 다시 실행할 일이 전혀 없게 되죠. 방금 tarball 시스템을 언급하셨는데요.
00:33:21제가 가장 궁금한 부분이 바로 그 점입니다. 배포 관점에서 볼 때, tarball은 정확히 어떤 방식으로 작동하나요?
00:33:26예를 들어 현재 오픈 마켓플레이스 같은 게 따로 있는 건지, 배포는 어떻게 이루어지는지 궁금합니다.
00:33:31아, 실제 tarball 안에는 무엇이 들어있냐고요? 네, 맞습니다.
00:33:33우리가 실제로 구현하는 메커니즘이나 tarball에 포함되는 내용 측면에서 말씀드리면,
00:33:38현재는 ES build를 사용하고 있습니다. 전체적인 실제 배포 파이프라인의 작동 방식을 설명하자면,
00:33:47NTN workers deploy를 실행하면 API 엔드포인트를 호출하고, 컴퓨터가 사전 서명된 URL을 받아
00:33:52모든 소스 코드를 번들링합니다. 그런 다음 소스 코드가 포함된 tarball을 생성하고, 해당 샌드박스에서
00:33:58ES build로 빌드 프로세스를 실행한 뒤, 그 결과물을 다시 블롭 스토리지에 저장하여
00:34:04거기서 실제 작업을 실행할 수 있게 합니다. 아직 워커를 위한 본격적인 마켓플레이스는 없습니다. 먼저 노션 워크스페이스
00:34:11내에서 공유할 수 있는 프리미티브 기능을 개발 중이지만, 향후에는 일종의 워커 마켓플레이스를 만들 계획도 확실히 있습니다.
00:34:18하지만 그 전까지는 GitHub에 배포하는 것만으로도 아주 잘 작동합니다. 요즘 사람들도 그렇게 하고요.
00:34:23네, 그냥 리포지토리를 공개하면 누구나 클론해서 NTN workers deploy를 실행하고 직접 사용할 수 있습니다.
00:34:29네.
00:34:34저기 뒤쪽에 한 분 계신 것 같네요.
00:34:37네, 과금에 관해서 질문이 있습니다.
00:34:40과금이요?
00:34:40네, 방금 살펴보았는데요—물론 모든 비밀을 다 알려주실 필요는 없지만—
00:34:44간단히 구글 검색을 해보니 커스텀 에이전트는 일종의 크레딧 시스템으로 작동하는 것 같더라고요.
00:34:48리소스 소비량과 연동되어 있는 것 같은데요.
00:34:51맞습니다, 맞아요.
00:34:51그게 Vercel 플랫폼과는 전체적으로 어떻게 연동되는지 궁금합니다.
00:34:54좋습니다. 질문은 이것이 Vercel 플랫폼에서 어떻게 과금되느냐는 것이죠.
00:35:02워커의 장점 중 하나는 이런 코드를 작성할 수 있다는 점입니다. 많은 팀들이 이 방식을 도입해
00:35:08에이전트에 제공하던 MCP 서버 기반의 거대한 지시 사항 세트에서 벗어날 수 있게 되었습니다. 에이전트는 작업을 호출할 때마다
00:35:16기본적으로 똑같은 일을 반복하면서 엄청난 양의 사고 토큰을 소비하게 됩니다.
00:35:21따라서 에이전트가 수행하는 이러한 반복 작업을 가져와 워커로 배포할 수 있다면,
00:35:33워커의 실행 시간에 대해서는 여전히 비용이 청구되지만, AI 연산 토큰보다는 비용이 훨씬, 훨씬 적게 듭니다.
00:35:40그러므로 커스텀 에이전트를 실행할 때, 에이전트가 생각을 좀 하다가 툴을 실행하고, 다시 생각을 더 하게 되는데요.
00:35:48툴을 호출하기 전까지 커스텀 에이전트가 수행하는 토큰 소비에 대해서는 비용이 청구됩니다.
00:35:53따라서 툴이 실행될 때는 다른 요율로 크레딧이 차감되며, 노션 AI 크레딧이 훨씬, 훨씬, 훨씬 더 낮은 비율로 소모됩니다.
00:36:01그리고 에이전트가 최종적으로 응답을 반환할 때 일반 AI 크레딧이 다시 청구되는 식이죠.
00:36:05따라서 반복적인 작업이 있는 경우, 노션 커스텀 에이전트를 사용하면 비용을 크게 절감할 수 있는 방법입니다.
00:36:16그것도 맞는 말씀입니다. 꼭 노션 AI를 써야 하는 것도 아닙니다. 여기서는 노션 AI와 연동되는 툴 호출 기능을 시연해 보였지만요.
00:36:24하지만 워커는 노션으로의 서드파티 동기화 같은 작업도 수행할 수 있습니다. AI 기능은 전혀 필요하지 않죠.
00:36:31그러니까 단순히 AI 제품만은 아닙니다. 사람들은 동기화 용도로도 이걸 사용하고 있어요.
00:36:36저 같은 경우는 제 레터박스 피드를 노션에 동기화해 주는 워커를 사용하고 있습니다. 개인적으로 가장 중요한 워커죠.
00:36:46좋습니다. 다른 질문 있으신가요?
00:36:52한 분 더 있으신가요?
00:37:06아, 노션 커스텀 에이전트를 자신의 사용자나 인터페이스를 통해 노출하는 것 말인가요?
00:37:13현재 저희가 지원하는 기능은 아닙니다.
00:37:16죄송합니다. MJ님, 질문은 자신만의 워커와 커스텀 에이전트가 있을 때,
00:37:22이를 자신의 소비자들을 위한 자체 애플리케이션을 통해 노출할 수 있는 방법이 있느냐는 것이었죠.
00:37:28현재로서는 그렇게 할 수 있는 방법은 없습니다. 다만 커스텀 에이전트 API의 알파 버전이 있어서
00:37:35그걸 활용하면 충분히 가능할 수 있습니다. 워크스페이스에 커스텀 에이전트와 워커,
00:37:40혹은 커스텀 에이전트를 정의해 둔다면 API를 이용해 해당 에이전트를 호출하고 스트리밍 응답을 받을 수 있습니다. 그러니 가능은 합니다.
00:37:46아직 많은 사람들이 쓰고 있는 것 같지는 않고요. 여전히 알파 단계이며, 공개되긴 했지만 알파 단계인 기능입니다.
00:37:57다른 질문 있으신가요?
00:38:00알겠습니다. 좋습니다. 오늘 시간을 내어
00:38:04이 자리에 참석해 주셔서 모두 감사드립니다. 그리고 노션, 노션 워커,
00:38:08또는 Vercel Sandbox에 대해 궁금한 점이 있으시면 NJRI를 찾아주세요. 감사합니다.