cua-bench 环境脚手架实战:用四个装饰器构建计算机使用 RL 任务环境
【免费下载链接】cuaScale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation.项目地址: https://gitcode.com/GitHub_Trending/cua/cua
cua-bench 环境脚手架指南(仓库中位于 drag-drop/CLAUDE.md)定义了创建计算机使用(Computer Use)强化学习环境的统一编程模型:用四个 Python 装饰器声明任务配置、环境搭建、求解与评估,再用一份gui/index.html承载全部界面逻辑。读完本文,你将掌握 cua-bench 任务环境从零搭建的完整流程,理解env.step/env.bot/window.__next_move()之间的职责边界,并能基于仓库中 13 个基础任务的真实代码,独立编写可被run_benchmark批量运行的 RL 任务。
一、环境脚手架的整体结构
cua-bench 创建计算机使用 RL 环境的目录结构约定为两部分,职责划分非常清晰:
<task-folder>/ ├── main.py # Python 装饰器承载全部任务逻辑 ├── gui/ │ └── index.html # HTML/CSS/JS 界面(自动引入 Tailwind 与 Iconify) ├── pyproject.toml # 项目元数据与依赖 └── CLAUDE.md # 脚手架开发文档其中main.py只负责「任务逻辑」,gui/负责「界面与交互」。这一约定在 cua-bench-basic 数据集 的每个任务目录中保持一致——click-button、drag-slider、fill-form、right-click-menu、drag-drop等 13 个基础任务均遵循该结构。数据集的 README.md 将其归纳为:main.py承载任务配置/搭建/评估/求解,gui/index.html提供带 Tailwind 样式的界面,CLAUDE.md为脚手架开发文档。
二、四个核心装饰器:任务环境的四段生命周期
main.py中的全部逻辑通过四个装饰器声明,它们按执行顺序覆盖了一个任务从定义到打分的完整生命周期。装饰器定义于 cua_bench/decorators.py,统一导出在cua_bench命名空间下(即代码中的cb)。
2.1@cb.tasks_config:声明任务与参数化变体
tasks_config装饰的函数返回list[cb.Task],每个cb.Task包含两件事:
description:AI Agent 听到的任务描述(例如 "Play 2048"、"Book a hotel");metadata:任务的参数化元数据(难度、棋盘大小、操作系统等)。
return [cb.Task(description="Play 2048", metadata={"size": 4, "os_type": "linux"})]从 cua_bench/core.py 可以看到Task的完整字段定义:
@dataclass class Task: description: str task_id: Optional[str] = None metadata: Optional[dict] = None computer: Optional[dict] = Nonemetadata是参数化的关键通道——通过枚举不同取值(难度、尺寸、轮数、OS),一个任务文件即可批量生成大量训练/评估变体。computer字段则允许在任务级别直接声明运行环境(provider 与setup_config),详见后文「环境生命周期」一节。
2.2@cb.setup_task:搭建沙箱与启动窗口
setup_task负责创建沙箱并启动 webview 窗口,保持最小化——只做环境搭建:
global pid env.create_sandbox(provider="computer", setup_config={"os_type": "linux", "width": 800, "height": 600}) pid = env.launch_window(html=html_content, title="Game", width=400, height=400) # create webview window两点值得展开:
provider="computer"是"native"provider 的别名。在 cua_bench/computers/base.py 的get_session()中,session 名统一走simulated(别名webtop,基于 Playwright 的浏览器模拟,无需 Docker)与native(别名computer,Docker/QEMU 中的真实桌面环境)两条路径。launch_window是DesktopSession协议的标准接口,完整签名见 base.py:支持url、html、folder三种内容来源,另有x/y、width/height(默认 600×400)、icon、use_inner_size、title_bar_style等窗口参数,返回窗口进程 ID(pid)。
在实际任务中,也可以在Task.computer字段里直接声明环境,由Environment.reset()自动完成沙箱创建(见第四节)。
2.3@cb.solve_task:驱动求解循环
solve_task从 GUI 的 AI 策略处获取下一步动作,并用env.step或env.bot执行:
global pid action = env.execute_javascript(pid, "window.__next_move()") while action is not None and action["type"] != "done": if not action or action["type"] == "wait": env.step(WaitAction(seconds=1.0)) elif action["type"] == "click_element": env.bot.click_element(pid, f"#{action['element_id']}") # safest way to click an element elif action["type"] == "click_absolute": env.step(ClickAction(x=action["x"], y=action["y"])) # x,y must be in screen coordinates (requires offsetting by window.screenX and window.screenY) elif action["type"] == "type": env.step(TypeAction(text=action["text"])) action = env.execute_javascript(pid, "window.__next_move()") env.step(DoneAction())该循环明确划分了三条职责边界,这是整个脚手架最重要的设计约束:
- 只有
env.step或env.bot能真正执行解决任务的动作; env.execute_javascript只能执行返回「最优动作信息」的辅助函数(如目标元素、或暴露了window.__next_move()的 AI 策略输出);window.__next_move()只返回下一步动作,绝不执行动作或修改环境/状态——它通常被solve_task循环调用直至任务解决,实际动作统一交由env.step/env.bot落地。
这一「策略与执行分离」的设计,保证了轨迹数据中观察(observation)与动作(action)的干净边界,便于训练与评估复用同一套任务。
2.4@cb.evaluate_task:从 GUI 状态计算奖励
evaluate_task读取界面状态并返回奖励值,RL 奖励建议落在 0.0–1.0 区间:
global pid score = env.execute_javascript(pid, "window.__score") return [float(score)] # 0.0-1.0 range preferred2.5 装饰器背后的注册机制
这四个装饰器并非简单标记,而是把函数注册进全局环境注册表。源码 decorators.py 的实现要点:
- 每个装饰器都支持两种用法:裸用
@cb.tasks_config或参数化@cb.tasks_config("train")/@cb.tasks_config(split="test"),默认 split 为"train"; - 被装饰的函数会被打上
_td_type(tasks_config/setup_task/solve_task/evaluate_task)与_td_split两个属性; Environment.make_from_module()遍历模块成员,按_td_type与_td_split匹配当前 split 对应的四个函数,组装出完整的Environment(见 environment.py)。
也就是说:你可以用同一份main.py同时定义train与test两套函数,运行时按 split 自动选择,数据泄漏风险从结构上被隔离。
三、gui/ 目录:界面即任务载体
gui/index.html中所有游戏/任务逻辑都在这里,HTML 会被渲染进一个内置 Tailwind + Iconify 模板的桌面 webview 窗口内,因此不要使用<html>或<body>标签,直接写语义化内容即可。
3.1 关键编写模式
- 语义化 HTML + ARIA 描述:使用
<main>、<section>、<button>、<nav>等语义元素,并添加aria-label、aria-describedby、role属性,既服务无障碍,也让视觉模型/Agent 更容易识别元素。 - 紧凑响应式设计:使用最小化 padding/margin(
p-1、p-2、gap-1、gap-2),布局需在弹窗尺寸(300×200)到全屏桌面之间自适应;避免固定宽高,优先min-h-0、overflow-auto,保证视口缩小时关键元素仍可见。 - 全局状态:当前得分存入
window.__score(RL 奖励统一为 0.0–1.0 区间)。 - AI 基线:在 JavaScript 中实现 AI 策略,通过
window.__next_move()暴露。 - 窗口填充:根元素使用
class="flex h-full w-full"填满整个窗口,所有元素保持紧凑响应(HTML 通常渲染在小尺寸桌面 webview 窗口或手机屏幕中)。 - 图标:使用
<iconify-icon icon="prefix:name"></iconify-icon>。
3.2 以 drag-drop 任务为例:一份完整的 gui/index.html
仓库中 drag-drop/gui/index.html 是上述模式的真实落地。它用<main>包裹、根元素flex flex-col h-full w-full p-6 overflow-auto填充窗口,role="main"+aria-label="Drag and drop interface"提供无障碍描述;可拖拽物品与放置区分别用role="button"(aria-label="Apple item"等)与role="region"(aria-label="Fruits drop zone"等)标注,为 Agent 的元素定位提供语义锚点。图标全部使用 Iconify:
<iconify-icon icon="mdi:food-apple" class="text-red-600" style="font-size: 1.25rem"></iconify-icon>其 JavaScript 部分展示了「全局状态」与「AI 基线」的具体写法——用window.__dropResults = {}记录每次投放结果供评估读取:
window.__dropResults = {}; // ... zone.addEventListener('drop', function (e) { e.preventDefault(); if (draggedElement) { const itemName = draggedElement.getAttribute('data-item'); const targetName = this.getAttribute('data-target'); window.__dropResults[itemName] = targetName; // 记录投放结果,供 evaluate 读取 // ...将物品克隆进目标容器并隐藏原元素 } });四、Action 类型全集:env.step()的可用动作
脚手架定义了完整的动作类,全部位于 cua_bench/types.py,统一作为env.step(action)的参数:
鼠标动作:
ClickAction(x, y)RightClickAction(x, y)DoubleClickAction(x, y)DragAction(from_x, from_y, to_x, to_y, duration=1.0)ScrollAction(direction="up|down", amount=100)
键盘动作:
TypeAction(text="hello")KeyAction(key="Enter")HotkeyAction(keys=["ctrl", "c"])
控制动作:
DoneAction()WaitAction(seconds=1.0)
types.py 中实际还定义了MiddleClickAction(鼠标中键)与MoveToAction(x, y, duration=0.0)(纯移动),并导出Action联合类型供类型标注使用。
env.step()的底层行为可在 environment.py 中看到:每次 step 校验会话与max_steps步数预算(超限抛MaxStepsExceeded)、经session.execute_action()落地动作、截图并记录step:before/step:after轨迹事件、递增step_count,最后返回截图。step还支持dry_run="before"|"after"用于只观察不执行的调试场景。
五、env.bot:更安全的元素级操作助手
指南特别强调env.bot.click_element(pid, f"#{action['element_id']}")是「最安全的点击方式」。其实现位于 cua_bench/bot.py:Bot通过 provider 的get_element_rect桥接层获取元素在屏幕坐标系中的矩形,计算中心点后派发ClickAction/RightClickAction,从而避免 Agent 手工估算像素坐标的误差:
def click_element(self, pid: int, selector: str) -> None: rect = self.env.get_element_rect(pid, selector, space="screen") if not rect: raise RuntimeError(f"Element not found for selector: {selector}") cx = int(rect["x"] + rect["width"] / 2) cy = int(rect["y"] + rect["height"] / 2) self.env.step(ClickAction(x=cx, y=cy))六、Iconify 图标
界面图标统一使用iconify-icon元素,支持可缩放矢量图标:
<iconify-icon icon="eva:people-outline"></iconify-icon> <iconify-icon icon="mingcute:ad-circle-line" width="24" height="24"></iconify-icon> <iconify-icon icon="mdi:play" class="text-blue-500" style="font-size: 2rem;"></iconify-icon>- 图标会被自动处理并替换为内联 SVG;
- 支持所有 iconify 图标集(eva、mingcute、mdi 等)。
drag-drop 任务的界面即使用了mdi:food-apple、mdi:carrot、mdi:fruit-citrus、mdi:fruit-grapes、mdi:corn、mdi:information等多组图标区分物品类别与提示信息。
七、屏幕尺寸:在setup_config中指定
屏幕尺寸通过env.create_sandbox的setup_config参数(或Task.computer.setup_config)指定。脚手架在 cua_bench/types.py 中定义了完整的StandardScreenSize联合类型:
# --- Screen size options for desktop environments --- StandardScreenSize = Union[ # Standard Desktop Resolutions tuple[Literal[1920], Literal[1080]], # Full HD (current default) tuple[Literal[1366], Literal[768]], # HD (laptop standard) tuple[Literal[2560], Literal[1440]], # 2K/QHD tuple[Literal[3840], Literal[2160]], # 4K/UHD tuple[Literal[1280], Literal[720]], # HD Ready tuple[Literal[1600], Literal[900]], # HD+ tuple[Literal[1920], Literal[1200]], # WUXGA tuple[Literal[2560], Literal[1600]], # WQXGA tuple[Literal[3440], Literal[1440]], # Ultrawide QHD tuple[Literal[5120], Literal[1440]], # Super Ultrawide # Mobile/Tablet Resolutions tuple[Literal[1024], Literal[768]], # iPad (portrait) tuple[Literal[768], Literal[1024]], # iPad (landscape) tuple[Literal[360], Literal[640]], # Mobile portrait tuple[Literal[640], Literal[360]], # Mobile landscape # Legacy Resolutions tuple[Literal[1024], Literal[600]], # Netbook tuple[Literal[800], Literal[600]], # SVGA tuple[Literal[640], Literal[480]], # VGA # Additional Common Resolutions tuple[Literal[1440], Literal[900]], # Custom laptop tuple[Literal[1680], Literal[1050]], # WSXGA+ tuple[Literal[1920], Literal[1440]], # Custom 4:3 ratio tuple[Literal[2560], Literal[1080]], # Ultrawide Full HD tuple[Literal[3440], Literal[1440]], # Ultrawide QHD tuple[Literal[3840], Literal[1080]], # Super Ultrawide Full HD ]选择屏幕尺寸时遵循两条原则:选择与任务和环境匹配的尺寸;桌面环境默认 1920×1080(Full HD),移动端任务可选用 360×640 等竖屏尺寸。
DesktopSetupConfig(见 computers/base.py)还支持更丰富的配置字段:os_type(win11/win10/macos/linux/android等)、background、wallpaper、installed_apps,以及 Docker/VM 相关的image、storage、memory、cpu、provider_type("docker"/"lume"/"cloud")。
八、完整实战:drag-drop 任务的 main.py 剖析
仓库中 drag-drop/main.py 是一个可运行的完整范本,将前面所有概念串联在一起。
① 任务配置与变体生成:tasks_config定义 5 种拖放场景(Apple→Fruits、Carrot→Vegetables、Banana→Fruits、Broccoli→Vegetables、Orange→Fruits),并枚举os_types = ["linux"],通过列表推导生成 5 个变体,每个变体的description与metadata(item、target、item_label、target_label)各不相同,且直接通过computer字段声明provider="native"与setup_config(1024×768、灰色背景#c0c0c0):
@cb.tasks_config(split="train") def load(): os_types = ["linux"] # ["macos", "win11", "win10"] drag_scenarios = [ {"item": "apple", "target": "fruit", "item_label": "Apple", "target_label": "Fruits", "description": "Drag the Apple to the Fruits box"}, # ...更多场景 ] return [ cb.Task( description=scenario["description"] + ".", metadata={...}, computer={ "provider": "native", "setup_config": { "os_type": os_type, "width": 1024, "height": 768, "background": "#c0c0c0", }, }, ) for os_type in os_types for scenario in drag_scenarios ]② 环境搭建:setup_task读取同目录gui/index.html文本并启动 600×500 的 webview 窗口:
@cb.setup_task(split="train") async def start(task_cfg: cb.Task, session: cb.DesktopSession): global pid pid = await session.launch_window( html=(Path(__file__).parent / "gui/index.html").read_text("utf-8"), title="Drag and Drop Task", width=600, height=500, )③ 求解:solve_task用execute_javascript读取物品与目标框的屏幕坐标(注意代码中itemRect.left + window.screenX的偏移处理,与指南「所有 x,y 均为屏幕坐标,需用window.screenX/screenY计算浏览器视口到屏幕左上角的偏移」完全对应),再用DragAction一次拖拽到位:
@cb.solve_task(split="train") async def solve(task_cfg: cb.Task, session: cb.DesktopSession): global pid coords = await session.execute_javascript(pid, f""" ... 读取 item/target 矩形中心并加 window.screenX/screenY ... """) await session.execute_action( cb.DragAction( from_x=coords["item_x"], from_y=coords["item_y"], to_x=coords["target_x"], to_y=coords["target_y"], duration=0.5, ) )④ 评估:evaluate_task读取window.__dropResults,判定物品是否落入了正确分类,返回1.0或0.0的稀疏奖励:
@cb.evaluate_task(split="train") async def evaluate(task_cfg: cb.Task, session: cb.DesktopSession) -> list[float]: global pid drop_results = await session.execute_javascript(pid, "window.__dropResults") if drop_results is None: return [0.0] item = task_cfg.metadata["item"] target = task_cfg.metadata["target"] item_location = drop_results.get(item) return [1.0] if item_location == target else [0.0]文件末尾的cb.interact(__file__)是本地交互式调试入口(实现在 core.py),以非 headless 模式加载环境、执行 setup、等待用户回车后打印评估结果。
九、环境生命周期:从 reset 到 evaluate 的源码视角
Environment(environment.py)是整个运行时的核心。关键调用链如下:
make(env_path)(core.py)通过importlib动态加载任务的main.py模块,再经make_from_module按 split 匹配四个装饰器函数,构建Environment实例;env.reset(task_id)负责生命周期前置:关闭旧会话、重置步数计数器、惰性加载tasks_config结果、根据Task.computer自动调用create_sandbox(provider, setup_config),随后调用setup_task并截图、记录reset轨迹事件;env.step(action)执行单步动作并截图返回(见第四节);env.solve()调用solve_task,捕获MaxStepsExceeded优雅终止;env.evaluate()调用evaluate_task,返回奖励并记录evaluate轨迹事件与遥测数据。
值得注意:create_sandbox也可以在setup_task内直接调用(脚手架指南示例的写法),而reset()会自动从Task.computer读取并创建——两种方式等价,但推荐后者,因为环境声明与任务变体绑定,天然支持「一个文件批量变体、每个变体不同的 OS/分辨率」。
十、运行与批量验证
数据集的 README.md 给出了交互式运行方式:
# 运行单个任务(交互式) python -m cua_bench.interact <task-folder>/main.py # 示例 python -m cua_bench.interact click-button/main.py如果要在程序中批量运行,cua_bench/runners.py 提供了三组高阶接口:
run_single_task(env_path, task_index, split, agent_fn, max_steps=100, oracle=False):按 gym 接口(make→reset→step→evaluate)运行单个任务变体;oracle=True时调用solve_task验证任务自身可解性,agent_fn模式则注入外部 Agent 循环;奖励 ≥ 0.5 判定成功;run_benchmark(dataset_path, agent_fn, max_steps=100, max_parallel=4, oracle=False, task_filter=None):自动发现数据集下所有含main.py的任务目录、展开全部参数化变体,用asyncio.Semaphore控制并发,汇总BenchmarkResult(含success_count、avg_reward、duration_seconds)——这是验证新任务脚手架是否合格的关键工具:先用oracle=True确认任务可解,再接入真实 Agent 评测;run_interactive(env_path, task_index, headless=False):返回(env, screenshot, task_cfg)三元组供脚本内交互控制。
十一、最佳实践清单
综合脚手架指南与源码实现,编写高质量任务环境时应遵守:
main.py保持最小化——只放装饰器与基本逻辑(环境搭建、任务加载),AI 策略全部放gui/的 JavaScript 中,通过window.__next_move()暴露;- 奖励统一用
window.__score(0.0–1.0 区间),便于跨任务横向对比与 RL 训练; - 通过 Task metadata 参数化变体(难度、尺寸、OS、轮数),一份
main.py产出整个变体矩阵; - 谨慎使用
WaitAction——仅当任务确实需要(如等待页面加载)或等待下一步动作就绪时使用;env.bot助手自带可操作性判断(等待元素可点击),会自动向前推进环境; - 所有 x,y 均为屏幕坐标(0,0 为屏幕左上角),计算浏览器内元素坐标时必须用
window.screenX/window.screenY加上视口到屏幕的偏移; - 选择与任务、环境匹配的屏幕尺寸(桌面任务默认 1920×1080,移动端任务选 360×640 等);
- 保持
window.__next_move()纯净——只返回下一步动作、不执行动作、不修改状态,把执行权完整交给env.step/env.bot。
遵循这套脚手架约定,你可以在 cua-bench 中快速沉淀可训练、可评估、可复用的计算机使用任务,并为 cua-bench-basic 这样的基础交互任务集持续贡献新的环境。
【免费下载链接】cuaScale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation.项目地址: https://gitcode.com/GitHub_Trending/cua/cua
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考