GUI Agent¶
⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。
学习目标:理解GUI Agent的核心概念、技术架构与实现方法,掌握视觉感知、动作规划、工具操作的完整链路,能够构建Web/桌面自动化Agent。
📌 定位说明:对标 hello-agents GUI Agent专题章节,我们在覆盖其核心内容的基础上,增加了最新的多模态GUI Agent架构、实战代码和性能评测方法。
目录¶
- 1. GUI Agent概述
- 2. 视觉感知与UI理解
- 3. 动作空间设计
- 4. 主流GUI Agent架构
- 5. Web自动化Agent实战
- 6. 桌面自动化Agent
- 7. 移动端GUI Agent
- 8. 评测基准与挑战
1. GUI Agent概述¶
1.1 什么是GUI Agent¶
Text Only
GUI Agent vs 传统Agent
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
传统Agent:
用户指令 → LLM推理 → API调用 → 返回结果
特点: 通过结构化API与系统交互
GUI Agent:
用户指令 → 视觉感知(截图) → LLM推理 → UI操作(点击/输入) → 观察结果 → 循环
特点: 像人一样"看"屏幕并"操作"界面
核心差异:
┌──────────────┬─────────────────┬─────────────────┐
│ 维度 │ API Agent │ GUI Agent │
├──────────────┼─────────────────┼─────────────────┤
│ 感知方式 │ 结构化数据 │ 屏幕截图/DOM │
│ 操作方式 │ API调用 │ 鼠标/键盘操作 │
│ 通用性 │ 需要专用API │ 任何有GUI的软件 │
│ 稳定性 │ 高(API不易变) │ 中(UI会改版) │
│ 理解难度 │ 低 │ 高(需视觉理解) │
│ 应用范围 │ 有API的系统 │ 所有GUI软件 │
└──────────────┴─────────────────┴─────────────────┘
1.2 GUI Agent的核心能力¶
Text Only
┌─────────────────────────────────────────┐
│ GUI Agent 能力栈 │
├─────────────────────────────────────────┤
│ │
│ 🔴 任务规划 Task Planning │
│ ├── 分解用户自然语言指令 │
│ ├── 生成多步操作计划 │
│ └── 动态调整策略 │
│ │
│ 🟡 视觉感知 Visual Perception │
│ ├── 屏幕截图理解 │
│ ├── UI元素识别与定位(Grounding) │
│ └── 文字识别(OCR) │
│ │
│ 🟢 动作执行 Action Execution │
│ ├── 鼠标操作(点击/拖拽/滚动) │
│ ├── 键盘输入(打字/快捷键) │
│ └── 等待/验证操作结果 │
│ │
│ 🔵 记忆推理 Memory & Reasoning │
│ ├── 操作历史记录 │
│ ├── 错误恢复与重试 │
│ └── 跨步骤上下文关联 │
│ │
└─────────────────────────────────────────┘
1.3 发展脉络¶
| 阶段 | 代表工作 | 特点 |
|---|---|---|
| 预LLM时代 | Selenium/Appium | 规则驱动,脆弱的定位策略 |
| 早期LLM | WebGPT (2021) | LLM作为决策核心,文本描述UI |
| 视觉LLM | CogAgent (2023) | 多模态直接理解截图 |
| 专用Agent | SeeClick, OS-Atlas (2024) | 专门训练的GUI Grounding模型 |
| 通用框架 | Claude Computer Use, UFO (2024) | 通用操作系统级Agent |
| 最新前沿 | UI-TARS, ShowUI (2025) | 端到端视觉-动作模型 |
2. 视觉感知与UI理解¶
2.1 输入表示方式¶
Text Only
GUI Agent 的三种输入表示:
━━━━━━━━━━━━━━━━━━━━━━━━━━━
方式1: 纯截图 (Screenshot-only)
┌──────────────────┐
│ 📷 屏幕截图 │ → 多模态LLM理解
└──────────────────┘
优点: 通用,不依赖DOM
缺点: 小元素难以识别
方式2: 结构化表示 (DOM/Accessibility Tree)
<button id="submit" class="btn-primary">
Submit Order
</button>
优点: 精确,包含语义信息
缺点: 不是所有应用都有
方式3: 混合表示 (Screenshot + 结构化)
截图 + 标注框 + Accessibility信息
← 目前最佳实践
2.2 Set-of-Mark (SoM) 标注¶
Python
"""
Set-of-Mark: 在截图上标注可交互元素的编号
这使得LLM可以通过编号引用具体元素
"""
from PIL import Image, ImageDraw, ImageFont
import json
class SetOfMarkAnnotator:
"""
SoM标注器:在截图上标注UI元素
"""
def __init__(self):
self.elements = []
def annotate_screenshot(self, screenshot_path: str,
elements: list) -> Image.Image:
"""
在截图上标注可交互元素
Args:
screenshot_path: 截图路径
elements: UI元素列表, 每个元素包含:
- bbox: [x1, y1, x2, y2] 边界框
- type: 元素类型 (button/input/link/...)
- text: 元素文本
Returns:
标注后的图片
"""
img = Image.open(screenshot_path)
draw = ImageDraw.Draw(img)
self.elements = elements
colors = {
'button': '#FF4444',
'input': '#44FF44',
'link': '#4444FF',
'text': '#FFFF44',
'other': '#FF44FF'
}
for i, elem in enumerate(elements):
x1, y1, x2, y2 = elem['bbox']
color = colors.get(elem.get('type', 'other'), '#FFFFFF')
# 绘制边界框
draw.rectangle([x1, y1, x2, y2], outline=color, width=2)
# 绘制编号标签
label = f"[{i}]"
draw.rectangle([x1, y1-18, x1+30, y1], fill=color)
draw.text((x1+2, y1-16), label, fill='white')
return img
def get_element_description(self) -> str:
"""生成元素描述文本,供LLM理解"""
desc = "可交互元素列表:\n"
for i, elem in enumerate(self.elements):
desc += f" [{i}] {elem.get('type', 'unknown')}: "
desc += f"\"{elem.get('text', '')}\" "
desc += f"位置: {elem['bbox']}\n"
return desc
# 使用示例
annotator = SetOfMarkAnnotator()
elements = [
{"bbox": [100, 200, 250, 240], "type": "input", "text": "搜索框"},
{"bbox": [260, 200, 340, 240], "type": "button", "text": "搜索"},
{"bbox": [100, 260, 300, 290], "type": "link", "text": "高级搜索选项"},
]
desc = annotator.get_element_description()
print(desc)
2.3 UI Grounding(元素定位)¶
Python
"""
UI Grounding: 将自然语言描述映射到具体的UI元素坐标
方法1: 基于OCR + 空间推理
方法2: 基于多模态LLM直接预测坐标
方法3: 基于专用grounding模型 (如SeeClick, OS-Atlas)
"""
class UIGrounding:
"""UI元素定位"""
@staticmethod
def ocr_based_grounding(screenshot, target_text: str,
ocr_results: list) -> dict:
"""
基于OCR的元素定位
1. OCR识别所有文字
2. 匹配目标文字
3. 返回对应位置
"""
for result in ocr_results:
if target_text.lower() in result['text'].lower():
return {
'found': True,
'bbox': result['bbox'],
'confidence': result['confidence'],
'center': (
(result['bbox'][0] + result['bbox'][2]) / 2,
(result['bbox'][1] + result['bbox'][3]) / 2
)
}
return {'found': False}
@staticmethod
def build_grounding_prompt(task: str, screenshot_desc: str) -> str:
"""
构造多模态LLM的grounding prompt
让LLM直接输出要操作的坐标
"""
prompt = f"""你是一个GUI操作助手。根据用户任务和屏幕截图,确定需要操作的UI元素位置。
当前屏幕内容:
{screenshot_desc}
用户任务: {task}
请回答:
1. 需要操作的元素是什么?
2. 该元素的坐标位置 [x, y](屏幕中心为参考点)
3. 需要执行的操作类型(click/type/scroll)
4. 如果是输入操作,输入的内容是什么?
以JSON格式回答:
{{"element": "...", "position": [x, y], "action": "...", "value": "..."}}
"""
return prompt
print("UI Grounding核心挑战:")
print("1. 小图标/小按钮的精确定位")
print("2. 动态内容(如弹窗、加载中状态)")
print("3. 多语言界面的OCR准确性")
print("4. 相似元素的区分(多个按钮/链接)")
3. 动作空间设计¶
3.1 标准化动作空间¶
Python
from dataclasses import dataclass
from enum import Enum
from typing import Optional, Tuple
class ActionType(Enum):
"""GUI Agent标准动作类型"""
CLICK = "click" # 单击
DOUBLE_CLICK = "double_click" # 双击
RIGHT_CLICK = "right_click" # 右键
TYPE = "type" # 输入文字
PRESS = "press" # 按键(如Enter, Tab)
HOTKEY = "hotkey" # 快捷键(如Ctrl+C)
SCROLL = "scroll" # 滚动
DRAG = "drag" # 拖拽
WAIT = "wait" # 等待
SCREENSHOT = "screenshot" # 截图(用于观察)
DONE = "done" # 任务完成
FAIL = "fail" # 放弃/失败
@dataclass
class GUIAction:
"""标准化GUI动作"""
action_type: ActionType
position: Optional[Tuple[int, int]] = None # (x, y) 坐标
text: Optional[str] = None # 输入文本
key: Optional[str] = None # 按键名
direction: Optional[str] = None # 滚动方向
end_position: Optional[Tuple[int, int]] = None # 拖拽终点
element_id: Optional[int] = None # SoM元素编号
def to_dict(self):
return {k: v for k, v in self.__dict__.items() if v is not None} # 字典推导+__dict__:过滤None值序列化对象属性
def __str__(self):
if self.action_type == ActionType.CLICK:
if self.position is None:
return "Click (no position)"
return f"Click at ({self.position[0]}, {self.position[1]})"
elif self.action_type == ActionType.TYPE:
pos_str = f"({self.position[0]}, {self.position[1]})" if self.position else "(no position)"
return f"Type '{self.text}' at {pos_str}"
elif self.action_type == ActionType.PRESS:
return f"Press {self.key}"
elif self.action_type == ActionType.SCROLL:
return f"Scroll {self.direction}"
elif self.action_type == ActionType.DONE:
return "Task completed"
return f"{self.action_type.value}: {self.to_dict()}"
# 示例动作序列
actions = [
GUIAction(ActionType.CLICK, position=(150, 220)),
GUIAction(ActionType.TYPE, position=(150, 220), text="Python GUI Agent"),
GUIAction(ActionType.PRESS, key="Enter"),
GUIAction(ActionType.WAIT),
GUIAction(ActionType.CLICK, position=(200, 350), element_id=3),
GUIAction(ActionType.DONE),
]
print("示例动作序列:")
for i, action in enumerate(actions):
print(f" Step {i+1}: {action}")
3.2 动作解析器¶
Python
import re
import json
class ActionParser:
"""
从LLM输出解析GUI动作
支持两种格式:
1. JSON格式: {"action": "click", "position": [100, 200]}
2. 自然语言格式: "Click on the search button at (100, 200)"
"""
@staticmethod
def parse_json(llm_output: str) -> GUIAction:
"""解析JSON格式的动作"""
# 提取JSON
json_match = re.search(r'\{[^{}]+\}', llm_output)
if not json_match:
raise ValueError("No JSON found in output")
try:
data = json.loads(json_match.group())
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in LLM output: {e}")
action_map = {
'click': ActionType.CLICK,
'type': ActionType.TYPE,
'press': ActionType.PRESS,
'scroll': ActionType.SCROLL,
'done': ActionType.DONE,
}
action_type = action_map.get(data.get('action', ''), ActionType.CLICK)
position = tuple(data['position']) if 'position' in data else None
return GUIAction(
action_type=action_type,
position=position,
text=data.get('text'),
key=data.get('key'),
element_id=data.get('element_id'),
)
@staticmethod
def parse_natural_language(text: str) -> GUIAction:
"""解析自然语言描述的动作"""
text = text.lower()
# 提取坐标
coord_match = re.search(r'\((\d+),\s*(\d+)\)', text)
position = tuple(map(int, coord_match.groups())) if coord_match else None # map将字符串坐标转为整数元组,匹配失败返回None
if 'click' in text:
return GUIAction(ActionType.CLICK, position=position)
elif 'type' in text or 'input' in text:
# 提取输入文本
input_match = re.search(r"['\"](.+?)['\"]", text)
input_text = input_match.group(1) if input_match else ""
return GUIAction(ActionType.TYPE, position=position, text=input_text)
elif 'scroll' in text:
direction = 'down' if 'down' in text else 'up'
return GUIAction(ActionType.SCROLL, direction=direction)
elif 'done' in text or 'finish' in text:
return GUIAction(ActionType.DONE)
return GUIAction(ActionType.CLICK, position=position)
# 测试解析
parser = ActionParser()
json_output = '我需要点击搜索按钮 {"action": "click", "position": [260, 220], "element_id": 1}'
action = parser.parse_json(json_output)
print(f"JSON解析: {action}")
nl_output = "Click on the search box at (150, 220)"
action = parser.parse_natural_language(nl_output)
print(f"NL解析: {action}")
4. 主流GUI Agent架构¶
4.1 架构总览¶
Text Only
主流GUI Agent架构对比
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方案A: 纯视觉方案 (CogAgent / SeeClick)
截图 → 多模态LLM → 坐标+动作
✓ 最通用,不需要DOM
✗ 精度受限于视觉分辨率
方案B: DOM + LLM (WebAgent / MindAct)
DOM/HTML → 文本LLM → 元素选择+动作
✓ 精确,利用结构信息
✗ 仅限Web,DOM可能很大
方案C: 混合方案 (SeeAct / UFO)
截图 + DOM → 多模态LLM → 动作
✓ 兼顾通用性和精确性
✗ 信息冗余,上下文长
方案D: Computer Use (Claude / OpenAI)
截图 → 大型多模态模型 → 系统级操作
✓ 最强泛化能力
✗ 成本高,延迟大
4.2 基础GUI Agent框架实现¶
Python
import time
from abc import ABC, abstractmethod
from typing import List, Dict, Any
class GUIAgent:
"""
通用GUI Agent框架
核心循环: Observe → Think → Act → Observe → ...
"""
def __init__(self, llm_client, max_steps: int = 20):
self.llm_client = llm_client # LLM调用接口
self.max_steps = max_steps
self.action_history: List[Dict] = []
self.observation_history: List[str] = []
def run(self, task: str) -> Dict[str, Any]:
"""
执行GUI任务
Args:
task: 用户的自然语言任务描述
Returns:
执行结果字典
"""
print(f"📋 任务: {task}")
print("=" * 60)
for step in range(self.max_steps):
print(f"\n--- Step {step + 1}/{self.max_steps} ---")
# 1. Observe: 获取当前屏幕状态
observation = self._observe()
self.observation_history.append(observation)
print(f"🔍 观察: {observation[:100]}...")
# 2. Think: 让LLM决定下一步动作
action = self._think(task, observation)
print(f"🧠 决策: {action}")
# 3. 检查是否完成
if action.action_type == ActionType.DONE:
print("\n✅ 任务完成!")
return {"status": "success", "steps": step + 1,
"history": self.action_history}
if action.action_type == ActionType.FAIL:
print("\n❌ 任务失败")
return {"status": "failed", "steps": step + 1,
"history": self.action_history}
# 4. Act: 执行动作
self._act(action)
self.action_history.append({
"step": step + 1,
"action": str(action),
"observation": observation[:200]
})
# 等待UI响应
time.sleep(0.5)
print("\n⚠️ 达到最大步数限制")
return {"status": "max_steps", "steps": self.max_steps,
"history": self.action_history}
def _observe(self) -> str:
"""
获取当前屏幕状态
实际实现中:截图 + OCR + 元素检测
"""
# 这里是框架代码,实际需要:
# 1. 截图: pyautogui.screenshot()
# 2. OCR: 识别文字
# 3. UI检测: 识别可交互元素
# 4. SoM标注: 给元素编号
return "当前屏幕状态描述(实际为截图+标注)"
def _think(self, task: str, observation: str) -> GUIAction:
"""
调用LLM进行决策
"""
# 构造prompt
prompt = self._build_prompt(task, observation)
# 调用LLM(伪代码)
# response = self.llm_client.chat(prompt, images=[screenshot])
# action = ActionParser.parse_json(response)
# 示例返回
return GUIAction(ActionType.CLICK, position=(100, 200))
def _act(self, action: GUIAction):
"""
执行GUI操作
实际使用 pyautogui 或 playwright
"""
# import pyautogui
if action.action_type == ActionType.CLICK:
# pyautogui.click(action.position[0], action.position[1])
print(f" ⚡ 执行点击: {action.position}")
elif action.action_type == ActionType.TYPE:
# pyautogui.click(action.position[0], action.position[1])
# pyautogui.typewrite(action.text)
print(f" ⚡ 输入文本: '{action.text}'")
elif action.action_type == ActionType.SCROLL:
# pyautogui.scroll(-3 if action.direction == 'down' else 3)
print(f" ⚡ 滚动: {action.direction}")
def _build_prompt(self, task: str, observation: str) -> str:
"""构造决策prompt"""
history_text = ""
for h in self.action_history[-5:]: # 最近5步历史
history_text += f" Step {h['step']}: {h['action']}\n"
prompt = f"""你是一个GUI自动化Agent。你需要通过操作图形界面来完成用户任务。
## 用户任务
{task}
## 操作历史
{history_text if history_text else " (这是第一步)"}
## 当前屏幕
{observation}
## 可用动作
- click(x, y): 点击指定坐标
- type(x, y, text): 在指定位置输入文本
- press(key): 按下键盘键
- scroll(direction): 上下滚动
- done(): 任务已完成
- fail(): 无法完成任务
## 请决定下一步动作
分析当前屏幕并选择最合适的操作。以JSON格式回答:
{{"thought": "分析推理过程", "action": "click/type/press/scroll/done/fail", "position": [x, y], "text": "可选输入文本"}}
"""
return prompt
# 框架使用示例
print("GUI Agent框架示例:")
agent = GUIAgent(llm_client=None, max_steps=10)
print("Agent已创建,核心循环: Observe → Think → Act")
print("实际运行需要: LLM API + 屏幕操作库(pyautogui/playwright)")
5. Web自动化Agent实战¶
5.1 基于Playwright的Web Agent¶
Python
"""
Web GUI Agent: 基于Playwright实现Web自动化
Playwright优势:
- 支持Chrome/Firefox/Safari
- 内置等待机制
- 可获取DOM和截图
- 支持无头模式
"""
# pip install playwright
# python -m playwright install
class WebGUIAgent:
"""
Web自动化Agent
结合Playwright操作 + LLM决策
"""
def __init__(self, llm_client=None, headless: bool = False):
self.llm_client = llm_client
self.headless = headless
self.browser = None
self.page = None
async def setup(self):
"""初始化浏览器"""
from playwright.async_api import async_playwright
pw = await async_playwright().start()
self.browser = await pw.chromium.launch(headless=self.headless)
self.page = await self.browser.new_page()
async def navigate(self, url: str):
"""导航到指定URL"""
await self.page.goto(url, wait_until='networkidle')
async def observe(self) -> dict:
"""
获取页面状态
"""
# 1. 截图
screenshot = await self.page.screenshot()
# 2. 获取可交互元素
elements = await self.page.evaluate("""() => {
const interactable = document.querySelectorAll(
'a, button, input, select, textarea, [role="button"], [onclick]'
);
return Array.from(interactable).map((el, idx) => {
const rect = el.getBoundingClientRect();
return {
id: idx,
tag: el.tagName.toLowerCase(),
type: el.type || '',
text: (el.textContent || el.value || el.placeholder || '').trim().slice(0, 50),
bbox: [rect.x, rect.y, rect.x + rect.width, rect.y + rect.height],
visible: rect.width > 0 && rect.height > 0
};
}).filter(el => el.visible);
}""")
# 3. 获取页面标题和URL
title = await self.page.title()
url = self.page.url
return {
"screenshot": screenshot,
"elements": elements,
"title": title,
"url": url,
"element_description": self._format_elements(elements)
}
def _format_elements(self, elements: list) -> str:
"""格式化元素描述"""
desc = []
for el in elements[:30]: # 限制数量
text = el['text'][:30] if el['text'] else '(empty)'
desc.append(f" [{el['id']}] <{el['tag']}> {text}")
return "\n".join(desc)
async def execute_action(self, action: dict):
"""
执行Web操作
"""
action_type = action.get('action')
if action_type == 'click':
element_id = action.get('element_id')
if element_id is not None:
elements = await self.page.query_selector_all(
'a, button, input, select, textarea, [role="button"], [onclick]'
)
if element_id < len(elements):
await elements[element_id].click()
elif 'position' in action:
x, y = action['position']
await self.page.mouse.click(x, y)
elif action_type == 'type':
element_id = action.get('element_id')
text = action.get('text', '')
elements = await self.page.query_selector_all('input, textarea')
if element_id is not None and element_id < len(elements):
await elements[element_id].fill(text)
elif action_type == 'press':
key = action.get('key', 'Enter')
await self.page.keyboard.press(key)
elif action_type == 'scroll':
direction = action.get('direction', 'down')
delta = 300 if direction == 'down' else -300
await self.page.mouse.wheel(0, delta)
# 等待页面响应
await self.page.wait_for_load_state('networkidle')
async def cleanup(self):
"""关闭浏览器"""
if self.browser:
await self.browser.close()
# 使用示例(伪代码)
print("""
Web Agent 使用示例:
async def main():
agent = WebGUIAgent(llm_client=openai_client)
await agent.setup()
await agent.navigate("https://www.google.com")
# 观察-决策-执行循环
observation = await agent.observe()
# LLM决策
action = llm_decide(observation)
# 执行
await agent.execute_action(action)
await agent.cleanup()
""")
5.2 DOM简化策略¶
Python
class DOMSimplifier:
"""
DOM简化器
问题: 完整DOM可能有几万个节点,超过LLM上下文限制
解决: 智能过滤和压缩
"""
@staticmethod
def simplify_html(html: str, max_tokens: int = 4000) -> str:
"""
简化HTML,保留关键交互元素
"""
from html.parser import HTMLParser
# 保留的标签
keep_tags = {
'a', 'button', 'input', 'select', 'option', 'textarea',
'form', 'h1', 'h2', 'h3', 'h4', 'p', 'label', 'img',
'nav', 'main', 'article', 'section', 'div' # 结构性标签
}
# 移除的属性(减少噪音)
remove_attrs = {
'style', 'class', 'data-', 'aria-', 'tabindex',
'onclick', 'onchange' # 事件处理器
}
# 简化逻辑(基于规则)
simplified = []
lines = html.split('\n')
for line in lines:
line = line.strip()
if not line:
continue
# 检查是否包含有用标签
for tag in keep_tags:
if f'<{tag}' in line.lower() or f'</{tag}>' in line.lower():
# 清理属性
simplified.append(line[:200]) # 限制行长度
break
result = '\n'.join(simplified)
# 截断到token限制
if len(result) > max_tokens * 4: # 粗略估算
result = result[:max_tokens * 4] + "\n... (truncated)"
return result
@staticmethod
def extract_accessibility_tree(page_content: str) -> str:
"""
提取Accessibility Tree(更结构化的表示)
Accessibility Tree比DOM更精炼:
- 只包含对用户可见/可交互的元素
- 包含语义角色(role)信息
- 自然层级结构
"""
# 示例Accessibility Tree结构
example = """
[document] Google
[navigation] 主导航
[link] Gmail
[link] 图片
[button] 登录
[main]
[img] Google Logo
[textbox] 搜索框 (focused)
[button] Google 搜索
[button] 手气不错
[footer]
[link] 关于Google
[link] 隐私
"""
return example
print("DOM简化策略:")
print("1. 只保留可交互元素 (input/button/link)")
print("2. 移除样式相关属性 (class/style)")
print("3. 保留语义信息 (aria-label/placeholder)")
print("4. 使用Accessibility Tree替代原始DOM")
print("5. 限制深度和节点数量")
6. 桌面自动化Agent¶
6.1 操作系统级Agent¶
Python
"""
桌面GUI Agent: 操作任何桌面应用
代表项目: Microsoft UFO, Claude Computer Use
"""
class DesktopAgent:
"""
桌面自动化Agent骨架
核心依赖:
- pyautogui: 鼠标/键盘控制
- Pillow: 截图处理
- pytesseract: OCR文字识别
"""
def __init__(self, llm_client=None):
self.llm_client = llm_client
self.screen_width = 1920 # 示例分辨率
self.screen_height = 1080
def take_screenshot(self):
"""截取全屏或区域截图"""
# import pyautogui
# screenshot = pyautogui.screenshot()
# return screenshot
pass
def find_element_by_text(self, text: str):
"""基于OCR找到文字位置"""
# import pytesseract
# screenshot = self.take_screenshot()
# data = pytesseract.image_to_data(screenshot, output_type=pytesseract.Output.DICT)
#
# for i, word in enumerate(data['text']):
# if text.lower() in word.lower():
# x = data['left'][i] + data['width'][i] // 2
# y = data['top'][i] + data['height'][i] // 2
# return (x, y)
pass
def execute(self, action: GUIAction):
"""在操作系统级别执行操作"""
# import pyautogui
#
# if action.action_type == ActionType.CLICK:
# pyautogui.click(*action.position)
# elif action.action_type == ActionType.TYPE:
# pyautogui.click(*action.position)
# pyautogui.typewrite(action.text, interval=0.05)
# elif action.action_type == ActionType.HOTKEY:
# pyautogui.hotkey(*action.key.split('+'))
# elif action.action_type == ActionType.SCROLL:
# delta = -3 if action.direction == 'down' else 3
# pyautogui.scroll(delta)
pass
print("""
桌面Agent典型应用场景:
1. 办公自动化
- "打开Excel,在A1输入今天日期,保存为report.xlsx"
- "打开邮箱,给张三发一封会议通知"
2. 软件测试
- "打开计算器,验证1+1=2"
- "在Photoshop中打开图片,调整亮度+20"
3. 数据录入
- "从PDF表格中提取数据,填入ERP系统"
- "批量处理100张发票的信息录入"
4. 跨应用工作流
- "从浏览器下载文件→解压→导入到数据库"
""")
6.2 UFO框架介绍¶
Text Only
Microsoft UFO (UI-Focused Agent)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
UFO是微软开源的Windows桌面Agent框架:
架构:
┌─────────────────────────────────────┐
│ AppAgent (应用层Agent) │
│ 理解特定应用(Word/Excel/Browser) │
├─────────────────────────────────────┤
│ HostAgent (宿主Agent) │
│ 管理多应用协调,任务分配 │
├─────────────────────────────────────┤
│ Control Module │
│ Win32 API / UI Automation / COM │
└─────────────────────────────────────┘
特点:
1. 双Agent架构: HostAgent负责跨应用协调,AppAgent负责应用内操作
2. 利用Windows UI Automation API获取精确的元素信息
3. 支持自然语言任务描述
4. 自动学习新应用的操作方式
与Claude Computer Use的区别:
- UFO: 利用Windows特有API,更精确,仅限Windows
- Claude: 纯视觉方案,更通用,跨平台
7. 移动端GUI Agent¶
7.1 移动端特性¶
Text Only
移动端GUI Agent挑战:
━━━━━━━━━━━━━━━━━━━━━━
与桌面/Web的差异:
┌──────────────┬──────────────┬──────────────┐
│ 维度 │ 桌面端 │ 移动端 │
├──────────────┼──────────────┼──────────────┤
│ 输入方式 │ 鼠标+键盘 │ 触摸+手势 │
│ 屏幕大小 │ 大(1920+) │ 小(360-428) │
│ UI信息获取 │ DOM/WinAPI │ Android │
│ │ │ View层级 │
│ 滚动方式 │ 滚轮/滚动条 │ 滑动手势 │
│ 特殊操作 │ 右键/拖拽 │ 长按/双指缩放│
│ 响应延迟 │ 低 │ 动画多,需等│
└──────────────┴──────────────┴──────────────┘
移动端额外操作:
- 滑动(Swipe): 上下左右
- 长按(Long Press)
- 捏合缩放(Pinch)
- 系统返回键(Back)
- 通知栏操作
- App切换
7.2 移动端Agent架构¶
Python
class MobileGUIAction:
"""移动端GUI动作扩展"""
ACTIONS = {
"tap": "单击",
"long_press": "长按",
"swipe_up": "上滑",
"swipe_down": "下滑",
"swipe_left": "左滑",
"swipe_right": "右滑",
"type": "输入文本",
"back": "返回",
"home": "回到主屏",
"app_switch": "切换应用",
"scroll_to": "滚动到元素",
}
print("""
移动端GUI Agent代表工作:
1. AppAgent (2023)
- 多模态LLM + Android UI层级
- 通过探索学习APP操作方式
- 建立操作知识库
2. CogAgent (2023)
- 高分辨率视觉编码
- 端到端的UI理解和操作
3. DigiRL (2024)
- 使用RL训练移动端Agent
- 在真实Android环境中在线学习
4. UI-TARS (2025)
- 统一的视觉-动作模型
- 支持Web/桌面/移动端
""")
8. 评测基准与挑战¶
8.1 评测基准¶
Text Only
GUI Agent评测基准全景
━━━━━━━━━━━━━━━━━━━━━━━━━
Web端:
┌──────────────┬────────────────────────────────┐
│ 基准 │ 说明 │
├──────────────┼────────────────────────────────┤
│ MiniWoB++ │ 100+简单Web任务(点击/填表) │
│ Mind2Web │ 真实网站,2000+任务 │
│ WebArena │ 自托管网站环境(GitLab等) │
│ VisualWebArena│ 图文混合Web任务 │
│ WebVoyager │ 真实网站端到端任务 │
└──────────────┴────────────────────────────────┘
桌面端:
┌──────────────┬────────────────────────────────┐
│ OSWorld │ Ubuntu/Windows/MacOS跨系统 │
│ WindowsArena │ Windows应用任务 │
│ ScreenSpot │ GUI元素定位准确率 │
└──────────────┴────────────────────────────────┘
移动端:
┌──────────────┬────────────────────────────────┐
│ AITW │ Android in the Wild │
│ AndroidEnv │ Android环境标准接口 │
│ Mobile-Bench │ 移动端综合评测 │
└──────────────┴────────────────────────────────┘
8.2 评测指标¶
Python
class GUIAgentEvaluator:
"""GUI Agent评测器"""
@staticmethod
def compute_metrics(predictions, ground_truths):
"""
计算GUI Agent评测指标
核心指标:
1. 任务成功率 (Task Success Rate)
2. 步骤效率 (Step Efficiency)
3. 元素定位精度 (Grounding Accuracy)
"""
metrics = {}
# 1. 任务成功率:完成目标的比例
successes = sum(1 for p, g in zip(predictions, ground_truths)
if p['status'] == 'success')
metrics['success_rate'] = successes / len(predictions)
# 2. 步骤效率:实际步数/最优步数
efficiencies = []
for p, g in zip(predictions, ground_truths):
if p['status'] == 'success' and g['optimal_steps'] > 0:
efficiency = g['optimal_steps'] / p['steps']
efficiencies.append(min(efficiency, 1.0))
metrics['step_efficiency'] = (sum(efficiencies) / len(efficiencies)
if efficiencies else 0)
# 3. 元素定位精度(IoU)
ious = []
for p, g in zip(predictions, ground_truths):
if 'bbox' in p and 'bbox' in g:
iou = GUIAgentEvaluator._compute_iou(p['bbox'], g['bbox'])
ious.append(iou)
metrics['grounding_accuracy'] = (sum(ious) / len(ious)
if ious else 0)
return metrics
@staticmethod
def _compute_iou(box1, box2):
"""计算两个边界框的IoU"""
x1 = max(box1[0], box2[0])
y1 = max(box1[1], box2[1])
x2 = min(box1[2], box2[2])
y2 = min(box1[3], box2[3])
intersection = max(0, x2 - x1) * max(0, y2 - y1)
area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
union = area1 + area2 - intersection
return intersection / union if union > 0 else 0
# 示例评测
predictions = [
{"status": "success", "steps": 5, "bbox": [100, 100, 200, 200]},
{"status": "success", "steps": 8, "bbox": [150, 150, 250, 250]},
{"status": "failed", "steps": 20, "bbox": [300, 300, 400, 400]},
]
ground_truths = [
{"optimal_steps": 4, "bbox": [100, 100, 200, 200]},
{"optimal_steps": 3, "bbox": [145, 148, 255, 252]},
{"optimal_steps": 5, "bbox": [310, 305, 395, 395]},
]
metrics = GUIAgentEvaluator.compute_metrics(predictions, ground_truths)
print("GUI Agent评测结果:")
for k, v in metrics.items():
print(f" {k}: {v:.4f}")
8.3 当前挑战与未来方向¶
Text Only
GUI Agent面临的核心挑战:
━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 视觉理解限制
├── 小图标/密集UI难以识别
├── 高分辨率输入需求 vs 模型限制
└── 动态内容(动画/视频/弹窗)处理
2. 长序列推理
├── 复杂任务需要10-50步操作
├── 中间状态的记忆和回溯
└── 错误恢复(死循环/误操作)
3. 安全性
├── 防止恶意操作(删除文件/发送消息)
├── 敏感信息处理(密码/个人数据)
└── 操作可逆性和确认机制
4. 泛化与效率
├── 新应用/新界面的零样本泛化
├── 推理延迟(每步需要截图→LLM推理)
└── API调用成本
未来方向:
━━━━━━━━━━
1. 端到端视觉-动作模型(不需要DOM/坐标标注)
2. World Model for GUI(预测操作结果,减少试错)
3. 多Agent协作(不同Agent负责不同应用)
4. 强化学习优化(在真实环境中在线学习)
5. 安全框架(沙箱执行 + 操作审核)
练习题¶
练习1:实现简单的SoM标注器¶
- 使用Playwright获取网页元素
- 在截图上标注可交互元素编号
- 生成元素描述文本
练习2:构建Web搜索Agent¶
- 使用Playwright + GPT-4o实现一个Web搜索Agent
- 任务:自动在Google搜索指定内容并返回前3条结果
- 实现错误恢复机制
练习3:评测框架¶
- 在MiniWoB++上评测你的Agent
- 实现任务成功率和步骤效率指标
- 对比纯视觉方案和DOM+视觉方案
📝 本章小结¶
本章系统学习了GUI Agent(图形界面智能代理)的核心知识:
- ✅ 理解了GUI Agent与传统API Agent的核心区别(视觉感知 vs 结构化接口)
- ✅ 掌握了GUI Agent的核心循环:Observe→Think→Act
- ✅ 学会了视觉感知技术:SoM标注、UI Grounding、无障碍树
- ✅ 掌握了动作空间设计(ActionType枚举、GUIAction数据结构、ActionParser解析器)
- ✅ 理解了四种主流架构方案(纯视觉、DOM+LLM、混合、Computer Use)
- ✅ 完成了Web Agent实战(Playwright自动化 + DOM简化)
- ✅ 了解了桌面/移动端GUI Agent的实现思路
- ✅ 掌握了GUI Agent评测指标(成功率、步骤效率、定位精度IoU)
✅ 学习检查清单¶
- 能解释GUI Agent与API Agent的核心区别
- 能描述Observe→Think→Act核心循环
- 能解释Set-of-Mark(SoM)标注的作用和实现
- 能列举标准GUI操作类型(点击、输入、滚动等)
- 能对比四种主流GUI Agent架构方案的优缺点
- 能基于Playwright实现基础Web Agent
- 能解释为什么需要简化DOM(减少token、聚焦交互元素)
- 能解释GUI Agent的评测指标(成功率、步骤效率、IoU定位精度)
- 了解主要GUI Agent评测环境(WebArena、OSWorld、AndroidWorld)
🔗 相关章节¶
- Agent基础与架构 → 01-Agent基础与架构
- 多Agent系统 → 04-多Agent系统与实战
- MCP与工具生态 → 03-MCP与工具生态
- Agent评估与测试 → 05-Agent评估与测试
🔗 下一步¶
下一章我们将从零构建一个完整的Agent框架,深入理解Agent的运行机制。
继续学习: 11-从零构建Agent框架
📚 参考资料¶
- Hong et al. "CogAgent: A Visual Language Model for GUI Agents" (2023)
- Zheng et al. "SeeClick: Harnessing GUI Grounding for Advanced Visual GUI Agents" (2024)
- Zhang et al. "UFO: A UI-Focused Agent for Windows OS Interaction" (2024)
- Anthropic "Introducing computer use" (2024)
- Yang et al. "Set-of-Mark Prompting Unleashes Extraordinary Visual Grounding" (2023)
- Zhou et al. "WebArena: A Realistic Web Environment for Building Autonomous Agents" (2023)
- Xie et al. "OSWorld: Benchmarking Multimodal Agents for Open-Ended Tasks" (2024)
- Bai et al. "DigiRL: Training In-The-Wild Device-Control Agents with Autonomous RL" (2024)
- Qin et al. "UI-TARS: Pioneering Automated GUI Interaction with Native Agents" (2025)
- Gur et al. "A Real-World WebAgent with Planning, Long Context Understanding, and Program Synthesis" (2023)
祝你学习愉快! 🎉
最后更新日期:2026-02-12 适用版本:AI Agent开发实战教程 v2026