极米投影仪
149.20M · 2026-03-22

在开发 AgentRun 的过程中,我们发现了一个让人头疼的问题:现有的沙箱方案太零散了。浏览器、代码执行、Shell 环境各自为政,导致开发效率大打折扣。于是我们决定自己动手,打造一个真正一体化的解决方案——All-In-One Sandbox(AIO)。
AIO 是 AgentRun 提供的云上浏览器自动化沙箱环境,它把浏览器、终端和代码执行能力全部集成在一个容器里。通过简单的 SDK 调用,你就能让 LLM 驱动复杂的 Web 自动化任务,再也不用在多个沙箱之间疲于奔命。
说实话,传统的沙箱方案让我们吃尽了苦头。大多数沙箱都是单一用途的(要么只能跑浏览器,要么只能执行代码),这带来了几个实实在在的痛点:
架构一览
统一文件系统
我们的解决方案很简单粗暴:把所有组件(浏览器、Shell、代码执行、文件系统)都塞进同一个沙箱实例里。
性能对比一目了然:
| 对比项 | 传统多沙箱方案 | All-In-One 沙箱 |
|---|---|---|
| 启动时间 | 2 个沙箱启动 = 4-15秒(串/并行创建) | 1 个沙箱启动 = 5秒 |
| 文件传递 | 通过 OSS,耗时 2-3秒 | 直接访问,<100ms |
| 内存占用 | 2×独立运行 = 2c2g+2c2g | 1×共享运行 = 2c2g |
底层技术栈很扎实:
五大核心能力,开箱即用:
云上沙箱,真的零配置
基于函数计算架构,我们做到了:
多种访问方式,灵活到飞起
不管你是开发者还是运维,都能找到适合自己的使用方式:
| 接口 | 用途 | 访问方式 |
|---|---|---|
| 代码执行 API | 执行 Node.js/Python 脚本 | SDK 方法 |
| VNC | 可视化浏览器交互、人工介入 | Web 集成 |
| Terminal WebSocket | 实时 Shell 交互 | WebSocket |
| 文件 API | 读写持久化文件 | SDK 方法 |
这些场景特别适合用 AIO:
每个沙箱实例本质上就是一个基于函数计算环境的会话容器,里面已经预装好了你需要的一切:
/home/user/ 目录)pip install agentrun-sdk['server','playwright']
from agentrun.sandbox import Sandbox, TemplateType
import asyncio
async def quick_start():
"""验证沙箱基本功能"""
sandbox = Sandbox.create(
template_type=TemplateType.AIO,
template_name="quick-test",
sandbox_idle_timeout_seconds=600
)
print(f"沙箱已创建: {sandbox.sandbox_id}")
# 核心:连接已运行的浏览器,提取页面信息
code = """
const puppeteer = require('puppeteer-core');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
const page = (await browser.pages())[0];
await page.goto('https://example.com');
console.log(await page.title());
await browser.disconnect();
"""
await sandbox.context.execute_async(code=code, language="javascript")
sandbox.destroy()
asyncio.run(quick_start())
关键技巧: 用 disconnect() 保持浏览器运行,通过文件系统传递状态。
第一步:打开登录页
const puppeteer = require('puppeteer-core');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
const page = (await browser.pages())[0];
await page.goto('https://example.com/login');
console.log('请在 VNC 中完成登录');
await browser.disconnect();
第二步:保存 Cookie
const fs = require('fs');
const puppeteer = require('puppeteer-core');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
const page = (await browser.pages())[0];
const cookies = await page.cookies();
fs.writeFileSync('/home/user/data/cookies.json', JSON.stringify(cookies));
console.log('Cookie 已保存');
await browser.disconnect();
第三步:用 Cookie 爬数据
const fs = require('fs');
const puppeteer = require('puppeteer-core');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
const page = (await browser.pages())[0];
const cookies = JSON.parse(fs.readFileSync('/home/user/data/cookies.json'));
await page.setCookie(...cookies);
await page.goto('https://example.com/data');
// 执行数据采集
await browser.disconnect();
为什么要用多步骤模式?
完整代码参考: Github 示例仓库
通过 sandbox.context.execute_async() 方法执行代码:
result = await sandbox.context.execute_async(
code="console.log('Hello')",
language="javascript", # 或 "python"
timeout=300 # 超时时间(秒)
)
返回格式很清晰:
{
"contextId": "ctx_xxx", # 上下文 ID
"results": [
{"type": "stdout", "text": "Hellon"},
{"type": "result", "value": null}
]
}
# 写入文件
sandbox.file.write(
path="/home/user/data/result.json",
content='{"key": "value"}',
encoding="utf-8"
)
# 读取文件
content = sandbox.file.read("/home/user/data/result.json")
# 上传本地文件
sandbox.file_system.upload(
local_file_path="./local_file.txt",
target_file_path="/home/user/data/file.txt"
)
# 下载文件
sandbox.file_system.download(
path="/home/user/data/result.json",
save_path="./result.json"
)
什么时候用? 需要登录、人工介入、或者数据量很大的时候。
标准流程:
步骤 1:打开登录页 → 通过 VNC 人工登录
步骤 2:保存 Cookie 到文件系统
步骤 3:使用 Cookie 执行数据采集
实操要点:
sandbox_idle_timeout_seconds >= 1800 避免超时断连disconnect() 保持浏览器运行,close() 会关掉浏览器/home/user/data/ 目录传递 Cookie 和进度sandbox.sandbox_id 和 base URL 拼起来就行sandbox.file.read() 和 sandbox.file.write()适用场景: 让 AI 自动生成和执行浏览器自动化代码,非技术用户也能用。
整体架构:
flowchart TD
A["用户需求
(自然语言)"] --> B["LLM生成
Puppeteer代码"]
B --> C["AIO Sandbox
执行代码"]
C --> D["返回结果
(文件/日志)"]
style A fill:#fff,stroke:#000,color:#000
style B fill:#fff,stroke:#000,color:#000
style C fill:#fff,stroke:#000,color:#000
style D fill:#fff,stroke:#000,color:#000
三条铁律(必须遵守):
puppeteer.launch() → 必须:puppeteer.connect()browser.close() → 必须:browser.disconnect()/home/user/data/xxx.json为什么这么严格?
什么时候需要? 要保持登录状态,跨会话复用的时候。
完整流程:
首次登录:
1. 人工登录 → 2. 保存 Cookie → 3. 持久化存储
后续使用:
1. 上传Cookie -> 2. 读取 Cookie → 3. 恢复会话 → 4. 执行任务
Cookie 保存示例:
const puppeteer = require('puppeteer-core');
const fs = require('fs');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
const page = (await browser.pages())[0];
const cookies = await page.cookies();
// 保存到文件系统
fs.writeFileSync('/home/user/data/cookies.json', JSON.stringify(cookies, null, 2));
console.log('Cookie 已保存');
await browser.disconnect();
Cookie 恢复示例:
const puppeteer = require('puppeteer-core');
const fs = require('fs');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
const page = (await browser.pages())[0];
// 读取 Cookie
const cookies = JSON.parse(fs.readFileSync('/home/user/data/cookies.json'));
// 恢复会话
await page.setCookie(...cookies);
await page.goto('https://example.com/protected');
console.log('登录状态已恢复');
await browser.disconnect();
关键提醒:
/home/user/data/ 目录,这样才有权限page.cookies() 获取所有 Cookie,一个都不能少page.setCookie(...cookies) 恢复,顺序很重要适用场景: 需要并发处理大量任务的时候。
两种策略:
策略 1:单沙箱顺序执行(简单任务,有依赖关系)
策略 2:多沙箱并发执行(复杂任务,无依赖关系)
选择建议:
| 策略 | 适用场景 | 优势 |
|---|---|---|
| 单沙箱顺序执行 | 简单任务,依赖前序结果 | 资源占用低,状态连续 |
| 多沙箱并发执行 | 复杂任务,无依赖关系 | 执行速度快,并行处理 |
并发控制示例:
# 使用 asyncio.gather() 实现并发
tasks = [
process_item(item)
for item in items
]
results = await asyncio.gather(*tasks, return_exceptions=True)

目标很明确: 抓取豆瓣电影 Top250 的电影信息(标题、评分、导演、年份等)
实际挑战:
我们的解法: AIO Sandbox 的 Cookie 持久化 + 多步骤任务模式

// 1. 打开登录页
const puppeteer = require('puppeteer-core');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
const page = (await browser.pages())[0];
await page.goto('https://accounts.douban.com/passport/login');
console.log('请在 VNC 中完成登录');
console.log('登录完成后,程序将自动保存 Cookie');
await browser.disconnect();
操作说明:
const puppeteer = require('puppeteer-core');
const fs = require('fs');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
const page = (await browser.pages())[0];
const cookies = await page.cookies();
fs.writeFileSync('/home/user/data/douban_cookies.json', JSON.stringify(cookies, null, 2));
console.log(`Cookie 已保存,共 ${cookies.length} 条`);
await browser.disconnect();

const puppeteer = require('puppeteer-core');
const fs = require('fs');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
const page = (await browser.pages())[0];
// 恢复 Cookie
const cookies = JSON.parse(fs.readFileSync('/home/user/data/douban_cookies.json'));
await page.setCookie(...cookies);
// 访问 Top250
await page.goto('https://movie.douban.com/top250', { waitUntil: 'networkidle2' });
// 提取数据
const movies = await page.evaluate(() => {
return Array.from(document.querySelectorAll('.item')).map(item => ({
title: item.querySelector('.title')?.textContent,
rating: item.querySelector('.rating_num')?.textContent,
quote: item.querySelector('.inq')?.textContent
}));
});
// 保存结果
fs.writeFileSync('/home/user/data/movies.json', JSON.stringify(movies, null, 2));
console.log(`爬取完成,共 ${movies.length} 部电影`);
await browser.disconnect();
具体实现可以参考项目的 src/ai_code_generator.py 和 src/sandbox_executor.py。
核心逻辑:
from agentrun.sandbox import Sandbox, TemplateType
import asyncio
async def scrape_douban():
# 1. 创建沙箱
sandbox = Sandbox.create(
template_type=TemplateType.AIO,
template_name="douban-scraper",
sandbox_idle_timeout_seconds=1800
)
# 2. 执行登录步骤(代码略,参考上面)
# 3. 保存 Cookie(代码略,参考上面)
# 4. 爬取数据(代码略,参考上面)
# 5. 读取结果
result = sandbox.file.read('/home/user/data/movies.json')
print(result)
asyncio.run(scrape_douban())
const movies = [];
for (let page_num = 0; page_num < 250; page_num += 25) {
await page.goto(`${page_num}`);
const items = await page.evaluate(() => {
// 提取逻辑
});
movies.push(...items);
// 延迟防止反爬
await page.waitForTimeout(2000);
}
fs.writeFileSync('/home/user/data/all_movies.json', JSON.stringify(movies));
async function scrapeWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
return true;
} catch (error) {
console.log(`重试 ${i + 1}/${maxRetries}: ${error.message}`);
await page.waitForTimeout(5000);
}
}
return false;
}
系统提示词(System Prompt)是 LLM Agent 的大脑,直接决定了 AI 如何理解和执行你的需求。对于 AIO Sandbox 集成来说,提示词必须明确告诉 AI 如何生成符合沙箱规范的代码。

在设计提示词之前,你得先理解 All-In-One Sandbox 的核心理念,这直接影响提示词的结构。
我们承认有些事情 AI 就是搞不定,比如验证码、滑块验证、短信验证。所以 AIO 采用"人机协作"的设计理念:
可观测性优先
人机协作而非完全自动
状态持久化
disconnect() 保持浏览器运行,状态不会丢提示词必须明确告诉 AI 这些关键约束:
为什么?看看对比就知道了:
传统方式 (错误):
const browser = await puppeteer.launch(); // 启动新浏览器 (1-3 秒)
// 执行任务
await browser.close(); // 状态全部丢失
All-In-One 方式 (正确):
const browser = await puppeteer.connect({ // 连接已运行的浏览器 (<100ms)
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
// 执行任务
await browser.disconnect(); // 保持浏览器运行
技术原因很简单:
launch() 会启动第二个浏览器,纯属浪费资源connect() 几乎瞬间连接,特别适合多步骤任务关键区别在这:
browser.close():
- 关闭所有页面
- 终止浏览器进程
- 清理所有状态
- 无法支持多步骤任务
- VNC 显示中断
browser.disconnect():
- 只断开 Puppeteer 连接
- 浏览器继续运行
- 所有状态保留
- 支持多步骤任务
- VNC 显示连续
登录状态的本质就是 Cookie:
用户登录 → 服务器设置 Cookie → 后续请求携带 Cookie → 服务器识别用户
没有持久化会怎样?
标准做法:
保存 Cookie:
const cookies = await page.cookies();
fs.writeFileSync(
'/home/user/data/cookies.json',
JSON.stringify(cookies, null, 2)
);
加载 Cookie:
const cookies = JSON.parse(
fs.readFileSync('/home/user/data/cookies.json')
);
// 先访问对应域名
await page.goto('https://example.com');
// 再设置 Cookie
await page.setCookie(...cookies);
你是 AgentRun AIO Sandbox 的代码生成助手。你的任务是将用户需求转换为可在沙箱中执行的 Puppeteer 代码。
【环境信息】
- 浏览器:Chromium (已预启动)
- 连接端点:ws://localhost:5000/ws/automation
- 文件系统:/home/user/data/ (持久化目录)
- 超时限制:单次执行 300 秒
【代码规范】
1. 连接浏览器:
const puppeteer = require('puppeteer-core');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:5000/ws/automation'
});
2. 结束会话:
await browser.disconnect();
3. 文件读写:
const fs = require('fs');
// 读取
const data = fs.readFileSync('/home/user/data/file.json');
// 写入
fs.writeFileSync('/home/user/data/file.json', JSON.stringify(data));
4. 错误处理:
try {
// 代码逻辑
} catch (error) {
console.error(`错误: ${error.message}`);
throw error;
}
【输出要求】
- 生成完整的 JavaScript 代码
- 包含必要的错误处理
- 关键步骤用 console.log() 记录
- 重要结果保存到文件系统
sequenceDiagram
participant User as 用户
participant LLM as LLM Agent
participant Sandbox as AIO Sandbox
User->>LLM: 自然语言需求
LLM->>Sandbox: 生成并执行代码
Sandbox-->>LLM: 执行结果/状态
LLM->>User: 返回结果或请求人工介入
User->>Sandbox: 通过VNC人工操作
Sandbox-->>LLM: 更新状态
LLM->>Sandbox: 继续执行后续步骤
Sandbox-->>User: 最终结果
AI 对话模式让非技术用户也能用浏览器自动化,系统提示词在里面起着关键的"翻译"作用。
好的提示词要能指导 AI 自动判断何时拆分任务:
简单任务(不拆分)
用户: 访问 example.com,获取页面标题
AI 直接生成单个代码块,执行后返回结果
需要登录(自动拆分)
用户: 登录豆瓣,然后获取我的收藏
AI 自动拆分为 3 个步骤:
步骤 1: 打开登录页 → [生成代码1] → "请在 VNC 中完成登录"
步骤 2: 保存 Cookie → [生成代码2] → "已保存 15 个 Cookie"
步骤 3: 获取收藏 → [生成代码3] → "找到 25 部收藏电影"
遇到需要人工介入的步骤,AI 要明确告诉用户该怎么做:
AI: 我已经打开了登录页面,请在 VNC 窗口中:
1. 输入用户名和密码
2. 输入验证码
3. 点击登录按钮
完成后告诉我"登录完成",我将继续后续步骤。
永远用 try-catch 包裹核心操作:
try {
// 核心操作
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
} catch (error) {
// 明确的错误信息
console.error(`操作失败: ${error.message}`);
// 可选:重试逻辑
if (error.name === 'TimeoutError' && retryCount < maxRetries) {
console.log(`超时,重试第 ${retryCount + 1} 次`);
await new Promise(resolve => setTimeout(resolve, 5000));
return executeWithRetry(url, retryCount + 1);
}
throw error;
}
禁用不必要的资源加载
await page.setRequestInterception(true);
page.on('request', (req) => {
const resourceType = req.resourceType();
// 丢弃图片、样式、字体等非关键资源
if (['image', 'stylesheet', 'font', 'media'].includes(resourceType)) {
req.abort();
} else {
req.continue();
}
});
性能提升效果惊人:
| 优化项 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 资源加载数量 | ~100个请求 | ~20个请求 | ↓80% |
| 页面加载时间 | 3-5秒 | 1-2秒 | ↑60% |
| 网络流量 | 5-10MB | 0.5-1MB | ↓90% |
Cookie 安全(重中之重)
# .gitignore 中必须包含
*_cookies.json
cookies.json
*.env
代码注入防护
// 危险:直接拼接用户输入
const userInput = req.query.selector;
await page.click(userInput);
// 安全:白名单验证
const allowedSelectors = ['.button-primary', '.submit-btn'];
if (!allowedSelectors.includes(userInput)) {
throw new Error('非法选择器');
}
await page.click(userInput);
VNC 实时观察(最有效)
# 创建沙箱后立即获取 VNC URL
vnc_url = f"https://vnc.example.com/sandbox/{sandbox.sandbox_id}"
print(f"打开 VNC: {vnc_url}")
截图调试(关键时刻救命)
// 登录前后都截图
await page.screenshot({ path: '/home/user/data/before_login.png', fullPage: true });
await page.screenshot({ path: '/home/user/data/after_login.png', fullPage: true });
使用 AIO sandbox 能够将状态传递和文件共享复杂度进行有效地降低,并且能够有如下收益:
puppeteer.connect(),禁止 launch()browser.disconnect(),禁止 close()/home/user/data/ 目录| 陷阱 | 症状 | 解决方案 |
|---|---|---|
用 launch() | 浏览器重复启动,内存爆了 | 改用 connect() |
用 close() | 后续步骤失败,状态丢了 | 改用 disconnect() |
| Cookie 没持久化 | 每次都要重新登录 | 保存到 /home/user/data/cookies.json |
| 等待时间不足 | 元素找不到报错 | 用 waitForSelector + networkidle2 |
| 路径不规范 | 文件丢失或权限错误 | 统一用 /home/user/data/ 目录 |
networkidle2 等待策略