UI-TARS 实践:用视觉模型驱动 Android 自动化测试
【免费下载链接】UI-TARSPioneering Automated GUI Interaction with Native Agents项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS
把 Android 真机连上电脑,截一张 App 登录页,把图交给 UI-TARS,三行 prompt 就能让它把登录点完。这篇带你看清这条视觉驱动的自动化测试链路怎么搭、哪些环节会翻车。
从截图到动作:UI-TARS 的视觉驱动执行链路
先建立心智模型,再谈怎么跑。整条链路是五个环节咬合的:
- 截图输入:抓一张当前屏幕的原始像素图;
- VLM 理解界面:把图丢给视觉语言模型(这里是 UI-TARS-1.5),模型"看到"按钮、输入框这些控件;
- 生成动作指令:模型先写一段
Thought(计划),再给出一行Action(如click(start_box=...)); - 坐标解析:把模型报的像素坐标归一化,再乘回原图尺寸,得到真正要点的像素;
- 设备执行:把坐标喂给
pyautogui(桌面)或adb input tap(手机),执行后重新截图,进入下一轮。
关键点在第 4 步:模型不是在原图坐标系上报告位置,而是在一张被"智能缩放"后的图上报告,所以坐标必须还原一次。UI-TARS 官方把这套感知、动作、推理拆成了分层结构:
安装 ui-tars 并跑通第一个解析用例
这里有个容易踩的坑:PyPI 上的ui-tars包只做后半段——解析模型文本、换算坐标、生成pyautogui代码。它不帮你截图、也不直接调模型,模型本身要另外部署(HuggingFace Endpoint 或本地)。所以"跑通第一个用例"先验证这条解析链路,而不是端到端操作手机。
先装包,一条命令即可:
pip install ui-tars下面用一个假的模型响应跑一遍解析和代码生成,确认装对了:
from ui_tars.action_parser import ( parse_action_to_structure_output, parsing_response_to_pyautogui_code, ) # 模型返回的一条典型响应(Thought + Action) response = "Thought: 登录按钮在屏幕中下方\nAction: click(start_box='(540,1873)')" w, h = 1080, 2400 # ← 你的设备实际值(宽, 高) actions = parse_action_to_structure_output( response, factor=1000, origin_resized_height=h, origin_resized_width=w, model_type="qwen25vl", ) print(actions[0]["action_type"], actions[0]["action_inputs"]) # 期望看到 click + 归一化 start_box print(parsing_response_to_pyautogui_code(actions, image_height=h, image_width=w))成功的信号:第一行打印出click和一个[x, y, x, y]形式的归一化坐标,第二行打印出一段以pyautogui.click(...)结尾的脚本。能看到这两行,说明解析与坐标换算都通了。
核心机制拆解:动作空间、Prompt 模板与坐标映射
这一节按模块讲,重点看每个齿轮和相邻齿轮怎么咬合,而不是罗列亮点。
动作空间定义:输入是模型被允许使用的操作集合,输出是模型只能从这个集合里挑的动作。移动端模板MOBILE_USE_DOUBAO里列了click(point=...)、long_press(point=...)、type(content=...)、scroll(point=..., direction=...)、open_app(app_name=...)、drag(start_point=..., end_point=...)、press_home()、press_back()、finished(content=...)。边界:模板里没有swipe,横向/纵向拖拽只能用drag表达,写swipe会被解析器当未知动作丢弃。
Prompt 模板:输入是模板字符串加任务指令,输出是发给模型的 system prompt。直接从ui_tars.prompt拿现成常量,移动端用MOBILE_USE_DOUBAO,它有两个占位符{language}和{instruction},把任务描述填进{instruction}即可,桌面端换COMPUTER_USE_DOUBAO。注意:占位符不填全会让Thought的语言和任务描述不确定,建议显式format后再发。
坐标映射:输入是原始截图宽高,输出是归一化坐标与可落地的像素点。这一步最容易出错,用仓库自带函数算一遍:
from ui_tars.action_parser import smart_resize w, h = 1080, 2400 # ← 原始截图宽高 rh, rw = smart_resize(h, w) # 模型实际"看到"的尺寸,两边都是 28 的倍数 nx, ny = 540 / rw, 1873 / rh # 模型在 rw×rh 空间报 (540,1873),先归一化 px, py = nx * w, ny * h # 再乘回原图,得到真正要点的像素 print(px, py)边界:只有当你发给模型的图确实被缩放到smart_resize算出的尺寸时,坐标才准。如果你自己改过图再发,映射就整体偏移,得人工校准。
响应解析:输入是模型原始文本,输出是结构化 dict(含action_type、action_inputs、thought)。
from ui_tars.action_parser import parse_action_to_structure_output as P resp = "Thought: 登录按钮在中下方\nAction: click(start_box='(540,1873)')" a = P(resp, factor=1000, origin_resized_height=2400, origin_resized_width=1080, model_type="qwen25vl") print(a[0]["action_type"], a[0]["action_inputs"]) # click 与归一化 start_box边界:factor参数只对非qwen25vl的模型生效(qwen25vl 走上面的smart_resize路径);type(content='...')里若带单引号/换行,解析器会尝试转义,但复杂转义仍可能失败,需要兜底。
一次完整登录走查:从截图到执行验证
以"自动登录"为例,按五步走,重点看每一步为什么这么做。
1. 输入:抓一张登录页截图(1080×2400),把MOBILE_USE_DOUBAO.format(instruction="启动某 App,输入账号 demo_user 和密码,点登录", language="中文")连同截图发给模型。这里指令要写清"账号/密码是什么",因为模型不会猜。
2. 模型原始响应:模型先给一段Thought("账号密码已填好,登录按钮在中下方"),再给一行Action: click(start_box='(540,1873)')。这是文本,还不能直接执行。
3. 解析成结构化动作:
from ui_tars.action_parser import parse_action_to_structure_output w, h = 1080, 2400 # ← 你的设备实际值(宽, 高) step = "Thought: 账号密码已填好,登录按钮在中下方\nAction: click(start_box='(540,1873)')" a = parse_action_to_structure_output( step, factor=1000, origin_resized_height=h, origin_resized_width=w, model_type="qwen25vl") box = eval(a[0]["action_inputs"]["start_box"]) # 归一化 [x, y, x, y] x, y = int(box[0] * w), int(box[1] * h) # 还原到原图像素 print("adb shell input tap", x, y) # 移动端用 adb 落地为什么这么做:解析出的start_box是归一化值,乘回原图宽高才得到adb能直接用的整数坐标。桌面环境则把这步换成parsing_response_to_pyautogui_code,坐标完全一样。
4. 执行:把x, y交给adb shell input tap x y(手机)或pyautogui.click(x, y)(桌面);type类动作则直接落content,不需要坐标。执行后重新截图,作为下一轮输入。
5. 结果验证:不要相信模型"说自己登录成功了"。截新图,用肉眼或一个简单的像素/文本断言确认登录页已消失、进入了主页,才算这步通过。
基准数据与当前局限
先说数字,均来自官方 README 的在线基准(100 步以内、temperature 接近 0 的评估条件):
- Android World(Phone Use,55 个任务):UI-TARS-1.5 拿到64.2,前 SOTA 为 59.5;
- OSWorld(Computer Use,100 步):42.5,前 SOTA 38.1;
- ScreenSpot-V2 / ScreenSpotPro(纯定位):94.2 / 61.6。
但它目前做不好的事也得摆出来:
- 误识别与幻觉:官方 Limitations 明确写,模型在模糊或陌生界面里会给出错误描述、点错元素、或基于错误推断采取次优动作。
- 算力开销:7B 模型推理吃 GPU,长流程里单步延迟和成本都上得去,不适合高频批量跑。
- 场景偏科:开源的 UI-TARS-1.5-7B 主打通用电脑/手机操作,没针对游戏场景优化(游戏上更大版本的 1.5 才占优);WebView 内嵌表单、CAPTCHA 这类仍是重灾区,甚至官方单独提示它能"绕验证码"带来的滥用风险。
生产环境落地注意事项
- 重试与退避:关键步骤失败别立刻放弃,重试 2~3 次并指数退避;连续失败就整轮重截图重来,避免在错误页面上继续叠加操作。
- 坐标偏移校准:真机常有状态栏/导航栏吃掉一部分像素,上线前用一两个已知按钮标定一次偏移量
offset_x/offset_y,再统一加回去。 - 多分辨率适配:务必把"真实截图宽高"传进
origin_resized_*,并保证发给模型的图与smart_resize算出的尺寸一致;换机型只改w, h,其余逻辑不动。 - 超时与兜底:每步设推理和执行的超时;
type解析失败或坐标越界时,落到一个默认动作(如press_back回退)而不是直接崩。 - 日志与可观测:把每一步的
Thought、Action、解析后的坐标都记下来,配合截图存盘,出错时能回放到底哪一步偏了。⚠ 注意:pyautogui生成的脚本默认time.sleep(1),真机执行前确认延迟够用,否则会点太快。
它是目前最接近"看截图就能操作手机"的开源视觉方案。想快速感受:装上包后,把上面第一节的 smoke 测试贴进终端跑一遍,看打印出的click与坐标。
- 核心源码:codes/ui_tars/ | 部署文档:README_deploy.md | 测试数据:data/test_messages.json
【免费下载链接】UI-TARSPioneering Automated GUI Interaction with Native Agents项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考