cua-bench 环境脚手架实战:用四个装饰器构建计算机使用 RL 任务环境
2026/9/13 6:15:40 网站建设 项目流程

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-buttondrag-sliderfill-formright-click-menudrag-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] = None

metadata是参数化的关键通道——通过枚举不同取值(难度、尺寸、轮数、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_windowDesktopSession协议的标准接口,完整签名见 base.py:支持urlhtmlfolder三种内容来源,另有x/ywidth/height(默认 600×400)、iconuse_inner_sizetitle_bar_style等窗口参数,返回窗口进程 ID(pid)。

在实际任务中,也可以在Task.computer字段里直接声明环境,由Environment.reset()自动完成沙箱创建(见第四节)。

2.3@cb.solve_task:驱动求解循环

solve_task从 GUI 的 AI 策略处获取下一步动作,并用env.stepenv.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.stepenv.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 preferred

2.5 装饰器背后的注册机制

这四个装饰器并非简单标记,而是把函数注册进全局环境注册表。源码 decorators.py 的实现要点:

  • 每个装饰器都支持两种用法:裸用@cb.tasks_config或参数化@cb.tasks_config("train")/@cb.tasks_config(split="test"),默认 split 为"train"
  • 被装饰的函数会被打上_td_typetasks_config/setup_task/solve_task/evaluate_task)与_td_split两个属性;
  • Environment.make_from_module()遍历模块成员,按_td_type_td_split匹配当前 split 对应的四个函数,组装出完整的Environment(见 environment.py)。

也就是说:你可以用同一份main.py同时定义traintest两套函数,运行时按 split 自动选择,数据泄漏风险从结构上被隔离。

三、gui/ 目录:界面即任务载体

gui/index.html中所有游戏/任务逻辑都在这里,HTML 会被渲染进一个内置 Tailwind + Iconify 模板的桌面 webview 窗口内,因此不要使用<html><body>标签,直接写语义化内容即可。

3.1 关键编写模式

  • 语义化 HTML + ARIA 描述:使用<main><section><button><nav>等语义元素,并添加aria-labelaria-describedbyrole属性,既服务无障碍,也让视觉模型/Agent 更容易识别元素。
  • 紧凑响应式设计:使用最小化 padding/margin(p-1p-2gap-1gap-2),布局需在弹窗尺寸(300×200)到全屏桌面之间自适应;避免固定宽高,优先min-h-0overflow-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-applemdi:carrotmdi:fruit-citrusmdi:fruit-grapesmdi:cornmdi:information等多组图标区分物品类别与提示信息。

七、屏幕尺寸:在setup_config中指定

屏幕尺寸通过env.create_sandboxsetup_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_typewin11/win10/macos/linux/android等)、backgroundwallpaperinstalled_apps,以及 Docker/VM 相关的imagestoragememorycpuprovider_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 个变体,每个变体的descriptionmetadataitemtargetitem_labeltarget_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_taskexecute_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.00.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)是整个运行时的核心。关键调用链如下:

  1. make(env_path)(core.py)通过importlib动态加载任务的main.py模块,再经make_from_module按 split 匹配四个装饰器函数,构建Environment实例;
  2. env.reset(task_id)负责生命周期前置:关闭旧会话、重置步数计数器、惰性加载tasks_config结果、根据Task.computer自动调用create_sandbox(provider, setup_config),随后调用setup_task并截图、记录reset轨迹事件;
  3. env.step(action)执行单步动作并截图返回(见第四节);
  4. env.solve()调用solve_task,捕获MaxStepsExceeded优雅终止;
  5. 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_countavg_rewardduration_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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询