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_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 的最小决策闭环。

相关推荐
传奇开心果编程1 天前
【SwiftUI提高练中学】第8课 网络层架构:拦截器、重试、缓存与离线优先
学习·macos·ui·ios·swiftui
传奇开心果编程1 天前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
大圣编蚕1 天前
Rancher证书轮换过期导致不能访问UI问题处理
ui·rancher
可乐ea1 天前
Agent 评测换判据:用终态数据库状态判定任务是否完成
数据库·ui·oracle·mcp工具·agent评测·终态断言·智能体可靠性
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程2 天前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack
传奇开心果编程2 天前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程2 天前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
传奇开心果编程2 天前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程2 天前
【ArkUI进阶练中学】第11课:安全与合规进阶
学习·ui·华为·harmonyos