# 交互:观察与动作空间的扩展 第一章提出过一个论断:在底层模型固定时,提升 Agent 任务表现最主要的系统工程手段,往往是重新定义或扩展**观察空间**与**动作空间**。在前五章的内容中,**Agent 与世界的交互是轮流的**。用户说完一句,Agent 想一段、调用几个工具,再回一句;在它思考的这段时间里,世界被默认为静止的。这个前提如此自然,以至于很少被当成一个假设写出来。这一章要挑战的正是这个前提。 ## 模态与触发时机的扩展 把观察空间和动作空间摊开,会发现它们各有两个可以扩展的方向。 - **模态**决定观察和动作的**形式**:Agent 是只读文本,还是也能听见声音、看见屏幕、感知力矩;是只能输出 token,还是也能发声、点击、驱动关节。 - **触发时机**决定观察和动作的**节奏**:观察是 Agent 主动去取,还是世界主动推来;动作是必须在一个回合内做完,还是可以跨越回合、中途被打断、被更紧急的事抢占。 前面几章扩展的是这两个空间的**内容**,这一章扩展的是它们的**模态**和**时机**: | | 观察空间的扩展 | 动作空间的扩展 | |---|---|---| | **内容**(第二至五章) | 上下文工程、记忆与知识库 | 工具、代码生成 | | **模态**(本章) | 语音、屏幕、物理传感器 | 说话、点击、关节运动 | | **时机**(本章) | 世界主动推送、连续流 | 跨回合、可打断、可抢占 | **回合制是训练留下的假设,不是环境的性质。** 模型的训练语料几乎全是回合制的——提问后面跟着回答,工具调用后面跟着工具结果,一句话说完了对方才开口。于是模型学到的策略默认世界会等它。但真实环境不会等模型作出反应:邮件在它思考时到达,用户在它说到一半时插话,页面在两次截图之间已经变了样,杯子在机械臂伸手的途中被碰倒。 | 尺度 | 场景 | 观察侧的变化 | 动作侧的变化 | | ----------- | ------------ | ----------------- | ------------------ | | 秒 — 天 | 异步与事件驱动 | 会被外部事件主动唤醒的 Agent | 动作跨回合:先发起,后续靠事件收尾 | | 10 毫秒 — 1 秒 | 语音 | 边说边听,不等一句说完 | 边想边说,可被打断 | | 亚秒 — 秒 | Computer Use | 屏幕在两帧之间持续变化 | 动作后必须重新确认现实是否仍符合计划 | | 毫秒 | 机器人 | 传感器连续回流 | 动作分块:一次规划一小段,可被抢占 | ## 异步与事件驱动:当世界主动找上门 第四章讨论的感知、执行、协作三类工具都由 Agent 主动调用。Agent 如何响应随时可能到达的外部事件?这需要事件驱动的异步架构来支撑;第一章五类工具中剩下的两类——事件触发工具与用户沟通工具——正是依托这一架构发挥作用的,因此也放在本节一并讨论。 这一节里模态没有变化,仍然是文本,变的只有时机。它是从前五章的回合制世界迈出的第一步。 ### 为什么需要异步 先用一个比喻说明为什么需要异步。同步(Synchronous)意味着“做完一件事才能做下一件”,异步(Asynchronous)意味着“多件事可以同时进行”。传统的同步 Agent 架构就像一个只会排队的柜台,每次只能处理一个顾客,处理完才能叫下一个号;而真正智能的助手更像一个灵活的秘书,桌上摆着多个待处理的事项(邮件、电话、来访者),秘书根据紧急程度决定先处理哪个,处理到一半如果有更紧急的事情也可以暂停切换。在同步模式下,Agent 要么等待后台任务完成才能与用户对话,要么等对话结束才能处理新到达的事件,无法应对真实助理场景所需的几项核心能力: - **异步执行是常态**——许多任务需要长时间运行,不应阻塞用户交互。 - **事件优先级的动态判断**——不是所有事件都同等重要,Agent 需要智能地选择处理策略:取消当前操作(紧急)、加入队列(常规)、还是并行处理(独立的轻量级查询)。 - **中断和恢复的流畅性**——被打断的对话或任务应该能够自然恢复。 而将异步范式应用于当前 LLM 时,遇到的根本矛盾在于:LLM 的训练范式假设同步——发出工具调用后,下一条消息必须是工具结果;而真实部署却要求异步——用户随时可能打断,多个任务可能并发推进,外部事件可能在工具尚未返回时就抵达。这一 “训练同步 / 部署异步” 的矛盾贯穿了本节后续讨论的所有工程取舍。 为此我们需要**事件驱动的异步 Agent 架构**。技术上,这意味着系统不再主动地反复检查 “有没有新消息”(这叫轮询,效率低),而是在新消息到达时自动触发处理逻辑。所有的输入、输出、思考过程和外部交互都被统一建模为事件流——一条时间线上依次排列的事件记录。图6-1 给出了事件驱动异步 Agent 的整体架构,展示事件源、事件队列与 Agent 处理流程之间的关系。 ![图6-1 事件驱动的异步 Agent 架构](images/fig6-1.svg) ### OpenClaw 的事件驱动机制实现 开源框架 OpenClaw 通过 Gateway 控制平面接收多渠道消息并路由到 Agent 运行时。它提供了三种内置的事件驱动机制: - **Hooks(事件钩子)**:响应 Agent 生命周期中的事件,如会话创建、重置等,类似 GitHub Actions 中的事件触发器 - **Cron(定时调度器)**:按 cron 表达式(Unix 系统广泛使用的定时任务语法,如 `0 9 * * 5` 代表每周五上午 9 点)执行周期性任务 - **Heartbeat(心跳守护进程)**:每隔 N 分钟唤醒一次 Agent,检查是否有需要关注的事项 这三种机制赋予了 OpenClaw Agent“自主”的外观——即使用户不在线,Agent 也能定时生成报告、检查系统状态、处理例行事务。Gateway 对内置渠道(如 IM、Web 界面)的消息本身是**推送式**的,消息一到就路由给 Agent;三种自动化机制里,真正让 Agent 在没有用户消息时“自己动起来”的只有 Cron 和 Heartbeat,而它们都是**时间驱动**的——Heartbeat 每隔固定间隔检查一次,Cron 按预设时间触发,Hooks 的事件来源是 OpenClaw 框架内部,而非外部。 真正的短板在于:对于内置渠道之外的第三方事件源,例如一封新邮件到达、一个外部 API 回调推送、一个紧急通知需要立即处理,OpenClaw 缺乏即时接入的通道,Agent 无法在事件发生后立即做出响应,只能等到下一个 Cron/Heartbeat 周期才可能察觉。 这种延迟在许多场景下是不可接受的。以 **PineClaw**(Pine AI 的 OpenClaw 插件)为例:Pine AI 是一款代替用户拨打真实电话的 AI 助手,典型场景包括协商账单、取消订阅和处理保险理赔。当用户通过 OpenClaw Agent 发起一个 Pine 电话任务后,Pine 的语音 AI 会代表用户拨打电话,但通话过程中可能随时需要用户介入: - **实时身份验证**:客服要求验证账户持有人身份,Pine 需要用户立即提供安全码或 OTP(一次性密码) - **三方通话确认**:客服要求与账户持有人直接对话,Pine 需要用户在几秒内接听电话 - **进展同步与决策确认**:协商到关键节点(如对方提出降价方案),Pine 需要用户确认是否接受 如果依靠 Heartbeat 的定时轮询,用户可能在客服等待验证码时迟迟收不到通知,导致客服挂断、通话失败。 PineClaw 的解决方案是引入 **Channel 机制**——在 OpenClaw 的 Gateway 和 Pine API 之间建立实时的事件通道。当电话接通、需要用户输入、通话结束等关键事件发生时,消息被即时推送到 OpenClaw Agent,Agent 立即处理并通知用户。 这个案例揭示了事件驱动架构对 Agent 框架的核心价值:**真正的 “主动服务” 不仅需要 Agent 能定时检查事件,更需要事件能主动通知 Agent**。将所有输入——用户消息、工具返回、外部回调、定时触发——统一建模为事件流,通过事件循环驱动 Agent 的思考和行动,是实现这一目标的架构基础。在这一架构之下,下面先介绍两类与事件直接相关的工具,以及支撑 Agent 独立行动的虚拟身份与隔离执行环境,再讨论事件处理机制的具体设计。 ### 事件触发工具 事件触发工具是外部事件驱动 Agent 行动的入口。如果没有事件触发工具,Agent 只能连续循环思考、调用工具,最后输出一个结果,然后等待用户的下一步输入。要让世界的变化转化为 Agent 可以处理的事件,常见的事件触发工具有三类。 **定时器**(set_timer)处理依赖物理时间的事件。例如,发送了一封邮件但对方没有回复,那么过一段时间应该再发一封邮件询问进展;打了一个电话但对方不在工作时间内,那么需要到下一个工作时间再尝试拨打。为此,OpenClaw、Claude Code 等工具都支持定时器工具,在指定的物理时间唤醒自己。**一次性定时器**用于有明确时间点的任务:例如用户要求 “给银行房贷部门打电话问办理进展”,当前是周六,Agent 就设置 “下周一上午 10:00 致电银行”,定时器触发后自动拨打。**循环定时器**用于周期性的任务:比如每小时检查一次服务器健康状况。此外,一些外部服务不支持主动推送进展,只能主动查询进展,此时就需要用循环定时器定时反复查询。上一节 OpenClaw 的 Heartbeat 正是这种机制的系统化实现,也是 OpenClaw 具备 “主动服务” 能力的根源。 **后台任务监控**(monitor_shell)处理来自异步执行的工具或命令行任务的事件。一些命令行任务需要长时间在后台执行,Agent 需要监控执行进展。如果让 Agent 不断 “盯着命令行看”,也就是不断调用工具查询当前进展,那么会浪费太多的 token;如果等命令行任务完全执行结束后再让 Agent 开始思考和行动,那么 Agent 将无法及时发现执行过程中的严重问题,甚至在命令行卡死的情况下无法介入,导致整个任务停滞。Claude Code 解决这个问题的方法是引入 monitor(监控)工具,允许 Agent 监控命令行的新增输出或者包含特定关键词的输出。 **外部事件通道**(connect_channel)把新邮件到达、API 回调、IM 消息等外部事件实时推送给 Agent,上一节 PineClaw 的 Channel 机制就是典型实现。 在设计层面,事件触发工具应定义清晰的触发条件和过滤规则,避免无关事件唤醒 Agent 浪费算力;事件载荷(payload)应包含足够的上下文信息,减少 Agent 被唤醒后还需要额外查询的次数。 ### 用户沟通工具 用户沟通工具是为了适应 Agent 与用户之间日益多元的沟通渠道而产生的。许多 Agent(如 Claude Code、Manus)采用原生 ReAct 循环,Agent “说” 的所有话(即 assistant 消息)都直接发送给用户,用户必须在应用中打开指定的会话才能与 Agent 对话。用户在会话中往往可以看到 Agent 调用工具的过程。 OpenClaw 打破了这一人机沟通范式。用户无需感知会话的存在,也无需关心 Agent 调用工具的细节;用户和 Agent 都可以随时给对方发送消息,而不是用户发一条、Agent 回一条。因此,很多人评价 OpenClaw 具备 **“活人感”**,就像一个秘书一样通过文本消息与用户异步沟通。OpenClaw 并不是直接把模型输出的 assistant 消息呈现给用户,而是使用专门的工具发送消息。这些消息还可以附带图片和文件,并可根据紧急程度附加推送提醒。 除了通过文本方式与用户沟通,越来越多的 Agent 具备**多模态沟通能力**,例如发送结构化卡片消息、发送提醒邮件。一些 Agent 已经开始尝试**生成式 UI**(Generative UI),即使用 HTML 等方式生成交互式界面,以更友好的方式向用户展示信息。在设计层面,用户沟通工具应支持异步消息模式(用户不一定在线),提供已读/未读状态追踪,并在多渠道场景下保持消息的一致性。 **多渠道的用户沟通与召回。** **Agent 的响应不应局限于单一渠道,通知机制同时也是用户召回机制**。消息发送扩展到即时通讯、短信、邮件、电话、推送等多种渠道。Agent 根据紧急程度、用户状态、内容性质、用户偏好综合决定渠道的选择,既保证不错过重要的消息,又避免重复打扰。 对于长时间运行的任务,Agent 需要在完成时主动通知用户,召回用户的注意力。对于定期性的任务(如每日总结、周报),通知可以帮助用户建立固定的交互习惯。 用户沟通工具解决了 “如何触达用户”。但 Agent 以什么身份出现在这些渠道上、在什么环境中代表用户执行操作,还需要一层身份与环境的基础设施,这就是下一节的主题。 ### 虚拟身份与隔离执行环境 本章开头提到,《Her》中的 Samantha 拥有独立的身份和操作环境。要实现这样的通用助理,首先面临一个关键的架构选择:Agent 应该直接管理用户的个人账号,还是拥有自己的虚拟身份?直接管理看似便捷,但一旦 Agent 出现错误或被攻破,用户的全部数字身份将会暴露。更稳妥的方案是赋予 Agent 一套独立的虚拟身份——如同秘书拥有自己的办公电话和邮箱。这套虚拟身份包括专属的通讯账号、存储空间、计算环境,使 Agent 能以透明的身份代表用户工作。身份的明确性不仅没有削弱信任,反而增强了沟通的真实性。 虚拟身份需要部署在隔离的执行环境中。**虚拟电脑**(VM/容器)和**虚拟手机**(Android 模拟器)为 Agent 提供操作系统级的隔离和完整的桌面/移动操作能力。首先,虚拟电脑可以全天候运行,不受用户设备联网状态的影响,也不影响用户正在操作的应用;其次,即使 Agent 执行了错误操作,最多也只会导致虚拟环境崩溃,不会影响用户的真实设备;最后,隔离环境可以防止 Agent 随意访问用户的本地文件,提高了安全性。 独立身份也带来两个现实挑战。一是**反机器人机制**:许多网站用 CAPTCHA 和 IP 信誉检测拦截自动化访问,使用数据中心 IP 的虚拟环境很容易被识别,实践中往往需要配置住宅代理网络(使用真实家庭 IP)才能正常访问。二是**访问用户真实账号的场景**:当任务必须以用户本人的身份登录时,应采用 Human-in-the-Loop 认证——通过 VNC/RDP 远程桌面让用户在可视化环境中亲自完成登录。用户能看到 Agent 正在操作的完整界面,理解为什么需要认证;认证后的会话令牌可以在有效期内复用,避免频繁打断用户,在自主性与安全性之间取得平衡。 Agent 与虚拟环境之间的数据交换通过**共享文件系统**完成:以卷挂载的方式(如 `/workspace/shared`)连接 Agent、虚拟电脑和虚拟手机,数据以文件路径引用传递而非内容拷贝,避免占用上下文窗口。以一个数据分析任务为例:用户上传 CSV 文件到共享目录,虚拟电脑中的 Agent 读取文件、执行分析、生成图表并保存回共享目录,Agent 只需将图表的文件路径返回给用户——各方之间传递的始终只是轻量级的路径字符串。 事件触发工具让世界能够唤醒 Agent,用户沟通工具让 Agent 能够触达用户,虚拟身份与隔离执行环境让 Agent 能以独立、可审计的身份行动。剩下的问题是:当多个事件同时涌向同一个 Agent 实例时,应该如何处理? ### 事件处理机制 一个 Agent 实例可能同时面对多个事件:用户的新消息、工具返回的结果、定时器到期、另一个 Agent 的协作请求。如何高效而正确地处理这些事件,直接影响着性能和用户体验。 这套机制的骨架是并发编程里的**事件循环**(event loop)。可以把异步 Agent 看作一个长期运行的循环:每一轮从输入队列取出若干事件,追加到轨迹,调用一次 LLM,执行它决定的工具,再回到循环开头等待下一批事件——这与 Go 的 goroutine 从 channel 读取消息、在 `for { select { ... } }` 中逐轮处理是同一个结构。 这个模型有一个关键性质:**事件只在每轮循环的边界被消费**。当 LLM 正在推理、工具正在执行时,新到达的事件不会凭空插入、打乱当前这一步,而是先在队列中等待,待本轮到达一个**安全点**(一段推理结束、一次工具返回)再统一处理。取消也遵循同样的纪律:不在任意时刻强行掐断,而是在安全点检查 “是否被要求停止”——这正是 Go 中 `ctx.Done()` 所扮演的角色。 理解了这一点,下面三种处理策略的区别就只在于对待安全点的方式:让事件等到下一个自然出现的安全点(队列式)、主动提前制造一个安全点(取消式),还是干脆另起一个循环、不必等待主循环的安全点(并行式)。 **事件的结构化建模。** 处理的前提是理解。通用 Agent 面对的输入不只来自用户一个人——第三方发来的消息不是用户发给 Agent 的,但 Agent 需要理解它、评估其重要性、决定如何介入。这要求将每个输入都建模为包含丰富语义的**结构化事件**: - **来源(谁)**:用户本人、联系人、陌生人、系统通知 - **渠道(方式)**:电话语音、短信、即时消息、邮件、社交媒体、定时器触发、异步工具调用结果、命令行监控状态更新 - **内容(什么)**:消息文本、情感色彩、紧急程度、是否需要回复 - **上下文(背景)**:是对之前某个对话的回复还是新发起的沟通,与当前任务的关联 以一封客户退款请求邮件为例,结构化事件的具体形式如下: ```json { "source": {"type": "email", "sender": "client@example.com"}, "channel": "gmail_webhook", "content": {"subject": "退款请求", "body": "订单 #12345 希望退款..."}, "context": {"priority": "high", "customer_tier": "vip", "related_orders": ["#12345"]} } ``` 只有当这些维度被清晰地建模为结构化事件,Agent 才能在多方通信中保持清晰的认知,避免将用户输入误当成工具结果,或将藏有指令的工具结果误认为用户指令而导致提示注入。多线程上下文管理的复杂性还要求 Agent 理解多个对话线程之间的关联——来自第三方的消息如何影响用户的情绪,用户在不同对话中扮演什么角色,何时需要将不同线程的信息综合起来提供建议。 从 n8n 等工作流平台的触发器生态可以看到,Webhook、定时器、邮件、数据库变更、文件监听——每一种触发器都是 Agent 感知世界的一个 “感官”。当这些异构的事件被统一建模为结构化格式之后,Agent 就能以一致的方式处理来自不同来源的刺激,下文的紧急度判定和处理策略也都建立在这一统一建模之上。 **基于紧急度的动态处理策略。** 人类在处理多个任务时,会根据紧急程度采取不同的策略。面对突发的紧急情况,会立即停下手头的工作;面对常规的待办事项,则加入任务列表稍后处理。Agent 的事件处理也应体现这种智能性。 ![图6-2 异步事件处理的三种策略](images/fig6-2.svg) **取消式处理(Cancellation-Based)** 用于紧急事件,其本质是为紧急事件**提前制造一个安全点**:主动中断当前步骤,把这一刻变成可以消费新事件的边界。当紧急事件到达时(如用户点击 “停止” 或监督系统发来高优先级指令):(1) 停止当前操作——如果 LLM 正在推理,立即取消流式响应;如果有同步工具在执行,发送取消信号;(2) 清空待处理队列,将所有事件取出;(3) 将队列中的事件和紧急事件一起追加到轨迹末尾;(4) 立即重新调用 LLM,以更新后的完整轨迹为输入来评估局势。例如,用户在 Agent 执行可能错误的操作时输入 “停!我说错了”,Agent 会立即看到这条新输入,重新理解真实意图,从而避免执行错误的操作。 **队列式处理(Queued)** 用于常规事件。当非紧急事件到达时(如异步工具返回结果或用户发来补充信息):(1) 将事件放入队列末尾,不打断当前操作;(2) 等待当前操作完成——让 LLM 完成推理,让同步工具执行完毕;(3) 当任何工具调用完成并返回 `tool.result` 时,检查队列,如果队列非空则将所有事件一次性追加到轨迹;(4) LLM 综合处理更新后的轨迹。这实现了批量处理,提高了效率,例如 Agent 调用搜索工具后,在等待期间用户补充了 “只看最近一个月的结果”,这条补充信息进入队列,搜索结果返回时两个事件一起呈现给 LLM,避免了不必要的往返。 **并行处理(Parallel)** 用于独立的轻量级查询。比如 Agent 正在分析大量数据时,用户突然问 “今天天气怎么样?” 此类查询具有三个特征:与主任务无关、需要快速响应、执行成本低。既不应该用取消式处理(会打断重要的主任务),也不应该用队列式处理(让用户等太久)。系统首先判断查询的独立性和复杂度,然后在一个并行的推理会话中独立执行,调用必要的工具生成响应后立即返回。查询和响应会追加到主任务的轨迹中,并明确标记为 “与主任务并行执行”,以避免 LLM 混淆。 **紧急度的判定。** 紧急事件:用户中断(`user.interrupt`)、监督指令(`supervisor.instruction`)、Agent 间中断(`agent.interrupt`)、标记为紧急的外部触发器(如系统告警、支付失败)。 非紧急事件:常规用户输入(`user.input`)、Agent 输入(`agent.input`)、工具结果(`tool.result`)、定时器触发(`timer.trigger`)、常规外部触发器。 硬编码的规则有其局限性,事件的语义决定了处理方式——“马上停下来” 用取消式、“今天天气怎么样” 用并行式、“报告需要用中文发给我” 用队列式。**建议使用轻量级的分类 LLM 作为事件路由器**,在事件到达时快速判断应该采用哪种策略。 取消点必须是工具或推理能够安全收尾的位置;未完成的工具结果用显式占位符表示,不能伪造成功。 下面通过一个事件驱动的邮件处理 Agent 实验,将上述事件处理策略落地为可运行的实现。 > **实验 6-1 ★★★:事件驱动的邮件处理 Agent** > > > ![图6-3 实验 6-1 事件驱动 Agent 架构](images/fig6-3.svg) > > > 本实验构建一个最简单的事件驱动 Agent:**自动邮件处理助手**。Agent 监听邮件收件箱,每当收到新邮件时自动触发处理流程——分类、摘要、起草回复,必要时通知用户。这是事件驱动 Agent 最直观的入门场景:一个外部事件(新邮件到达)触发一次完整的 Agent 思考循环。 > > **实验目标**是理解事件驱动的核心概念:Agent 不再只是被动地等待用户输入,而是可以响应外部事件来主动行动。通过这个实验,读者将掌握事件源注册、事件队列以及“事件到达 → Agent 处理 → 结果输出”的基本闭环。 > > **事件源与事件队列。** > > 系统支持多种事件源的统一接入: > > - **邮件事件** (`on_email_received`):通过定期检查收件箱或接收推送通知,在新邮件到达时触发 > - **IM/短信消息** (`on_im_message`,`on_sms_message`):即时通讯消息触发 > - **GitHub 事件** (`on_github_pr_update`,`on_github_issue_update`):PR review 意见、状态变化 > - **定时器触发** (`on_timer_expire`):定时任务(如每日摘要、周报生成) > - **Webhook** (`on_webhook_received`):通用的外部系统回调 > - **系统事件** (`on_user_inactive`,`on_process_timeout`,`on_resource_alert`):内部状态变化 > > 所有事件进入一个统一的**事件队列**,按到达顺序依次处理。每个事件触发一次独立的 Agent 思考循环:Agent 读取事件内容,调用相关的工具(如查询知识库、读取附件、搜索相关的邮件历史),生成处理结果(分类标签、摘要、草稿回复),最后通过通知工具告知用户或直接执行操作。 > > **验证场景**:配置 Agent 监听测试邮箱。模拟接收三封邮件——一封会议邀请、一封客户投诉、一封营销广告。Agent 依次处理:为会议邀请自动检查日历冲突并起草接受/拒绝回复;为客户投诉提取关键信息并标记为高优先级,通知用户处理;将营销广告自动归档。整个过程无需用户介入。 实验 6-1 展示了最简单的事件驱动模式——事件进入队列,Agent 依次处理。但当 Agent 需要在长时间运行的工具执行过程中响应打断,或同时管理多个并发任务时,简单的事件队列就不够用了。接下来讨论更深层的工程挑战。 ### 如何让同步模型支持异步打断 实验 6-1 只处理串行事件——事件依次进入队列,Agent 一个接一个处理完毕。现在回到本节开头提出的 “训练同步 / 部署异步” 矛盾:当工具尚未返回时用户突然打断,同步格式该如何容纳?本节给出当前业界的工程解法。 先用一个具体场景说明这个矛盾。假设 Agent 正在帮用户起草一封邮件(工具调用:搜索联系人信息),搜索还没返回结果时,用户突然说 “等一下,先帮我查一下明天的天气”。在同步的 ReAct 循环中,Agent 必须等搜索返回后才能处理下一条消息,因为 API 要求 “发出工具调用后,下一条消息必须是工具结果”。但在异步的真实世界里,事件随时可能打断正在进行的任务。如何在“同步格式”的约束下表达“异步打断”的语义,正是下面这套工程方案要回答的问题。 **工程权宜之计:模拟同步的异步实现。** 核心思想是:**在没有打断发生的常态下,让 LLM 看到标准的同步轨迹,只在打断时才插入占位符来修复格式**。以下是五条关键规则: **规则 1**:LLM 输出后立即记录 assistant message(包含 thinking、content 和 tool call)。 **规则 2**:工具调用完成时才记录 tool result。执行中轨迹处于 “部分完成” 状态。 **规则 3**:工具执行中的打断需要占位符。为未完成的工具生成占位符响应(如“工具正在后台执行,请优先处理新事件”),追加打断事件,重新调用 LLM。从 LLM 的视角看,assistant message 仍然有配对的 tool result。 **规则 4**:LLM 思考中的打断直接丢弃当前思考。不写入轨迹,新事件直接追加后启动新一轮思考。 **规则 5**:非打断事件进入队列等待批处理。当前周期完成后才一次性追加。 以 Agent 正在起草邮件时用户打断询问天气为例,这五条规则的运作过程如下: 1. Agent 调用 `search_contacts` 搜索联系人信息,assistant message 立即写入轨迹(规则 1)。 2. 搜索工具尚未返回结果时,用户发来“先帮我查一下明天的天气”。由于这是用户打断,系统为未完成的 `search_contacts` 生成占位符 tool result(“工具正在后台执行,请优先处理新事件”,规则 3),然后将用户的天气查询追加到轨迹,重新调用 LLM。此刻 LLM 看到的轨迹格式完全合法——assistant message 与 tool result 配对完好。 3. 天气查询完成并回复用户后,原先的 `search_contacts` 结果到达,作为新事件追加到轨迹(规则 2),Agent 读取联系人信息后继续起草邮件。 这套方案的核心优势是:**常态下 LLM 看到的是完美的同步轨迹**——assistant message 与 tool result 严格配对,时间顺序清晰。这对当前基于同步训练范式的 LLM 最为友好,最大程度地保证了思考质量。只有在确实需要打断时才引入占位符这个 “必要的妥协”。 但仍存在加剧幻觉的风险。在这个场景中,尽管占位符明确说明工具 “尚未完成”,系统仍可能在后续思考中 “编造” 一个工具结果,误以为工具已经返回了有效数据,基于这个虚构的结果做出不恰当的决策。这是因为模型在训练时见到的绝大多数轨迹中,工具调用之后紧接着就是真实的结果,它从未学会如何处理“结果还没回来”的情况。因此实践中只在真正紧急时才打断,非紧急的事件则放入队列批量处理。 **适合现有模型的异步工具接口。** 既然模型的同步假设难以突破,一个更根本的策略是**从工具接口的设计层面拥抱异步语义**。 传统的工具设计隐含了 “调用即完成” 的语义。例如 `phone_call` 这个名字暗示 “调用将拨打电话并等待通话结束,返回通话记录”。在异步范式下应该将 “启动” 和 “完成” 解耦: - `initiate_phone_call`:启动电话呼叫,立即返回任务标识符和初始状态(如 “呼叫已发起,正在拨号”) - 通话进展通过事件通知(`phone_call_connected`、`phone_call_ended`) 关键在于工具的名称和描述本身就要传达异步的语义。当模型看到 `initiate_phone_call` 时,它会自然推断这是 “发起” 而非 “完成”。工具描述应进一步强化这一点:“此工具将启动由子 Agent 处理的电话任务。任务成功发起后立即返回任务 ID,你可以继续处理其他事项。通话结束后会收到单独的通知事件。” **队列式处理中的注意力分散问题。** 在批量事件处理时,模型往往只关注最后一个事件。根源在于**模型被训练为对最新的输入做出反应,而批量事件打破了这一假设**。 可以从两个层面进行干预: **提示词层面**:告知模型 “当收到多个连续事件时,请确保全面考虑所有信息”。 **Agent 状态栏标记**:在每个事件前添加显式标记: ```text [未处理事件 1/4] Tool result from database_query:... [未处理事件 2/4] User 补充说明:只看北京地区的数据 [未处理事件 3/4] 系统提醒:报告截止时间还有 30 分钟 [未处理事件 4/4] User 询问:进度如何? ``` 在末尾添加汇总:“上面有 4 个未处理事件,包括 1 个工具结果、2 条用户消息、1 个系统提醒。请确保回应涵盖所有信息。” ### 深层矛盾与未来方向 ![图6-4 同步训练范式与异步部署现实](images/fig6-4.svg) 归根结底,前几节的占位符、异步工具接口、状态栏标记,都是在用提示工程弥补同一个 “训练同步 / 部署异步” 的矛盾(图6-4)——这一矛盾的成因已在本节开头详述,此处不再重复,只聚焦它的根本解法。 **期待模型进化:从同步到异步。** 上述工程技巧本质上是**用提示工程来弥补模型训练的不足**,是过渡期的权宜之计。真正的解决方案需要在模型训练层面发生范式转变。 机器人领域的 VLA(Vision-Language-Action,视觉-语言-动作,详见本章机器人一节)模型已经开始面对类似的挑战:感知和动作之间存在不可避免的延迟。VLA 的成功为 Agent 模型的进化指明了方向。下一代模型需要通过异步环境中的强化学习获得三种核心能力: 1. **理解轨迹中事件的异步穿插**:这是最核心的能力缺陷。当前模型期望严格的同步序列,但在真实的异步环境中,tool call 之后可能不是 tool result 而是新的 user 消息;thinking 进行到一半可能被打断,但中间状态应保留在轨迹中,新消息处理完后继续思考而非从头开始。模型需要在这种“乱序”的轨迹中保持清晰的认知——哪些工具调用还在等待结果,哪些思考是未完成的片段。 2. **恢复被打断的任务和思考**:当被打断去处理紧急事件后,仍然记得未完成的任务。例如 Agent 在执行数据分析工具时用户突然问天气,回答后应该自然地等待数据分析结果,而不是忘记还有工具在运行。特别要避免产生幻觉,误以为被打断的工具调用已经完成。 3. **批量事件的综合处理**:多个事件批量追加到轨迹时,不能只关注最后一个,必须综合考虑所有未处理的信息。 实现这种异步 RL 训练需要新的基础设施:异步环境模拟器(生成工具延迟返回、用户随机打断等场景)和针对异步能力的专项奖励机制(正确理解乱序轨迹、成功恢复被打断的思考、避免幻觉、综合处理批量事件)。 不过,“持续思考” 并不必等到下一代模型才能拥有。用一层约两百行的编排逻辑,就能让一个**现成的**文本思考模型变成 **持续思考(continuous-time)** 的 Agent,恰好把上面 “工程权宜” 和 “模型进化” 两半接了起来。它的机制正是前面规则 4 的升级版:与其在被打断时**丢弃**半截思考,不如把整个交互建成**一条不间断的思维流**——随时可以强行合上模型正在写的 `` 块,把新到达的观察(一条工具返回、一次用户打断、一段新的识别结果)作为普通消息注入,再让模型接着往下解码。 它利用了一个常被浪费的资源:模型每秒能生成上百个 token,而一次工具调用、一段用户说话往往要花好几秒。这些“等待”时间都可以被模型用于思考。由此 Agent 可以产生两种行为:**边等边想**:不等工具返回、不等用户说完,就基于已有的半截信息往下思考,甚至提前把下一步工具调起来;以及**边做边想**:一边输出、一边继续思考,并能在动作进行到一半时纠正自己。 > **实验 6-2 ★★★:带并行执行和打断能力的异步 Agent** > > > ![图6-5 实验 6-2 异步 Agent 打断与恢复](images/fig6-5.svg) > > > 在实验 6-1 的简单事件队列基础上,本实验进入异步 Agent 的深水区:**并行工具执行、执行取消和状态管理**。Agent 不再只是逐个处理事件,而是需要同时管理多个并发任务,处理打断和恢复,并根据实时状态做出动态决策。 > > **1. 异步工具执行**:支持耗时工具的异步执行(至少 3-5 秒),启动后立即返回占位符。**验证场景**:Agent 执行一个长时间的终端命令,期间用户问“现在几点了?”,Agent 立即回应,等分析结果返回后再呈现。 > > **2. 事件队列与批量处理**:累积非紧急事件,批量追加到轨迹。**验证场景**:Agent 执行长任务,用户连续发送“记得用日语回复”和“整理成网页”,任务完成时一次性处理所有事件,生成日语网页。 > > **3. 打断机制**:用户的“停止”立即终止执行流并取消异步工具。**验证场景**:Agent 执行长任务,用户发送“取消”,Agent 立即停止,轨迹记录打断事件和取消操作。 > > **4. 并行工具的取消与状态查询**:异步工具完成后通过新事件将真实结果注入对话,支持通过任务 ID 取消或查询进度。**验证场景**:用户请求“帮我同时运行这三个脚本,哪个先完成了,就看看剩下的脚本进度怎么样,如果还没超过 50%,就取消”。三个脚本模拟分析进程,运行时不断输出进度,速度分别为每秒 3%、2%、1%。Agent 同时启动三个异步终端命令,当每秒 3% 的脚本在约 33 秒后完成时,Agent 查询剩下两个终端的状态,发现一个执行到约 66%、另一个约 33%,于是取消不超过 50% 的那个。两个终端都完成后整合结果生成完整报告。 > 异步与事件驱动实现了 “世界可以在任意时刻唤醒 Agent”,但它假设事件到达之后,模型可以从容地想完再回应。接下来三节将挑战这个假设:当环境变化的速度达到甚至超过模型的生成速度时,“想完再说” 本身就变成了不可接受的延迟。 ## 语音:最自然的人机接口 语音的价值不只是把文字换成声音。正常说话的速度约为打字的四倍,而且不占用双手与视线,因此它天然适合把 Agent 放进持续工作、随时可能被打断的输入输出回路。语音输入法把口述转成文字,语音 Agent 则让用户直接与 Agent 协作;两者都可以支持引言中提到的 whisper coding。 本节同时讨论两个方向:用户对 Agent 说话,以及 Agent 代替用户对外部世界说话。语音模型决定“能回答什么”,交互架构决定“能否听清、及时回应、自然换手,并在通话中完成确认和工具调用”。后文先讨论交互时序,再讨论深度思考和表达质量。 ### 交互时序:从级联到全双工 OpenAI 在 GPT-Live 的介绍中用“级联、轮次式、全双工”概括了语音系统的三种交互范式[^ch6-12]。它们不是简单的新旧替代,而是不同延迟、成本和可观测性约束下的取舍: | 范式 | 核心结构 | 主要优势 | 主要限制 | | -------- | --------------------- | ----------------- | ---------------- | | 级联 | VAD → ASR → LLM → TTS | 模块清晰、易替换、易调试 | 延迟累积,副语言信息在接口处丢失 | | 端到端 Omni | 原生音频输入输出,按轮次交互 | 延迟较低,能保留语气、情绪和环境声 | 仍依赖轮次,训练和调试成本较高 | | 全双工 | 原生音频输入输出,持续听、说和决策 | 支持重叠说话、自然打断和连续流 | 模型训练、控制和评估都更复杂 | 贯穿三种范式的主线是:如何摆脱“轮流说话”和 VAD 对发言权的猜测。级联和 Omni 仍要划分轮次,只有全双工把“该谁说话”变成模型的持续决策。 [^ch6-12]: OpenAI. *Introducing GPT-Live.* 2026-07-08. https://openai.com/index/introducing-gpt-live/ 。本节“级联 / 轮次式 / 全双工”三分法即出自该文对 ChatGPT 语音三代演进的总结;文中“端到端全模态(Omni)”对应其“turn-based voice models”一类。 ### 范式一 · 级联流水线(Cascading) 绝大多数商业语音助手都基于串行流水线(图6-6):VAD 判断用户何时说完,ASR 把音频转成文字,LLM 理解并生成回复,TTS 再把文字念出来。模块化让每个组件可以独立优化,但每一级都可能增加等待时间。 ![图6-6 语音 Agent 串行流水线](images/fig6-6.svg) | 模块 | 作用 | 典型瓶颈 | | --- | --- | --- | | VAD | 判断是否说完 | 静音阈值带来等待和误切分 | | ASR | 音频转文字 | 识别延迟与上下文丢失 | | LLM | 理解、思考、生成 | 首 token 延迟,开启 reasoning 后等待更长 | | TTS | 文字转语音 | 首包合成和播放缓冲 | 在一个简短、不开启 reasoning 的回复中,VAD、ASR、LLM 和 TTS 的等待会串行累积(图6-7)。真实数值取决于输入长度、模型、硬件、网络和负载。 ![图6-7 延迟瀑布:串行累积总响应时间](images/fig6-7.svg) 生产环境的排队还会进一步放大空载延迟(图6-8),但这属于服务容量规划,本章不展开排队模型。 ![图6-8 排队延迟曲线](images/fig6-8.svg) > **实验 6-3 ★:构建传统语音 Agent** > > 本实验用 WebSocket 串起麦克风、Silero VAD、本地 Whisper、流式 LLM 和 Fish S1 TTS,建立后续方案的级联基线(baseline)。 #### 从串行到流式感知 图6-7描述的是 VAD+ASR+LLM+TTS 的完全串行情形,这种串行感知方案有三个问题: 1. **延迟累积**:必须等待一段静音才能确认说完。 2. **信息丢失**:有声/无声二值信号无法表达犹豫、情绪、附和和环境声。 3. **上下文被切断**:邮箱、人名和专有名词可能被分片识别而出错。 为了解决这个问题,在保留模块化分工的前提下,一种优化方案是**流式感知**,让各阶段尽早产出增量结果: - **ASR 边听边转**:VAD 检测到用户开始说话时,就按照一定的时间间隔调用 ASR 模型,流式生成临时转录内容;VAD 检测到用户说话结束后,再确认最终文本。 - **LLM 推测执行**:临时转录内容生成后,就送给 LLM;如果最终文本与临时转录内容相同就不再调用 LLM,否则取消前序推测执行的思考,重新调用 LLM。 - **LLM 分段输出**:第一段适合播报的文本生成后立即交给 TTS,不等完整回复。 - **TTS 增量合成**:持续返回音频块,让后续生成、合成和播放重叠进行。 真正的流式 ASR 需要模型支持。Whisper 的解码虽然是自回归的,但编码器需要完整音频段,因此不能直接等同于流式模型。基于 LLM 的流式听觉模型可以从连续音频中输出文本和语义事件,把“识别”和部分“理解”放进同一个模型。它保留从对话开始到当前时刻的上下文,也可以利用世界知识处理品牌、人名和专有名词。 如果只想解决“用户是否说完”,也可以把轮次判断直接做进流式识别器:模型综合语义和静音判断一句话是否表达完整。端点判断的训练标签必须只使用决策时刻可见的信息,否则会因“上帝视角”产生线上无法复现的判断。 模型输出的不仅是文字,还可以包含声学事件标记: - **speak_start/end、interrupt**:说话起止与打断意图; - **emotion**:情感、犹豫等状态; - **laugh、sigh、noise**:副语言和环境声。 这些标记和文字 token 形成统一事件流,Agent 可以据此识别犹豫、打断和环境变化,而不必把所有声音压成纯文本。 > **实验 6-4 ★:使用 Qwen2-Audio 模拟流式语音感知** > > Qwen2-Audio 本身不是流式模型。本实验用递增音频前缀模拟连续感知,并与 600ms VAD + Whisper 对照。 ### 范式二 · 端到端全模态模型(Omni) 级联即使采用流式感知,听、想、说仍通过离散接口交接;情绪、语调和环境声等信息可能在转成纯文本时丢失。Omni 方案用同一个模型直接听音频、生成回复并输出语音,因而有机会保留这些信息,但训练的成本更高(图6-9)。相比范式一的级联方案,Omni 的优势主要体现在延迟和非文字信息的理解和生成上。 在理解方面,Omni 模型可以理解声音中的停顿和。在生成方面,Omni 模型可以传递更丰富的副语言信息,例如唱歌、用特殊的语调讲一句话。 Omni 模型仍然假设轮流说话,通常要靠 VAD 划分发言权。因此,用户报数字时的中途停顿仍可能被误判为说完。 ![图6-9 端到端多模态语音模型架构对比](images/fig6-9.svg) > **实验 6-5 ★★:本地运行 MiniCPM-o 4.5,对比端到端与自级联** > > 本实验使用本地 MiniCPM-o 4.5,关闭 thinking mode,比较直接从音频作答与同模型自级联先转录再作答。它测的是音频信息是否被保留,**不是**后文的“边想边说”。 ### 范式三 · 全双工交互模型 Omni 仍然把对话分成“用户说”和“模型说”两个时段,但同声传译等任务要求两者重叠进行。全双工模型因此不再预设轮次,而是持续听、持续说,并不断决定继续、停顿、打断或调用工具。 研究上的先声是 Kyutai 的 **Moshi**(2024)。它并行建模用户和模型的音频流,因此重叠说话和打断可以成为模型的自然行为。 Thinking Machines Lab 将这类路线称为**交互模型(Interaction Model)**[^ch6-14]:交互性不再依靠 VAD 等外部 harness 拼装实现,而是内建在模型中。其微轮次机制以短音频块持续推进,让静音、重叠和打断都作为连续上下文保留。交互模型还可以把完整对话委派给后台推理模型,自己继续维持话头;后台结果返回后,前台再在合适时机接入。 [^ch6-14]: Thinking Machines Lab, “Interaction Models: A Scalable Approach to Human-AI Collaboration,” 2026-05. https://thinkingmachines.ai/blog/interaction-models/ OpenAI 的 GPT-Live 则把全双工路线带到生产规模:模型持续处理输入并生成输出,能等待用户、附和、被打断,也能处理实时翻译。它与交互模型一样,把复杂任务委派给后台模型,前台继续维持对话。 ### 认知时序:实时交互与深度思考 “交互表现”和“智能上限”是两个维度:前台模型要在用户仍然在线时回应,后台模型则可以花更多时间思考。下面三种方案是设计取舍,不是线性迭代;前两种可以套在级联或 Omni 上,第三种则把深度思考与实时表达统一在同一模型内部。 #### 方案一:快思考回应,慢思考回答 快思考可以在几百毫秒内先给出即时回应,慢思考则在后台完成更深的推导。它的问题是简单问题会被重复处理,复杂问题又可能出现前后不一致:快模型先建议购买,慢模型随后发现套餐缺少关键功能,用户在几秒内便听到相互冲突的答案。根本原因是两个实例各自完成了一次独立思考。 ![图6-10 快/慢思考架构与方案对比](images/fig6-10.svg) #### 方案二:快思考交互,慢思考提醒 方案二让后台模型通过状态栏或专门接口向前台模型提供建议,前台继续维持话头并决定如何表达。它比方案一稳定,但通信仍然间接:前台可能误解建议,也看不到后台的中间思考;在后台完成前,用户追问时前台仍只能依靠自己的能力应答。它可以自然地“等结果”,但不能真正做到边想边说。 #### 方案三:端到端思考与表达统一 方案三把思考能力直接内化到端到端音频模型中。Step-Audio R1 用两个互补机制解决两个问题:**模态锚定思考蒸馏(MGRD)** 让模型基于声学特征思考,**MPS 双脑架构**让构思与表达并行。前者保证“想得对”,后者解决“说得及时”。 理想情况下,模型应该从音高、节奏和语调判断情绪,而不是只看转录文本。MGRD 筛选真正引用声学特征的思考过程,再用这些数据训练模型,并通过强化学习防止模型跳过思考直接猜答案。MPS 让构思脑持续产出思考片段,表达脑收到片段后结合已有回复立即生成语音。两者以流水线方式并行,因此不必等完整思考结束才让用户听到第一句话。 #### 快慢思考分离与端到端思考的取舍 统一模型最紧密地实现了 “边想边说”,代价是思考和实时表达需要一起重新训练;解耦路线更容易替换后台大脑。两者是取舍,不是简单的替代关系。 在前沿思考模型快速演进的当下,快慢分离有一个重要的工程优势:它能直接承接慢模型的迭代红利。前台快模型只负责低延迟地倾听、应答和维持对话,后台慢模型负责推理、规划和工具调用;更强的思考模型发布后,只需替换后台模型,不必重新训练整套实时语音系统。统一路线则把推理与交互绑定在同一个训练周期中,每次升级都要重新兼顾智能水平、响应延迟和表达自然度。因此,快慢分离并不只是对延迟的妥协,也是一种让交互能力与智能上限分别演进的模块化选择。 这种分离也不必然牺牲任务效果。截至 2026 年 8 月,采用快慢思考分离架构的 Pine AI 语音 Agent 在 τ³-Voice Leaderboard 上取得第一名,超过 Grok Voice、GPT-Realtime-2 等实时语音系统。这个结果至少说明,在同时考察深度推理与实时对话的任务上,解耦架构并不天然落后于端到端模型。[^ch6-17] [^ch6-17]: Pine AI. “The Most Natural Human-Computer Interface Is Your Voice.” 2026-06-23(2026-08-06 更新). https://www.19pine.ai/blog/pine-ai-the-most-natural-human-computer-interface-is-your-voice 这里需要澄清“端到端模型”常被赋予的两个含义。第一是上一节所述的**语音通路的端到端**:模型直接接收音频、生成音频,不再由多个模型通过离散文本串接。Omni 和交互模型都属于这个意义上的端到端模型,但 Omni 模型通常仍按轮次推进,交互模型则可以边听边说,二者在架构上差别很大。第二是本节所述的**认知架构的端到端**:实时交互与深度思考是在同一模型内部共享状态、共同训练,还是拆成前台快模型与后台慢模型。两条轴彼此独立,一个系统完全可以在语音通路上端到端,同时在认知架构上保持快慢分离;Thinking Machines Lab 把复杂任务委派给后台推理模型就是这种组合。 ### 更像人的语音合成 传统 TTS 过于流畅、零停顿,反而容易暴露机器身份。停顿、填充词和偶尔的重复,是人类表达不确定性和思考状态的信号。 可以让主 LLM 在文本之外输出控制标记,例如 **THINKING**、**EMO:happy** 和 **SPEED:0.8x**,由 TTS 将它们映射为停顿、韵律、语速或笑声、叹气等非语言音频。实现上可以自研支持控制标记的 TTS,也可以用语音克隆准备不同情绪和风格的参考音频。 > **实验 6-6 ★★:基于 Fish Audio 的控制标记驱动 TTS** > > 使用 Fish Audio S1 构建多参考语音库,比较无控制标记、单一参考音和多参考音三种配置。执行层根据标记选择匹配的情绪、语速和风格。 ## Computer Use:GUI 自动化 Agent 语音把时机轴推到了毫秒级,但它的观察仍是一维的声音流。Computer Use 把同一个问题搬上二维的屏幕:观察变成持续变化的像素,动作变成坐标上的点击与输入。语音场景强调“何时开口”,Computer Use 则强调“下一步点哪里”,以及一个语音交互中不存在的问题——动作执行之后,现实是否还与计划一致。 Computer Use(也称 GUI 自动化 Agent)让 AI 像人类一样通过观察屏幕、操作鼠标键盘来使用软件——比如打开浏览器搜索信息、在表格软件中填写数据或在系统设置中调整配置。其核心是一个**感知-思考-行动**的循环(图6-11): 1. Agent 截取当前屏幕画面 2. 多模态模型接收截图和任务指令,输出一段思考和一个具体动作 3. 执行层在真实环境中执行该动作(移动鼠标、点击、输入文字等) 4. 等待界面响应后再次截图,进入下一轮循环 这里要区分“看懂界面”和“完成任务”。前者更接近多模态理解能力,可以用一次截图问答来测量;后者则要求模型把理解和生成动作放进闭环,处理页面加载、状态变化、误操作和不可逆后果。Computer Use 的难点因此不只是让模型在截图上答对,而是让它在每一步之后重新确认现实是否仍符合计划。 ![图6-11 Computer Use Agent 的感知-思考-行动循环](images/fig6-11.svg) 这个循环中有三个关键设计维度:**动作空间**(Agent 能执行哪些操作)、**视觉定位**(如何在截图中找到目标元素)、以及**模型架构**(如何从截图生成正确动作)。 ### 动作空间设计 Anthropic 的参考实现把完整交互能力分成三类工具(图6-12)。这是一个清晰的动作空间设计,但不是模型供应商必须遵守的私有协议:只要 Harness 能把同样的截图、动作约束和执行结果转换成目标模型支持的消息与结构化输出,Claude、开放权重视觉模型和自托管端点都可以驱动同一个感知-思考-行动循环。 ![图6-12 Computer Use 动作空间](images/fig6-12.svg) **GUI 操作工具**(computer tool):鼠标操作包括移动(mouse_move)、左/右/中键点击、双击/三击、拖拽(left_click_drag),以及更精细的按下/松开(left_mouse_down/up)。滚动(scroll)支持四个方向并可配合修饰键。键盘操作包括逐字输入(type,每个字符间隔 12ms 模拟真实打字)、组合键(key,如 Ctrl+C)、长按(hold_key)。感知动作:截图(screenshot)、获取光标位置(cursor_position)、等待(wait)。 **命令执行工具**(bash tool):提供持久的 bash 终端会话,120 秒超时,通过哨兵字符串检测命令是否执行完毕,多次调用之间保持环境状态(比如 cd 到某个目录后下次调用还在那个目录)。 **文件编辑工具**(str_replace_editor):通过字符串匹配实现安全编辑,支持查看、创建、替换、插入和撤销操作,比直接覆盖整个文件更精确,不容易误改其他内容。 > **实验 6-7 ★:运行 Computer Use(Anthropic 参考路径或开放模型路径)** > > 路径 A 使用 Anthropic Computer Use Demo:容器打包完整的 Ubuntu 桌面环境(含浏览器、终端等常用工具),前端接收任务,后端把指令与截图发送给 Claude,再执行模型返回的鼠标、键盘、终端或编辑动作。 > > 路径 B 使用本书的 [`chapter6/computer-use-open-model`](../chapter6/computer-use-open-model/) 示例代码:默认以开放权重的 Qwen3-VL 32B Instruct 驱动 browser-use,可通过 OpenRouter 托管 API,也可连接自托管的 vLLM/SGLang 等服务。 ### 视觉定位(Grounding) 在循环的每一轮中,模型需要在截图中准确定位目标元素——“搜索框在哪里?”“提交按钮的坐标是什么?”这就是视觉定位(Grounding)问题。当前主要有**两大思路**:一是把定位变成**选择题**——先把界面元素标注好编号,模型只需从中选一个;二是**纯坐标预测**——让模型像人一样直接“看”着截图报出坐标。其中选择题思路又有两种实现方式:**纯视觉标注**(原始的 Set-of-Mark,用分割模型在像素上切出候选区域)和**结构化元素索引**(DOM/Accessibility Tree,直接读取界面自带的结构)。选择题思路的共同优势,是把开放式的“在截图中找到按钮并预测坐标”转化为封闭式的“从已标注好的元素中选一个”。就像考试中选择题比填空题更容易答对一样,模型只需说“点击 [123]”而不是“点击屏幕 (350, 464) 处的按钮”。输出坐标对模型来说挑战尤其大,需要大量训练才能做准确,而且在不同屏幕分辨率下很容易出错。 **Set-of-Mark:视觉标注法。** 原始的 Set-of-Mark(SoM)由微软研究院于 2023 年提出,最初是为了释放 GPT-4V 的视觉定位能力。它是一个**纯视觉**方法:用图像分割模型(SAM、SEEM 等)在截图上自动切出候选区域,为每个区域叠加编号标记,模型看到的是一张带编号的图,只需报出编号,由系统换算成对应区域的中心坐标。整个过程不需要 DOM,也不需要任何界面内部结构,因此原生桌面软件、游戏界面同样适用——只要分割模型能把候选区域切出来。 **结构化元素索引:SoM 思想在 Web 上的结构化实现。** 当界面本身能提供结构化信息时,标注可以做得更精确。现代网页在渲染之前就已经定义了完整的元素结构(DOM 树)和语义角色(哪个是按钮、哪个是输入框),无障碍接口(Accessibility Tree)为许多桌面应用提供了类似的信息。以 browser-use 项目为代表的 Web Agent 方案正是这样做的:从 DOM 中枚举可交互元素并编号,可以看作 SoM 思想在 Web 上的结构化实现(图6-13)。流程分四步: 1. 通过浏览器调试接口(CDP,Chrome DevTools Protocol)获取网页的结构化表示(DOM 树)和无障碍信息 2. 自动检测哪些元素可以交互(按钮、输入框、链接等) 3. 为每个可交互元素标注唯一 ID 并在截图上绘制边界框 4. 同时生成文本列表描述每个 ID 对应的元素 ```text Screenshot: [图片中关键元素标注了 [1]、[2]、[3]、[4] 等 ID] Elements: [1] [2]