ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Cua Bench UI:为 Computer-Use 基准环境提供 pywebview 窗口控制能力的轻量级库

Cua Bench UI:为 Computer-Use 基准环境提供 pywebview 窗口控制能力的轻量级库 Cua Bench UI为 Computer-Use 基准环境提供 pywebview 窗口控制能力的轻量级库【免费下载链接】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本文围绕libs/python/bench-ui包的官方文档展开深入讲解 cua-bench-ui 的三个核心 APIlaunch_window/get_element_rect/execute_javascript的完整用法与参数语义并基于仓库源码剖析其父子进程 本地 HTTP 控制端口的架构、屏幕/窗口两种坐标空间的换算原理以及它在 cua-bench 远程计算机环境中的实际集成方式。读完本文你可以直接在本地或远程桌面环境中用几行 Python 代码弹出 GUI 窗口、按 CSS 选择器查询元素屏幕坐标、执行任意 JavaScript从而为 computer-use 任务构造可观测、可断言的 GUI 环境。一、包定位Cua bench 环境的窗口控制器libs/python/bench-ui的 README 将本包定位为一句话Lightweight webUI window controller for Cua bench environments using pywebview——基于 pywebview 构建、面向 Cua bench计算机使用基准评测环境的轻量级 WebUI 窗口控制器。它的解决的问题很具体在 computer-use 基准任务中评测脚本需要动态弹出一个包含 GUI 元素的窗口比如一个按钮、一个表单并且要能以屏幕绝对坐标拿到某个元素的矩形区域供后续的点击动作、截图标注或断言使用。cua-bench-ui 把这件事压缩成了三个函数from bench_ui import launch_window, get_element_rect, execute_javascript # Launch a window with inline HTML content pid launch_window(htmlhtmlbodyh1Hello/h1/body/html) # Get element rect in screen space rect get_element_rect(pid, h1, spacescreen) print(rect) # Execute arbitrary JavaScript text execute_javascript(pid, document.querySelector(h1)?.textContent) print(text)这就是 README 给出的最小完整示例启动窗口后拿到 PID之后所有控制操作都以 PID 作为句柄。二、安装与依赖安装方式即 README 中的一行命令pip install cua-bench-ui从 pyproject.toml 可以看到该包的发布元信息与硬依赖包名cua-bench-ui当前版本0.7.0Python 版本要求3.12requires-python 3.12三个运行时依赖pywebview5.3创建原生 GUI 窗口并在其中加载网页aiohttp3.9.0在子进程中运行本地 HTTP 控制服务器psutil5.9父进程通过它从 PID 反查监听端口后文详述。构建后端为pdm-backendwheel 只打包bench_ui/目录。适用前提需要注意pywebview 依赖桌面图形后端因此该库需要运行在有 GUI 会话的机器上或带虚拟桌面的远程环境这也是为什么它在 Cua bench 的远程计算机场景里主要跑在远端而非 CI 无头环境。三、三个公开 API 与完整参数bench_ui/init.py 对外只暴露三个符号from .api import execute_javascript, get_element_rect, launch_window __all__ [launch_window, get_element_rect, execute_javascript]3.1 launch_window三种内容来源bench_ui/api.py 中launch_window的完整签名如下参数默认值取自源码def launch_window( url: Optional[str] None, *, html: Optional[str] None, folder: Optional[str] None, title: str Window, x: Optional[int] None, y: Optional[int] None, width: int 600, height: int 400, icon: Optional[str] None, use_inner_size: bool False, title_bar_style: str default, ) - int:参数语义参数默认值说明urlNone位置参数加载已有 URL首选输入htmlNone内联 HTML 字符串作为窗口内容folderNone静态目录路径作为根目录服务并在窗口中打开其index.htmltitleWindow窗口标题x/yNone窗口左上角位置缺省交给窗口管理器width/height600/400窗口尺寸iconNone窗口图标路径use_inner_sizeFalse是否把宽高校准为内容区inner尺寸title_bar_styledefault标题栏样式三个内容来源url/html/folder至少必须提供一个否则抛出ValueError(launch_window requires either a url, html, or folder)。返回值是子进程 PID作为后续get_element_rect和execute_javascript的句柄。3.2 get_element_rect两种坐标空间def get_element_rect(pid: int, selector: str, *, space: str window):selector为 CSS 选择器例如h1、#targetspace取window默认或screen返回形如{x: float, y: float, width: float, height: float}的 dict选择器匹配不到元素时返回None。两种空间的差别直接体现在子进程注入的 JS 里见 bench_ui/child.py 的rect_handlerwindow 空间直接返回el.getBoundingClientRect()的left/top/width/height即元素相对视口的坐标screen 空间在此基础上叠加窗口偏移。源码注入的脚本为const s selector; const el document.querySelector(s); if(!el){return null;} const r el.getBoundingClientRect(); const sx (window.screenX ?? window.screenLeft ?? 0); const syRaw (window.screenY ?? window.screenTop ?? 0); const frameH (window.outerHeight - window.innerHeight) || 0; const sy syRaw frameH; return {x:sx r.left, y:sy r.top, width:r.width, height:r.height};注意sy syRaw frameH这一步window.screenY在很多引擎中指向的是窗口框架外沿而getBoundingClientRect的top是相对视口的因此需要加上outerHeight - innerHeight估算的标题栏高度把视口坐标换算到屏幕绝对坐标。源码注释也明确说明这是approximate screen coordinates近似屏幕坐标——这是使用spacescreen时需要知道的精度前提。computer-use 点击动作最终需要屏幕绝对坐标所以示例脚本里都用spacescreen再交给截图/点击链路。3.3 execute_javascript任意 JS 执行def execute_javascript(pid: int, javascript: str):在窗口上下文中执行任意 JavaScript 并返回结果子进程的/eval端点兼容javascript或code两种请求字段名。典型用途包括读取document.title、检查window.__submitted之类的测试钩子状态、读取按钮的disabled属性来断言交互是否发生。四、内部架构父子进程 127.0.0.1 控制端口README 只呈现了 API 表面源码里真正值得注意的是它如何跨进程控制一个 GUI 窗口。整体机制是父进程 spawn 一个子进程跑 pywebview 事件循环子进程另起一个只监听127.0.0.1的 aiohttp 服务作为控制面。4.1 父进程侧写临时 JSON 配置、读一行启动信息bench_ui/api.py 中launch_window的实现要点把全部窗口参数序列化为一个 JSON 配置写入tempfile.NamedTemporaryFile生成的临时文件避免命令行传大段 HTML 触碰参数长度限制用subprocess.Popen([sys.executable, -m, bench_ui.child, cfg_path])拉起子进程从子进程 stdout 读第一行 JSON{pid: pid, port: port}把pid - port缓存在模块级字典_pid_to_port中随后删除临时配置文件返回 PID。4.2 子进程侧自选空闲端口、先启动窗口再阻塞于 GUIbench_ui/child.py 的main()按以下顺序执行解析命令行传入的config.json_get_free_port()通过socket.bind((127.0.0.1, 0))让内核分配一个空闲端口按url/folder/html三种分支调用webview.create_window(...)url分支直接加载外部地址folder分支把窗口指向http://127.0.0.1:{port}/index.html由控制服务器以app.router.add_static(/, folder_path, show_indexTrue)静态服务该目录html分支把窗口指向http://127.0.0.1:{port}/由index_handler以text/html返回内联 HTML。三种分支统一传入confirm_closeFalse, text_selectTrue, background_color#FFFFFF注册window.events.loaded _on_loaded页面加载完成后置位window_ready事件控制端点会等待它最多 2 秒再执行 JS避免窗口还没加载完就注入脚本的竞态_start_http_server在独立 daemon 线程中运行 asyncio 事件循环aiohttp 服务绑定(127.0.0.1, port)注册三个路由POST /rect、POST /eval以及根路径返回内联 HTML 或一个 JSON 状态信息打印{pid: ..., port: ...}这一行给父进程webview.start(debug...)阻塞运行 GUI。调试模式由环境变量CUA_BENCH_UI_DEBUGtrue/1控制两个官方示例脚本都会先设置os.environ[CUA_BENCH_UI_DEBUG] 1。/rect端点的错误语义也值得注意JSON 体解析失败返回 400invalid_json选择器不是字符串返回 400selector_required窗口 2 秒内仍未加载完返回 409window_not_readyJS 执行抛异常返回 500 并附带异常信息。4.3 PID 到端口的反查与重试父进程调用get_element_rect/execute_javascript时如果_pid_to_port中没有该 PID例如换了父进程、缓存被清掉会调用_detect_port_for_pid用psutil.net_connections(kindtcp)全量扫描 TCP 连接过滤出属于该 PID、状态为LISTEN、且本地地址是127.0.0.1/::1/0.0.0.0/::的连接取其端口。扫不到则直接抛出RuntimeError(fCould not detect listening port for pid {pid})属于快速失败设计psutil 不可用时同样报错提示安装。两个控制函数都内置了等待窗口就绪的重试循环最多 30 次、每次间隔 0.1 秒合计约 3 秒对window_not_ready、invalid_json以及其它瞬时错误统一短重试超窗仍未成功则抛出RuntimeError并附带最后一次响应体。五、官方示例从内联 HTML 到静态目录仓库提供了两个可直接运行的端到端示例比 README 的代码片段更接近真实评测场景。5.1 simple_example内联 HTML 屏幕坐标标注examples/simple_example.py 的完整流程os.environ[CUA_BENCH_UI_DEBUG] 1 # 1. 以内联 HTML 启动 800x600 窗口 pid launch_window(htmlHTML, titleBench UI Example, width800, height600) print(fLaunched window with PID: {pid}) time.sleep(1.0) # 2. 以 SCREEN 空间查询 #target 元素矩形 rect get_element_rect(pid, #target, spacescreen) print(Element rect (screen space):, rect) # 3. 截全屏图并用 PIL 在矩形位置画红色边框保存 overlay img ImageGrab.grab() draw ImageDraw.Draw(img) x, y, w, h rect[x], rect[y], rect[width], rect[height] draw.rectangle((x, y, x w, y h), outline(255, 0, 0), width3) img.save(Path(__file__).parent / output_overlay.png) # 4. 用 JS 读取页面里设置的交互状态 text execute_javascript(pid, window.__submitted)内联 HTML 中预置了一个#target色块和一个点击后置window.__submitted true的 Submit 按钮——这正是 computer-use 任务里执行动作后用 JS 断言状态变化的典型套路。运行该示例会在 examples 目录生成 output_overlay.png即文首那张标注了红色边框的截图它验证了spacescreen返回的坐标可以直接与全屏截图坐标对齐。5.2 folder_example静态目录服务examples/folder_example.py 演示folder分支gui_folder Path(__file__).parent / gui pid launch_window(folderstr(gui_folder), titleStatic Folder Example, width800, height600) time.sleep(1.5) # 窗口空间查询按钮矩形 rect get_element_rect(pid, #testButton, spacewindow) # 用 JS 断言按钮是否已被点击点击后 disabled 置 True clicked execute_javascript(pid, document.getElementById(testButton).disabled) # 读取页面标题 title execute_javascript(pid, document.title)被服务的目录是 examples/gui/包含index.html、styles.css与logo.svg页面加载了外部 CSS 和 SVG 图片并带一个点击后变disabled的#testButton按钮用于验证文件夹里相对路径资源CSS/SVG能否被控制服务器正确服务 JS 状态断言这条链路。六、测试如何验证端口反查tests/test_port_detection.py 覆盖的正是 4.3 节描述的降级路径launch_window启动一个内联 HTML 窗口断言_pid_to_port初始已有映射del _pid_to_port[pid]人为清除缓存模拟父进程不知道端口的场景此时调用execute_javascript验证它会自动走_detect_port_for_pidpsutil 扫描找到端口并成功执行 JS断言取回hello-world文本finally中用psutil.Process(pid)做 terminate/wait/kill 的尽力清理。测试用pytest.importorskip(webview)处理无图形后端的机器跳过而非失败这与该库需要 GUI 会话的运行前提一致。七、在 cua-bench 远程计算机中的集成cua-bench-ui 的真正主场是 cua-bench 的远程桌面评测环境。libs/cua-bench/cua_bench/computers/remote.py 中的远程计算机实现把这三个 API 完整封装进了异步接口open_url(url..., html..., folder..., ...)参数与launch_window一一对应含x/y/width/height/icon/use_inner_size/title_bar_style。远端处理有两个值得注意的工程细节folder参数会先按路径哈希生成远端临时目录cua_folder_hash8把本地文件树逐一上传后再以 folder 模式启动窗口html参数为避免远程命令参数过长会先包进_HTML_TEMPLATE片段不含html标签时自动补全文档骨架、按内容哈希写入远端cua_html_hash8/index.html再走 folder 模式get_element_rect(pid, selector, space..., timeout0.5)在远端执行from bench_ui import get_element_rect外层按timeout做轮询重试间隔max(0.1, timeout/2)元素尚未出现时返回None而非抛异常更适合等待元素出现的评测语义execute_javascript(pid, javascript)远端直接执行并返回结果启动过的窗口 PID 会记入self._webview_pids供环境生命周期结束时统一清理。也就是说同一套launch_window/get_element_rect/execute_javascript语义既可以在本地 Python 进程里直接调用也可以作为远端 Python 命令被 cua-bench 的计算机抽象透明地调度——这是该库窗口控制器定位的完整体现。八、使用小结安装pip install cua-bench-ui要求 Python ≥ 3.12依赖 pywebview / aiohttp / psutil且运行环境需要图形会话最小流程launch_window拿 PID →get_element_rect(pid, selector, spacescreen)拿屏幕坐标 →execute_javascript读状态/断言三种内容来源url外部页面、html内联字符串、folder静态目录根路由服务index.html坐标系spacewindow是视口坐标spacescreen是叠加了窗口框架偏移的屏幕近似坐标供点击动作与截图标注使用控制通道父进程通过子进程 stdout 的一行 JSON 获得127.0.0.1上的控制端口并缓存pid - port缓存缺失时可用 psutil 从 PID 反查监听端口反查不到则快速失败调试设置CUA_BENCH_UI_DEBUG1可开启 pywebview 调试模式延伸阅读本地示例 simple_example.py、folder_example.py核心实现 bench_ui/api.py 与 bench_ui/child.py远程集成 computers/remote.py回归测试 tests/test_port_detection.py。【免费下载链接】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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进