深度解析 @tarko/agent 的 Agent 类:UI-TARS 多模态 Agent 运行时的核心 API 使用指南
【免费下载链接】UI-TARS-desktopThe Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS-desktop
Agent类是 UI-TARS 多模态 Agent 技术栈中@tarko/agent(位于仓库multimodal/tarko/agent)的核心运行时组件,它把 LLM 调用、工具注册执行、多模态上下文管理与事件流监控统一封装在一个事件驱动架构中。本指南以官方运行时 API 文档(agent-api.md)为主体骨架,结合仓库内真实源码展开,帮助你在构建自己的 GUI / 多模态智能体时掌握构造、运行、监控、取消与释放 Agent 的全部关键 API 与最佳实践。
一、Agent 运行时是什么:框架定位与架构速览
Agent类不是一次性的函数封装,而是一个可复用的多轮推理运行时。从源码注释可以清晰看到它的设计目标(agent.ts):
- 多轮推理 Agent 循环(multi-turn reasoning agent loop);
- 高度可定制,易于构建更高层级的 Agent;
- 工具注册与执行;
- 多模态上下文感知与管理;
- 与多个 LLM Provider 通信;
- 事件流管理,用于跟踪 Agent 循环状态。
在类层次上,Agent<T>继承自抽象基类BaseAgent<T>(base-agent.ts),并实现了接口IAgent<T>。BaseAgent负责全部生命周期钩子(如onLLMRequest、onBeforeToolCall、onPrepareRequest、onBeforeLoopTermination等)与循环终止控制;Agent则在其上实现工具管理器(ToolManager)、执行控制器(AgentExecutionController)、事件流处理器(AgentEventStreamProcessor)与 Runner(AgentRunner)的装配。因此调用agent.run()后真正跑起来的是一套由 runner/loop-executor/tool-processor/llm-processor 组成的推理管线。
二、实例化 Agent:构造函数与 AgentOptions 全量配置
new Agent(options?)
创建 Agent 实例是使用它的第一步。构造函数接受一个可选的AgentOptions配置对象:
import { Agent } from '@tarko/agent'; const agent = new Agent({ instructions: 'You are a helpful assistant', tools: [myTool], model: { provider: 'openai', id: 'gpt-4' }, maxIterations: 10 });AgentOptions 字段详解
AgentOptions在 agent-options.ts 中定义,它由 9 个分组接口组合而成。下表汇总了每个配置项的语义、默认值及源码依据:
| 分组 | 配置项 | 说明 | 默认值 |
|---|---|---|---|
AgentBaseOptions | id | 唯一实例标识,用于跟踪与日志 | '@tarko/agent'(见 agent.ts) |
name | Agent 名称,便于追踪 | 'Anonymous'(agent.ts) | |
instructions | 系统提示词,提供后完全替换默认提示词 | 内置默认提示词(见下) | |
AgentModelOptions | model | LLM 模型设置,含provider/id/displayName等 | 运行时按需解析 |
maxTokens | 单次请求最大 token 数 | 未限制 | |
temperature | 采样温度,越低越确定 | 0.7(agent.ts) | |
top_p | 核采样参数,范围 0.0–1.0 | 不设置则用模型默认 | |
thinking | 推理内容控制(LLMReasoningOptions) | { type: 'disabled' };若传入非对象会抛Invalid thinking option错误 | |
AgentToolOptions | tools | 注册给 Agent 的工具定义数组 | undefined |
tool | 工具过滤配置(include/exclude) | 不过滤 | |
toolCallEngine | 工具调用引擎:'native'/'prompt_engineering'/'structured_outputs',或自定义引擎构造器 | 'native' | |
AgentLoopOptions | maxIterations | 推理循环最大迭代次数 | 1000(agent.ts) |
AgentMemoryOptions | context | 多模态上下文管理,如maxImagesCount | maxImagesCount默认5(agent.ts) |
eventStreamOptions | 事件流处理器配置 | — | |
enableStreamingToolCallEvents | 是否流式输出工具调用构建过程事件 | false | |
initialEvents | 从持久化存储恢复会话历史用的事件数组 | undefined | |
AgentMiscOptions | logLevel | 日志级别(LogLevel) | 开发INFO/ 生产WARN |
metric | 是否启用指标采集(TTFT、TTLT 等),metric.enable | false | |
AgentWorkspaceOptions | workspace | 涉及文件读写时的文件系统与命令执行作用域目录 | 当前工作目录 |
AgentSandboxOptions | sandboxUrl | 工具沙箱 URL | — |
默认指令(instructions 缺省时)来自 getDefaultPrompt:
You are an intelligent assistant that can use provided tools to answer user questions. Please use tools when needed to get information, don't make up answers. Provide concise and accurate responses.注意:文档示例里的maxIterations: 10只是业务演示值,框架默认上限是1000次迭代——现代 LLM 的长程 Agentic 任务能力允许更复杂的多步推理,这正是源码注释中提高默认上限的原因。
三、执行任务:run() 的重载与流式 / 非流式模式
run(input)/run(options)
run是 Agent 的主入口,执行完整的“LLM 推理 → 工具调用 → 再次推理”闭环,直到产出最终答案或达到maxIterations。它有三个重载(agent.ts):
// 简单文本输入(非流式) const response = await agent.run('What is the weather like?'); // 对象选项 + 非流式,input 可为多模态内容数组 const response = await agent.run({ input: 'Analyze this image', model: 'gpt-4-vision-preview' }); // 流式模式:返回 AsyncIterable<Event> const stream = await agent.run({ input: 'Help me plan a trip', stream: true }); for await (const event of stream) { console.log(event); }三种签名的返回差异如下:
| 调用形式 | 返回类型 |
|---|---|
run(input: string) | Promise<AssistantMessageEvent> |
run(options: AgentRunNonStreamingOptions) | Promise<AssistantMessageEvent> |
run(options: AgentRunStreamingOptions) | Promise<AsyncIterable<Event>> |
AgentRunObjectOptions 字段
执行选项在 agent-run-options.ts 中定义:
interface AgentRunObjectOptions { input: string | ChatCompletionContentPart[]; // 用户输入,支持多模态内容部件 stream?: boolean; // 是否流式 sessionId?: string; // 会话标识;缺省自动生成 model?: string; // 本次运行覆盖模型 provider?: string; // 本次运行覆盖 Provider toolCallEngine?: ToolCallEngineType; // 本次运行覆盖工具引擎 environmentInput?: EnvironmentInput; // 环境上下文(以 environment_input 事件注入) abortSignal?: AbortSignal; // 取消信号(由 Agent 内部自动注入) }从源码理解 run() 的行为细节
对照 agent.ts 的实现,有几个容易忽略的运行时语义:
- 并发保护:如果 Agent 正在执行任务,再次调用
run()会抛出错误Agent is already executing a task. Complete or abort the current task before starting a new one.,需要先abort()或等待本次运行结束。 - 会话标识自动生成:未传
sessionId时按Date.now() + 随机串生成。 - 事件自动发报:无论流式与否,
run()都会先发出user_message事件(多模态输入会被标记),随后发出agent_run_start(携带sessionId、provider、model、modelDisplayName、agentName),结束或出错时发出agent_run_end(携带iterations、elapsedMs、status)。agent_run_start中的选项会被sanitizeRunOptions清洗,敏感字段abortSignal会被移除,复杂多模态输入会被替换为占位串。 - 环境上下文注入:传入
environmentInput时,其content/description/metadata会打包成environment_input事件注入会话,且不会算作 user 消息。 - 初始化延迟执行:首次
run()前会先调用initialize(),派生 Agent 可借此完成耗时的初始化工作。
四、注册与筛选工具:让 Agent 具备行动能力
registerTool(tool)
工具是 Agent 能力的载体,registerTool底层调用ToolManager.registerTool,以工具名为主键存储(agent.ts)。也可在构造函数里通过tools: [...]批量注册。一个标准的Tool定义包含name、description、JSON Schema 格式的schema和真正执行的function:
import { Tool } from '@tarko/agent'; const weatherTool: Tool = { name: 'get_weather', description: 'Get current weather for a location', schema: { type: 'object', properties: { location: { type: 'string', description: 'City name' } }, required: ['location'] }, function: async (args) => { const { location } = args as { location: string }; return `Weather in ${location}: Sunny, 25°C`; } }; agent.registerTool(weatherTool);getTools()/getAvailableTools()
getTools():同步返回注册的全部工具,但会先经this.options.tool的过滤配置(include/exclude,先 include 后 exclude)处理;getAvailableTools():异步返回经过钩子修饰后真正可用的工具集——它会把getTools()的结果再交给onRetrieveTools生命周期钩子处理(agent.ts)。
const availableTools = await agent.getAvailableTools(); console.log(`${availableTools.length} tools available for execution`);需要说明的是:源码注释提醒,如果onRetrieveTools的实现依赖运行期状态,getAvailableTools()的结果可能与run()实际使用的工具存在差异。此外,base-agent.ts 提供了更推荐的onPrepareRequest钩子,可在每轮请求发出前统一改写系统提示词与工具集。
执行期钩子链:在每一轮循环中,工具执行还会经过onBeforeToolCall(执行前拦截/改写参数)→ 引擎执行 →onAfterToolCall(改写结果)→onToolCallError(工具抛错时转换为可恢复返回值)这些钩子,全部定义在 base-agent.ts。
五、直接调用 LLM:callLLM / getLLMClient / setCustomLLMClient
在某些场景(如单独做一次摘要、判断、分类)无需走完整 Agent 循环,可以直接调用当前 Agent 已选定的 LLM。
callLLM(params, options?)
它对“获取 LLM 客户端 + 当前模型”的通用模式做了封装:自动把当前模型id合并进请求参数(无需手动传model),并在客户端或模型不可用时抛出清晰错误。同样有两个重载,根据stream推断返回类型:
// 非流式调用 const response = await agent.callLLM({ messages: [{ role: 'user', content: 'Hello' }], temperature: 0.7 }); // 流式调用:逐块返回 ChatCompletionChunk const stream = await agent.callLLM({ messages: [{ role: 'user', content: 'Hello' }], stream: true }); for await (const chunk of stream) { console.log(chunk.choices[0]?.delta?.content); }类型签名:
callLLM(params: Omit<ChatCompletionCreateParams, 'model'> & { stream?: false }, options?: RequestOptions): Promise<ChatCompletion>callLLM(params: Omit<ChatCompletionCreateParams, 'model'> & { stream: true }, options?: RequestOptions): Promise<AsyncIterable<ChatCompletionChunk>>
getLLMClient()/setCustomLLMClient(client)
getLLMClient()返回当前可用的 OpenAI 兼容客户端(OpenAI | undefined)。解析顺序(agent.ts):优先返回自定义客户端 → 其次复用 runner 上已创建的客户端 → 都没有但存在当前模型时即时创建并回填给 runner。setCustomLLMClient(client)用于测试或自定义实现(例如指向兼容 OpenAI 协议的自建网关)。调用后日志提示“Custom LLM client set, will ignore model parameters in run()”,且该客户端会同步下发给 runner 的llmProcessor。
import OpenAI from 'openai'; const customClient = new OpenAI({ apiKey: 'your-api-key', baseURL: 'https://custom-llm-endpoint.com' }); agent.setCustomLLMClient(customClient);generateSummary(request)
基于既有会话消息生成简短会话标题/摘要。底层(agent.ts)会追加一条系统指令“生成不超过 6 个词的标题”,以temperature: 0.3、max_tokens: 25、JSON mode 调用 LLM,解析 JSON 中的title字段:
const summary = await agent.generateSummary({ messages: [ { role: 'user', content: 'What is machine learning?' }, { role: 'assistant', content: 'Machine learning is...' } ] }); console.log(`Summary: ${summary.summary}`);返回Promise<SummaryResponse>,其中summary为生成的标题文本(解析失败时回退'Untitled Conversation'),并带上model与provider信息。注意源码注释中标注了 FIXME:当前基于运行中的事件流生成摘要,使用时需留意这一实现细节。
六、事件流与可观测性:getEventStream()
Agent 的事件驱动本质体现在AgentEventStreamProcessor上。getEventStream()返回事件流管理器,可订阅会话全过程的各类事件:
const eventStream = agent.getEventStream(); eventStream.on('assistant_message', (event) => { console.log('Assistant:', event.content); }); eventStream.on('tool_call', (event) => { console.log(`Calling tool: ${event.name}`); });事件类型清单
文档列出的核心事件如下,它们在 agent-event-stream.ts 中有着更完整的定义:
| 事件 | 触发时机 | 事件负载要点 |
|---|---|---|
user_message | 收到用户输入 | content(可多模态) |
assistant_message | Agent 生成回复 | content、toolCalls、finishReason |
tool_call | 工具执行开始 | 工具名、参数 |
tool_result | 工具执行完成 | 结果 |
system | 系统事件与错误 | — |
agent_run_start | 一次 run 开始 | sessionId、provider、model、agentName |
agent_run_end | 一次 run 结束 | sessionId、iterations、elapsedMs、status |
在真实实现中事件分类更细,还包括流式中间态assistant_streaming_message、assistant_streaming_thinking_message、assistant_streaming_tool_call,思考类事件assistant_thinking_message,规划类plan_start/plan_update/plan_finish,环境上下文environment_input,以及结构化终态final_answer/final_answer_streaming。所有事件统一携带id、type、timestamp基础字段,便于序列化、持久化与回放。@tarko/agent还提供了 Agent Snapshot 快照框架(源码中的isReplaySnapshot/_setIsReplay()与此相关),可据此重放历史会话。
七、状态查询与生命周期管理:status() / abort() / dispose()
status()
返回当前执行状态(AgentStatus),底层来自执行控制器:
const currentStatus = agent.status(); console.log(`Agent status: ${currentStatus}`);abort()
中断当前运行中的任务。返回true表示确实中止了一次执行,false表示当时没有可中止的任务(agent.ts):
const isAborted = agent.abort(); if (isAborted) { console.log('Agent execution aborted'); }实现上,run()会从AgentExecutionController.beginExecution()拿到一个AbortSignal并注入执行上下文,因此中止信号可以沿请求链路(含generateSummary等直连 LLM 的调用)传播,被取消的请求会表现为AbortError。
dispose()
释放 Agent 占用的全部资源。基类dispose()(base-agent.ts)具备幂等保护(重复调用直接忽略),Agent 实现其onDispose()(agent.ts)做两件事:先中止并收尾任何仍在运行的执行,再清空事件流:
await agent.dispose(); console.log('Agent disposed successfully');getCurrentLoopIteration()/getCurrentModel()
调试与状态观测还需要两个便捷方法:
const iteration = agent.getCurrentLoopIteration(); // 1-based;未运行时为 0 console.log(`Currently on iteration ${iteration}`); const model = agent.getCurrentModel(); // AgentModel | undefined if (model) { console.log(`Using ${model.provider}/${model.id}`); }getCurrentModel()在源码中实际返回构造时解析出的AgentModel,即 resolveModel 的结果,可据此在运行前确认最终生效的 Provider / 模型。
八、错误处理:常见异常与捕获模式
Agent 运行期间可能抛出多种异常,官方文档给出了如下捕获模板:
try { const response = await agent.run('Process this request'); console.log(response.content); } catch (error) { if (error.name === 'AbortError') { console.log('Request was cancelled'); } else { console.error('Agent error:', error.message); } }常见错误场景归纳:
| 错误 | 含义与触发点 |
|---|---|
AbortError | 通过abort()或AbortSignal取消请求 |
ModelError | LLM Provider 或模型配置问题(例如callLLM时模型未解析) |
ToolError | 工具执行失败,可通过onToolCallError钩子转换为可恢复值 |
ValidationError | 输入或配置非法(例如thinking传入非对象在构造期即抛错) |
另外两类极易踩坑的运行时错误来自源码而非模型层:并发执行错误(run()未结束就再次调用)和客户端不可用错误(getLLMClient()/getCurrentModel()缺失时callLLM、generateSummary会抛出带明确指引信息的异常)。
九、完整实战:装配一个带工具的数学计算 Agent
下面整合全文 API,给出一个可直接运行的完整示例(继承并完善自官方文档的用例)。通过事件流实时观测每一轮工具调用与助手回复,异常与资源清理均由finally保障:
import { Agent, Tool } from '@tarko/agent'; // 1. 定义工具 const calculatorTool: Tool = { name: 'calculate', description: 'Perform mathematical calculations', schema: { type: 'object', properties: { expression: { type: 'string', description: 'Math expression to evaluate' } }, required: ['expression'] }, function: async (args) => { const { expression } = args as { expression: string }; try { // 生产环境请替换为安全的数学求值器,这里仅为演示 const result = eval(expression); return `Result: ${result}`; } catch (error) { return `Error: Invalid expression`; } } }; // 2. 创建 Agent const agent = new Agent({ instructions: 'You are a helpful math assistant. Use the calculator tool for computations.', tools: [calculatorTool], model: { provider: 'openai', id: 'gpt-4' }, maxIterations: 5, temperature: 0.1, logLevel: 1 // Info level }); // 3. 订阅事件,实时观测执行过程 const eventStream = agent.getEventStream(); eventStream.on('tool_call', (event) => { console.log(`[Tool] Calling ${event.name}:`, event.args); }); eventStream.on('assistant_message', (event) => { console.log(`[Assistant] ${event.content}`); }); eventStream.on('agent_run_end', (event) => { console.log(`[Run] finished in ${event.elapsedMs}ms, ${event.iterations} iterations`); }); // 4. 执行并确保资源释放 async function main() { try { const response = await agent.run('What is 15 * 23 + 7?'); console.log('Final answer:', response.content); } catch (error) { if (error.name === 'AbortError') { console.error('Request was cancelled'); } else { console.error('Error:', error.message); } } finally { await agent.dispose(); } } main();十、最佳实践总结
结合官方文档 Best Practices 与源码行为,构建生产级 Agent 时建议遵循以下原则:
- 资源管理:用完即调
dispose();它具备幂等保护,可安全地在finally中重复调用。 - 错误处理:所有
run()/callLLM()调用包进 try-catch,并优先识别AbortError以区分“用户取消”与“真异常”。 - 工具设计:保持工具职责单一、描述详实(description 直接决定 LLM 是否会选中它),JSON Schema 要完整,
required字段必不可少。 - 上下文预算:使用
context.maxImagesCount限制对话历史中的图片数量(默认 5),超出部分会以保留语义的文本占位符替换,避免多模态上下文撑爆模型窗口。 - 流式优先:长任务与需要实时反馈的交互场景优先使用
stream: true,逐条消费事件而非等待最终一次性结果。 - 监控与观测:订阅
tool_call、tool_result、agent_run_end等事件用于日志与指标分析;开启metric.enable可采集 TTFT / TTLT 等时序指标。 - 并发约束:Agent 实例默认不并发执行任务,长时间运行的场景请设计任务队列,或先
abort()再发起新任务。 - 长会话策略:借助
initialEvents可在重建 Agent 实例时从存储恢复事件历史;generateSummary可为多轮会话生成短标题,便于归档检索。
延伸阅读
- Agent 生命周期钩子的完整说明见同目录姊妹文档 agent-hooks.md(
onBeforeToolCall、onBeforeLoopTermination、onPrepareRequest等); - 包级介绍与更多示例见 @tarko/agent README 及其 examples 目录;
- 关注接口定义可阅读 agent-options.ts 与 agent-run-options.ts、事件类型全集见 agent-event-stream.ts;
- 多模态 GUI 场景的能力超集可参考同一框架中的
GUIAgent(gui-agent.ts)。
【免费下载链接】UI-TARS-desktopThe Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS-desktop
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考