在前几篇文章中,我们已经完成了 UI-TARS 的整体铺垫。
第一篇,我们讲了 GUI Agent 是什么。
第二篇,我们梳理了 UI-TARS 仓库结构。
第三篇,我们分析了论文里的 Enhanced Perception、Unified Action Modeling 与 System-2 Reasoning。
第四篇,我们对比了 UI-TARS 和传统 RPA 的区别。
从这一篇开始,我们正式进入源码。
今天要看的文件是:
codes/ui_tars/prompt.py
这个文件很短,整个文件只有几十行,但它非常重要。
因为它定义了 UI-TARS 模型在执行 GUI 任务时:
应该以什么身份行动
应该输出什么格式
可以使用哪些动作
什么时候应该思考
什么时候应该直接输出动作
换句话说:
prompt.py是 UI-TARS 的"动作说明书"。
一、为什么 prompt.py 这么重要?
很多人读 GUI Agent 源码时,会直接跳到动作解析、坐标转换、pyautogui 执行这些部分。
但在 UI-TARS 里,prompt.py 其实是整个工程链路的起点。
原因很简单:
后面的 action parser 能不能稳定解析,取决于前面的 prompt 有没有把模型输出约束好。
如果没有 Prompt 约束,模型可能会输出:
我建议你点击右上角的搜索框,然后输入关键词,最后按回车。
这句话人能看懂,但程序很难执行。
程序真正需要的是:
Thought: 我需要点击搜索框以便输入关键词。
Action: click(point='<point>850 120</point>')
也就是说,Prompt 的作用不是简单地"提示模型一下",而是把一个自由聊天模型约束成一个 GUI 动作生成器。
UI-TARS 的 README 也明确说明,项目提供了三类 Prompt 模板,用来适配不同设备环境和任务复杂度:COMPUTER_USE、MOBILE_USE 和 GROUNDING。其中桌面端用于 Windows、Linux、macOS 等环境,移动端用于手机或 Android 模拟器,GROUNDING 用于只关注动作输出或模型评估的轻量场景。
二、prompt.py 里有哪三个模板?
打开 prompt.py,可以看到三个核心字符串常量:
COMPUTER_USE_DOUBAO = """..."""
MOBILE_USE_DOUBAO = """..."""
GROUNDING_DOUBAO = """..."""
这三个模板分别对应三种使用场景:
COMPUTER_USE_DOUBAO
适合桌面端 GUI 操作,例如 Windows、Linux、macOS。
MOBILE_USE_DOUBAO
适合移动端 GUI 操作,例如 Android 手机或模拟器。
GROUNDING_DOUBAO
适合只测试模型定位能力,通常只输出 Action,不输出 Thought。
这和 README 中的 Prompt Usage Guide 是对应的。README 里明确提到,COMPUTER_USE 支持桌面常见操作,MOBILE_USE 包含移动端特有动作,GROUNDING 则只输出 Action,不包含推理过程。
所以,prompt.py 虽然代码少,但已经把 UI-TARS 的使用场景分成了三类。
三、COMPUTER_USE:桌面端 GUI Agent 的核心模板
我们先看 COMPUTER_USE_DOUBAO。
它的开头大意是:
你是一个 GUI agent。
你会获得任务、动作历史和截图。
你需要执行下一步动作来完成任务。
这句话看似普通,但它包含三个关键信息。
第一,模型的身份是 GUI Agent。
它不是普通聊天助手,也不是只回答问题的 VLM,而是要操作图形界面的 Agent。
第二,模型不只看当前任务,还会看到 action history。
也就是说,它可以根据之前执行过什么动作,判断当前任务进行到哪一步。
第三,模型的目标不是一次性输出完整方案,而是输出"下一步动作"。
这一点非常关键。
GUI Agent 的执行方式不是:
一次性生成完整脚本
而是:
看当前截图
输出下一步动作
执行动作
再截图
再输出下一步动作
这就是 Agent Loop。
四、COMPUTER_USE 的输出格式:Thought + Action
COMPUTER_USE_DOUBAO 要求输出格式为:
Thought: ...
Action: ...
源码中这个格式非常明确。prompt.py 中 COMPUTER_USE_DOUBAO 和 MOBILE_USE_DOUBAO 都要求模型输出 Thought 和 Action 两部分。
这也是 UI-TARS 和普通坐标脚本的一个重要区别。
普通脚本只有动作:
click(800, 300)
UI-TARS 要求模型先说明自己的判断:
Thought: 我需要点击右上角的搜索框,准备输入关键词。
Action: click(point='<point>850 120</point>')
Thought 的作用有三个。
第一,帮助模型先做一步推理。
它不是看到按钮就点,而是要先判断当前状态和下一步目标。
第二,方便调试。
如果模型点错了,我们可以从 Thought 里看出它到底理解错了什么。
第三,方便后续做安全控制。
如果 Thought 里出现高风险意图,比如删除文件、提交表单、支付等,外层系统可以提前拦截。
所以,Thought + Action 并不是为了好看,而是为了让 GUI Agent 的行为更可解释、更容易调试。
五、COMPUTER_USE 的动作空间
COMPUTER_USE_DOUBAO 中定义的动作空间包括:
click
left_double
right_single
drag
hotkey
type
scroll
wait
finished
这些动作基本覆盖了桌面软件自动化中最常见的操作。源码中可以看到,桌面端模板支持单击、双击、右键、拖拽、快捷键、输入、滚动、等待和完成任务等动作。
我们逐个分析。
六、click:最基础的点击动作
click 是最常见的动作。
它的格式是:
click(point='<point>x1 y1</point>')
这里的 x1 y1 是模型认为应该点击的位置。
例如:
Thought: 我需要点击搜索框。
Action: click(point='<point>850 120</point>')
注意,这里的坐标不是传统 RPA 录制出来的死坐标。
它是模型根据当前截图动态判断出来的。
后续 action_parser.py 会把这个 point 解析出来,再转换成结构化坐标,最后生成 pyautogui 点击代码。
七、left_double 与 right_single:桌面端特有操作
桌面端和移动端的一个重要区别是:
桌面端有双击和右键。
在文件管理器、桌面图标、专业软件里,这两个动作很常见。
例如:
双击文件打开
右键弹出菜单
双击桌面图标启动程序
右键图片选择另存为
所以 COMPUTER_USE_DOUBAO 中有:
left_double(point='<point>x1 y1</point>')
right_single(point='<point>x1 y1</point>')
这说明 UI-TARS 的动作空间不是随便定义的,而是紧贴桌面操作系统的真实交互习惯。
如果只有 click、type、scroll,很多桌面任务会很难完成。
八、drag:拖拽动作
drag 的格式是:
drag(
start_point='<point>x1 y1</point>',
end_point='<point>x2 y2</point>'
)
拖拽动作适合这些场景:
拖动窗口
拖动滑块
拖拽文件
框选内容
调整分隔线
移动地图
和 click 不同,drag 需要两个坐标:
起点
终点
所以它对模型的视觉 grounding 能力要求更高。
模型不仅要知道从哪里开始,还要知道拖到哪里结束。
这也是为什么 GUI Agent 不能只做简单的"元素识别"。真实桌面任务里,拖拽、滚动、快捷键、右键菜单都很常见。
九、hotkey:键盘快捷键
hotkey 的格式是:
hotkey(key='ctrl c')
源码里的注释要求:快捷键用空格分隔,使用小写,并且不要超过 3 个按键。
这个限制很有工程意义。
因为如果模型随便输出:
hotkey(key='press control and maybe shift plus something')
后面的 parser 就很难稳定解析。
所以 Prompt 直接把格式限制死:
ctrl c
ctrl v
alt tab
ctrl shift s
这类格式非常适合被 pyautogui 转换成:
pyautogui.hotkey("ctrl", "c")
这就是 Prompt 和 Parser 之间的配合。
Prompt 负责让模型输出可解析文本。
Parser 负责把可解析文本转成可执行代码。
十、type:输入文本
type 的格式是:
type(content='xxx')
源码中特别提醒,如果要提交输入,可以在 content 结尾使用换行符;同时还要求使用转义字符,保证 content 能被按正常 Python 字符串格式解析。
这点也非常关键。
因为输入内容可能包含:
单引号
双引号
换行符
特殊字符
如果 Prompt 不提前约束,模型可能输出无法解析的字符串。
例如:
type(content='it's good')
这里中间的 ' 就可能破坏字符串结构。
所以 Prompt 明确要求使用转义字符。
这看起来只是一个小细节,但对自动化系统很重要。GUI Agent 不只是"模型能理解",还必须"程序能解析"。
十一、scroll:滚动页面
scroll 的格式是:
scroll(point='<point>x1 y1</point>', direction='down or up or right or left')
它不仅需要方向,还需要一个 point。
为什么滚动也需要 point?
因为在很多 GUI 里,鼠标停在哪里会影响滚动区域。
例如:
鼠标在左侧菜单上,滚动的是菜单
鼠标在主内容区,滚动的是页面
鼠标在表格里,滚动的是表格
鼠标在弹窗里,滚动的是弹窗
所以,滚动不是简单的:
scroll down
而应该是:
在某个区域滚动
这就是 point + direction 的意义。
十二、wait:等待并重新观察
wait() 的注释是等待一段时间并截图检查变化。源码中说明 wait() 会 sleep 并重新截图检查是否有变化。
这个动作非常重要。
因为 GUI 操作经常不是立即生效的。
例如:
网页加载
文件下载
弹窗出现
动画过渡
后台处理
搜索结果刷新
如果没有 wait,模型可能在页面还没加载完时连续点击,导致任务失败。
传统脚本里经常写:
sleep(2)
但 UI-TARS 把等待也纳入动作空间。
这意味着模型可以根据当前界面判断:
现在不应该点击
现在应该等待页面变化
这比固定 sleep 更灵活。
十三、finished:任务完成信号
finished 的格式是:
finished(content='xxx')
它表示任务已经完成。
例如:
Thought: 文件已经下载完成,任务结束。
Action: finished(content='The file has been downloaded.')
这个动作的意义是结束 Agent Loop。
如果没有 finished,外层系统不知道模型什么时候认为任务完成,可能会一直循环。
所以,finished 是 GUI Agent 的终止条件。
在工程实现中,外层调度器通常会判断:
如果 action_type == finished
则停止执行
这也说明动作空间不仅用于操作界面,还用于控制整个 Agent 生命周期。
十四、COMPUTER_USE 的 Note:语言、小计划、下一步摘要
COMPUTER_USE_DOUBAO 里还有一个 Note 部分。
它要求:
在 Thought 部分使用指定语言
写一个小计划
最后用一句话总结下一步动作和目标元素
源码中可以看到 {language} 和 {instruction} 两个占位符:{language} 用于控制 Thought 使用的语言,{instruction} 则用于注入用户任务。
这说明 Prompt 模板不是静态文本,而是可以被外层程序格式化。
例如:
prompt = COMPUTER_USE_DOUBAO.format(
language="Chinese",
instruction="打开浏览器并搜索 UI-TARS"
)
这样模型就知道:
用户任务是什么
Thought 应该用什么语言
输出格式是什么
能用哪些动作
这里的小计划也和论文中的 System-2 Reasoning 对应。
模型不是直接输出动作,而是先做一点任务分解和当前判断。
十五、MOBILE_USE:移动端 GUI Agent 模板
接下来是 MOBILE_USE_DOUBAO。
它和 COMPUTER_USE_DOUBAO 的整体结构很像,也要求输出:
Thought: ...
Action: ...
但动作空间不同。
移动端模板包括:
click
long_press
type
scroll
open_app
drag
press_home
press_back
finished
README 里也明确说,MOBILE_USE 适合移动设备或 Android 模拟器,并包含 long_press、open_app、press_home、press_back 这类移动端特有动作。
十六、为什么移动端没有 hotkey、right_single、left_double?
移动端和桌面端交互模型不一样。
桌面端常见:
鼠标右键
鼠标双击
键盘快捷键
窗口拖动
文件管理器操作
移动端常见:
点击
长按
滑动
返回
回到主页
打开 App
所以 MOBILE_USE_DOUBAO 里没有:
left_double
right_single
hotkey
但增加了:
long_press
open_app
press_home
press_back
这说明 UI-TARS 并没有强行用一套完全相同的动作覆盖所有平台,而是在"统一动作建模"的基础上,为不同设备保留必要的差异。
这也是 Unified Action Modeling 的工程化体现。
统一,不等于完全一样。
统一的是动作表达方式和解析方式;差异化的是不同平台真实存在的操作。
十七、long_press:移动端长按
long_press 是移动端常见操作。
例如:
长按复制文本
长按图片保存
长按图标弹出菜单
长按消息显示更多操作
它的格式是:
long_press(point='<point>x1 y1</point>')
它和 click 一样需要一个坐标,但语义不同。
同一个位置,点击和长按可能触发完全不同的结果。
所以移动端必须单独定义 long_press。
十八、open_app:直接打开应用
open_app 的格式是:
open_app(app_name='')
这个动作是移动端比较特殊的地方。
在手机任务中,用户经常会说:
打开微信
打开地图
打开设置
打开浏览器
如果只靠点击图标,模型必须先找到桌面上的 App 图标。
但移动端系统通常可以通过应用名启动 App。
所以 open_app 是一个更高层的动作。
它不是纯鼠标坐标操作,而是系统级动作。
这说明 UI-TARS 的动作空间不只是"点哪里",还包括一些更抽象的 GUI 操作。
十九、press_home 与 press_back:移动端导航动作
移动端还有两个非常关键的动作:
press_home()
press_back()
这两个动作对应手机系统级导航。
桌面端一般没有"返回键"和"主页键"这种统一操作,但移动端几乎所有任务都会用到。
例如:
返回上一页
关闭当前页面
退出 App
回到桌面
重新打开另一个 App
所以 MOBILE_USE_DOUBAO 必须包含它们。
这也说明,设计 GUI Agent 的动作空间时,不能只从程序员角度想"鼠标键盘有哪些 API",还要从真实用户习惯出发:
人在这个设备上会怎么操作?
二十、GROUNDING:只输出 Action 的轻量模板
第三个模板是 GROUNDING_DOUBAO。
和前两个不同,GROUNDING_DOUBAO 不要求输出 Thought,只要求输出:
Action: ...
它的动作空间也非常简单,基本只保留:
click(point='<point>x1 y1</point>')
README 里说,GROUNDING 推荐用于只关注动作输出的轻量任务,或者用于模型训练与评估;它只输出 Action,不输出 Thought,适合评估 grounding 能力。
这里的 grounding 可以理解成:
给定一个目标描述,让模型指出目标在屏幕上的位置。
例如:
用户指令:点击搜索按钮
模型输出:
Action: click(point='<point>850 120</point>')
它不关心复杂任务规划,也不关心多步骤执行,只关心:
模型能不能找到正确元素?
模型能不能输出正确坐标?
二十一、为什么需要 GROUNDING 模板?
如果已经有 COMPUTER_USE 和 MOBILE_USE,为什么还需要 GROUNDING?
原因是:
评估"能不能看准"和评估"能不能完成任务"是两件事。
完整 GUI Agent 任务包含很多能力:
理解任务
识别界面
规划步骤
定位元素
执行动作
观察反馈
错误恢复
判断完成
如果一个任务失败了,我们很难立刻判断失败原因。
可能是:
模型没有理解任务
模型找错了按钮
模型坐标点偏了
动作格式解析失败
页面加载慢
执行层出错
而 GROUNDING 模板把问题简化为:
给你截图和目标元素
你只需要输出点击坐标
这样就可以专门评估模型的视觉定位能力。
这对训练、测试和调参都很重要。
二十二、三个模板的核心区别
可以用一张表概括:
COMPUTER_USE
场景:桌面端
输出:Thought + Action
特点:支持点击、双击、右键、拖拽、快捷键、输入、滚动、等待、完成
适合:浏览器、办公软件、文件管理器、桌面应用
MOBILE_USE
场景:移动端
输出:Thought + Action
特点:支持点击、长按、输入、滚动、打开 App、拖拽、主页键、返回键、完成
适合:手机 App、Android 模拟器、移动端任务
GROUNDING
场景:定位评估
输出:Action
特点:通常只关注点击坐标
适合:测试模型是否能把目标元素定位到屏幕坐标
从这个对比可以看出:
COMPUTER_USE 和 MOBILE_USE 面向完整 Agent 任务;
GROUNDING 面向单步定位能力测试。
二十三、prompt.py 和 action_parser.py 是如何配合的?
prompt.py 不是孤立存在的。
它的输出格式,必须被 action_parser.py 解析。
README 的 Quick Start 中也展示了后处理过程:先用 parse_action_to_structure_output 把模型输出解析成结构化结果,再用 parsing_response_to_pyautogui_code 转换成 pyautogui 代码。
也就是说,UI-TARS 的工程链路是:
prompt.py
约束模型输出格式
↓
模型输出
Thought + Action
↓
action_parser.py
解析动作和坐标
↓
pyautogui
执行鼠标键盘动作
如果 Prompt 设计得太自由,Parser 就会很复杂。
如果 Prompt 设计得太死,模型又可能不够灵活。
UI-TARS 的做法是在两者之间取平衡:
Thought 部分允许模型自由表达推理
Action 部分必须符合严格格式
这是一种很实用的设计。
二十四、为什么 Action 要像函数调用?
你会发现,UI-TARS 的 Action 不是自然语言,而是类似函数调用:
click(point='<point>x1 y1</point>')
type(content='xxx')
hotkey(key='ctrl c')
finished(content='xxx')
这样设计有几个好处。
第一,容易解析。
函数名就是动作类型,参数就是动作输入。
第二,容易扩展。
以后可以增加:
screenshot()
select_file(path='xxx')
confirm()
第三,容易校验。
比如:
click 必须有 point
drag 必须有 start_point 和 end_point
scroll 必须有 direction
type 必须有 content
第四,容易映射到底层执行层。
例如:
click → pyautogui.click
type → pyperclip + pyautogui.hotkey
scroll → pyautogui.scroll
hotkey → pyautogui.hotkey
这种"类函数调用"的动作格式,正是 GUI Agent 工程化的关键。
二十五、为什么 point 要写成 <point>x y</point>?
UI-TARS 没有直接写:
click(x=100, y=200)
而是使用:
click(point='<point>100 200</point>')
这看起来有点绕,但它有实际意义。
第一,<point>...</point> 可以明确标记坐标区域。
模型输出中可能有很多数字,比如文件名、日期、金额、文本内容。用 point 标签可以帮助 parser 明确哪些数字是坐标。
第二,这种格式更适合和多模态模型的 grounding 输出对齐。
模型只需要在 point 标签里输出坐标。
第三,它方便后续统一转换。
action_parser.py 可以专门识别 point 标签,再统一转换成内部的 start_box 或其他结构化格式。
这也是 Prompt 和 Parser 之间的协议。
二十六、prompt.py 的设计思想
读完三个模板后,我们可以总结出 prompt.py 的设计思想。
第一,明确模型身份。
你是 GUI agent。
这让模型不再以聊天助手身份回答,而是以操作执行者身份输出下一步动作。
第二,明确输入上下文。
任务
动作历史
截图
这让模型根据当前状态做决策,而不是只根据用户一句话生成计划。
第三,明确输出格式。
Thought + Action
这让模型既能推理,又能被程序解析。
第四,明确动作空间。
click / type / scroll / drag / hotkey / wait / finished
这避免模型自由发挥。
第五,区分设备环境。
桌面端一套动作
移动端一套动作
Grounding 一套轻量动作
这让系统既统一,又能适配不同平台。
二十七、从二次开发角度看 prompt.py
如果你想基于 UI-TARS 做自己的桌面自动化项目,prompt.py 是最值得改造的地方之一。
比如,你可以增加自己的动作:
screenshot()
copy_selected_text()
switch_window(title='xxx')
open_file(path='xxx')
confirm_dialog()
但要注意一点:
修改 Prompt 的同时,必须同步修改 Parser 和执行层。
例如你在 Prompt 里增加:
switch_window(title='xxx')
那么 action_parser.py 就必须能解析它,执行层也必须知道如何切换窗口。
否则模型即使输出了这个动作,程序也无法执行。
所以,动作扩展必须遵循这条链路:
Prompt 增加动作定义
↓
Parser 增加动作解析
↓
Executor 增加动作执行
↓
测试用例覆盖新动作
这也是做 GUI Agent 工程化时最重要的原则。
二十八、prompt.py 的局限
虽然 prompt.py 很关键,但它不是万能的。
它主要解决的是:
让模型按指定格式输出动作
但它不能保证:
模型一定理解对了界面
坐标一定点得准
动作一定安全
任务一定成功
UI-TARS README 的限制部分也提到,模型在模糊或陌生环境中仍可能产生不准确描述、误识别 GUI 元素或采取次优动作。
所以,在真实产品里,只靠 Prompt 不够。
还需要:
动作安全校验
高风险操作确认
坐标边界检查
失败重试
异常状态检测
任务日志
人工接管
这也是 GUI Agent 和普通脚本不同的地方。
模型可以生成动作,但外层系统必须负责安全和可靠性。
总结
这篇文章我们正式分析了 UI-TARS 的 prompt.py。
这个文件虽然很短,但它定义了 UI-TARS GUI Agent 的核心交互协议。
其中:
COMPUTER_USE_DOUBAO
用于桌面端任务,支持点击、双击、右键、拖拽、快捷键、输入、滚动、等待和完成。
MOBILE_USE_DOUBAO
用于移动端任务,支持点击、长按、输入、滚动、打开 App、拖拽、主页键、返回键和完成。
GROUNDING_DOUBAO
用于轻量定位任务,只关注 Action 输出,适合评估模型 grounding 能力。
更重要的是,prompt.py 体现了 UI-TARS 的几个关键设计:
用 Thought + Action 分离推理和执行;
用函数调用式 Action 约束模型输出;
用标准动作空间连接模型和执行层;
用不同模板适配桌面、移动端和评估场景。
所以,prompt.py 不是普通提示词,而是 UI-TARS 工程链路的第一层协议。