跳到主要内容

Cua — 架构与原理

30 秒导读: Cua 让你写一个能“用电脑”的 AI agent——它截屏、点按钮、打字、完成任务。你只给它一个模型名(claude-...computer-use-preview...+gemini/...)和一台“电脑”(本地 VM、云沙箱、容器),框架就自动挑对应的驱动逻辑,跑“看截图 → 模型出动作 → 执行 → 再截图”的循环。核心价值是模型无关:换模型只改一个字符串,循环、工具、回调全不动。

本卷文档讲的是 Python agent 框架(libs/python/agent)和它依赖的运行时(libs/python/computer + computer-server)。这是整个 monorepo 里“agent-runtime”最核心的两块。VM 虚拟化(Lume)、基准测试(cua-bench)、后台桌面驱动(cua-driver)是兄弟子系统,见 §6 边界。


1. 这是什么(零基础也能懂)

一句话定义: Cua 是一个 computer-use agent 框架——让大模型像人一样操作一台电脑(看屏幕、移动鼠标、敲键盘),并把这套能力做成“换模型只改一行”的统一接口。

“computer use”是什么? 普通 LLM 只会输出文字。computer-use 模型(如 Anthropic 的 claude computer-use、OpenAI 的 computer-use-preview)被训练成:给它一张屏幕截图 + 任务,它回你一个动作(“在 (412, 380) 左键单击”“输入 hello”)。Cua 负责把这个动作真正执行,然后把执行后的新截图喂回去,如此往复直到任务完成。

解决什么问题 / 给谁用:

  • 假设你想让 AI 自动在网页上订机票在 Excel 里填表在陌生 App 里点一串菜单——这些没有 API,只能靠“看屏幕、动鼠标”。
  • 不同模型的“动作格式”各不相同(OpenAI 一套、Anthropic 一套、开源 VLM 又一套),沙箱也五花八门(本地 QEMU、云端、Docker 容器)。Cua 把这两层差异都抹平。

它能做什么:

  • 20+ 种模型(闭源 computer-use、开源 grounding 模型、本地 HuggingFace/MLX 模型)接到同一个 ComputerAgent 接口下。
  • 任意 OS 的沙箱(Linux 容器、Linux/macOS/Windows VM、Android)里执行动作,动作空间统一。
  • 提供一套可插拔回调:预算上限、只保留最近 N 张图、PII 脱敏、轨迹保存、遥测——都不用改主循环。

用起来什么样: 一个最小例子——给 agent 一台沙箱和一个任务,异步迭代它产生的事件:

# 示意,非源码(基于 libs/python/agent/cua_agent/example.py 的真实 API)
from cua_agent import ComputerAgent
from computer import Computer

# 一台 Linux 沙箱当“电脑”
computer = Computer(os_type="linux", provider_type="docker")

agent = ComputerAgent(
model="claude-sonnet-4-5-20250929", # 只改这个字符串就能换模型
tools=[computer], # 把“电脑”作为工具交给它
max_trajectory_budget=5.0, # 花到 $5 就停
)

async for event in agent.run("打开浏览器,搜索 'cua github'"):
for item in event["output"]:
print(item["type"]) # message / computer_call / computer_call_output ...

一句话直觉/类比: 把 Cua 想成汽车的“驾驶适配层”——方向盘和踏板(动作)的位置每台车都不一样(每个模型/每个 OS),Cua 在中间装了万向接头:无论你换什么“引擎”(模型)或什么“底盘”(沙箱),驾驶动作(看路→打方向→踩油门)的循环逻辑不变。

本节不碰代码细节。记住一句话就够:Cua = 「模型说动作」与「电脑执行动作」之间的标准化循环。


2. 顶层全景(它大概怎么转)

整个系统分三层:入口(ComputerAgent)→ 大脑(agent loop)→ 手脚(Computer 运行时)。一条任务就是在这三层之间转圈。

你的代码
│ agent.run("订张机票")

┌─────────────────────────────────────────────────────────────┐
│ ComputerAgent (agent.py) —— 入口 + 主循环 + 生命周期钩子 │
│ │
│ ① 按模型名选 loop ──────────► find_agent_config(model) │
│ ② while 未结束: (decorators.py 正则注册表) │
│ ├─ 回调预处理 messages ◄── callbacks/ (裁图/PII/预算) │
│ ├─ loop.predict_step() ──► ┌──────────────────────┐ │
│ │ (问模型要动作) │ agent loop (大脑) │ │
│ │ │ loops/anthropic.py 等 │ │
│ │ ◄── 返回 computer_call ──│ 把模型原生格式 ⇄ 统一 │ │
│ ├─ _handle_item() 执行动作 │ Responses 事件格式 │ │
│ │ │ └──────────────────────┘ │
│ │ ▼ │
│ │ computer_handler.click(x,y) ──► ┌─────────────────┐ │
│ │ │ Computer 运行时 │ │
│ │ ◄──── 新截图(base64) ──────────│ (手脚) │ │
│ └─ 回调后处理 + 把截图喂回 messages │ computer/ + │ │
│ │ computer-server │ │
└────────────────────────────────────────────└─────────────────┘──┘

怎么读这张图: 从上往下是一次 run();②里的 while 是“看-想-做”循环,每转一圈问一次模型、执行一批动作、截一张新图喂回去,直到模型说“做完了”(返回里出现 role: assistant 的纯文字消息)。

部件一句话职责:

部件干什么在哪个文件
ComputerAgent入口类:选 loop、跑主循环、执行动作、串联回调agent/cua_agent/agent.py
agent loop(20+ 个)“大脑”:把统一格式 ⇄ 某模型的原生 API,问模型要下一步动作agent/cua_agent/loops/*.py
@register_agent 注册表用正则把模型名映射到对应 loop,带优先级agent/cua_agent/decorators.py
callbacks横切关注点(预算/裁图/PII/遥测/轨迹)做成钩子agent/cua_agent/callbacks/*.py
AsyncComputerHandler把统一动作(click/type)适配到具体电脑接口agent/cua_agent/computers/*.py
Computer SDK启动/连接一台沙箱,暴露底层接口(left_click 等)computer/computer/computer.py
computer-server跑在沙箱内部的服务器,真正执行鼠标键盘computer-server/computer_server/main.py

主线走一遍(高层):

  1. ComputerAgent(model=...) —— 构造时用 find_agent_config 按模型名选定一个 loop,并自动塞入内置回调。
  2. agent.run("任务") —— 进入 while 循环。
  3. 每一圈:回调预处理消息 → loop.predict_step() 问模型要动作 → _handle_item() 执行动作(经 computer_handler 落到沙箱)→ 截新图 → 拼回消息。
  4. 模型输出一条纯文字 assistant 消息(没有更多动作)时,循环结束。

统一格式是关键: 所有 loop 对外都说同一种“话”——OpenAI Responses API 的事件项(message / computer_call / computer_call_output / function_call)。每个 loop 内部负责把它翻成自己模型的原生格式、再翻回来。这就是“换模型不改主循环”的秘密(见 02 章)。


3. 核心原理(去哪读)

这个项目机制多,拆成了 5 章,由浅入深:

  1. 01-agent-loop.md — 先看懂主循环 run() 一圈圈怎么转、动作怎么执行、截图怎么喂回。这是骨架。
  2. 02-loop-registry.md — 模型名 → loop 的正则注册表 + 优先级。理解“为什么改个字符串就换了大脑”。
  3. 03-composed-grounded.md — 全库最妙的设计:planning模型+grounding模型 组合,前者说“点红按钮”,后者把描述变坐标。
  4. 04-callbacks.md — 预算/裁图/PII/遥测/轨迹全做成回调钩子,主循环只管在固定生命周期点广播事件。
  5. 05-computer-runtime.md — 动作如何真正落地:Computer SDK + 沙箱内的 computer-server,以及统一动作空间如何跨 OS。

建议顺序:01 → 02 → 03 → 04 → 05。只想懂“它凭什么模型无关”就读 01+02+03。


6. 边界与局限(诚实)

  • 本卷只覆盖 Python agent + 运行时。 Lume(Swift 写的 Apple Virtualization.Framework 虚拟化,libs/lume)、Lumier(Docker 化的 Lume,libs/lumier)、cua-bench(基准/RL 环境,libs/cua-bench)、cua-driver(后台桌面驱动)、TypeScript 移植(libs/typescript)都未在此逐机制展开
  • 环境检测是写死的。 cuaComputerHandler.get_environment() 直接 return "linux",源码里标着 # TODO: detect actual environment(agent/cua_agent/computers/cua.py:28-31)。
  • 安全检查目前是放行的。 主循环里 pending_safety_checks 被无条件 acknowledged,真正的“弹窗确认”回调还是 TODO(agent/cua_agent/agent.py:807-817)。
  • Ollama 不支持图像输入,主循环有专门的守卫直接报错(agent/cua_agent/agent.py:981-1008)。
  • 每一步都要一张截图,token/带宽开销大;ImageRetentionCallback 只能缓解(只留最近 N 张),不能消除。

7. 横向对比(同 shelf 兄弟)

  • vs. 编码 agent(如 aider/letta 这类“改文件”的 agent): 那些 agent 的“手脚”落到代码文件(模糊匹配 + 替换);Cua 的手脚落到整台电脑的 GUI(坐标 + 鼠标键盘)。同样是“把模型说的话精确落到真实目标”,只是目标域不同。
  • vs. 纯 browser agent: Cua 也能只驱动浏览器(tool_type="browser" 的 loop,如 FARA、Yutori),但它的全集是整个桌面,浏览器只是其中一种环境。
  • 设计取舍: Cua 选择用 OpenAI Responses 事件格式作为内部通用语,让每个模型 loop 各自做双向翻译——代价是每加一个模型要写一个 loop,收益是主循环和回调永久稳定。

8. 代码地图(导航索引)

主题文件路径符号名
入口类 + 主循环agent/cua_agent/agent.pyComputerAgent, ComputerAgent.run
执行单个动作/工具agent/cua_agent/agent.pyComputerAgent._handle_item
选 loopagent/cua_agent/decorators.pyfind_agent_config, register_agent
loop 协议agent/cua_agent/loops/base.pyAsyncAgentConfig
组合 groundingagent/cua_agent/loops/composed_grounded.pyComposedGroundedConfig
回调基类agent/cua_agent/callbacks/base.pyAsyncCallbackHandler
动作适配器agent/cua_agent/computers/cua.pycuaComputerHandler
沙箱 SDKcomputer/computer/computer.pyComputer, Computer.run
沙箱内服务器computer-server/computer_server/main.pywebsocket_endpoint