返回 文章 apply CMS 文章

Vercel 发布 Run SDK:为智能体提供安全评估执行环境

Run SDK 让智能体生成的代码在沙箱中运行,仅通过宿主函数与真实应用交互,并支持中断以进行身份验证或人工审批。

Run SDKVercel智能体安全执行
成长分 / 100 75 综合收获、行动、留存与影响

Vercel 发布 Run SDK:为智能体提供安全评估执行环境
为什么值得读了解如何安全执行智能体生成的代码,避免直接访问应用机密和内部服务。

掌握 Run SDK 的核心机制:宿主函数、中断恢复、资源限制。

关键洞察
  1. Run SDK 在工作线程内的 QuickJS 上下文中执行代码,无直接 Node.js 或网络访问路径。
  2. 应用通过 hostFunctions 暴露选定操作,调用通过序列化跨越沙箱边界。
  3. 宿主函数可以中断执行以进行身份验证或人工审批,恢复时重放程序但复用已完成调用的结果。
转成行动

深入阅读

正文与原文对照

原文保真覆盖:全文原文字符:7068

智能体越来越多地编写 TypeScript 程序来协调各种工具并处理其结果。一旦这些程序接触到真实应用,有些步骤需要身份验证,有些则需要人工批准。

用 eval 执行这类代码,等于赋予它与周围应用相同的访问权限,包括应用的密钥和内部服务,而且在这些边界上无法持久地暂停。

今天,我们发布 Run SDK,这个包用于执行不受信任的 JavaScript 和 TypeScript,而不让它直接访问你的应用或系统。应用只暴露少量精简的宿主函数,并且可以因身份验证或人工介入(human-in-the-loop)审批而中断执行。程序在得到决定后继续运行,已完成的工作不会重复执行。

pnpm add run

一个通向宿主的精简接口

Run SDK 在工作线程内的全新 QuickJS 上下文中运行 JavaScript 或剥离类型的 TypeScript,且无法直接访问 Node.js 或网络。

应用通过 hostFunctions 暴露选定的操作。这些都是普通函数,在沙箱内会成为可调用的全局函数:

import { run } from 'run';

const result = await run({
  source: `
    const orders = await store.listOrders("customer_123");
    const total = orders.reduce((sum, order) => sum + order.amount, 0);
    return { count: orders.length, total };
  `,
  hostFunctions: {
    store: {
      listOrders: async (customerId: string) => {
        return database.orders.findMany({ customerId });
      },
    },
  },
});

if (result.status === 'completed') {
  console.log(result.value);
}

在这里,生成的程序只知道 store.listOrders()。数据库客户端及其凭据仍留在应用中。

调用通过序列化跨越沙箱边界。宿主函数可以返回 promise,因此现有的服务客户端可以放在这个接口之后,而无需传入沙箱。

你可以在演练场中试用。你在那里运行的代码只能访问页面上的宿主函数。

代码模式的实际应用

Run SDK 是驱动 AI SDK 中代码模式工具执行的内部模块。给智能体一段程序,会改变工作的基本单元:一次模型响应就能描述这些调用以及串联它们的逻辑:

const result = await run({
  source: `
    const accountId = "account_123";
    const [account, invoices] = await Promise.all([
      crm.getAccount(accountId),
      billing.listInvoices(accountId),
    ]);
    const overdue = invoices.filter(invoice => invoice.status === "overdue");
    return { account: account.name, overdue };
  `,
  hostFunctions: {
    crm: { getAccount },
    billing: { listInvoices },
  },
});

两个请求并发执行,发票过滤则留在程序本地完成。只有有用的结果才返回给应用。

这非常适合需要跨多个内部服务工作的智能体。研究智能体可以先合并搜索结果再作答。客服智能体可以查看某个账户,而不必把整个账单响应放回自己的上下文。

同一个包还可以驱动代码解释器,或是接受客户自定义转换的产品功能。在这两种情况下,可用的数据和操作都由应用来决定。

宿主函数最好与产品中的具体动作一一对应,比如退款。暴露 orders.refund(id) 能让应用有一个清晰的位置来检查用户和订单。而通用的请求函数会让这种权限更难理清。

原生支持人工介入与身份验证

读取发票和发放退款是两回事。当生成的代码走到敏感操作时,宿主函数可以中断执行:

import { getHostFunctionContext } from 'run';

const hostFunctions = {
  documents: {
    publish: async (draftId: string) => {
      const context = getHostFunctionContext();
      if (context.resume === undefined) {
        context.interrupt({
          kind: 'approval',
          message: `Publish ${draftId}?`,
        });
      }
      if (context.resume.resolution !== true) {
        return { published: false };
      }
      return publishDraft(draftId);
    },
  },
};

运行被中断时,结果中会包含一个签名令牌,应用可以将它与审批请求一起保存,并在决定到达时用它恢复运行。

恢复时程序会重放,但已完成的宿主函数调用会使用记录下来的结果。中断之前已完成的宿主函数工作不会再次运行。被中断的函数会收到审批结果,并从那里继续。

当工作流必须等待身份验证时,这一机制同样适用。等待期由应用负责,工作线程无需一直保持存活。

在限制范围内运行

沙箱仍然需要应对无限循环或产生超大结果的代码。createRunner() 用于设置共享限制:

import { createRunner } from 'run';

const runner = createRunner({
  limits: {
    timeoutMs: 10_000,
    memoryLimitBytes: 32 * 1024 * 1024,
  },
});

限制也可以按每次运行单独设置。默认值涵盖 QuickJS 堆以及跨越宿主边界的值;应用可以根据自身工作负载收紧这些限制。

每次调用都会获得全新的 QuickJS 上下文。动态求值被禁用,内置原型也经过加固。这一边界针对的是生成的程序,而宿主函数仍属于受信任的应用代码,必须执行常规的授权检查。

Run SDK 面向的是应用内部的 JavaScript 计算。需要操作系统、安装软件包或进程级隔离的工作负载,应改用 Vercel Sandbox。

抽取执行层

这个运行时的第一个版本位于 just-bash 内部,名为 js-exec,由 QuickJS 支撑。它让智能体可以针对该 shell 的虚拟文件系统和命令集编写 TypeScript。

我们把这一层抽取出来做成 Run SDK,并用由应用定义的宿主函数取代了类 Node 环境。我们在 eve 中测试了底层机制,用它针对真实工具运行智能体生成的 TypeScript。现在它驱动着 AI SDK 中的代码模式:现有的 AI SDK 工具被映射为宿主函数,而 Run SDK 负责它们的沙箱化执行。

快速上手

Run SDK 支持 Node.js 22.13+ 和 Bun。你需要在本地开发机器上安装 pnpm 或其他包管理器。

pnpm add run

请查看文档和 API 参考。