UI-TARS 源码解析 #7:Thought + Action 输出格式:GUI Agent 的最小决策闭环

在上一篇文章中,我们分析了 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_DOUBAOMOBILE_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_actionparse_action_to_structure_outputparsing_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 的最小决策闭环。

相关推荐
曹牧1 天前
C#:关闭UI,清理线程
开发语言·ui·c#
春卷同学1 天前
HarmonyOS掌上记账APP开发实践第29篇:条件渲染与 ForEach — 动态 UI 的构建策略
ui·华为·harmonyos
小小测试开发1 天前
Midscene.js:14K Stars 视驱 UI 自动化框架,用截图替代 DOM 选择器写测试
javascript·ui·自动化
不吃辣4901 天前
从0-1学习导航页面布局优化和搭建
ui·平面·前端框架
大师兄66682 天前
HarmonyOS7 状态驱动 UI 原理:为什么改了数据视图就变
ui·arkui·实战案例·harmonyos7
不甘平凡的小鸟2 天前
Element-UI记录
javascript·vue.js·ui·elementui
এ慕ོ冬℘゜2 天前
原生 JS 实现自定义单选日历组件(无 UI 库、纯手写、禁用过去日期)
开发语言·javascript·ui
天天进步20152 天前
UI-TARS 源码解析 #6:动作空间设计:click、drag、hotkey、type、scroll 如何统一成模型输出?
ui