首页
/ UI-TARS SDK 实践指南:用 GUIAgent、Operator 与 UITarsModel 构建跨平台 GUI 自动化 Agent

UI-TARS SDK 实践指南:用 GUIAgent、Operator 与 UITarsModel 构建跨平台 GUI 自动化 Agent

2026-09-05 12:35:29作者:凤尚柏Louis

本文基于 UI-TARS-desktop 仓库中已归档的 1.0 版 SDK 文档(docs/archive-1.0/sdk.md),系统讲解 @ui-tars/sdk 的核心架构与实战用法:从 GUIAgent 的运行循环、状态机与回调协议,到 Operator 接口契约、screenshot()/execute() 的实现要求,再到自定义模型与“规划-执行”分层方案。读完后,你可以在 Node.js 或 Web 环境中装配一个能驱动真实图形界面的 UI-TARS Agent,并知道如何为自己的设备(桌面、浏览器、移动端)编写 Operator。

说明:该文档在仓库中标记为归档(archived),对应的是 SDK 1.0 时代的实验性 API;当前实现位于 packages/ui-tars/sdkpackage.json 中版本为 1.2.3),下文的源码引用均以当前仓库实际代码为准。

一、SDK 定位:一个“模型 + 操作器”的跨平台 Agent 框架

@ui-tars/sdk 被官方描述为 “a powerful cross-platform (ANY device/platform) toolkit for building GUI automation agents”,它同时支持 Node.jsWeb Browser 两种运行环境(从 packages/ui-tars/sdk/package.jsonbrowser 字段与 ./core 独立入口可以印证这一点)。

其核心抽象可以用原档中的类图表达:

classDiagram
    class GUIAgent~T extends Operator~ {
        +model: UITarsModel
        +operator: T
        +signal: AbortSignal
        +onData
        +run()
    }

    class UITarsModel {
        +invoke()
    }

    class Operator {
        <<interface>>
        +screenshot()
        +execute()
    }

    class NutJSOperator {
        +screenshot()
        +execute()
    }

    class WebOperator {
        +screenshot()
        +execute()
    }

    class MobileOperator {
        +screenshot()
        +execute()
    }

    GUIAgent --> UITarsModel
    GUIAgent ..> Operator
    Operator <|.. NutJSOperator
    Operator <|.. WebOperator
    Operator <|.. MobileOperator

在仓库源码中,这套结构落位清晰:

二、快速上手

2.1 命令行体验

原档给出的最快捷入口是官方 CLI:

npx @ui-tars/cli start

输入 UI-TARS 模型服务配置(baseURLapiKeymodel)后,即可用自然语言控制电脑:

Need to install the following packages:
Ok to proceed? (y) y

│
◆  Input your instruction
│  _ Open Chrome
└

对应实现位于 packages/ui-tars/cli/src/cli/start.ts(CLI 包入口为 packages/ui-tars/cli/src/index.ts)。

2.2 SDK 基础用法

基础用法围绕 @ui-tars/sdk 包展开,以 nut-js(跨平台电脑控制库)作为 Operator 的例子如下(继承自原档,保持可直接复制):

注:NutJS Operator 支持常见桌面自动化动作:鼠标 click / double click / right click / drag / hover、键盘输入与热键、滚动、截图。

import { GUIAgent } from '@ui-tars/sdk';
import { NutJSOperator } from '@ui-tars/operator-nut-js';

const guiAgent = new GUIAgent({
  model: {
    baseURL: config.baseURL,
    apiKey: config.apiKey,
    model: config.model,
  },
  operator: new NutJSOperator(),
  onData: ({ data }) => {
    console.log(data)
  },
  onError: ({ data, error }) => {
    console.error(error, data);
  },
});

await guiAgent.run('send "hello world" to x.com');

run(instruction, historyMessages?, remoteModelHdrs?) 的完整签名见 packages/ui-tars/sdk/src/GUIAgent.tshistoryMessages 允许跨任务携带历史消息(例如“规划-执行”模式中的上下文接力)。

三、Agent 执行循环:截图 → 推理 → 动作的闭环

原档的时序图描述了每一轮循环的四步协作:

sequenceDiagram
    participant user as User
    participant guiAgent as GUI Agent
    participant model as UI-TARS Model
    participant operator as Operator

    user -->> guiAgent: "`instruction` + <br /> `Operator.MANUAL.ACTION_SPACES`"

    activate user
    activate guiAgent

    loop status !== StatusEnum.RUNNING
        guiAgent ->> operator: screenshot()
        activate operator
        operator -->> guiAgent: base64, Physical screen size
        deactivate operator

        guiAgent ->> model: instruction + actionSpaces + screenshots.slice(-5)
        model -->> guiAgent: `prediction`: click(start_box='(27,496)')
        guiAgent -->> user: prediction, next action

        guiAgent ->> operator: execute(prediction)
        activate operator
        operator -->> guiAgent: success
        deactivate operator
    end

    deactivate guiAgent
    deactivate user

对照 packages/ui-tars/sdk/src/GUIAgent.tswhile (true) 主循环,可以确认并补充几个文档没有明说的实现细节:

  1. 截图带重试与校验operator.screenshot() 包裹在 asyncRetry 中(retry?.screenshot?.maxRetries),随后用 Jimp.fromBuffer 解码校验宽高是否合法;非法截图计入 snapshotErrCnt,累计达到 MAX_SNAPSHOT_ERR_CNT(源码常量 constants.ts 中为 10 次)后以 SCREENSHOT_RETRY_ERROR 状态终止。
  2. 滑动窗口送入 VLM。每轮把 conversationstoVlmModelFormat + processVlmParams 转换后发给模型(GUIAgent.ts),即文档中 screenshots.slice(-5) 的“最近 5 张截图”策略;同时携带 screenContext(截图宽高)与 scaleFactor,这是后续坐标还原的关键输入。
  3. 模型调用也带重试minTimeout: 30s;若抛出 abort 类错误则 bail 直接退出循环(GUIAgent.ts)。
  4. 动作逐个执行parsedPredictions 是数组,逐个交给 operator.execute(...),其中 screenWidth/screenHeight/scaleFactor/factors 全部透传(GUIAgent.ts)。内部动作 call_user 会把状态置为 CALL_USERfinished 置为 END 并跳出循环(GUIAgent.ts)。
  5. 用户主动停止的收尾钩子finally 中若状态为 USER_STOPPED,会向 operator.execute 补发一个 action_type: 'user_stop' 的动作,让 Operator 有机会做清理(GUIAgent.ts)。

模型侧,UITarsModel.invoke() 的实现见 packages/ui-tars/sdk/src/Model.ts:先按 uiTarsVersion 选择 maxPixels 阈值对截图做压缩(preprocessResizeImage),转成 OpenAI 消息格式后调用模型,最后用 @ui-tars/action-parser 包的 actionParser 把原始 prediction 文本解析为 parsedPredictions(含坐标还原所用的 factorscreenContextscaleFactor)。max_tokens 默认值为 1000(V1.5 为 65535),temperature 默认 0,top_p 默认 0.7(Model.ts)。

四、配置选项全解:GUIAgentConfig

GUIAgent 构造函数接受的配置在源码中定义为 packages/ui-tars/sdk/src/types.tsGUIAgentConfig。结合原档说明整理如下:

选项 类型 / 默认值 说明
model OpenAI 兼容配置对象 或 UITarsModel 实例 baseURL(API 端点)、apiKey(鉴权)、model(模型名),其余可选参数同 OpenAI Chat Completions 参数
operator 实现了 Operator 接口的实例 决定运行在什么设备上(桌面/浏览器/移动)
signal AbortSignal 取消操作的信号
onData (params: { data: GUIAgentData }) => void 接收 Agent 数据/状态更新
onError (params: { data, error }) => void 错误处理回调,errorGUIAgentError
systemPrompt string(可选) 自定义系统提示词;缺省时由 Operator 的 MANUAL.ACTION_SPACES 自动拼装
maxLoopCount number,默认 25 最大交互轮数(源码默认值来自 MAX_LOOP_COUNT,见 GUIAgent.ts
loopIntervalInMs number,默认 0 两轮循环之间的间隔毫秒数
retry { model?, screenshot?, execute? },各含 maxRetriesonRetry 三段式重试策略,默认不重试
logger 可选 默认 console
uiTarsVersion UITarsModelVersion 模型版本,影响 max_tokens 与图像压缩像素阈值

4.1 onData 的增量语义(重点)

原档特别强调:data.conversations 是 delta,不是完整对话历史。每条 conversation 对象包含 fromhuman / gpt / screenshotBase64 等角色标识)与 value(消息内容)。在 GUIAgent.ts 中可以印证这一点——每次回调都传 conversations: data.conversations.slice(-1),即只推最后一条;状态变化(如进入 RUNNING / PAUSE)时则推 conversations: [] 的空增量。因此消费方若要还原完整轨迹,需要自行按顺序累积这些增量。完整的 GUIAgentData 结构见 packages/ui-tars/shared/src/types/data.ts(含 versioninstructionsystemPromptmodelNamestatusconversations 等字段)。

4.2 状态机

stateDiagram-v2
    [*] --> INIT
    INIT --> RUNNING
    RUNNING --> RUNNING: Execute Actions
    RUNNING --> END: Task Complete
    RUNNING --> MAX_LOOP: Loop Limit Reached
    END --> [*]
    MAX_LOOP --> [*]

当前源码中的 StatusEnumpackages/ui-tars/shared/src/types/agent.ts)比 1.0 文档更丰富:init / running / pause / end / call_user / max_loop(已标记 deprecated,为兼容保留)/ error / user_stopped。其中 PAUSE 配合 GUIAgent.pause()/resume()GUIAgent.ts)可在执行中途挂起再恢复;user_stopped 对应 AbortSignal 触发后的终态。

五、处理 Abort 信号

文档给出的标准取消姿势是向 GUIAgent 传入 AbortController.signal

const abortController = new AbortController();

const guiAgent = new GUIAgent({
  // ... other config
  signal: abortController.signal,
});

// ctrl/cmd + c to cancel operation
process.on('SIGINT', () => {
  abortController.abort();
});

从源码看,signal?.aborted 会在每轮循环顶部被检查(GUIAgent.ts),命中后置为 USER_STOPPED 并跳出;模型请求内部也会透传该 signal(通过 useContext 注入,见 GUIAgent.ts),使进行中的 OpenAI 调用可被中断。

六、高级用法(一):自定义 Operator

实现自定义 Operator 只需两个核心方法:screenshot()execute()

6.1 包骨架

原档给出的 operator 包 package.json 模板(保持完整,可直接照抄改造):

{
  "name": "your-operator-tool",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "scripts": {
    "dev": "rslib build --watch",
    "prepare": "npm run build",
    "build": "rsbuild",
    "test": "vitest"
  },
  "files": [
    "dist"
  ],
  "publishConfig": {
    "access": "public",
    "registry": "https://registry.npmjs.org"
  },
  "dependencies": {
    "jimp": "^1.6.0"
  },
  "peerDependencies": {
    "@ui-tars/sdk": "^1.2.0-beta.17"
  },
  "devDependencies": {
    "@ui-tars/sdk": "^1.2.0-beta.17",
    "@rslib/core": "^0.5.4",
    "typescript": "^5.7.2",
    "vitest": "^3.0.2"
  }
}

仓库内的 packages/ui-tars/operators/nut-js/package.json 等 Operator 包即采用同构的 rslib 构建方案,可作为参照。

6.2 screenshot() 契约

返回 ScreenshotOutput(继承自共享类型 ScreenshotResult,见 packages/ui-tars/shared/src/types/agent.ts):

interface ScreenshotOutput {
  // Base64 encoded image string
  base64: string;
  // Device pixel ratio (DPR)
  scaleFactor: number;
}

源码注释强调 base64 应保持物理像素尺寸(physical_pixels = logical_resolution * scaleFactor),这与后续坐标换算直接相关。

6.3 execute() 契约

ExecuteParams 完整定义(与 packages/ui-tars/sdk/src/types.ts 一致):

interface ExecuteParams {
  /** Raw prediction string from the model */
  prediction: string;
  /** Parsed prediction object */
  parsedPrediction: {
    action_type: string;
    action_inputs: Record<string, any>;
    reflection: string | null;
    thought: string;
  };
  /** Device Physical Resolution */
  screenWidth: number;
  /** Device Physical Resolution */
  screenHeight: number;
  /** Device DPR */
  scaleFactor: number;
  /** model coordinates scaling factor [widthFactor, heightFactor] */
  factors: Factors;
}

注意 factors 是模型坐标系到设备坐标系的缩放因子,UI-TARS 模型输出的坐标默认归一化到 [1000, 1000](源码常量 DEFAULT_FACTORS = [1000, 1000],见 packages/ui-tars/sdk/src/constants.ts),actionParser 在解析阶段即结合 factorsscreenContextscaleFactor 完成还原,Operator 拿到的是可直接落点的 action_inputs

6.4 完整示例:继承 @ui-tars/sdk/coreOperator

import {
  Operator,
  type ScreenshotOutput,
  type ExecuteParams
  type ExecuteOutput,
} from '@ui-tars/sdk/core';
import { Jimp } from 'jimp';

export class CustomOperator extends Operator {
  // Define the action spaces and description for UI-TARS System Prompt splice
  static MANUAL = {
    ACTION_SPACES: [
      'click(start_box="") # click on the element at the specified coordinates',
      'type(content="") # type the specified content into the current input field',
      'scroll(direction="") # scroll the page in the specified direction',
      'finished() # finish the task',
      // ...more_actions
    ],
  };

  public async screenshot(): Promise<ScreenshotOutput> {
    // Implement screenshot functionality
    const base64 = 'base64-encoded-image';
    const buffer = Buffer.from(base64, 'base64');
    const image = await sharp(buffer).toBuffer();

    return {
      base64: 'base64-encoded-image',
      scaleFactor: 1
    };
  }

  async execute(params: ExecuteParams): Promise<ExecuteOutput> {
    const { parsedPrediction, screenWidth, screenHeight, scaleFactor } = params;
    // Implement action execution logic

    // if click action, get coordinates from parsedPrediction
    const [startX, startY] = parsedPrediction?.action_inputs?.start_coords || '';

    if (parsedPrediction?.action_type === 'finished') {
      // finish the GUIAgent task
      return { status: StatusEnum.END };
    }
  }
}

要点:

  • 必须实现screenshot()(捕获当前屏幕)、execute()(按模型预测执行动作);
  • 可选静态属性MANUAL.ACTION_SPACES 定义动作空间及说明,供 UI-TARS 模型理解;
  • execute() 返回 ExecuteOutput{ status: StatusEnum } 的扩展,见 types.ts),用 status: StatusEnum.END 主动结束任务;
  • 加载进 GUIAgent 时,ACTION_SPACES 会被注入系统提示词。源码中的拼装逻辑见 GUIAgent.ts buildSystemPrompt:若 Operator 定义了 MANUAL.ACTION_SPACES,则用 SYSTEM_PROMPT_TEMPLATE 并替换占位符 {{action_spaces_holder}};否则回退到内置默认动作空间(click / left_double / right_single / drag / hotkey / type / scroll / wait / finished / call_user,见 constants.ts)。
const guiAgent = new GUIAgent({
  // ... other config
  systemPrompt: `
  // ... other system prompt
  ${CustomOperator.MANUAL.ACTION_SPACES.join('\n')}
  `,
  operator: new CustomOperator(),
});

参考实现:仓库中的 NutJSOperator 同样以 static MANUAL = { ACTION_SPACES: [...] } 声明动作空间,并在 execute 中按 action_type 分发到 nut-js 的鼠标/键盘 API。

七、高级用法(二):自定义模型

通过继承 UITarsModel 并覆写 invoke() 可以实现自定义模型逻辑(原档示例):

class CustomUITarsModel extends UITarsModel {
  constructor(modelConfig: { model: string }) {
    super(modelConfig);
  }

  async invoke(params: any) {
    // Implement custom model logic
    return {
      prediction: 'action description',
      parsedPredictions: [{
        action_type: 'click',
        action_inputs: { /* ... */ },
        reflection: null,
        thought: 'reasoning'
      }]
    };
  }
}

const agent = new GUIAgent({
  model: new CustomUITarsModel({ model: 'custom-model' }),
  // ... other config
});

原档附带一条重要提示:不推荐轻易实现自定义模型,因为 UITarsModel 内部封装了大量数据处理逻辑——按 uiTarsVersion 选择像素阈值压缩截图、OpenAI 消息格式转换、Response API 的增量消息与 responseId 管理(Model.ts)、以及 actionParser 的坐标解析与还原(Model.ts)。除非接入非标推理服务,通常只需换 baseURL/model 即可。

八、高级用法(三):规划模型 + 执行 Agent 的分层组合

对于多步骤复杂任务,原档建议组合规划/推理模型(如 OpenAI-o1、DeepSeek-R1)做任务分解,再逐条交给 GUIAgent 执行:

const guiAgent = new GUIAgent({
  // ... other config
});

const planningList = await reasoningModel.invoke({
  conversations: [
    {
      role: 'user',
      content: 'buy a ticket from beijing to shanghai',
    }
  ]
})
/**
 * [
 *  'open chrome',
 *  'open trip.com',
 *  'click "search" button',
 *  'select "beijing" in "from" input',
 *  'select "shanghai" in "to" input',
 *  'click "search" button',
 * ]
 */

for (const planning of planningList) {
  await guiAgent.run(planning);
}

这种“推理模型出计划、GUI 模型管执行”的分工,与 SDK 的设计契合:GUIAgent.run 每次执行一个子指令并走完整的截图-推理-动作循环,而 run() 第二参数 historyMessages 可进一步在不同子任务间接力上下文。

九、小结与延伸阅读

本文以 docs/archive-1.0/sdk.md 为骨架,结合仓库源码对 @ui-tars/sdk 的三大件做了印证与补充:GUIAgent 的主循环(重试、滑动窗口、终止态)、Operator 的双方法契约与 MANUAL.ACTION_SPACES 的提示词注入机制、UITarsModel 的图像预处理与 action-parser 坐标还原。继续深入可查阅:

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384