首页
/ UI-TARS GUI Agent 2.0 最小实战:AIOHybridOperator 与 Doubao 模型的浏览器任务自动化

UI-TARS GUI Agent 2.0 最小实战:AIOHybridOperator 与 Doubao 模型的浏览器任务自动化

2026-09-05 13:30:32作者:晏闻田Solitary

本篇以 examples/gui-agent-2.0 中的最小可运行 Demo 为主体,讲解如何基于 @gui-agent/agent-sdk(GUI Agent SDK 2.0)+ AIOHybridOperator + 火山引擎 Doubao 模型,搭建一个能真正执行"查询上海天气"这类浏览器 GUI 任务的 Agent。读完后你将掌握:Demo 的完整配置与运行流程(环境变量、安装、构建、启动)、系统提示词的动作空间约定,以及 GUIAgent 的截图-推理-执行循环在源码层面的工作方式,为自行替换 Operator、接入模型服务提供可直接复用的参考。

Demo 做了什么

根据 README,这个最小 Demo 只做三件事:

  1. 使用 AIOHybridOperator 和一个 Doubao 模型初始化 GUIAgent
  2. 发送一个单轮请求:"Check the weather in Shanghai";
  3. 把 Agent 的响应内容打印到控制台。

它虽然只有两个源文件,却完整覆盖了 GUI Agent SDK 2.0 的核心调用链:环境加载 → 模型配置 → Operator 配置 → Agent 初始化 → run() 执行 → 响应输出入口文件 全文仅 40 行左右,是理解整套 SDK 用法的最小样本。

运行前提与项目结构

README 列出的前置条件:

  • Node.js 18+,pnpm 或 npm;
  • 火山引擎 Ark 的可用凭据(API Key、Base URL)和一个 Doubao 模型 ID;
  • 一个正在运行的 AIO Sandbox 服务 Base URL。

项目结构(见 README 项目结构一节):

  • src/index.ts:入口,负责搭建 Agent 并运行 Demo;
  • src/constants.ts:Agent 的系统提示词(SYSTEM_PROMPT);
  • package.json:构建与运行脚本及依赖声明;
  • .env.local:环境变量文件(不提交到仓库)。

package.json 看,运行时依赖为:dotenv(环境变量加载)、@gui-agent/agent-sdk(Agent 核心)、@gui-agent/operator-aio(AIO Operator)、@gui-agent/action-parser(动作解析);开发依赖包含 tsx(开发态直接运行 TS)、typescript(构建)和 @types/node。注意当前仓库中这几个 @gui-agent/* 包锁定在 0.3.0-beta.12-canary 版本,属于 beta 阶段产物,实际接入时应以仓库中声明的版本为准。

环境变量配置:四把钥匙

examples/gui-agent-2.0 目录下创建 .env.local,README 给出的模板如下:

ARK_BASE_URL=https://your-ark-base-url
ARK_API_KEY=your-ark-api-key
DOUBAO_SEED_1_6=your-doubao-model-id
SANDBOX_URL=http://your-aio-sandbox-url

四个变量的分工在 入口文件 中一一对应:

变量 用途 消费位置
ARK_BASE_URL 火山引擎 Ark 模型服务 Base URL model.baseURL
ARK_API_KEY Ark 鉴权 API Key model.apiKey
DOUBAO_SEED_1_6 Doubao Seed 1.6 模型端点 ID model.id
SANDBOX_URL AIO Sandbox 服务地址 AIOHybridOperator.baseURL

加载方式上,入口文件 使用 dotenv 显式指定了相对路径:

import { config } from 'dotenv';
import path from 'node:path';
config({ path: path.join(__dirname, '..', '.env.local') });

这里依赖 CommonJS 环境下可用的 __dirname,因此 tsconfig.json 固定为 "module": "CommonJS""moduleResolution": "node",且 package.json 中没有 "type": "module"——这也是同目录 quick-start-for-agent.md 中反复强调的硬性约束:不要加 "type": "module",否则 __dirname 不可用,环境变量加载会失败。

同目录的 quick-start-for-agent.md 还给出了更完整的环境变量说明,包括模型端点 ID 的格式(形如 ep-{timestamp}-{hash})以及可选的 DOUBAO_1_5_VP 变量,可作为排障时的补充参考。

入口代码逐段解读

模型配置:as const 的必要性

const doubao = {
  id: process.env.DOUBAO_SEED_1_6!,
  provider: 'volcengine' as const,
  baseURL: process.env.ARK_BASE_URL!,
  apiKey: process.env.ARK_API_KEY!,
};

provider: 'volcengine' as const 是关键细节:SDK 的模型配置要求 provider 是字面量联合类型(如 ModelProviderName),而普通字符串字面量在对象字面量中会被类型系统拓宽为 string 导致赋值报错。quick-start-for-agent.md 的"COMMON ISSUES"章节把它列为高频问题:错误写法是 provider: 'volcengine',正确写法必须加 as const,同时不要显式导入 ModelProviderNameAgentModel 等类型,以避免类型导入引发的编译问题。

Operator 配置:AIOHybridOptions 的三个参数

const operator = new AIOHybridOperator({
  baseURL: process.env.SANDBOX_URL!,
  timeout: 10000,
});

operator-aio 的类型定义 看,AIOHybridOptions 支持三个字段:

  • baseURL: string:必填,AIO Sandbox 服务的 HTTP 地址,所有动作与截图请求都发往该地址;
  • timeout?: number:请求超时,Demo 中设为 10000ms;
  • headers?: Record<string, string>:可选的自定义请求头,用于需要额外鉴权的沙箱部署。

Agent 初始化与执行

const guiAgent = new GUIAgent({
  operator,
  model: doubao,
  systemPrompt: SYSTEM_PROMPT,
});

const response = await guiAgent.run({
  input: [{ type: 'text', text: 'Check the weather in Shanghai' }],
});
console.log(response.content);

run() 的输入是结构化的内容块数组,文本块形如 { type: 'text', text: '...' }quick-start-for-agent.md 中还给出中文任务示例 [{ type: 'text', text: '打开百度搜索页面并搜索TypeScript教程' }]。返回对象通过 content 属性承载 Agent 最终响应。

系统提示词与动作空间

constants.ts 中定义的 SYSTEM_PROMPT 决定了模型每一步"思考-行动"的输出契约,完整内容值得逐条对照:

You are a GUI agent. You are given a task and your action history, with screenshots. You need to perform the next action to complete the task.

## Output Format
Thought: ...
Action: ...

## Action Space
navigate(url='xxx')                      # The url to navigate to
navigate_back()                          # Navigate back to the previous page.
click(point='<point>x1 y1</point>')
left_double(point='<point>x1 y1</point>')
right_single(point='<point>x1 y1</point>')
drag(start_point='<point>x1 y1</point>', end_point='<point>x2 y2</point>')
hotkey(key='ctrl c')                     # Space-separated, lowercase, at most 3 keys
type(content='xxx')                      # Use \', \", \n escapes; trailing \n submits
scroll(point='<point>x1 y1</point>', direction='down or up or right or left')
wait()                                   # Sleep 5s and take a screenshot
finished(content='xxx')                  # End the task with result content

## Note
- Use Chinese in `Thought` part.
- Write a small plan and finally summarize your next action (with its target element) in one sentence in `Thought` part.

## User Instruction

其中几个约定直接影响后续解析与执行:

  • 坐标格式<point>x1 y1</point> 是严格的 XML 风格标记,动作解析器依赖它提取坐标;
  • 转义规则typefinished 的 content 需要用 \'\"\n 转义,且以 \n 结尾表示提交输入;
  • hotkey 限制:按键以小写、空格分隔,最多 3 个键;
  • 语言要求Thought 部分使用中文思考并给出一句话行动计划,Action 使用英文动作语法。

对照 AIOHybridOperator 源码supportedActions(),SDK 侧实际支持的动作类型是:navigatenavigate_backwaitmouse_moveclickdouble_clickright_clickmiddle_clickdragtypehotkeypressscrollcall_userfinished。系统提示词里的动作名与 Operator 的执行分支存在一层映射,例如提示词中的 left_double 在执行器中同时匹配 left_doubledouble_click 分支、right_single 匹配 right_click 分支、navigate 的入参兼容 urlcontent 两种写法。也就是说:提示词是模型语言,Operator 执行器是机器语言,动作解析层负责把前者翻译成后者

源码纵深:GUIAgent 的截图-推理-执行循环

Demo 只调用了 run(),但真正的工作发生在 agent-sdk 的 GUIAgent 实现 中,理解它才能理解 Demo 输出为什么是"思考 + 行动"多轮迭代的结果。

工具注册与坐标归一化

GUIAgent 构造时接收 operatormodelsystemPromptmaxLoopCount(映射为 maxIterations)、loopIntervalInMs(默认 500ms)、normalizeCoordinatesdetailCalculator 等配置。initialize() 阶段会注册一个无参数的 GUI 工具:

// multimodal/gui-agent/agent-sdk/src/GUIAgent.ts
const result = await this.operator!.doExecute({
  actions: [input.operator_action],
});

执行前,如果动作带有 operator_action,会先经过 normalizeActionCoords 做坐标归一化处理(使用 normalizeCoordinates 配置,默认实现见 defaultImpls.ts)。这一步解释了为什么动作解析出的原始坐标能适配不同分辨率的屏幕——AIOHybridOperator 的 calculateRealCoords 会把归一化坐标乘以当前屏幕尺寸与缩放系数换算为真实像素:

realX: coords.normalized.x * screenContext.screenWidth * screenContext.scaleX,
realY: coords.normalized.y * screenContext.screenHeight * screenContext.scaleY,

而屏幕尺寸来自最近一次截图:screenshot() 会用 Base64ImageParser 解析 base64 图像,把返回的宽高写回 screenshotWidth/Height(初始默认 1280x1024)。

每轮工具调用后自动截图回注

onAfterToolCall 钩子是 GUI Agent 感知环境的关键:每次 GUI 工具调用完成后,先 sleep(loopIntervalInMs) 等待页面稳定,然后调用 operator.doScreenshot(),把截图 base64 转成 data URI 图片内容块,并附上 detail 参数(由 detailCalculator 根据图片宽高计算);如果截图带有页面 URL,还会追加一条文本"当前页面 URL"。最后通过事件流发出 environment_input 事件(metadata 标记为 screenshot),让下一轮 LLM 请求"看到"执行动作后的屏幕状态。

这就是 GUI Agent 2.0 的核心闭环:模型输出 Thought + Action → 解析并执行动作 → 截图回注 → 模型基于新截图继续决策,直到输出 finished(content='...') 或达到 maxIterations 上限。

AIOHybridOperator:浏览器 + 桌面混合执行

AIOHybridOperator 源码 看,它是"混合"算子的原因:内部同时持有 AIOBrowser(浏览器控制,负责 navigate、navigate_back、获取当前 URL)和 AIOComputer(桌面级输入控制,负责 move、click、double_click、right_click、drag、type、hotkey、scroll 等)。初始化时它会先对沙箱做一次截图 ping(aioComputer.screenshot(0))确认可达,再创建并 launch 浏览器实例。

几个值得注意的执行细节:

  • wait 默认 3 秒:执行器中 wait 分支默认 sleep 3000ms,若动作携带 time 输入则按 time * 1000 计算。注意这与系统提示词中"wait() Sleep for 5s"的表述不完全一致——提示词描述的是模型视角的语义,实际时长以执行器代码为准;
  • type 自动剥离换行content 会先 trim(),再剥离末尾的 \n(含字面 \\n),即提示词要求"以 \n 结尾表示提交"的语义由解析/执行链消化;
  • hotkey 按键映射:按键串会被小写化、按空白拆分,逐键经 keyNameMap 映射为标准键名;多键走 hotkey(mappedKeys),单键走 press(key)
  • scroll 的方向换算up/down/left/right 被映射为固定的 dx/dy 步长(如 up 为 dy=10、down 为 dy=-10),且若提供 point 会先移动鼠标到该点再滚动;
  • drag 四步走moveTo → mouseDown → dragTo → mouseUp
  • 底层协议:从 types.ts 看,AIOComputer 与沙箱之间交换的是 MOVE_TOCLICKMOUSE_DOWNDRAG_TOSCROLLTYPINGHOTKEY 等大写动作类型,以及 ScreenshotResponse(含 base64scaleFactor 字段)——Operator 是把 GUI 动作空间翻译为沙箱 HTTP 协议的适配层。

构建与运行流程

README 与 quick-start-for-agent.md 给出的完整流程:

  1. 安装依赖:
pnpm install
# 或
npm install
  1. 两种运行方式(脚本定义见 package.json):
# 开发模式:tsx 直接执行 TypeScript,跳过编译
pnpm dev        # 等价于 tsx src/index.ts

# 生产模式:tsc 编译到 dist/ 后运行
pnpm build      # 等价于 tsc
pnpm start      # 等价于 node dist/index.js
  1. 预期输出:
📦 Testing AIO Operator...

📝 Agent with AIO Operator Response:
================================================
<agent response content>
================================================

其中 response.content 即模型最终 finished(content='...') 携带的任务结果。pnpm clean 可用于删除 dist/ 产物。

排障要点

结合 quick-start-for-agent.md 的故障排查章节与源码行为,常见问题可归纳为四类:

  • 模块/类型错误ERR_MODULE_NOT_FOUNDCannot assign string to ModelProviderName——前者多因跳过 pnpm build 或误加了 "type": "module";后者加 as const 修复;
  • 环境变量未加载ARK_API_KEY is not defined——确认 .env.local 位于 examples/gui-agent-2.0/ 目录下(加载路径是 __dirname/../.env.local,即相对于编译/运行产物目录);
  • 沙箱连接失败Connection refused to SANDBOX_URL——确认 AIO Sandbox 服务已启动且 URL 正确;Operator 初始化时的截图 ping 会第一时间暴露该问题;
  • 模型端点无效Model endpoint not found——核对 DOUBAO_SEED_1_6 端点 ID 是否在你的火山引擎账号中处于激活状态。

此外,timeout 参数影响的是 Operator 与沙箱之间的请求超时;若模型响应慢导致整体超时,应调整 Operator 配置或重试,而不是修改仓库内示例代码本身。

小结与扩展路径

这个 Demo 的价值在于用最少的代码串起了 GUI Agent SDK 2.0 的三大件:

组件 职责 仓库内实现位置
Agent 核心 @gui-agent/agent-sdk 系统提示词、动作解析、截图回注循环 GUIAgent.ts
动作解析 @gui-agent/action-parser 把 Thought/Action 文本解析为结构化动作 multimodal/gui-agent/action-parser/src
执行算子 @gui-agent/operator-aio 沙箱 HTTP 协议适配、坐标换算 AIOHybridOperator.ts

以它为基础做扩展时的替换点也很清晰:换 Operator(如 operator-browseroperator-nutjsoperator-adb 等仓库内其他实现)只影响"在哪执行",换 model 只影响"谁来决策",而 SYSTEM_PROMPT 的动作空间需要与目标 Operator 的 supportedActions() 对齐——这正是 Demo 中提示词动作集与 AIOHybridOperator 支持动作列表 逐条对应的原因。

登录后查看全文
热门项目推荐
相关项目推荐