将Apps Script工作负载卸载到Gemini托管代理的Linux沙箱中。
Google Apps Script 驱动 Gemini 管理的代理 Linux 沙箱:通过 ggsrun 将文件上传至 Drive,规避 50 MB 的限制,同时运行 Playwright、FFmpeg 和 esbuild。
来源: Taking Advantage of Gemini Managed Agents with Google Apps Script
为何要将 Apps Script 与 Linux 沙箱结合使用
Google Apps Script 是自动化处理 Google Workspace 的便捷工具,但它属于轻量级且功能受限的无服务器运行环境,无法访问操作系统级资源。因此许多复杂任务都无法实现。其对底层网络和协议的操控能力十分有限,没有用于渲染动态页面的无头浏览器,也无法运行用于媒体转码或信号处理的原生二进制文件,更没有现代编译器或构建工具链,同时运行时间和数据量也受到严格限制。
Gemini 托管代理为解决这些问题提供了方案。作为 Gemini v1beta Interactions and Environments API 的一部分,它们允许开发者配置远程 Linux 沙箱,这些沙箱能够独立执行代码、运行 Shell 命令并管理软件包。将其与 Google Workspace CLI 结合使用已引起了社区的极大兴趣。
此处描述的架构将二者连接起来。Apps Script负责管理一个持久化的Linux容器,该容器配备4个vCPU和16GB内存,内置Python 3.12、Node.js 22以及常用的包管理工具(apt、npm、pip),并通过ggsrun命令行工具将生成的文件直接上传至Google Drive。由于文件无需通过API响应返回,这种设计避免了数据量限制和令牌开销,从而实现了高吞吐量的云自动化处理。
以Base64格式返回二进制文件的问题
假设沙箱生成了较大的内容,比如高分辨率截图、音频波形或打包后的 JavaScript 文件,而这些内容需要存储到 Drive 中。显而易见的处理方式是通过 Gemini API 的响应以 Base64 字符串的形式返回原始字节,然后在 Apps Script 中对其进行解码。但这种做法会立即面临多个问题。
- Apps Script 中的
UrlFetchApp对响应数据的大小设置了 50 MB 的上限。 - Interactions API 的代码执行环境会限制标准输出缓冲区的大小,因此数兆字节的 Base64 字符串可能在传输过程中就被截断。
- 托管代理每分钟仅有 200,000 个令牌的配额。Base64 编码会使二进制数据大小增加约三分之一,在多轮对话中,之前的 Base64 字符串会不断累积在对话历史中,直到输入配额耗尽,随即出现
429 Quota Exceeded错误。
下图展示了该方式与直接流式传输的差异。
更好的方法是直接在沙箱内部运行基于 Go 语言的 ggsrun 工具,通过 ScriptApp.getOAuthToken() 在运行时注入 OAuth 访问令牌。随后沙箱可通过 Google Cloud 的内部网络以超过 2 MB/s 的速度将二进制文件上传至 Drive,从而一次性避开 Apps Script 的内存限制、响应大小限制以及令牌配额问题。
流式传输支持双向传输
直接的云对云传输也有助于文件下载。当大型外部数据集,如高分辨率图像、音频、视频、多千兆字节的CSV或JSON文件以及机器学习模型需要传入沙箱时,若以Base64或序列化文本的形式嵌入提示词中,几乎会立即耗尽输入令牌配额,从而导致429错误。而使用ggsrun时,沙箱会直接从Drive中获取文件,提示词只需简短的指令,例如“从Drive下载目标数据集并进行分析”。这样一来,输入令牌的使用量几乎降为零,从而避免了速率限制问题。
共享同一个持久化沙箱可进一步降低成本。只需一个配置步骤即可生成一个environmentId,Apps Script项目、本地Node.js工作站、Python脚本以及CI/CD流水线都能使用该标识。存储在/workspace/下的主数据集、语料库、库或预训练模型可供所有客户端使用,因此无需每次都重新上传数据或重复设置流程。这降低了延迟、网络带宽消耗以及累积的API开销,同时各任务还能复用共享的工作文件和预安装的包。
下图展示了完整架构:Apps Script和本地Node.js工作站通过共享的environmentId协调同一个持久化沙箱,利用双向流传输和共享数据集来实现快速的内容生成。
该架构可分为四个部分:
- 有部分客户端使用单个容器运行。基于云的 Apps Script 采用同步触发机制和动态 OAuth 令牌,而本地 Node.js 工作站则使用实时 SSE 流传输及
gcloud认证方式。 - 沙箱环境会保存共享数据集以及 Playwright、FFmpeg、esbuild 等构建工具,这样无需重新上传数据即可生成内容。
ggsrun download能将 Drive 中的大容量数据集流式传输到沙箱中,从而避免即时数据嵌入的问题,同时保障 20 万 TPM 配额不受影响。ggsrun upload可以以 2 MB/s 或更高的速度将处理完成的二进制文件上传到 Drive,从而避开 50 MB 的数据限制及标准输出被截断的问题。
完整的源代码(包括 Apps Script 类、Node.js 流客户端、测试套件以及原始执行日志)都保存在项目的公共 GitHub 仓库中。
设置步骤
首先在 Google AI Studio 中生成一个 API 密钥,该密钥用于验证对 Gemini v1beta Interactions 和 Environments API 的请求。接着通过两种方式之一创建 Apps Script 项目:要么在 script.google.com 上通过“新建项目”创建独立项目,要么打开表格、文档或表单,选择“扩展程序”后再选择 Apps Script 来创建基于容器的项目。
将仓库中的两个文件复制到编辑器中:
ManagedAgentSandboxClient.js是核心客户端类,负责管理沙箱的生命周期和动态环境变量,将会话数据保存在PropertiesService中,并在遇到 429 错误时采取合理的重试策略。tests.js是主要的测试套件,涵盖了沙箱配置、工具检测、媒体处理、网络爬取以及性能基准测试等内容。
接下来,打开“项目设置”,然后选择“脚本属性”,添加一个名为 GEMINI_API_KEY 的属性并填入对应的密钥。最后,确保清单文件(appsscript.json)包含了正确的 OAuth 权限范围。进行 UrlFetchApp 调用时需要 https://www.googleapis.com/auth/script.external_request 这一权限范围,而创建目标文件夹及上传文件则需 https://www.googleapis.com/auth/drive。如果代码直接使用现有文件夹而未调用 DriveApp.createFolder(),则使用 https://www.googleapis.com/auth/drive.file 即可。每次测试的执行日志都保存在仓库的 gas-src/execution-logs.md 文件中。
配置共享沙箱
运行 provisionSharedSandbox() 会启动一个新的远程 Linux 容器,安装 CLI 工具及依赖项,配置目标驱动器路径,并将生成的 environmentId 保存在 PropertiesService 中。
该信息图展示了四个步骤。首先,Drive会创建一个名为ManagedAgent_Artifacts_YYYYMMDD的目标文件夹。接着,该容器拥有4个vCPU和16GB内存,通过初始化提示加载ggsrun、ffmpeg、sox、jq、typescript、esbuild以及带有Chromium的Playwright。随后,沙箱会验证已安装的二进制文件并显示READY状态。最后,唯一的environmentId会被存储在SHARED_SANDBOX_SESSION下,以便后续测试和其他客户端重新使用。之后调用testListSandboxes()函数可查询环境API,确认沙箱处于活跃状态并获取其元数据。
测试1:Apps Script覆盖的自定义标头
第一个测试显示了UrlFetchApp无法做到的事情。Apps Script会自动将自定义的User-Agent请求头替换为Google的代理身份字符串,而沙箱环境则会保留您设置的任何请求头,因为它是通过原始POSIX套接字和原生curl来发送请求的。
该图展示了一个携带 User-Agent: sample user agent 的请求发送到 httpbin.org/anything 的过程。在 Apps Script 端,平台代理策略会替换该请求头;而在沙箱环境中,curl 会保留原始字符串不变。随后通过内嵌的 Python 脚本对比反射出的 JSON 数据,并输出验证结果矩阵。具体而言,Apps Script 会发送带有自定义请求头的 GET 请求,沙箱则发送相同的 curl 请求,通过对比可以确认 Apps Script 已将请求头替换为 Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; ...),而沙箱则完整保留了 sample user agent。
测试 2:从沙箱内部对 Drive 进行身份验证
该测试通过将ScriptApp.getOAuthToken()生成的新鲜OAuth令牌注入到对应执行环节中,来检测Drive身份验证以及通过ggsrun进行的直接访问功能。
第一步中,Apps Script会提取该令牌并将其作为GGSRUN_AT环境变量注入,从而避免令牌一小时到期带来的风险。第二步中,沙箱会在/workspace/test2/目录下生成名为00_ggsrun_verification.txt的验证文件,然后通过ggsrun upload以非阻塞覆盖模式(--nc --cm OverwriteIfNewer -j)上传该文件。第三步中,ggsrun searchfiles会查询目标文件夹并返回结构化的元数据,以确认所有九个文件均存在。从创建、上传到验证的整个流程耗时12.1秒,且没有敏感令牌在不同会话之间保留。
测试3:使用Playwright进行无头爬取
接下来,沙箱环境中的自动化无头Chromium会话将爬取由JavaScript渲染的动态内容,并在多种视口尺寸下截取屏幕截图。
一个 Node.js Playwright 脚本会打开一个由 JavaScript 渲染的名言网站(quotes.toscrape.com/js/),以桌面尺寸(1280x800)和 iPhone 模拟尺寸(375x812)截取整页截图,接着点击分页控件,获取第二页的桌面截图,并将其中的名言提取到 02_Page2_Quotes.json 文件中。桌面截图的大小约为 92.5 KB,移动端截图约为 51.6 KB,第二页截图约为 171.9 KB,再加上 4.1 KB 的 JSON 文件,四份文件的总体大小约为 320 KB。通过一条 ggsrun upload 命令,无需进行 Base64 转换即可将三个 PNG 文件和 JSON 数据集上传到 Drive,整个处理过程耗时 20.4 秒。这是客户端渲染方式,App Script 无法直接解析。
测试 4:使用 FFmpeg 和 SoX 进行音频合成
第四项测试在沙箱环境中运行原生数字信号处理功能,使用FFmpeg和SoX生成多音和弦。
三个频率分别为440 Hz(A4)、554.37 Hz(C#5)和659.25 Hz(E5)的正弦波发生器通过ffmpeg的amix滤波器混合,最终生成一个时长为3秒、大小为73.4 KB的大调和弦MP3文件。ffprobe随后将流元数据提取到03_Audio_Analysis.json文件中,该文件大小为1.8 KB。ggsrun upload命令将03_Chord_Major.mp3文件及对应的JSON文件直接上传至Drive。从合成音频、提取元数据到上传整个过程耗时9.1秒。
测试5:TypeScript解析与esbuild打包
此项测试展示了现代JavaScript及TypeScript构建工具在沙箱环境中的运行情况。
有两个工具链在处理位于 /workspace/test5/ 的 TypeScript 模块 matrix.ts,该模块定义了泛型类和接口。在第一个分支中,一个 Node.js 脚本使用官方的 TypeScript 编译器 API 来解析抽象语法树,并将接口和方法的架构导出到 04_TypeScript_AST.json 文件中,文件大小仅为 152 字节。在第二个分支中,esbuild 会将该模块打包并压缩成一个独立的 IIFE 文件 04_Matrix_Bundle.iife.js,文件大小为 1.2 KB,构建时间仅 13 毫秒。ggsrun upload 随后将这两个输出结果上传到 Drive,整个运行过程耗时 10.0 秒。
测试 6:通过 Apps Script 以 Base64 格式直接上传
该基准测试通过两种不同的方法将一个 10,000 字节的二进制文件从沙箱环境上传到 Drive。
在方法A中,沙箱从/dev/urandom生成一个10 KB的二进制文件,然后通过ggsrun在单次交互过程中将其上传到Drive(freshInteraction: true)。在方法B中,沙箱将相同的字节编码为Base64格式,并在API响应文本中返回,随后Apps Script会对该字符串进行解码并将文件保存到Drive。方法A的处理时间为16.20秒,传输速率为0.60 KB/s,且没有消耗Apps Script的CPU资源。而方法B则需要32.13秒,传输速率为0.30 KB/s,同时还消耗了1.23秒的Apps Script CPU时间,因此方法A的速度是其1.98倍。此外,方法A还能避免约33%的Base64编码膨胀问题,以及多轮交互会遇到的令牌耗尽问题。记录的报告如下所示。
================================================================================
PERFORMANCE BENCHMARK REPORT: 10,000 BYTES FILE TRANSFER TO GOOGLE DRIVE
================================================================================
| Metric | Approach A: Direct ggsrun Upload | Approach B: Base64 via Gemini API -> GAS |
| :--------------------------- | :------------------------------- | :--------------------------------------- |
| Transfer Method | Direct Sandbox-to-Drive (Go CLI) | Base64 Stream -> GAS -> Drive |
| Drive File Name | benchmark_10kb_ggsrun.bin | benchmark_10kb_gas.bin |
| Verified File Size | 10,000 bytes (9.77 KB) | 10,000 bytes (9.77 KB) |
| API Turns Required | 1 Turn (Direct Offload) | 1 Turn (Base64 Retrieval) |
| Local GAS Processing Time | 0.00 s (Zero CPU overhead) | 1.23 s (Base64 Decode & Blob Creation) |
| Total End-to-End Duration | 16.20 s | 32.13 s |
| Effective Throughput | 0.60 KB/s | 0.30 KB/s |
| Performance Multiplier | 1.98x FASTER | Baseline (Higher Latency & Token Usage) |
================================================================================
通过 ggsrun 进行流式传输的速度快1.98倍,无需承担 Apps Script 在解码过程中产生的CPU和内存消耗,也不会占用对话令牌配额。对于数兆字节的传输数据,正是这种直接传输方式避免了 429 配额超出 错误的发生。
从本地工作站控制同一个沙箱
为证明同一个持久化的 Linux 沙箱既可以通过 Apps Script 控制,也可以通过笔记本电脑控制,我们基于 @google/genai SDK 开发了一个支持服务器推送事件流式的 Node.js 客户端。Apps Script 以同步且阻塞的方式运行,在HTTP请求结束时收集代理事件,而本地运行程序则能为开发者提供 Apps Script 所不具备的多种功能。
- 可实时查看运行过程。推理步骤(
thought)、Shell命令(code_execution_call)、沙箱的stdout和stderr(code_execution_result)以及模型输出文本(model_output)会以ANSI颜色形式实时显示在终端上。 - 适合开发的混合工作流。可在本地通过实时反馈对提示语和工具链进行原型设计、调试和优化,之后将其集成到可自动运行的Apps Script触发器中,实现无人值守执行。
- 便捷的沙箱共享功能。将Apps Script配置过程中生成的标识符放入本地
.env文件中的ENVIRONMENT_ID字段,即可将客户端连接到现有的容器,该容器已包含所有已安装的包、编译后的二进制文件以及工作区文件,无需重新安装。
gcloud auth print-access-token 获取新的 Google 访问令牌,并将其注入 GGSRUN_AT 中,这样即可像在 Apps Script 中一样直接将文件上传到 Drive,而无需手动复制凭证。运行本地测试需要四个步骤。
- 克隆仓库,然后在
local-node.js-src目录中执行npm install。 - 将
.env.example复制为.env,并填写三个值:将 API 密钥设为GEMINI_API_KEY,将长期有效的沙箱标识符设为ENVIRONMENT_ID,将目标写入的 Drive 文件夹设为TARGET_FOLDER_ID。 - 运行
npm test,或使用npm run test:1至test:6运行特定测试,实时观察代理程序的工作情况。 - 完成后,运行
npm run test:teardown以删除远程沙箱并释放云资源。
包含实时流式输出的完整原始记录保存在仓库的 local-node.js-src/execution-logs.md 中,这些记录显示的功能结果与 Apps Script 的运行结果一致。
托管代理的 API 模式
以下模式总结了使用 Gemini v1beta Interactions 和 Environments API 的常见方式。每种方式都会向同一个基础端点发送 POST 请求。
POST https://generativelanguage.googleapis.com/v1beta/interactions?key=${API_KEY}
Content-Type: application/json
场景1是在多个客户端之间共享同一个持久化沙箱。只需创建一次远程环境,将environment.type设置为"remote",记下返回的environment_id,之后在Apps Script、Node.js、Python或CI/CD任务发出的请求中以普通字符串的形式传递该ID即可。
{
"agent": "antigravity-preview-05-2026",
"input": "Run task in shared container...",
"environment": "environments/env-12345"
}
场景2为每次运行提供独立的沙箱环境。当任务需要完全全新的Linux环境时,应在每次调用中将environment.type设置为"remote"。
{
"agent": "antigravity-preview-05-2026",
"input": "Execute client-specific isolated task...",
"environment": {
"type": "remote"
}
}
场景3能够在多轮对话中保持上下文连贯性。每当智能体需要记住之前的推理过程、变量或命令输出时,都应包含previous_interaction_id。
{
"agent": "antigravity-preview-05-2026",
"input": "Based on the previous output, proceed to step 2...",
"environment": "environments/env-12345",
"previous_interaction_id": "interaction-prev-67890"
}
场景4通过freshInteraction选项重用带有新上下文的沙箱。只需提供现有的environment_id,无需填写previous_interaction_id。容器中的所有文件及已安装的工具都会保留,而对话历史会被重置为零个标记,从而避免超出TPM速率限制。
{
"agent": "antigravity-preview-05-2026",
"input": "Execute a completely new task in the existing sandbox...",
"environment": "environments/env-12345"
}
一览对比
总结矩阵将各种方法并排展示。
其优势所在
将 Apps Script 与 Gemini Managed Agents 结合使用,能够突破传统无服务器运行时的限制。通过永久性的远程沙箱,以及云存储与沙箱之间的双向流式数据传输,使得仅靠 Apps Script 无法处理的工作负载成为可能,同时还能规避 API 数据量限制及对话令牌配额问题。
- 突破了 Apps Script 的限制。现在可以通过 Apps Script 启动那些需要原生 Linux 环境的任务,例如使用 Playwright 进行无头浏览器抓取、用 FFmpeg 进行音频合成,以及使用
esbuild进行 TypeScript 编译。