GUI-Owl 与 v3/v3.5:把感知·定位·规划·反思内化进原生基座模型
30 秒导读: 前三章(v1/v2/E)都是「通用大模型 + 一堆外挂工具」——OCR 认字、DINO 找图标、 CLIP 配图。这一章讲家族的范式转折:训练一个原生跨平台 GUI 基座模型 GUI-Owl,让它自己看截图、 自己吐出「点这个坐标」。v3 在这个基座外面套一层多智能体(规划/反思/记忆);v3.5 更进一步, 让手机、网页、PC 共用同一个基座,只换一份工具描述。
本章属于 Mobile-Agent 家族系列。建议先读 家族总览 建立坐标系;本章多次和 v1 单智能体、v2 多智能体、 E 与 PC-Agent 对照;基座模型怎么训出来的在 训练与批判器。
1. 这是什么(零基础也能懂)
一句话定义: GUI-Owl 是一个原生的图形界面基座模型——你给它一张手机/电脑截图和一句任务, 它直接回你「下一步做什么、点屏幕哪个坐标」,不需要任何外部的文字识别或图标检测工具帮忙。
它要取代的旧做法。 回想 v1 的流水线(01 章):一张截图进来,先过 OCR 读出所有文字框、再过图标检测模型(DINO)框出所有图标、必要时还要 CLIP 判断「哪个图标最像用户说的那个」, 把这些结果拼成一段文字喂给一个通用 VLM(如 GPT-4V),让它在文字描述里选一个框。感知和决策是两拨模型、 两个阶段。
GUI-Owl 的新做法。 把这一切压进一个模型的一次前向:
v1 旧路子(多模型、多阶段):
截图 ──OCR──▶ 文字框
──DINO─▶ 图标框 ┐
──CLIP─▶ 相似度 ├──拼成文本──▶ 通用VLM ──▶ "点第 3 个框"
┘
GUI-Owl 新路子(一个基座、一次前向):
截图 + 任务 ──▶ GUI-Owl ──▶ {"action":"click","coordinate":[480,633]}
(看图 + 定位 + 决策 全在模型内部)
给谁用、解决什么。 给做手机/电脑自动化的人。旧路子的痛点是:外挂工具是误差源——OCR 认错字、 DINO 漏检图标,后面的决策再聪明也白搭;而且每加一个平台(从手机到 PC)就要重配一套感知工具。GUI-Owl 把「看懂界面」这件事变成模型的内生能力,于是定位更准、也天然跨平台。
用起来什么样。 v3.5 的手机版启动就是一行命令(源码 Mobile-Agent-v3.5/mobile_use/run_gui_owl_1_5_for_mobile.py:1-11 的 docstring):
python run_gui_owl_1_5_for_mobile.py \
--adb_path "你的 adb 路径" \
--base_url "GUI-Owl 的 vllm 服务地址" \
--model "模型名" \
--instruction "帮我在设置里打开飞行模式"
一句话直觉/类比。 旧做法像「让一个盲人破案,先请三个助手(读字的、认脸的、比对的)口述现场, 他再推理」;GUI-Owl 是「让侦探自己长了眼睛」——看和想不再分家。
2. 顶层全景(它大概怎么转)
这一章其实讲两个东西,别混:
| 名字 | 是什么 | 类比 |
|---|---|---|
| GUI-Owl | 那个原生基座模型本身(看图→定位→决策) | 发动机 |
| v3 / v3.5 | 把 GUI-Owl 装进去驱动真机的框架 | 装了发动机的车 |
两代框架用同一台发动机,但车壳不同:
┌───────────────── GUI-Owl 基座模型 ─────────────────┐
│ 输入: 截图 + 任务 输出: <tool_call> 函数调用 │
└───────────────────────────────────────────────────┘
▲ ▲
│ 被下面两个框架复用 │
┌──────────┴───────────┐ ┌─────────────┴──────────────┐
│ v3:多智能体外壳 │ │ v3.5:单模型直驱 + 跨平台 │
│ 规划→执行→反思→记忆 │ │ 手机 / 网页 / PC 同一基座 │
│ (基座被调用 2~4 次/步)│ │ (基座被调用 1 次/步) │
└──────────────────────┘ └─────────────────────────────┘
部件一句话职责:
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
GUIOwlWrapper | 包住基座模型的 HTTP 调用(OpenAI 兼容接口),predict_mm 发图+文、收函数调用 | v3: mobile_v3/utils/call_mobile_agent_e.py:62;v3.5: mobile_use/utils.py:480 |
| v3 五智能体 | Manager/Executor/ActionReflector/Notetaker 各写一段 prompt、各调一次基座 | mobile_v3/utils/mobile_agent_e.py:56/187/274/317 |
| v3 主循环 | 编排「规划→执行→反思→记忆」,并把坐标从归一空间还原到真机 | mobile_v3/run_mobileagentv3.py:55 |
| v3.5 主循环 | 一步只调一次基座、parse_action 取函数调用、还原坐标、AdbTools 执行 | mobile_use/run_gui_owl_1_5_for_mobile.py:170 |
| 坐标还原 | 把模型给的 1000×1000 归一坐标换算回真实像素 | v3: run_mobileagentv3.py:198;v3.5: rescale_coordinates |
AdbTools | 真机执行层:click/long_press/type/slide/back/home | mobile_use/utils.py:70 |
主线走一遍(高层)。 不管哪代,一步都是这条链:
截图 ──▶ 拼消息(系统prompt里塞工具描述 + 截图 + 历史)──▶ GUI-Owl
──▶ 模型回一段 <tool_call> 函数调用(含 1000×1000 归一坐标)
──▶ 解析出 action + 坐标 ──▶ 坐标还原到真机像素 ──▶ ADB 执行 ──▶ 下一张截图
v3 在「模型」这一格里塞了四次不同角色的调用(先规划、再执行、再反思、必要时记笔记); v3.5 把这一格收成一次——基座自己在一次前向里把该想的都想了。
3. 核心原理(逐个机制,由浅入深)
3.1 定位与决策合一:函数调用直接产坐标
它要解决的小问题。 模型「想点某个按钮」,怎么把这个意图变成真机上一次精确的点击?
旧思路 vs 新思路。 v1 要先让感知工具枚举所有可点元素、编号,再让决策模型选编号。GUI-Owl 直接让 基座模型自己吐出坐标——定位就是模型输出的一部分,没有中间的元素枚举。
基座怎么被喂、怎么被读。 关键在系统 prompt 里塞的那份工具描述:它告诉模型「屏幕分辨率是 1000×1000,
点击要给中心坐标」,并规定必须用 <tool_call> 包一个 JSON 函数调用回来。见
mobile_use/utils.py:293-334 的 SYSTEM_PROMPT,其中写死:
* The screen's resolution is 1000x1000.(mobile_use/utils.py:302)
以及要求的回复格式(mobile_use/utils.py:319-322):
<tool_call>
{"name": <function-name>, "arguments": <args-json-object>}
</tool_call>
原理演示(示意,非源码):
# 示意,非源码:基座一次前向就把「看图+定位+决策」做完
model_output = gui_owl(screenshot, task="打开飞行模式")
# 模型直接回一段带函数调用的文本:
# Action: 点击「飞行模式」开关
# <tool_call>
# {"name":"mobile_use","arguments":{"action":"click","coordinate":[880,472]}}
# </tool_call>
# 注意:coordinate 是 0~1000 的归一坐标,不是真机像素
真实实现。 v3.5 用 parse_action 从输出里抠出那段 JSON:它按 <tool_call>\n 切、再按 }}\n 收尾,
json.loads 成字典(mobile_use/run_gui_owl_1_5_for_mobile.py:61-71 的 parse_action)。拿到的
action["arguments"] 里就含 action 名和 coordinate。
关键细节。 工具描述里的动作枚举就是这个基座认识的全部原子动作(mobile_use/utils.py:316 的
enum):key / click / long_press / swipe / type / system_button / open / wait / answer / interact / terminate。
这份「动作词表」是模型和执行层之间的契约——模型只会吐这些名字,执行层也只认这些名字。