数据截至 (上游 commit 5d92feea9f1e)
ScaleCUA — 跨平台计算机使用智能体(CUA):架构与原理
30 秒导读: ScaleCUA 是一个开源的「计算机使用智能体(CUA)」框架。你给它一句话任务(比如「把这份表格里的销售额求和」),它像人一样看屏幕截图、想下一步、点鼠标敲键盘,一步步把任务做完——而且同一套代码同时跑在 Ubuntu 桌面、Android 手机、网页浏览器三种真实界面上。它的核心工程点不是「调模型」,而是「把模型说出来的那句动作,精确落到屏幕上正确的像素」。
1. 这是什么(零基础也能懂)
-
一句话定义: 一个让**视觉语言模型(VLM,能同时看图和读文字的大模型)**去操作真实电脑/手机/网页的自动化框架。
-
解决什么问题 / 给谁用: 假设你想让 AI 帮你在 LibreOffice 里改个单元格、在手机 App 里发条消息、在网页上填个表单。这些界面没有 API,只能靠「看屏幕 + 动鼠标键盘」。ScaleCUA 就是把这条「看→想→动」的闭环工程化,给做 GUI 自动化、CUA 研究、Agent 评测的人用。
-
它能做什么(功能,来自 README):
- 一个跨 6 种操作系统、3 类 GUI 任务域的大规模数据集(ScaleCUA-Data)。
- 训练好的跨平台通用 Agent 模型(ScaleCUA-Models)。
- 基于 Qwen2.5-VL / InternVL 的训练代码(
agent-sft/)。 - 可交互的 Playground(Ubuntu / Android / Web 真实环境)。
- 一套在线评测基准(OSWorld、AndroidWorld、WebArena-Lite-v2 等)。
-
用起来什么样: 在
playground/里跑一条命令,进入一个「问答式」循环——你输入任务,Agent 就在虚拟机/模拟器里自己操作,最多 20 步,边做边截图存证据:# playground/agent_run_interactive.pypython agent_run_interactive.py \--platform ubuntu \--env_config_path config/envs/ubuntu.yaml \--agent_config_path config/agent/scalecua_native_agent.yaml# 然后终端提示 "Query: " —— 输入 "打开 Firefox 并搜索天气" 即可 -
一句话直觉/类比: 把 VLM 当大脑(看截图、决定「点哪个按钮」),把 ScaleCUA 当神经+手脚——它负责把大脑那句模糊的「点登录按钮」翻译成一次精确的
pyautogui.click(x=1723, y=486),并且不管手脚长在电脑、手机还是浏览器上,大脑说的话都不用变。
本节到此,不碰底层细节。记住一句话就够:模型负责「想」,ScaleCUA 负责「把想法精确落到真实屏幕上」。
2. 顶层全景(它大概怎么转)
2.1 全景闭环图
这是一次「一步操作」的数据流。从左到右是一个循环:截图进 → 模型出动作 → 落到屏幕 → 屏幕变 了再截图,循环最多 20 次直到 terminate。
┌────────────────── 主循环(最多 20 步)◀─────────────────┐
│ │
任务指令 ▼ │
"求和这列" ──▶ ① 环境 Env ──截图 bytes──▶ ② Agent(大脑)──▶ ③ 归一化动作 ──▶ ④ 环境执行
(跨平台) VLM 看图+想 {name,x∈[0,1]} 反归一化→真实像素
▲ │ → pyautogui/adb/playwright
└──────────────── 屏幕状态改变,进入下一步 ◀────────────────┘
怎么读这张图: ②「大脑」有两种搭法(见 §3),但对①④环境层完全透明——环境只认第③ 步那个「归一化到 [0,1] 的标准动作」。这就是整个框架能跨平台的关键:动作格式统一,坐标与平台无关。
2.2 部件一句话职责
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
主循环 run_agent | 反复「截图→predict→执行」最多 20 步,存截图/动作/录像 | playground/agent_run_interactive.py:49 |
| Agent(大脑) | 看截图 + 历史,产出「想法 + 动作」 | playground/agents/native_agent.py:18、agentic_workflow.py:14 |
| UI 定位器 Grounder | 把「点登录按钮」这类描述变成一个坐标点 | playground/agents/ui_grounding.py:27 |
| VLM 引擎 | 组装消息、调 OpenAI/Anthropic/Azure/vLLM/HF 后端 | playground/core/mllm.py:19、core/engine.py:17 |
| 环境层 Env | 收归一化动作 → 反归一化 → 打到真实平台 | playground/envs/base_env.py:12 及各平台子类 |
| 动作空间 / 提示词 | 定义模型「能做哪些动作」的契约(以 Python 函数签名写在系统提示里) | playground/config/prompt_template/ours_ubuntu.json |
2.3 主线走一遍(高层)
- 输入: 一句任务 + 平台配置(
--platform ubuntu)。主循环先env.get_obs()拿到一张屏幕截图(PNG 字节流,agent_run_interactive.py:60)。 - 想:
agent.predict(instruction, observation)把截图 + 任务 + 历史喂给 VLM,模型回一段带<action>click(x=..., y=...)</action>的文本(native_agent.py:70)。 - 归一化: Agent 把模型输出的像素坐标除以定位尺寸,压成
[0,1]的小数(native_agent.py:98-104)——从此坐标与具体分辨率无关。 - 落点:
env.step(code)里,环境把[0,1]乘回真实屏幕宽高得到真实像素(ubuntu_env.py:281-284),再翻译成该平台的原生调用:Ubuntu 走pyautogui、Android 走 adb、Web 走 playwright。 - 循环: 屏幕变化后再截图,回到第 2 步;直到模型给出
terminate或跑满 20 步(agent_run_interactive.py:58-101)。
目标:看懂「大盘」——一切都绕着「归一化坐标 + 统一动作」这条中轴转。
3. 巧妙之处(这套设计妙在哪)
这里只点「精华」,展开的原理与源码走读在各章。
-
坐标契约把「跨平台」变成一句话。 模型永远只输出
[0,1]的相对坐标,环境永远负责乘回自己的真实分辨率。于是同一个模型输出,不改一个字就能落到 1920×1080 的桌面、任意尺寸的手机、任意视口的网页。归一化断言写死在base_env.onScreen(envs/base_env.py:30-37),反归一化各平台自己做(如ubuntu_env.py:281-284)。→ 统一动作空间与坐标契约、定位落点:smart_resize 与坐标反归一化 -
动作空间「写成 Python 函数签名」直接塞进系统提示。
click/scroll/press/hotkey/write/wait/terminate...这些动作不是硬编码在解析器里,而是以带 docstring 的函数定义写在提示词 JSON 里(config/prompt_template/ours_ubuntu.json的sys_prompt_planning_cot),模型照着「写代码」的方式产出动作。改动作集只改提示词,不改框架。→ 统一动作空间与坐标契约 -
一份 Agent 接口,两种大脑搭法。
NativeAgent(一个模型同时规划+定位)和AgenticWorkflow(强规划模型 + 专用定位模型)对主循环暴露同一个predict(),主循环靠配置文件里有没有ui_grounding_model自动二选一(agent_run_interactive.py:140-169)。想用 GPT-4o 当规划、ScaleCUA 当「眼睛」也行。→ 两种推理模式 -
smart_resize:先把图缩到模型的「视觉分辨率」,再据此反算坐标。 模型看的其实是被缩放(长宽都对齐到 28 的倍数、总像素卡在区间内)后的图,所以它吐的坐标也在那个缩放空间里;smart_resize复现同样的缩放尺寸,才能把坐标正确除回[0,1](agents/utils.py:39-69)。这是「落点准不准」的关键一环。→ 定位落点:smart_resize 与坐标反归一化 -
VLM 引擎抽象掉了「五种后端 + 三种消息格式」。 OpenAI、Anthropic、Azure、vLLM、HuggingFace 各有各的图片/消息封装,
VLMEngine.add_message按引擎类型分派不同的 payload 结构(core/mllm.py:108-252),上层 Agent 完全无感。本地部署(vLLM)和云 API 一键切换。→ 运行时底座
4. 阅读地图(建议顺序)
按「地基 → 大脑 → 落点 → 手脚 → 底座」由浅入深读:
- 先读 统一动作空间与坐标契约(一切的地基) —— 弄懂「动作长什么样、坐标为什么是
[0,1]」。这是理解后面一切的前提。 - 再读 两种推理模式:原生单模型 vs 规划器+定位器 —— 两种「大脑」怎么搭、
predict()里各自怎么走。 - 然后 定位落点:smart_resize 与坐标反归一化 —— 「点得准」背后的缩放数学与坐标解析。
- 接着 跨平台环境层:一份动作 → pyautogui / adb / playwright —— 同一个归一化动作在三个平台上分别怎么执行。
- 最后 运行时底座:VLM 多后端引擎、消息 组装与主循环 —— 引擎抽象、消息拼装、20 步主循环的工程细节。
想快速判断相关性:任务涉及动作格式/坐标看 §01、规划器与定位器看 §02、点不准/缩放看 §03、某个平台怎么执行看 §04、换模型后端/主循环看 §05。
5. 代码地图(导航索引)
用符号名 grep 比行号更抗漂移。以下是理解这个子库该打开的关键文件与符号。
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 交互主循环(截图→predict→执行,20 步) | playground/agent_run_interactive.py:49 | run_agent |
| 按配置二选一装配 Agent | playground/agent_run_interactive.py:106 | main(判断 ui_grounding_model) |
| 原生单模型 Agent | playground/agents/native_agent.py:18 | NativeAgent.predict |
| 规划器+定位器 Agent | playground/agents/agentic_workflow.py:14 | AgenticWorkflow.predict |
| UI 定位器(描述→坐标点) | playground/agents/ui_grounding.py:27 | UIGroundingAgent.generate_coords |
| 图像缩放(视觉分辨率对齐) | playground/agents/utils.py:39 | smart_resize |
| 从模型输出里解析坐标点 | playground/agents/utils.py:72 | parse_point_from_string |
动作文本解析成 {name,parameters} | playground/agents/native_agent.py:142 | NativeAgent.parse_action |
| VLM 引擎(消息组装 + 分派后端) | playground/core/mllm.py:19 | VLMEngine.add_message |
| 五种模型后端(OpenAI/Anthropic/Azure/vLLM/HF) | playground/core/engine.py:17 | LMMEngineOpenAI 等 |
| 带重试的安全调用 | playground/utils/common_utils.py:27 | call_llm_safe |
| 环境基类 + 动作契约(归一化断言) | playground/envs/base_env.py:12 | BaseEnv.onScreen、agent_action |
| Ubuntu 执行(反归一化 → pyautogui) | playground/envs/ubuntu/ubuntu_env.py:271 | UbuntuEnv.execute_single_action |
| Web 执行(playwright) | playground/envs/web/web_env.py:669 | WebEnv.execute_single_action |
| Android 执行(转发到 adb 环境 API) | playground/envs/android/android_env.py:286 | AndroidEnv.execute_single_action |
| 动作空间/提示词契约(以函数签名书写) | playground/config/prompt_template/ours_ubuntu.json | sys_prompt_planning_cot / sys_prompt_grounding |
| 两种模式的配置样例 | playground/config/agent/ | scalecua_native_agent.yaml / scalecua_agentic_workflow.yaml |
说明:本文所有引用锚定在 commit
5d92feea9f1e14b8303ce37da45b286fb1f4d3aa。agent_run_interactive.py:13里from agents.framework_agent import AgenticWorkflow的模块名与实际文件agents/agentic_workflow.py不一致(agentic_workflow 才是AgenticWorkflow的定义处),读代码时以类名为准。