在上一篇文章中,我们分析了 UI-TARS 的动作空间设计。
UI-TARS 没有让模型自由输出自然语言,而是把 GUI 操作约束成一组标准动作:
click
drag
hotkey
type
scroll
wait
finished
这些动作解决的是一个问题:
模型应该如何把"我要操作界面"这件事,转成程序可以解析和执行的动作?
但仅有动作还不够。
因为 GUI Agent 不只是"点一下按钮",而是在不断变化的图形界面里完成多步骤任务。
它每一步都需要判断:
当前界面是什么?
任务进行到哪一步?
刚才的操作有没有成功?
下一步应该点哪里?
现在应该输入、滚动、等待,还是结束?
所以 UI-TARS 在 prompt.py 里并不是只要求模型输出 Action,而是要求输出:
Thought: ...
Action: ...
这篇文章就来分析:
为什么
Thought + Action是 GUI Agent 的最小决策闭环?
一、先看最基本的输出格式
在 UI-TARS 的桌面端和移动端 Prompt 中,模型输出格式都是:
Thought: ...
Action: ...
例如:
Thought: 当前页面已经打开浏览器,我需要点击地址栏输入搜索关键词。
Action: click(point='<point>420 80</point>')
再比如:
Thought: 搜索框已经获得焦点,现在输入关键词并按回车提交搜索。
Action: type(content='UI-TARS\n')
从源码看,COMPUTER_USE_DOUBAO 和 MOBILE_USE_DOUBAO 都采用了 Thought + Action 的输出格式;而 GROUNDING_DOUBAO 则只输出 Action,主要用于轻量定位任务和 grounding 能力评估。
这说明 UI-TARS 把使用场景分成了两类:
完整 GUI Agent 任务:
需要 Thought + Action。
单步定位或评估任务:
可以只输出 Action。
为什么完整任务必须有 Thought?
因为完整 GUI 任务不是一次点击,而是连续决策。
二、只输出 Action 会有什么问题?
假设用户任务是:
帮我在浏览器里搜索 UI-TARS。
如果模型只输出 Action,可能是:
Action: click(point='<point>420 80</point>')
这个动作本身也许是对的。
但我们不知道:
模型为什么点这里?
它认为这里是地址栏,还是搜索框?
它是否看到了浏览器?
它有没有考虑当前页面状态?
它下一步想做什么?
如果点错了,我们也很难分析错误原因。
例如它点击了网页中的广告输入框,而不是浏览器地址栏。
只看 Action,我们只能看到:
点错了。
但不知道模型为什么点错。
如果有 Thought:
Thought: 我需要点击页面顶部的地址栏,以便输入搜索关键词。
Action: click(point='<point>420 80</point>')
就可以知道模型的意图是正确的,但 grounding 可能有偏差。
再比如:
Thought: 我需要点击页面中间的搜索框。
Action: click(point='<point>420 80</point>')
如果当前任务应该用浏览器地址栏,而模型却选择页面搜索框,那么问题就是策略判断不同。
所以,Thought 的第一个作用是:
让动作变得可解释。
三、Thought 是"观察"和"动作"之间的中间层
GUI Agent 的每一步都可以拆成三段:
Observation
↓
Thought
↓
Action
其中:
Observation:
当前截图、用户任务、动作历史。
Thought:
模型对当前状态的判断,以及下一步操作理由。
Action:
可执行动作,例如 click、type、scroll、wait。
如果没有 Thought,链路就是:
Observation
↓
Action
这会让模型更像一个反射式系统。
看到按钮,就点按钮。
看到输入框,就输入。
但复杂任务需要的不只是反射,而是计划、判断和纠错。
论文中也强调,UI-TARS 引入 System-2 Reasoning,是因为仅靠 System-1 式的直觉决策不足以应对动态 GUI 环境;它通过 Thought Augmentation 让模型在每个动作前生成推理内容,从而支持任务分解、长期一致性、里程碑识别、试错和反思等推理模式。
所以,Thought 的核心作用是:
在"看见什么"和"做什么"之间,增加一个显式判断过程。
四、为什么说它是"最小决策闭环"?
一个完整的 GUI Agent 循环通常是:
用户任务
↓
当前截图
↓
模型推理
↓
输出动作
↓
执行动作
↓
重新截图
↓
继续推理
如果再细化一点,就是:
Observation
↓
Thought
↓
Action
↓
Execution
↓
New Observation
这就是最小闭环。
其中,Thought + Action 是模型在每一轮里的输出。
Thought:
我为什么要这么做。
Action:
我具体要做什么。
这两者合起来,构成了一次最小决策。
如果只有 Thought,没有 Action,模型只是分析,不能执行。
如果只有 Action,没有 Thought,系统可以执行,但很难解释、调试和控制。
所以:
Thought 负责决策理由,Action 负责执行接口。
两者组合起来,才是 GUI Agent 每一步最小可用的决策单元。
五、Thought 解决多步骤任务中的"短视问题"
很多 GUI 任务不是一步完成的。
例如:
打开浏览器,搜索 UI-TARS,进入 GitHub 页面,查看 README。
这个任务至少包含几个阶段:
1. 打开浏览器
2. 输入关键词
3. 查看搜索结果
4. 进入 GitHub 页面
5. 找到 README
如果模型没有 Thought,很容易变成局部反应:
看到搜索框 → 输入
看到按钮 → 点击
看到链接 → 点击
但它可能忘记最终目标。
例如,它可能进入了论文页面,而不是 GitHub 页面。
有 Thought 时,模型可以在每一步提醒自己:
我的目标是进入 UI-TARS 的 GitHub 页面,而不是随便打开一个结果。
这就是长期目标一致性。
论文中把 long-term consistency 作为 System-2 reasoning 的重要推理模式之一,目的就是让模型在长任务中保持对最终目标的记忆,而不是被当前界面中的局部元素带偏。
所以 Thought 的第二个作用是:
防止模型只看局部界面,而忘记整体任务。
六、Thought 帮助模型识别任务阶段
GUI 任务通常有阶段性。
例如登录流程:
输入账号
输入密码
点击登录
等待跳转
确认登录成功
每个阶段的判断方式不同。
如果模型不知道当前阶段,就可能重复操作。
比如已经登录成功了,它还在找登录按钮。
如果有 Thought,模型可以表达:
Thought: 当前页面已经跳转到首页,说明登录成功。下一步应该查找报表入口。
Action: click(point='<point>120 260</point>')
这个 Thought 中包含了一个里程碑判断:
登录成功。
论文中也提到,UI-TARS 的 Thought Augmentation 会加入 milestone recognition,也就是里程碑识别能力,让模型判断当前阶段是否完成,并决定是否进入下一阶段。
这对 GUI Agent 很重要。
因为没有里程碑识别,模型很容易在一个阶段里打转。
例如:
一直点登录
一直刷新
一直滚动
一直找同一个按钮
所以 Thought 的第三个作用是:
帮助模型判断任务进展到哪一步。
七、Thought 帮助处理失败和异常
真实 GUI 环境里,动作不一定成功。
例如:
按钮没点中
页面没加载完
弹窗挡住了
网络请求失败
输入框没有获得焦点
快捷键被软件拦截
如果模型没有 Thought,失败后可能继续机械操作。
比如:
点击搜索
没有结果
继续点击搜索
还是没有结果
继续点击搜索
有 Thought 时,它可以做反思:
Thought: 我刚才点击搜索按钮后页面没有变化,可能是输入框没有内容,或者按钮没有点中。下一步先检查搜索框是否已经输入关键词。
Action: click(point='<point>380 90</point>')
或者:
Thought: 当前出现了权限提示,说明导出操作被阻止。需要先关闭提示并告知用户权限不足。
Action: finished(content='当前账号没有导出权限,任务无法继续。')
论文中也把 trial and error、reflection thinking 作为 System-2 reasoning 的重要部分,用来增强模型在动态环境中的错误恢复能力。
所以 Thought 的第四个作用是:
让模型在失败时有机会反思,而不是继续盲目执行。
八、Action 是程序可执行的"接口层"
Thought 负责解释和推理,但最终系统不能执行 Thought。
系统真正执行的是 Action。
例如:
Action: click(point='<point>850 120</point>')
或者:
Action: type(content='UI-TARS\n')
这些 Action 的格式在 prompt.py 中被严格定义,比如桌面端支持点击、双击、右键、拖拽、快捷键、输入、滚动、等待和完成;移动端则增加长按、打开 App、返回主页、返回上一级等动作。
这样做的目的就是让后处理程序可以稳定解析。
UI-TARS README 的 Quick Start 展示了后处理链路:先使用 parse_action_to_structure_output 把模型输出解析成结构化结果,再使用 parsing_response_to_pyautogui_code 转换成 pyautogui 代码。
也就是说:
Thought 给人和模型看。
Action 给程序和执行层看。
这两部分的职责是分开的。
九、Thought 和 Action 的分离,是一种工程解耦
Thought + Action 看起来只是输出格式,但它其实是一种工程解耦。
可以这样理解:
Thought:
自然语言层、推理层、解释层。
Action:
结构化层、协议层、执行层。
Thought 可以比较自由。
例如:
当前界面显示搜索结果,我需要选择官方 GitHub 仓库,而不是第三方文章。
Action 必须严格。
例如:
click(point='<point>512 342</point>')
这种设计的好处是:
既保留了模型的推理能力;
又保证了程序的解析稳定性。
如果所有内容都写成自然语言,程序不好解析。
如果所有内容都写成结构化动作,模型又缺少推理空间。
所以 UI-TARS 选择:
Thought 自由表达
Action 严格约束
这是一种非常实用的 Agent 输出格式。
十、action_parser.py 如何利用这个格式?
在 UI-TARS 的后处理链路中,action_parser.py 需要从模型响应中提取动作。
模型响应可能长这样:
Thought: I need to click the search box.
Action: click(point='<point>850 120</point>')
后处理需要做几件事:
1. 提取 Thought 内容
2. 提取 Action 字符串
3. 解析 action_type
4. 解析 action_inputs
5. 转换坐标格式
6. 生成可执行代码
源码中的 action_parser.py 包含 parse_action、parse_action_to_structure_output、parsing_response_to_pyautogui_code 等函数,用来完成从模型文本到结构化动作,再到 pyautogui 代码的转换。
所以 Thought + Action 不只是 Prompt 层的格式,也是 Parser 层的输入协议。
如果模型输出不符合这个协议,后面的解析就可能失败。
十一、为什么 GROUNDING 可以没有 Thought?
前面说 Thought 很重要。
但 UI-TARS 里还有一个 GROUNDING 模板,它只输出:
Action: ...
为什么?
因为 GROUNDING 的目标不是完成完整任务,而是测试模型能不能定位目标元素。
例如:
用户指令:点击搜索按钮
模型输出:
Action: click(point='<point>850 120</point>')
这里评估的是:
模型能不能把"搜索按钮"定位到正确坐标。
不需要任务分解,也不需要多步推理。
README 中也说明,GROUNDING 推荐用于轻量任务或模型训练评估,它只输出 Action,不包含 Thought,适合评估 grounding 能力。
所以是否需要 Thought,取决于任务类型。
单步定位:
可以只要 Action。
多步骤任务:
最好使用 Thought + Action。
十二、Thought + Action 对调试非常重要
做过自动化项目的人都知道,最难的不是"跑成功一次",而是"失败后知道为什么失败"。
传统脚本失败时,日志可能只有:
Click at 850,120 failed.
这类日志很难排查。
而 GUI Agent 如果保留 Thought,就可以记录每一步:
第 1 步:
Thought: 我需要点击地址栏。
Action: click(...)
第 2 步:
Thought: 地址栏已经获得焦点,现在输入关键词。
Action: type(...)
第 3 步:
Thought: 搜索结果已经出现,需要点击 GitHub 官方仓库。
Action: click(...)
如果第 3 步点错了,我们可以判断:
模型是否识别错了结果?
模型是否目标理解错了?
坐标是否偏移?
页面是否和预期不同?
这对产品化非常关键。
因为 GUI Agent 一旦进入真实桌面环境,错误一定会发生。
没有 Thought,错误很难定位。
有 Thought,至少可以把错误拆成几类:
观察错误
推理错误
目标选择错误
坐标 grounding 错误
执行层错误
页面状态变化
十三、Thought + Action 也有安全价值
GUI Agent 可以操作真实电脑。
这意味着它可能执行高风险动作:
删除文件
发送邮件
提交表单
付款
修改系统设置
上传隐私文件
关闭安全软件
如果只有 Action,系统可能只能在最后一刻看到:
click(point='<point>...')
但不知道这个点击意味着什么。
如果有 Thought,系统可以提前看到模型意图:
Thought: 我需要点击"确认删除"按钮来删除该文件。
Action: click(...)
这就给外层安全模块提供了拦截机会。
例如:
检测到 Thought 中包含"删除文件"
暂停执行
请求用户确认
当然,不能完全依赖 Thought 做安全判断,因为模型的 Thought 也可能不完整或不准确。但它至少提供了一个可用于审计和风险识别的信号。
UI-TARS README 的限制部分也提醒,强 GUI 操作能力可能带来误用风险,模型也可能在模糊或陌生环境中误识别元素、采取次优动作,因此真实系统需要额外安全评估和约束。
所以 Thought + Action 的另一个价值是:
让 GUI Agent 的行为更容易被监控和审计。
十四、Thought 不等于绝对可靠的真实原因
这里也要强调一点:
Thought 很有用,但不能把 Thought 当成模型内部真实思维的完整还原。
它是模型生成的解释文本,是一种可观察的中间输出。
它可以帮助调试和约束,但不保证百分百准确。
例如模型可能写:
Thought: 我需要点击保存按钮。
Action: click(point='<point>900 700</point>')
但实际那个坐标是"取消"按钮。
这说明 Thought 和 Action 之间也可能不一致。
所以真实系统应该同时检查:
Thought 是否合理
Action 是否符合动作空间
坐标是否在安全区域
目标元素是否可能匹配
执行后界面是否发生预期变化
也就是说,Thought 是辅助信号,不是唯一真相。
这点在做产品时非常重要。
十五、从代码角度看,Thought 应该如何保存?
如果你基于 UI-TARS 做自己的 GUI Agent,我建议每一步都保存完整日志:
{
"step": 3,
"observation": "screenshot_003.png",
"thought": "当前页面显示搜索结果,我需要点击 GitHub 官方仓库。",
"action": "click(point='<point>512 342</point>')",
"parsed_action": {
"action_type": "click",
"action_inputs": {
"point": "512 342"
}
},
"execution_result": "success",
"next_observation": "screenshot_004.png"
}
这样做有几个好处:
方便复盘
方便调试
方便训练数据回收
方便安全审计
方便用户理解 Agent 做了什么
如果未来要做反思式训练,类似 UI-TARS 论文里的 Reflective Online Traces,这些日志也可以成为非常有价值的数据来源。论文中提到,UI-TARS 通过自动收集、过滤和反思式优化交互轨迹,来缓解 GUI Agent 数据瓶颈。
十六、Thought + Action 和传统 RPA 日志的区别
传统 RPA 日志一般是:
执行步骤 1:点击按钮
执行步骤 2:输入文本
执行步骤 3:等待 2 秒
执行步骤 4:点击导出
它记录的是"执行了什么"。
但 GUI Agent 的日志应该记录:
看到了什么
怎么判断
为什么这么做
具体执行了什么
执行后发生了什么
所以它更像:
Observation:
当前页面是搜索结果页。
Thought:
我需要找到 UI-TARS 官方 GitHub 仓库,避免点击第三方博客。
Action:
click(point='<point>512 342</point>')
Result:
页面跳转到 GitHub 仓库。
这就是智能体日志和脚本日志的区别。
前者包含决策过程。
后者只包含执行过程。
十七、最小闭环里的每个角色
现在我们可以把完整闭环再总结一下:
Observation:
提供当前事实,包括截图、任务、历史动作。
Thought:
解释当前判断,决定下一步策略。
Action:
输出标准化、可解析、可执行的动作。
Parser:
把 Action 转成结构化数据。
Executor:
执行鼠标键盘操作。
New Observation:
反馈执行结果,进入下一轮。
其中,Thought + Action 是模型每轮决策的核心输出。
它刚好连接了两边:
向上连接用户任务和视觉观察;
向下连接 parser 和执行层。
所以它不是简单格式,而是整个 GUI Agent 架构的中间接口。
十八、二次开发时如何设计自己的 Thought + Action?
如果你要基于 UI-TARS 做自己的桌面自动化项目,可以借鉴这个格式。
建议保留:
Thought: ...
Action: ...
但可以根据自己的场景增加更细的字段。
例如:
Observation Summary: ...
Plan: ...
Thought: ...
Action: ...
Risk: ...
不过要注意:
字段越多,模型输出越复杂,解析失败概率越高。
对于大多数场景,最小格式已经够用:
Thought:
我当前的判断和下一步理由。
Action:
一个严格格式的可执行动作。
如果想增加安全控制,可以在外层系统做,而不是让模型输出过多字段。
例如:
模型输出 Thought + Action
↓
系统解析 Action
↓
安全模块检查
↓
必要时请求用户确认
↓
执行动作
这样比让模型自己输出复杂安全判断更稳定。
十九、一个完整示例
假设任务是:
在浏览器中搜索 UI-TARS,并打开 GitHub 仓库。
Agent 的多步输出可能是:
Step 1
Thought:
当前屏幕显示浏览器首页,我需要点击地址栏输入搜索关键词。
Action:
click(point='<point>430 80</point>')
Step 2
Thought:
地址栏已经获得焦点,现在输入关键词并提交搜索。
Action:
type(content='UI-TARS GitHub\n')
Step 3
Thought:
搜索结果已经出现,我需要选择 bytedance/UI-TARS 官方 GitHub 仓库,而不是第三方文章。
Action:
click(point='<point>520 310</point>')
Step 4
Thought:
当前页面已经进入 bytedance/UI-TARS GitHub 仓库,任务目标完成。
Action:
finished(content='已打开 UI-TARS GitHub 仓库。')
可以看到,每一步都是一个小闭环:
判断当前状态
说明下一步理由
输出一个动作
等待环境反馈
这就是 Thought + Action 的真正意义。
总结
这篇文章我们分析了 UI-TARS 中非常核心的输出格式:
Thought + Action
它看起来只是 Prompt 里的两行格式,但实际上构成了 GUI Agent 的最小决策闭环。
其中:
Thought 负责推理、解释、阶段判断、失败反思和安全审计。
Action 负责输出标准化、可解析、可执行的动作。
只有 Thought,没有 Action,模型无法真正操作界面。
只有 Action,没有 Thought,系统虽然能执行,但难以解释、调试和控制。
所以,Thought + Action 的价值在于:
既保留模型的推理能力,
又提供程序可执行的动作接口。
从源码角度看,prompt.py 负责定义这种格式,action_parser.py 负责解析 Action 并生成 pyautogui 执行代码。
从 Agent 架构角度看,它连接了:
截图观察
任务理解
模型推理
动作解析
鼠标键盘执行
环境反馈
这也是为什么我们说:
Thought + Action 是 GUI Agent 的最小决策闭环。