第17章 Computer Use + GUI Agent —— AI 操控电脑

17.1 为什么需要 Computer Use?

传统 Agent 的局限:Agent 只能调 API → 但世界上绝大多数软件没有 API!

  • 企业内部的遗留系统
  • 桌面软件(Photoshop、Excel)
  • 图形化界面的 SaaS 工具

Computer Use 的突破:

Agent 不再需要对方提供 API

它直接「看屏幕 → 分析画面 → 控制鼠标键盘」

就像人类一样和任何软件交互

类比:

传统 Agent = 只能打电话的人(必须对方有号码)

Computer Use = 能走进办公室的人(可以和任何人面对面交流)

17.2 Screenshot-Action Loop ------ 核心循环

这是所有 Computer Use 系统的底层逻辑:

📊 架构示意

复制代码
  ┌──────────────────────────────────────────────────┐
  │                                                    │
  │  1. 📸 Screenshot: 截取当前屏幕画面                 │
  │         │                                          │
  │         ▼                                          │
  │  2. 👁️ Analyze: LLM 视觉分析画面                    │
  │     - 识别窗口、按钮、文本框                        │
  │     - 读取屏幕上的文字内容                          │
  │     - 理解当前界面的状态                            │
  │         │                                          │
  │         ▼                                          │
  │  3. 🤔 Decide: 决定下一步操作                       │
  │     - 应该点击哪里?                                │
  │     - 应该输入什么?                                │
  │     - 是否需要滚动?                                │
  │         │                                          │
  │         ▼                                          │
  │  4. 🖱️ Execute: 执行操作                            │
  │     - mouse_move(x, y)                             │
  │     - left_click()                                 │
  │     - type("文本")                                 │
  │     - scroll(direction)                            │
  │     - key_press("Enter")                           │
  │         │                                          │
  │         ▼                                          │
  │     回到步骤 1(直到任务完成)                       │
  │                                                    │
  └──────────────────────────────────────────────────┘

关键挑战:像素坐标的精确计算

问题:LLM 需要输出 「点击 (450, 200)」这样的坐标

但 LLM 是文本模型,不理解像素

Anthropic 的解决方案(训练阶段):

专门训练 Claude 精确计数像素的能力

"Training Claude to count pixels accurately was critical.

Without this skill, the model finds it difficult to give mouse commands."

实操中的坐标系统:

  • 截图尺寸通常是 1280x800 或 1920x1080
  • LLM 返回的坐标需要缩放到实际屏幕分辨率
  • 返回格式:(x_pct, y_pct) 百分比比绝对像素更稳健

17.3 Anthropic Computer Use vs OpenAI CUA

📊 架构示意

复制代码
┌──────────────┬─────────────────────────┬─────────────────────────┐
│     维度      │  Anthropic Computer Use  │   OpenAI CUA            │
├──────────────┼─────────────────────────┼─────────────────────────┤
│ 发布时间      │ 2024.10 (API)            │ 2025.01 (Operator)       │
│             │ 2025.10 (正式发布)        │                         │
│ 操作范围      │ 整个操作系统              │ 浏览器内(虚拟浏览器)    │
│ 模型          │ Claude 3.5+ Sonnet      │ GPT-4o (CUA 微调版)     │
│ 环境          │ 用户真实桌面/Docker      │ 安全的虚拟浏览器环境      │
│ 安全性        │ 依赖使用者自行沙箱         │ 平台内置安全隔离         │
│ 动作类型      │ 鼠标+键盘+截图            │ 浏览器操作(点击/输入/滚动)│
│ 成本          │ 截图Token昂贵            │ 浏览器操作Token消耗较低   │
│ Benchmark    │ OSWorld 14.9%            │ 未公布独立评分           │
└──────────────┴─────────────────────────┴─────────────────────────┘

Anthropic 的设计哲学:「给 Claude 真实的电脑,让它按人类的方式工作」

OpenAI 的设计哲学:「给 GPT-4o 一个安全沙箱,专注于 Web 任务」

选型建议:

  • 需要控制桌面软件 → Anthropic Computer Use
  • 只需要浏览器操作 → OpenAI CUA
  • 想要完全控制 → Anthropic + Docker 沙箱

17.4 性能数据与局限

OSWorld Benchmark 成绩:

📊 架构示意

复制代码
  ┌──────────────────┬───────────┐
  │      系统         │   得分     │
  ├──────────────────┼───────────┤
  │ 人类              │   75.0%   │
  │ Claude 3.5 Sonnet │   14.9%   │
  │ GPT-4V            │    7.8%   │
  └──────────────────┴───────────┘

→ Claude 翻倍了前最好成绩,但离人类还很远

延迟:

  • 每个动作 3-4 秒(截取→分析→执行)
  • 10步任务 = 30-40秒
  • 对比 Selenium 的 0.1秒/步,差距巨大

成本:

  • 每张 1080p 截图消耗约 1500 tokens
  • 每分钟成本约 $0.10-0.30
  • 对比 API 调用的 $0.001/分钟,贵 100 倍

当前定位(2025-2026):

→ 不是 Selenium 的替代品

→ 适合「API 无法覆盖的长尾场景」

→ 适合「快速原型验证」

→ 生产级自动化仍需传统方案

17.5 安全沙箱 ------ 必须学!

给 AI 鼠标键盘的权限 = 极高的安全风险:

✗ 读取屏幕上的密码

✗ 复制敏感数据

✗ 误操作删除文件

✗ Prompt Injection 利用 AI 执行危险命令

安全措施(必须!):

  • Docker 容器隔离

    docker run -d \

    --security-opt=no-new-privileges \

    --cap-drop=ALL \

    --network=none \

    --read-only \

    computer-use-sandbox

  • 操作系统级隔离

  • 非管理员用户

  • 只读挂载关键目录

  • 网络访问白名单

  • 操作确认(Human-in-the-Loop)

  • 危险操作需用户确认

  • 大额交易/删除文件 → 二次确认

  • 审计日志

  • 记录每一次鼠标点击和键盘输入

  • 可追溯所有操作

Anthropic 官方建议:

"Always sandbox in Docker containers with limited permissions.

Never run with admin privileges."

17.5.1 坐标系统工程 ------ 为什么 LLM 总是点不准?

▍ 坐标转换:从 LLM 输出到屏幕像素

LLM 输出的坐标通常是「归一化坐标」或「基于截图分辨率的坐标」。

但实际屏幕分辨率可能不同 → 需要坐标缩放。

问题链:

截图 1280x800 → LLM 分析 → 输出「点击 (640, 400)」→ 但实际屏幕是 2560x1600→ 如果直接发 (640, 400),点到错误位置!

标准做法:

a) 发给 LLM 的截图使用固定分辨率(如 1280x800)

b) LLM 输出坐标基于 1280x800

c) 执行前做坐标缩放:x_real = x_llm × (screen_width / screenshot_width)

d) 使用百分比坐标更稳健:(x_pct, y_pct) 而非绝对像素

▍ 为什么 LLM 的坐标会有 ±5px 的误差?

这是 Computer Use 的一个核心难点:「让文本模型理解空间坐标」。

Anthropic 专门训练了 Claude 的「像素计数能力」,但仍然存在天然误差:

  • 小按钮(20x20px)→ 5px 误差可能点到按钮外
  • 密集列表 → 可能点到相邻项
  • 动态 UI(动画中的元素)→ 坐标过了检测时已经变了

工程应对:

a) 坐标容错 ------ 点击后截图验证,不符预期则微调坐标重试

b) 区域点击代替点点击 ------ 「点击按钮中心区域」而非精确坐标

c) 元素描述作为主导航 ------ 「点击 'Login' 按钮」→ 先用 OCR 定位

▍ 截图分辨率的经济学

分辨率越高 → LLM 的坐标越精准 → 但 Token 成本越高

分辨率越低 → 成本低 → 但可能 LLM 看错按钮

最佳实践(Anthropic 推荐):

  • 日常操作:1024x768 → 约 800 tokens/图
  • 需要细节:1280x800 → 约 1200 tokens/图
  • 文本密集(代码、表格):1920x1080 → 约 2000 tokens/图

动态分辨率策略:

第一轮 → 低分辨率 1024x768(快速判断页面状态)

发现无法识别 → 升级到 1280x800

仍然需要细节 → 局部截图 500x500(放大特定区域)

17.5.2 沙箱深度 ------ 不是「run docker」就完了

▍ 多层安全隔离(从外到内 4 层)

第1层:容器隔离 ------ Docker/VM 层

禁用网络(--network=none)、只读根文件系统(--read-only)、

限制 CPU/Memory、丢弃所有 Linux Capabilities

第2层:用户隔离 ------ 容器内部跑非 root 用户

USER agent(非 root),家目录只读

授予的目录要白名单管理,不是黑名单

第3层:动作白名单 ------ 不是所有键盘组合都允许

禁止: Ctrl+Alt+Del、Win+R、rm -rf、格式化命令

允许: 文本输入、鼠标移动、窗口切换

第4层:结果审查 ------ Agent 操作完的截图交给另一个 LLM 审核

检查:是否打开了敏感页面?是否输入了敏感内容?

这是最后一道防线

▍ 成本优化 ------ Computer Use 的「省钱三板斧」

  • 缓存重复截图 ------ 同一个页面多次截取 → 对比 hash,相同则复用 LLM 分析结果
  • 最小化截图区域 ------ 不全屏截图,只截需要操作的应用窗口
  • 混合自动化 ------ 能用 API 的操作用 API(便宜 100 倍),

只在 API 无法覆盖时启动 Computer Use

面试可以提:「我们不是用 Computer Use 替代 Selenium,而是用 Computer Use

覆盖 Selenium 覆盖不到的 5% 的长尾操作场景。」

17.6 模拟 Computer Use Agent

下面实现一个 ComputerUseAgent 模拟器,模拟 Claude 的「截图→分析→动作」

核心循环。真实的 Computer Use 每次迭代需要传入 desktop screenshot 的 base64

给 Claude Vision,这里用描述文字替代。关键保留了三阶段:环境感知、动作决策、

执行反馈,以及坐标计算的容错逻辑。

📝 对应的代码实现

add_elementfind_element_atdescribeanalyze_screendecide_actionexecute_actionrun_taskdemo_computer_useVirtualScreenComputerUseAgent

复制代码
import time
import json
from typing import Optional


class VirtualScreen:
    """模拟计算机屏幕 ------ 用作 Computer Use 的目标环境。"""

    def __init__(self, width: int = 1280, height: int = 800):
        self.width = width
        self.height = height
        self.elements = {}  # 屏幕上的 UI 元素 {name: (x, y, w, h)}

    def add_element(self, name: str, x: int, y: int,
                    w: int, h: int, text: str = ""):
        """添加一个 UI 元素(按钮/输入框/文本)。"""
        self.elements[name] = {
            "x": x, "y": y, "w": w, "h": h, "text": text,
        }

    def find_element_at(self, click_x: int, click_y: int) -> Optional[str]:
        """根据坐标查找被点击的元素。"""
        for name, elem in self.elements.items():
            if (elem["x"] <= click_x <= elem["x"] + elem["w"] and
                    elem["y"] <= click_y <= elem["y"] + elem["h"]):
                return name
        return None

    def describe(self) -> str:
        """生成屏幕描述(模拟 LLM 视觉分析的结果)。"""
        lines = [f"屏幕分辨率: {self.width}x{self.height}"]
        for name, elem in self.elements.items():
            lines.append(
                f"  [{name}] 位置({elem['x']},{elem['y']}) "
                f"大小({elem['w']}x{elem['h']}) 文本:「{elem['text']}」"
            )
        return "\n".join(lines)


class ComputerUseAgent:
    """Computer Use Agent ------ 模拟完整的 Screenshot-Action Loop。

    核心循环:
      while not done:
          screenshot → analyze → decide → execute → observe
    """

    def __init__(self):
        self.action_log = []
        self.max_actions = 10

    def analyze_screen(self, screen: VirtualScreen) -> dict:
        """模拟 LLM 分析屏幕截图的结果。

        真实实现中,这一步由 Claude 的视觉能力完成:
          - 上传截图(base64)到 Claude API
          - Claude 返回对界面元素的分析和下一步操作的建议

        Args:
            screen: 虚拟屏幕对象。

        Returns:
            分析结果。
        """
        description = screen.describe()
        return {
            "resolution": f"{screen.width}x{screen.height}",
            "elements_found": len(screen.elements),
            "description": description,
        }

    def decide_action(self, task: str,
                      screen: VirtualScreen) -> Optional[dict]:
        """模拟 LLM 决定的下一步操作。

        真实实现中,Claude 返回结构化的工具调用:
          tool: "computer"
          action: {"type": "left_click", "x": 450, "y": 200}

        这里用简化的规则模拟:
          根据任务关键词匹配屏幕上的元素。

        Args:
            task: 任务描述。
            screen: 当前屏幕。

        Returns:
            操作指令字典。
        """
        for name, elem in screen.elements.items():
            if name.lower() in task.lower():
                # 计算元素中心坐标
                cx = elem["x"] + elem["w"] // 2
                cy = elem["y"] + elem["h"] // 2
                return {
                    "type": "left_click",
                    "x": cx,
                    "y": cy,
                    "target": name,
                    "reasoning": f"找到了匹配元素 '{name}',点击其中心({cx}, {cy})",
                }

        return {
            "type": "type_text",
            "text": task,
            "target": "search_box",
            "reasoning": "未找到匹配按钮,尝试搜索",
        }

    def execute_action(self, action: dict,
                       screen: VirtualScreen) -> dict:
        """执行操作并返回结果。

        Args:
            action: 操作指令。
            screen: 当前屏幕。

        Returns:
            执行结果。
        """
        action_type = action["type"]
        result = {"success": True, "action": action}

        if action_type == "left_click":
            target = screen.find_element_at(action["x"], action["y"])
            result["clicked"] = target or "空白区域"
            if target is None:
                result["success"] = False
                result["error"] = "未找到可点击的元素"

        elif action_type == "type_text":
            result["input"] = action["text"]

        elif action_type == "scroll":
            result["scroll"] = action.get("direction", "down")

        self.action_log.append(result)
        return result

    def run_task(self, task: str, screen: VirtualScreen) -> dict:
        """运行完整的 Computer Use 任务。

        Args:
            task: 任务描述。
            screen: 虚拟屏幕环境。

        Returns:
            包含执行记录的结果。
        """
        print(f"\n  🎯 任务: {task}")
        print(f"  📺 {screen.describe()}\n")

        for step in range(1, self.max_actions + 1):
            print(f"  --- Step {step} ---")

            # 1. 分析屏幕
            analysis = self.analyze_screen(screen)
            print(f"  👁️  分析: 发现 {analysis['elements_found']} 个元素")

            # 2. 决策
            action = self.decide_action(task, screen)
            if action is None:
                print(f"  ✅ 任务完成,无需更多操作")
                break
            print(f"  🤔 决策: {action['reasoning']}")

            # 3. 执行
            result = self.execute_action(action, screen)
            status = "✅" if result["success"] else "❌"
            print(f"  🖱️  执行: {status} {action['type']} → {result.get('clicked', result.get('input', ''))}")

            # 4. 判断是否完成
            if action.get("target") and result["success"]:
                print(f"  🎉 成功点击目标元素,任务完成!")
                break

            time.sleep(0.3)  # 模拟操作延迟

        return {
            "task": task,
            "steps": len(self.action_log),
            "actions": self.action_log,
        }


def demo_computer_use():
    """演示 Computer Use 的完整流程。"""
    print("=" * 60)
    print("  Computer Use Agent 演示")
    print("=" * 60)

    # 场景1:模拟「登录网页」
    print("\n  ── 场景1:登录网页 ──")
    login_screen = VirtualScreen(1280, 800)
    login_screen.add_element("username_input", 500, 300, 200, 30, "请输入用户名")
    login_screen.add_element("password_input", 500, 350, 200, 30, "请输入密码")
    login_screen.add_element("login_button", 550, 400, 100, 40, "登录")

    agent = ComputerUseAgent()
    result = agent.run_task("点击登录按钮", login_screen)

    # 场景2:模拟「搜索」
    print("\n  ── 场景2:搜索操作 ──")
    search_screen = VirtualScreen(1280, 800)
    search_screen.add_element("search_box", 400, 200, 400, 35, "搜索...")
    search_screen.add_element("search_button", 810, 200, 80, 35, "搜索")
    search_screen.add_element("result1", 400, 300, 500, 50, "结果1: Python教程")
    search_screen.add_element("result2", 400, 360, 500, 50, "结果2: AI Agent 入门")

    agent = ComputerUseAgent()
    result = agent.run_task("点击搜索按钮", search_screen)

17.7 本章总结

核心要点回顾:

  • Computer Use = AI 用人类的方式操作电脑

  • Screenshot-Action Loop: 截图→分析→决策→执行

  • 不依赖 API,可以操作任何软件

  • 核心挑战:像素坐标计算 + 视觉理解

  • 两大阵营

  • Anthropic: 控制真实电脑(通用但危险)

  • OpenAI CUA: 虚拟浏览器(安全但局限)

  • 当前局限性(面试时坦诚讨论)

  • OSWorld 14.9%(人类 75%)------ 还有很长的路

  • 延迟 3-4秒/步,成本 100倍于 API

  • 安全风险高

  • 安全第一

  • Docker 沙箱 + 非管理员 + 只读挂载

  • 操作确认 + 审计日志

  • 「Always sandbox. Never admin.」

面试速记:

"Computer Use 的原理和挑战?"

→ 原理:Screenshot-Action Loop(截图→视觉分析→坐标→操作)

→ 挑战:像素坐标精确度、延迟、安全风险

→ 定位:不是替代传统自动化,而是覆盖「API无法触及」的长尾

📝 对应的代码实现

复制代码
import time
import json
from typing import Optional

if __name__ == "__main__":
    print("╔══════════════════════════════════════════════════════╗")
    print("║  第17章:Computer Use + GUI Agent                     ║")
    print("║  Screenshot-Action Loop · 坐标计算 · 安全沙箱        ║")
    print("╚══════════════════════════════════════════════════════╝")

    demo_computer_use()

    print("\n▶ OSWorld Benchmark 成绩")
    print("-" * 50)
    print("  人类                75.0%")
    print("  Claude 3.5 Sonnet  14.9%  (Computer Use)")
    print("  GPT-4V              7.8%  (传统视觉模式)")
    print()
    print("  结论:Computer Use 翻倍了前最好成绩,")
    print("        但离人类水平仍有巨大差距。")

    print("\n▶ Anthropic vs OpenAI Computer Use 对比")
    print("-" * 50)
    comparisons = [
        ("操作范围", "Anthropic: 整个操作系统", "OpenAI: 浏览器内"),
        ("安全性", "Anthropic: 需自行沙箱", "OpenAI: 内置安全隔离"),
        ("适用场景", "Anthropic: 桌面软件+Web", "OpenAI: Web任务"),
        ("成本", "Anthropic: 截图Token较贵", "OpenAI: 操作Token较低"),
    ]
    for dim, a, o in comparisons:
        print(f"  {dim:10s}  {a:30s}  {o}")

    print("\n✅ 第17章完成!")

复制代码