1. 从“能看”到“能动手”:Computer Use 到底改变了什么
大多数人第一次接触 Computer Use 这个概念,脑子里浮现的画面大概是“AI 帮我点鼠标”。这个理解不算错,但太浅了。真正让 Computer Use 值得花时间研究的,是它把大模型的能力边界从“生成文本”推到了“操作界面”——模型不再只是告诉你“你应该点那个按钮”,而是直接接管鼠标键盘,替你把那一步做完。
我最初对这个方向是持怀疑态度的。原因很简单:让模型操作图形界面,听起来很酷,但实际落地时,坐标偏移、控件识别失败、页面加载时序错乱,任何一个环节出问题都会导致整个任务链断掉。直到我在 Codex 的界面里跑通了第一套验证流程,才意识到这件事的可行性比想象中高得多。Codex 在这里扮演的角色,是一个“可控的试验场”——它提供了相对稳定的界面结构和可预期的交互路径,让你能先把 Computer Use 的核心链路跑通,再去面对真实世界里那些乱七八糟的页面。
而 LCU 接入,则是把这套能力从“实验室”推向“生产环境”的关键一步。LCU 可以理解为一个本地控制单元,它负责在模型和实际操作系统之间做一层桥接。没有这层桥接,模型输出的动作指令就是一堆悬在空中的坐标数字;有了它,这些指令才能变成真实的点击、输入和拖拽。
这篇文章适合三类人看:一是已经听说 Computer Use 但不知道从哪下手的技术爱好者;二是想在 Codex 环境里做界面自动化验证的开发者;三是需要把模型操作能力接入自己系统、正在研究 LCU 方案的工程师。我会把从 Codex 界面验证到 LCU 接入的完整路径拆开讲,包括每一步为什么这么做、实测中会遇到什么问题、以及那些文档里不会写的坑。
2. Codex 界面验证:为什么它是 Computer Use 的最佳起点
2.1 Codex 界面作为验证环境的独特优势
选择 Codex 界面作为 Computer Use 的第一站,不是随便挑的。我在尝试过好几个不同的验证环境之后,发现 Codex 有几个别人替代不了的特点。
第一是界面结构的确定性。Codex 的界面元素布局相对固定,按钮、输入框、输出区域的位置不会因为窗口大小变化而剧烈跳动。这对 Computer Use 来说太重要了——模型依赖截图来识别控件位置,如果每次截图布局都不一样,识别准确率会断崖式下跌。我在一个布局随机的网页上做过对比测试,同样的点击任务,Codex 界面下的成功率能到九成以上,而那个随机布局的页面只有六成左右。
第二是交互反馈的即时性。Codex 在执行操作后,界面会立刻给出视觉反馈——按钮变色、输出区域刷新、状态栏更新。这些反馈是 Computer Use 判断“上一步是否成功”的关键依据。如果操作后界面毫无变化,模型就不知道自己该继续还是重试。Codex 在这方面的响应速度很快,基本在几百毫秒内就能看到变化,这让整个验证循环跑得很顺畅。
第三是操作的可逆性。在 Codex 里做验证,最坏的情况也就是关掉重开。不像在某些生产系统里,一个误操作可能触发不可逆的流程。这种“安全失败”的特性,让你可以大胆地让模型去试错,快速积累经验。
提示:如果你手头没有 Codex 环境,也可以找一个界面结构稳定、操作可逆的本地应用作为替代验证场。核心原则是:布局固定、反馈及时、失败可恢复。
2.2 验证流程的四个阶段拆解
我把 Codex 界面验证分成了四个阶段,每个阶段有明确的验证目标和退出条件。
阶段一:静态识别验证。这个阶段不让模型做任何操作,只让它看截图,然后回答“屏幕上有没有某个按钮”“输入框在哪个位置”。目的是确认模型对 Codex 界面的视觉理解能力。我当时的做法是截了二十张不同状态的 Codex 界面图,让模型逐一标注关键控件的位置。实测下来,模型对按钮和输入框的识别准确率很高,但对一些图标类控件(比如没有文字标签的小图标)偶尔会认错。这个阶段的退出条件是:关键控件的识别准确率达到九成以上。
阶段二:单步操作验证。模型开始执行单个动作——点一个按钮、在输入框里打一段字、滚动一次页面。每执行完一个动作,就截一张图确认结果。这个阶段最容易暴露的问题是坐标偏移。模型给出的点击坐标,有时候会落在目标控件的边缘甚至外面。我的解决办法是在点击前加一个“坐标校验”步骤:模型先输出它认为的控件中心坐标,然后我用一个简单的图像匹配算法去验证这个坐标附近是否真的有预期控件。如果匹配度低于阈值,就让模型重新识别。
阶段三:短序列验证。把三到五个操作串起来,比如“点击输入框→输入文本→点击提交按钮→等待结果出现”。这个阶段的核心挑战是时序控制。模型点完提交按钮后,如果不等页面刷新就急着截下一张图,截到的还是旧界面,后续判断全错。我在这个阶段踩了不少坑,后面会专门讲。
阶段四:异常恢复验证。故意制造一些异常——比如点击一个不存在的按钮、在输入框里输入超长文本、在页面加载中途截图——看模型能不能识别出“出问题了”并采取恢复动作。这个阶段的通过标准是:模型能在两次尝试内回到正常流程。
2.3 验证过程中必须记录的三类数据
在 Codex 界面验证阶段,有三类数据必须完整记录,否则后面的 LCU 接入会缺少关键参考。
第一类是动作-结果映射表。每执行一个动作,记录下动作类型、目标控件、执行前的界面状态、执行后的界面状态、以及是否成功。这张表看起来笨重,但它是后续调试的命根子。我一开始偷懒没记,结果在 LCU 接入阶段遇到一个诡异问题时,完全无法回溯是哪个环节出的错,只能从头重跑一遍验证。
第二类是失败案例的截图序列。每次失败,把失败前一张、失败时一张、失败后一张截图都存下来。这三张图连起来看,往往能直接定位问题。比如有一次模型连续三次点击同一个位置都失败,我把三张截图并排一看,发现那个按钮在第二次点击后变成了禁用状态——模型没意识到状态变了,还在死磕。
第三类是时间戳日志。每个动作的发起时间和完成时间都要记。这个数据在验证阶段看起来没什么用,但到了 LCU 接入阶段,你需要用它来设置合理的超时阈值。没有这个数据,你只能拍脑袋定一个超时时间,要么太短导致误判,要么太长导致任务卡死。
3. LCU 接入的核心逻辑:模型指令如何变成真实操作
3.1 LCU 在架构中的位置与职责
LCU 这个词听起来有点抽象,换个说法就清楚了:它是模型和操作系统之间的“翻译官兼执行者”。模型输出的是“点击坐标 (320, 480)”这样的抽象指令,操作系统需要的是真实的鼠标事件。LCU 就负责把前者翻译成后者。
具体来说,LCU 承担了四个职责。第一是指令解析,把模型输出的结构化指令(通常是 JSON 格式)解析成内部的动作对象。第二是坐标映射,如果模型的截图分辨率和实际屏幕分辨率不一致,LCU 需要做坐标缩放。第三是动作执行,调用操作系统的接口去模拟鼠标点击、键盘输入、滚动等操作。第四是状态回传,执行完动作后,截取新的屏幕画面,回传给模型作为下一步决策的依据。
这四个职责里,最容易出问题的是坐标映射。我遇到过好几次这样的情况:模型在 1920x1080 的截图上识别出按钮在 (960, 540),但实际屏幕是 2560x1440,LCU 如果没有正确缩放,点下去的位置就偏了。解决办法很简单,在 LCU 初始化时就把截图分辨率和屏幕分辨率的比例算好,每次坐标转换都乘上这个比例。但就是这么简单的一件事,我见过不少人在接入时忘了做。
3.2 接入前的环境准备清单
在正式写 LCU 接入代码之前,有几项环境准备工作必须做扎实。我列了一个清单,按优先级排序。
| 准备项 | 具体内容 | 为什么重要 |
|---|---|---|
| 屏幕分辨率确认 | 记录实际屏幕分辨率和模型截图分辨率 | 坐标映射的基础,搞错全盘皆输 |
| 权限配置 | 确保 LCU 有模拟输入和截屏的系统权限 | 权限不足时动作会静默失败,很难排查 |
| 依赖库安装 | 安装屏幕捕获和输入模拟相关的库 | 不同库的行为差异很大,选错会走弯路 |
| 超时参数预设 | 根据验证阶段的时间戳数据设定各级超时 | 没有超时保护,任务卡死会拖垮整个流程 |
| 日志系统搭建 | 建立动作级别的日志记录 | 出问题时没有日志等于盲人摸象 |
权限配置这一项我要多说两句。在有些系统上,模拟输入和截屏需要显式授权,如果不授权,程序不会报错,但动作就是没效果。我一开始以为是代码写错了,排查了半天才发现是权限问题。所以接入前第一件事,就是写一个最简单的“点击屏幕中心”的测试脚本,确认权限没问题再往下做。
3.3 指令协议的设计要点
模型和 LCU 之间的指令协议,是整个接入方案的核心。协议设计得好,后面扩展和维护都轻松;设计得烂,每加一个功能都要伤筋动骨。
我的建议是采用扁平化的 JSON 结构,每个指令包含四个必填字段:action(动作类型)、target(目标描述)、params(动作参数)、expect(预期结果)。举个例子:
{ "action": "click", "target": "submit_button", "params": { "x": 960, "y": 540, "button": "left" }, "expect": { "screen_change": true, "timeout_ms": 3000 } }expect字段是我强烈建议加的。它让 LCU 在执行完动作后,能有一个明确的判断依据——屏幕有没有变化、变化是否在预期时间内发生。如果没有这个字段,LCU 执行完就完事了,模型根本不知道上一步成没成。加了之后,LCU 可以在超时后主动回传一个“预期未满足”的状态,模型就能据此决定是重试还是换策略。
动作类型不需要一开始就设计得很全。我建议先支持四种:click、type、scroll、wait。这四种能覆盖八成的界面操作场景。等这四种跑稳了,再按需加drag、hotkey、screenshot等。
4. 从验证到接入:那些让我卡了半天的坑
4.1 时序问题:为什么“等一等”比“快一点”更难
时序问题是 Computer Use 接入过程中最隐蔽的坑。它不像坐标偏移那样一眼就能看出来,而是表现为“偶尔成功偶尔失败”,让你以为是随机故障。
我遇到的最典型场景是这样的:模型点击了一个按钮,按钮触发了一个异步加载,界面在 500 毫秒后才更新。但模型在点击后 200 毫秒就截了图,截到的还是旧界面。模型一看界面没变,以为点击失败了,又点了一次。结果第二次点击落在了已经变化的界面上,点到了别的东西。
这个问题的根因是模型没有“等待”的概念。它执行完一个动作就立刻想看结果,但真实世界的界面更新是需要时间的。解决办法是在 LCU 层面加一个“变化检测”机制:执行完动作后,不立刻截图,而是以 100 毫秒为间隔连续截几张图,对比相邻两张图是否有差异。一旦检测到差异,就认为界面更新了,把最新截图回传给模型。如果连续截了十张图(也就是一秒)都没有差异,就回传“无变化”状态。
这个机制听起来简单,但实现时有个细节要注意:有些界面的变化是局部的,比如只有一个小角落的进度条在动。如果对比整张图,差异可能很小,容易被忽略。我的做法是把屏幕分成若干个区域,每个区域单独做差异检测,只要有一个区域的差异超过阈值,就认为界面更新了。
注意:变化检测的阈值不要设得太敏感。有些界面有闪烁的光标或者动态背景,这些微小的持续变化会干扰检测。我一般会把阈值设在“肉眼可见的变化”这个级别,具体数值需要根据实际界面调。
4.2 坐标漂移:一个被低估的稳定性杀手
坐标漂移是指模型识别出的控件位置和实际位置之间存在系统性偏差。它不是随机误差,而是每次都在同一个方向上偏。比如模型总是把按钮的 y 坐标识别得比实际位置高 20 个像素。
这个问题在 Codex 界面验证阶段不太明显,因为 Codex 的控件比较大,偏 20 个像素还在按钮范围内。但到了 LCU 接入阶段,面对的是真实系统的界面,有些按钮很小,偏 20 个像素就点到外面去了。
坐标漂移的来源通常有两个。一是截图分辨率和屏幕分辨率不一致导致的缩放误差。这个前面提过,解决办法就是老老实实做坐标映射。二是模型本身的视觉定位精度有限。模型看截图时,对控件边界的判断不是像素级的,而是有一个模糊范围。这个误差没法完全消除,但可以通过“点击前校验”来缓解。
我的做法是在 LCU 里加一个校验步骤:模型给出点击坐标后,LCU 不直接点,而是先截取坐标附近的一个小区域(比如 100x100 像素),然后用模板匹配或者简单的颜色检测,确认这个区域里确实有预期控件。如果匹配度不够,LCU 就回传一个“坐标可疑”的状态,让模型重新识别。这个步骤会增加一点延迟,但能显著降低误点率。
4.3 状态同步:模型看到的和实际发生的对不上
状态同步问题比前两个更隐蔽。它的表现是:模型认为自己在一个状态下操作,但实际系统已经进入了另一个状态。
我遇到过一个典型案例:模型在 Codex 界面里点击了一个“运行”按钮,然后等待结果输出。但那个按钮点击后,界面弹出了一个确认对话框,模型没注意到对话框,还在等结果输出。等了半天没等到,模型就判定任务失败。实际上任务根本没开始,因为确认对话框还没处理。
这个问题的根因是模型对界面的“注意力”是有限的。它关注了主要区域,忽略了弹窗、提示条、状态栏这些次要区域。解决办法是在 LCU 回传截图时,除了回传全屏截图,再额外回传几个关键区域的局部截图——比如屏幕中央区域(弹窗通常出现在这里)、屏幕顶部(提示条通常在这里)、屏幕底部(状态栏通常在这里)。模型在决策时,会同时看全屏图和局部图,不容易漏掉关键信息。
这个方案会增加一些数据传输量,但相比任务失败重跑的代价,这点开销完全值得。
5. 一套可复用的接入模板与参数参考
5.1 LCU 接入的最小可行代码结构
下面这套代码结构是我在实际项目中反复打磨后留下来的,去掉了业务相关的部分,只保留最核心的骨架。你可以直接拿去改。
import time import json from screen_capture import capture_screen from input_simulator import click, type_text, scroll class LCU: def __init__(self, screen_resolution, capture_resolution): self.screen_w, self.screen_h = screen_resolution self.capture_w, self.capture_h = capture_resolution self.scale_x = self.screen_w / self.capture_w self.scale_y = self.screen_h / self.capture_h self.action_log = [] def map_coordinates(self, x, y): return int(x * self.scale_x), int(y * self.scale_y) def execute(self, instruction): action = instruction.get("action") params = instruction.get("params", {}) expect = instruction.get("expect", {}) timeout = expect.get("timeout_ms", 3000) before = capture_screen() if action == "click": sx, sy = self.map_coordinates(params["x"], params["y"]) click(sx, sy, params.get("button", "left")) elif action == "type": type_text(params["text"]) elif action == "scroll": scroll(params.get("amount", 3)) elif action == "wait": time.sleep(params.get("ms", 500)) changed = self.wait_for_change(before, timeout) self.log_action(instruction, changed) return {"changed": changed, "screenshot": capture_screen()} def wait_for_change(self, before, timeout_ms): interval = 100 elapsed = 0 while elapsed < timeout_ms: time.sleep(interval / 1000) elapsed += interval current = capture_screen() if self.has_significant_change(before, current): return True return False def has_significant_change(self, img1, img2): # 分区对比,返回是否有区域差异超过阈值 regions = self.split_regions(img1) for r1, r2 in zip(regions, self.split_regions(img2)): if self.region_diff(r1, r2) > 0.05: return True return False def log_action(self, instruction, changed): self.action_log.append({ "timestamp": time.time(), "instruction": instruction, "changed": changed })这段代码里,map_coordinates处理坐标映射,wait_for_change处理时序等待,has_significant_change做分区差异检测。三个核心机制都在里面了。
5.2 关键参数的取值建议
参数取值这件事,没有万能答案,但有一些经验范围可以参考。下面这张表是我在多个项目中总结出来的,你可以作为起点,再根据实际情况微调。
| 参数 | 建议取值 | 调整方向 |
|---|---|---|
| 变化检测间隔 | 100ms | 界面更新快的场景可以降到 50ms,慢的可以升到 200ms |
| 变化检测超时 | 3000ms | 涉及网络请求的操作建议升到 5000ms |
| 区域差异阈值 | 5% | 界面元素密集的场景可以降到 3%,稀疏的可以升到 8% |
| 坐标校验区域大小 | 100x100 像素 | 小控件场景可以缩到 50x50 |
| 最大重试次数 | 3 次 | 关键操作可以升到 5 次,非关键操作降到 2 次 |
| 动作间隔 | 200ms | 连续操作之间留一点缓冲,避免事件队列堆积 |
这些数值不是拍脑袋定的,每一个都是我在实际调试中反复调整后稳定下来的。比如变化检测间隔,我试过 50ms,发现太频繁了,截图本身的开销就开始影响性能;试过 200ms,又太慢了,有些快速变化的界面会漏掉中间状态。100ms 是一个比较平衡的值。
5.3 日志与回放:出问题时怎么快速定位
日志这件事,我在前面反复强调了。这里给一个具体的日志格式建议,以及怎么用日志做回放。
每条日志记录五个字段:时间戳、动作指令、执行前的截图路径、执行后的截图路径、执行结果(成功/失败/超时)。截图不要存在内存里,存到磁盘上,用时间戳命名。这样出问题时,你可以按时间顺序把截图一张张翻过去,像看幻灯片一样还原整个操作过程。
回放就更简单了:把日志里的动作指令按顺序重新执行一遍,每执行一步就对比截图和日志里记录的截图是否一致。如果不一致,说明这一步的行为和当时不一样,问题大概率就出在这里。
我用这个回放机制定位过一个很诡异的问题:模型在某个特定操作后总是失败,但单独执行那个操作又没问题。回放之后发现,失败的那次操作前面多了一个“等待”动作,而那个等待动作在日志里显示“无变化”。也就是说,模型等了一个永远不会发生的变化,然后超时了。把那个等待动作去掉之后,问题就解决了。
6. 从跑通到跑稳:稳定性优化的几个实战思路
6.1 重试策略:不是所有失败都值得重试
模型操作界面时,失败是常态。但失败和失败不一样,有些失败重试一下就能过,有些失败重试一百次也没用。区分这两类失败,是稳定性优化的第一步。
我一般把失败分成三类。第一类是瞬时失败,比如截图时刚好赶上界面在刷新,截到了一张模糊的图。这类失败重试一次基本就能解决。第二类是状态失败,比如模型以为按钮是可点击的,但实际上按钮处于禁用状态。这类失败重试没用,需要模型先改变状态。第三类是逻辑失败,比如模型想点击一个根本不存在的按钮。这类失败重试也没用,需要模型重新识别界面。
区分的方法很简单:看失败后的界面状态和失败前是否一致。如果一致,说明操作根本没生效,大概率是瞬时失败,可以重试。如果不一致,说明操作生效了但结果不对,需要模型重新决策。
基于这个分类,我的重试策略是这样的:瞬时失败自动重试,最多三次;状态失败和逻辑失败不自动重试,直接把控制权交回模型,让模型重新看界面做决策。
6.2 界面变化的预判与处理
有些界面的变化是可以预判的。比如点击一个“展开”按钮,界面肯定会多出一块区域;点击一个“提交”按钮,界面肯定会有一个加载状态。如果模型能预判这些变化,就能提前做好准备,而不是等变化发生了再反应。
预判的实现方式是在指令的expect字段里加一个expected_change描述。比如:
{ "action": "click", "target": "expand_button", "params": {"x": 400, "y": 300}, "expect": { "expected_change": "new_region_appears", "region_hint": "below_click_point", "timeout_ms": 2000 } }LCU 收到这个指令后,就知道点击后应该在点击位置下方出现新区域。它可以在等待变化时,重点检测那个区域,而不是全屏扫描。这样检测速度更快,也更准确。
这个机制对提升稳定性很有帮助,尤其是那些变化区域比较固定的界面。但也不是所有场景都适用——如果界面变化的位置不固定,预判反而会误导。所以这个字段是可选的,模型觉得有把握就填,没把握就不填。
6.3 多轮验证循环的收敛控制
Computer Use 的任务执行本质上是一个循环:截图→决策→执行→再截图→再决策。这个循环如果不加控制,可能会无限跑下去。我见过最夸张的一次,模型在一个死循环里跑了四十多轮,每轮都在做同样的操作,因为每次操作后界面都没变化,模型就以为没成功,又试一次。
控制循环收敛的方法有两个。一是设置最大轮数,比如二十轮,超过就强制终止。二是设置“无进展检测”,如果连续三轮操作后界面状态都没有实质性变化,就判定任务卡住了,终止循环。
“实质性变化”的判断标准需要根据任务来定。对于表单填写类任务,实质性变化是输入框里出现了新内容;对于页面导航类任务,实质性变化是页面主体区域发生了切换。我在 LCU 里加了一个可配置的“进展检测器”,针对不同类型的任务用不同的检测逻辑。
提示:最大轮数不要设得太小。有些复杂任务确实需要十几轮操作才能完成。我一般把默认值设在二十轮,特殊任务再单独调。
6.4 跨应用场景的适配经验
Codex 界面验证跑通之后,下一步往往是接入真实的应用。这里面的坑比 Codex 环境多得多,因为真实应用的界面行为千奇百怪。
我总结了几条跨应用适配的经验。第一,先做界面测绘。在让模型操作之前,先手动把目标应用的界面结构摸清楚——有哪些主要区域、关键控件在哪、操作后的典型变化是什么。这份测绘结果可以作为模型决策的参考,也可以作为 LCU 校验的依据。
第二,从简单任务开始。不要一上来就让模型做多步骤的复杂任务。先让它做一个单步操作,确认没问题了,再逐步增加步骤。每增加一步,都要重新验证稳定性。
第三,准备好降级方案。有些应用的界面实在不适合模型操作——比如控件太小、变化太快、或者有复杂的动画效果。这种情况下,与其硬让模型去操作,不如设计一个降级方案,比如用快捷键代替鼠标点击,或者用命令行接口代替界面操作。Computer Use 不是万能的,知道什么时候不用它,也是一种能力。
第四,关注应用的更新频率。如果一个应用经常更新界面,那基于界面结构的 Computer Use 方案就需要频繁调整。这种情况下,要么选择界面稳定的应用,要么把界面适配层做得足够抽象,让更新带来的影响降到最低。
7. 我在这条路上踩过的几个真实坑
7.1 那个让我排查了一整天的“幽灵点击”
有一次,模型在 Codex 界面里执行一个点击操作,日志显示点击成功,界面也变化了,但变化的结果不对——点到了旁边的另一个按钮。我检查了坐标映射,没问题;检查了截图分辨率,也没问题。把日志里的截图翻出来一看,发现模型识别出的按钮位置和实际位置差了大概三十个像素。
但奇怪的是,同样的按钮,在之前的测试里点击都是准的。为什么这次偏了?我反复对比了成功和失败的截图,终于发现了原因:失败的那次,界面上多了一个浮动的提示条,把按钮往下挤了三十个像素。模型看的是旧截图,不知道按钮已经移位了。
这个问题的教训是:截图的新鲜度至关重要。模型决策用的截图,必须是执行动作前最后一刻截的。如果截图和动作之间隔了哪怕几百毫秒,界面就可能已经变了。我在 LCU 里加了一个强制规则:每次执行动作前,必须重新截一张图,不能用缓存的截图。
7.2 输入法引发的“字符消失术”
另一个让我头疼了很久的问题是:模型在输入框里打字,日志显示输入成功,但输入框里只出现了部分字符,有时候甚至一个字符都没有。
排查过程很曲折。我先怀疑是输入速度太快,加了延迟,没用。又怀疑是输入框有字符限制,检查了限制,没问题。最后用慢速录屏才看清楚:模型输入的文字触发了输入法的候选框,候选框弹出来挡住了输入框,后续的字符全打到候选框里去了。
解决办法是在输入之前,先确保输入法处于英文直输状态。具体做法是在 LCU 初始化时,发一个切换输入法的快捷键,把输入法切到英文模式。如果目标应用必须用中文输入,那就需要在输入完成后,额外发一个回车或空格来确认候选词。
这个坑的隐蔽性在于:它只在特定输入法、特定应用组合下才会出现。我在 Codex 环境里测试时一直没遇到,因为 Codex 的输入框对输入法做了处理。到了真实应用里才暴露出来。
7.3 窗口焦点丢失导致的“无效操作”
还有一个坑是窗口焦点问题。模型在操作一个应用时,如果那个应用的窗口失去了焦点(比如被其他窗口遮挡,或者用户手动切走了),后续的所有操作都会失效——点击点到了别的窗口上,输入打到了别的应用里。
这个问题在自动化测试里很常见,但在 Computer Use 场景下更隐蔽,因为模型不知道“焦点”这个概念。它只看截图,截图里目标应用还在,它就以为可以操作。
我的解决办法是在 LCU 里加一个“焦点检查”步骤:每次执行动作前,先确认目标窗口是否在前台。如果不在,就先把它切到前台,再执行动作。这个检查会增加一点开销,但能避免大量无效操作。
判断窗口是否在前台的方法因操作系统而异。在 Windows 上可以用GetForegroundWindow接口,在 macOS 上可以用NSWorkspace的相关接口。核心思路是一样的:拿到当前前台窗口的标识,和目标窗口的标识对比。
8. 关于这套方案后续可以怎么扩展
这套从 Codex 验证到 LCU 接入的方案,跑通之后其实还有不少可以延伸的方向。我自己后续尝试过几个,效果还不错,这里简单分享一下思路。
第一个方向是多模型协作。让一个模型专门负责界面识别,另一个模型负责决策,还有一个模型负责异常处理。三个模型各司其职,整体稳定性比单模型方案高不少。缺点是成本上去了,适合对稳定性要求极高的场景。
第二个方向是操作录制与回放。把模型成功执行的操作序列录下来,下次遇到类似任务时直接回放,不用再让模型一步步决策。这个方案适合那些重复性高的任务,能大幅降低模型调用成本。回放时如果遇到界面变化导致回放失败,再切回模型决策模式。
第三个方向是界面变化的主动学习。让 LCU 记录每次界面变化前后的截图对,积累一段时间后,就能训练一个轻量的界面变化预测模型。有了这个预测模型,LCU 就能更准确地判断“操作后应该等多久”“应该重点检测哪个区域”,进一步提升响应速度和稳定性。
这些扩展方向不需要一次性全做,可以先把基础方案跑稳,再根据实际需求逐个尝试。我在实际项目中的体会是:基础方案能覆盖七八成的场景,剩下的两成特殊场景,再用扩展方案去补。不要一开始就追求大而全,先把核心链路跑通,比什么都重要。