跳到主要内容

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/homemobile_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-334SYSTEM_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-71parse_action)。拿到的 action["arguments"] 里就含 action 名和 coordinate

关键细节。 工具描述里的动作枚举就是这个基座认识的全部原子动作(mobile_use/utils.py:316enum):key / click / long_press / swipe / type / system_button / open / wait / answer / interact / terminate。 这份「动作词表」是模型和执行层之间的契约——模型只会吐这些名字,执行层也只认这些名字。


3.2 坐标还原:1000×1000 归一空间 → 真机像素

它要解决的小问题。 手机分辨率千奇百怪(1080×2400、1440×3200…)。如果让模型直接输出真实像素, 它得先知道这台机器多大,泛化很差。GUI-Owl 的办法是:永远在一个虚拟的 1000×1000 画布上思考, 由框架负责换算回真机。

换算怎么做。 就是按比例缩放:真实x = 归一x / 1000 × 图像宽。v3 写在主循环里 (run_mobileagentv3.py:198-202):

if coor_type != "abs":
if "coordinate" in action_object:
action_object['coordinate'] = [int(action_object['coordinate'][0] / 1000 * width),
int(action_object['coordinate'][1] / 1000 * height)]

v3.5 把它抽成独立函数 rescale_coordinates(run_gui_owl_1_5_for_mobile.py:74-87),对 coordinate / coordinate1 / coordinate2 三个键统一 /1000 * resized_宽高

一个容易踩的坑:缩放的基准不是原图,而是 smart_resize 后的尺寸。 GUI-Owl 是 Qwen-VL 系的视觉模型, 喂图前要先把图 resize 成「边长都能被 16 整除、总像素落在区间内」的尺寸——模型「看到」的就是这张 resize 后的图, 所以它的归一坐标也应该还原到 resize 后的宽高。v3.5 主循环因此先算 smart_resizerescale (run_gui_owl_1_5_for_mobile.py:197-204):

img = Image.open(screenshot_path)
resized_h, resized_w = smart_resize(img.height, img.width, factor=16,
min_pixels=3136, max_pixels=1003520 * 200)
action_parameter = rescale_coordinates(action_parameter, resized_w, resized_h)

smart_resize 在干嘛(mobile_use/utils.py:244-286): 三条约束——① 宽高都被 factor(16)整除; ② 总像素落在 [min_pixels, max_pixels];③ 尽量保住原始长宽比。像素超上限就按 sqrt(原面积/上限) 等比缩小(utils.py:277-280),不足下限就等比放大。

这套设计的价值。 模型只学「在标准画布上的相对位置」,和具体分辨率解耦——同一个 GUI-Owl 能直接上任何 分辨率的手机、乃至 PC,不用重训。这正是下一节跨平台统一的地基。


3.3 v3.5 的跨平台统一:同一基座,换工具描述就换平台

它要解决的小问题。 手机、网页、PC 是三种交互(触屏 / 网页元素 / 鼠标键盘)。传统做法要三套 agent。 GUI-Owl 的主张是:底层「看界面、定坐标、做决策」的能力是通用的,平台差异只在「有哪些动作可选」。

怎么做到。 v3.5 三个平台的 runner 各自维护一份系统 prompt,里头是同构的工具描述—— 同样的 <tool_call> 协议、同样的 1000×1000 归一坐标约定,只有动作枚举不同:

平台工具名动作枚举(节选)定位方式源码
手机mobile_useclick / long_press / swipe / type / system_button / open1000×1000 归一坐标mobile_use/utils.py:299-316
PCcomputer_useleft_click / right_click / double_click / left_click_drag / scroll / hscroll / key1000×1000 归一坐标computer_use/utils.py:504-567
网页browser_useclick / type / scroll / select / go_back / wikipedia数值标签(Set-of-Mark)browser_use/prompts.py:7-20

共用的证据。 三个 runner 用的是同一个类 GUIOwlWrapper,build_messages 组消息的骨架也一致 (手机:mobile_use/utils.py:337;PC:computer_use/utils.py:587,连函数签名 build_messages(image_path, instruction, history_output, model_name, history_n=4) 都逐字相同)。也就是说:换平台=换那段工具描述字符串

  • 换执行层(AdbTools 换成 ComputerTools / PlaywrightComputer),模型和编排骨架不动

一个诚实的差异(别被"完全统一"骗了)。 手机和 PC 用的是同一套归一坐标定位;但网页走的是 Set-of-Mark——先给页面每个元素打数字标签,模型输出「点标签 3」而不是坐标(browser_use/prompts.py:9: "Numerical Labels placed at the TOP LEFT of each Web Element")。网页 DOM 天然有结构,标签比像素更稳,所以 v3.5 在这里选了标签定位。"同一基座"成立在"看图+函数调用"这一层,定位模态按平台择优,不是三处像素坐标一刀切。 (browser 的动作走 browser_use/agent.py:339<tool_call> 正则解析,和手机同协议。)


4. 深入实现:两条真实执行路径对照

把 v3 和 v3.5 的一步端到端并排走,最能看清「外挂多智能体」到「单模型直驱」的收敛。

4.1 v3 的一步:基座被调用 2~4 次(多智能体反思)

v3 保留了 v2/E 的多智能体骨架(02 章),但每个角色的大脑都换成了 GUI-Owl。 一步的时序(run_mobileagentv3.py:55-306):

① Manager 规划 ──调基座──▶ 产出/修订 Plan + 已完成子目标
(可跳过:上一步是 invalid 就先重试,run:96-99)
│ 若 Plan == "Finished" 就结束(run:125)

② Executor 执行 ──调基座──▶ 产出 {action, coordinate} + 描述
│ 坐标 1000→真机还原(run:198-202)
│ AdbTools 执行 click/swipe/type/system_button(run:204-214)

③ 再截一张图 ──▶ ActionReflector 反思 ──调基座(喂前后两张图)──▶ 判 A/B/C
A=成功 / B=进错页要回退 / C=没变化(run:270-277)

④ 若成功且开了记事 ──▶ Notetaker ──调基座──▶ 更新 important_notes(run:289-304)

几个关键源码锚点:

  • 五智能体的定义都在 mobile_v3/utils/mobile_agent_e.py:Manager(:56)、Executor(:187)、 ActionReflector(:274)、Notetaker(:317),共享一个 InfoPool(:7)当黑板。
  • Executor 的动作词表 ATOMIC_ACTION_SIGNITURES_noxml(mobile_agent_e.py:158)把每个原子动作连同 用法示例写进 prompt(如 click(coordinate): ... {"action": "click", "coordinate": [x, y]}), 让基座按这个格式吐 JSON。
  • 反思的两图对比:ActionReflectorpredict_mm 同时喂「动作前」和「动作后」两张截图 (run_mobileagentv3.py:250-256),让基座对比判断动作是否奏效。
  • 卡死上报:连续 err_to_manager_thresh(默认 2)次拿到 B/C,就置 error_flag_plan (run_mobileagentv3.py:82-92),下一轮 Manager 会看到失败日志并重规划(mobile_agent_e.py:119-127)。

v3 里 GUI-Owl 是「被四个不同 prompt 反复调用的同一个大脑」——反思、记忆这些框架层能力还在外面。

4.2 v3.5 的一步:基座被调用 1 次(反思内化)

v3.5 把上面四次调用塌缩成一次(run_gui_owl_1_5_for_mobile.py:170-281):

① 截图(AdbTools.get_screenshot)
② build_messages(截图 + 任务 + 最近 history)──▶ 一次 predict_mm
③ parse_action 抠出 <tool_call> 的函数调用
④ smart_resize + rescale_coordinates 还原坐标
⑤ 按 action_type 分派到 AdbTools 执行
⑥ history.append(本步输出),annotate 存图,进入下一步

动作分派表(run_gui_owl_1_5_for_mobile.py:207-272)——注意这就是「基座词表」到「真机动作」的落地:

模型给的 action执行源码行
clickAdbTools.click(x, y):209-213
long_pressAdbTools.long_press(x, y)(用 swipe 原地停 800ms 实现):215-219 / utils.py:115
typeAdbTools.type(text)(切 ADB Keyboard、广播文本):221-222 / utils.py:134
scroll / swipeAdbTools.slide(x1,y1,x2,y2):224-230
system_buttonback() / home():232-237
openhandle_open_action:查包名→装不上就 LLM 兜底解析应用名:248-258 / :90-141
waittime.sleep:239-241
answer / terminate打印结论并 break:243-264

反思去哪了? v3 那套「动作后再截图、再调基座判 A/B/C」的显式反思循环,在 v3.5 主循环里没有独立角色了—— 基座在每一步的一次前向里,自己看当前截图 + 历史动作,自行决定是纠错、重试还是继续。过去要靠框架多调一次模型 才能做的「反思」,现在被内化进了基座的单次决策。 这正是本章标题「把……反思内化进原生基座」的字面兑现。

一个小而实的工程点:open 动作的应用名兜底。 模型说「打开微信」,但设备上装的包名五花八门。 handle_open_action 先在名称→包名字典里直查,查不到就调一个小 LLM(resolve_app_name_via_llm, utils.py:549)在已装应用列表里挑一个匹配的,还挑不到才请用户去装(run:140)。这体现了 v3.5 的分工: 难的界面理解交给 GUI-Owl,琐碎的名称消歧交给便宜的文本 LLM。


5. 巧妙之处(可借鉴的技术)

  • 把「感知」变成模型能力而非管线阶段。 妙在删掉了 OCR/DINO/CLIP 这些误差累积源——定位准不准 从此是基座一个模型的事,可端到端优化。见 §3.1,契约在 mobile_use/utils.py:293SYSTEM_PROMPT
  • 1000×1000 归一坐标 = 分辨率无关的思考空间。 模型只学相对位置,框架负责换算(rescale_coordinates, run_gui_owl_1_5_for_mobile.py:74)。这一层解耦是「同一基座跨设备/跨平台」的地基。
  • 同构工具描述做跨平台。 手机/PC/网页共用 GUIOwlWrapperbuild_messages,只换动作枚举那段字符串 (mobile_use/utils.py:316 vs computer_use/utils.py:549)。加平台≈写一份工具描述 + 一个执行层适配器。
  • 反思从「框架多调一次」内化成「基座单次决策」。 对比 §4.1(v3 显式 ActionReflector 调基座)与 §4.2(v3.5 无独立反思角色),同样的容错能力,调用数从 2~4 次/步降到 1 次/步。
  • 难易分层派活。 界面理解给贵的 GUI-Owl,应用名消歧给便宜的文本 LLM(resolve_app_name_via_llm, mobile_use/utils.py:549)——不是所有子问题都值得动用视觉基座。

6. 边界与局限(诚实)

  • 基座本身的训练不在本章。 这里读的是推理框架(怎么调、怎么解析、怎么落地)。GUI-Owl 怎么练出来 (半在线强化学习等)见 05 训练与批判器;本仓的 UI-S1 目录是训练侧。
  • 强依赖服务化的模型端点。 GUIOwlWrapper 走 OpenAI 兼容 HTTP(call_mobile_agent_e.py:80 / mobile_use/utils.py:498),你得自己起一个 vllm 服务托管 GUI-Owl 权重;失败就固定间隔重试 (predict_mm,最多 10 次,utils.py:530-542)。
  • 解析对输出格式敏感。 parse_action<tool_call>\n}}\n 字符串切分 (run_gui_owl_1_5_for_mobile.py:66-69);模型若不严格按格式吐,直接 ValueError。稳健性押在基座的 格式遵从上。
  • 跨平台不是一套坐标包打天下。 网页走 Set-of-Mark 数值标签而非像素坐标(§3.3),依赖前端能给元素打标; 「统一」体现在基座 + 函数调用协议,不在定位模态。
  • v3.5 主循环里每步 new GUIOwlWrapper(...)run_gui_owl_1_5_for_mobile.py:187——每步新建客户端, 是可读性优先的示例代码,非省资源的生产写法 (inferred)。

7. 横向对比(家族内的位置)

维度v1(01)v2/E(02/03)v3(本章)v3.5(本章)
感知外挂 OCR+DINO+CLIP外挂感知 + 记忆GUI-Owl 内化GUI-Owl 内化
决策通用 VLM 选框多智能体分工多智能体,大脑换成 GUI-Owl单基座一次前向
反思无/弱独立反思智能体独立 ActionReflector(调基座)内化进基座单步
基座/步调用数感知多模型 + 决策 1每角色各 1每步 2~4每步 1
平台手机手机(+PC-Agent 分支)手机/鸿蒙手机 + 网页 + PC 同基座

读法。 从左到右是一条主线:外挂能力不断被吸进模型。v1→E 是「把更多框架层能力(记忆、反思、 分层规划)做厚」;v3→v3.5 是反过来「把这些能力内化进基座,让框架变薄」。两股力在 GUI-Owl 这里交汇。

跨库对照:GUI-Owl 这种「原生 GUI 基座」和 computer-use 领域其它「通用 VLM + 无障碍树/像素外挂」的路线, 是同一问题(把模型的话精确落到真实 UI 目标)的两种取舍——一个赌专训基座,一个赌通用模型 + 强外挂


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

主题文件路径符号名
v3.5 手机主循环(单基座直驱)Mobile-Agent-v3.5/mobile_use/run_gui_owl_1_5_for_mobile.py:144main
从输出抠函数调用Mobile-Agent-v3.5/mobile_use/run_gui_owl_1_5_for_mobile.py:61parse_action
归一坐标→真机像素Mobile-Agent-v3.5/mobile_use/run_gui_owl_1_5_for_mobile.py:74rescale_coordinates
open 动作应用名兜底Mobile-Agent-v3.5/mobile_use/run_gui_owl_1_5_for_mobile.py:90handle_open_action
手机工具描述(1000×1000 契约)Mobile-Agent-v3.5/mobile_use/utils.py:293SYSTEM_PROMPT
Qwen-VL 式图像缩放Mobile-Agent-v3.5/mobile_use/utils.py:244smart_resize
消息组装(跨平台同构骨架)Mobile-Agent-v3.5/mobile_use/utils.py:337build_messages
基座 HTTP 封装Mobile-Agent-v3.5/mobile_use/utils.py:480GUIOwlWrapper.predict_mm
真机执行层Mobile-Agent-v3.5/mobile_use/utils.py:70AdbTools
应用名消歧(文本 LLM)Mobile-Agent-v3.5/mobile_use/utils.py:549resolve_app_name_via_llm
PC 工具描述(computer_use 枚举)Mobile-Agent-v3.5/computer_use/utils.py:504SYSTEM_PROMPT
网页工具描述(Set-of-Mark 标签)Mobile-Agent-v3.5/browser_use/prompts.py:7SYSTEM_PROMPT
v3 多智能体主循环Mobile-Agent-v3/mobile_v3/run_mobileagentv3.py:55run_instruction
v3 五智能体 + 黑板Mobile-Agent-v3/mobile_v3/utils/mobile_agent_e.py:7InfoPool / Manager / Executor / ActionReflector / Notetaker
v3 原子动作词表Mobile-Agent-v3/mobile_v3/utils/mobile_agent_e.py:158ATOMIC_ACTION_SIGNITURES_noxml
v3 基座封装Mobile-Agent-v3/mobile_v3/utils/call_mobile_agent_e.py:62GUIOwlWrapper
v3 动作名常量Mobile-Agent-v3/mobile_v3/utils/new_json_action.py:17CLICK / TYPE / SWIPE / SYSTEM_BUTTON