前段时间我接到一个内部需求每天要花将近两小时从后台系统里核对订单数据再把异常项复制粘贴到另一套系统。一开始我想得很简单用常规自动化工具录个脚本就行。可现实很打脸界面弹层换了个样式、按钮多了一层遮罩脚本马上就崩。后来我干脆换了个方向不写死坐标和选择器而是让程序像人一样“看屏幕、做判断、点鼠标”。这个项目我内部代号叫“cua”做完之后不光解决了报表整理的问题还顺手把它用到了软件回归测试和内部运营流程里。如果你也在做流程自动化、桌面端工具或者想给智能体加一个能直接操作系统的手这篇拆解应该能给你不少参考。后面我不会讲太多概念性的东西更多是我在搭建过程中怎么设计、怎么做取舍、怎么一步步踩坑填坑的。1. 为什么我要自己搭一套CUA而不是继续用RPA1.1 传统RPA脚本的痛到底在哪很多人一提到桌面自动化第一反应就是RPA。这个方向本身没问题但我实际用下来最大的麻烦不是“录脚本”这个动作而是脚本上线之后面对真实环境的脆弱性。传统RPA的脚本通常依赖界面元素选择器比如窗口标题、按钮ID、控件路径。开发阶段跑得挺顺畅可只要目标系统一升级前端组件库一换选择器就失效。我遇到过最夸张的一次是对方系统只是把一个按钮的CSS类名从btn-primary改成了btn-confirm整套流程就卡在中间步骤没有任何报错提示界面停在原地三十秒最后还是靠人工轮询发现任务挂掉了。另一个痛点是异常处理太单薄。RPA脚本录的是“正常路径”一旦出现弹窗、网络延迟、权限提示脚本往往不知道当前处于什么状态只会按原计划继续往下走。结果就是要么重复点击同一个地方要么把数据填到错误的表单里。想要把异常分支写全工作量比录主流程还大几倍。1.2 CUA的核心变化让机器理解“看见的东西”CUA这个方向我习惯叫它计算机操作智能体Computer-Usage Agent。它和传统RPA最本质的区别在于不再把界面看作一堆需要精确匹配的控件树而是把界面当作一张图像。程序通过截图获得信息再用视觉模型理解屏幕上的内容然后决定下一步该做什么。这就解决了一个关键问题即使按钮位置变了、图标换了、甚至整个系统的前端框架重写了只要界面上还保留着对应的文字和形状代理就能继续工作。举个例子。做报表整理时我需要在网页表格里找到所有状态为“异常”的行点击后进入详情页。之前RPA方案是定位表格特定单元格的XPATH一旦表格列顺序调整就全废掉。换成CUA思路之后我直接截取表格区域图像让模型识别出“异常”两个字所在的行再根据行位置计算详情按钮的坐标整个过程不再依赖具体控件ID。1.3 CUA适合做什么场景不适合做什么这个方向并不是万能的。我梳理过最适合CUA发挥价值的场景大致有这几类老系统或第三方软件的自动化很多遗留系统没有提供API甚至没有稳定的控件结构但你肉眼能操作的流程CUA基本都能复现。跨系统数据搬运比如从网页复制数据写入桌面应用或者反过来这类动作强依赖界面而非业务接口。软件回归测试特别是对视觉回归敏感的界面CUA既能检查功能是否正常也能顺便发现界面错位、文案异常。日常重复性办公整理表格、批量录入、信息核对这些流程通常没有复杂业务规则但很吃人工。反过来如果任务涉及极高速的实时操作比如毫秒级的鼠标轨迹控制或高帧率游戏操作现阶段并不适合。还有一类情况也不建议就是操作错误会导致严重不可逆后果的场景比如删除生产数据库或者处理核心财务数据除非你在代理外面罩一层很强的人工确认机制否则风险太高。2. CUA的运行骨架我把整个流程拆成“看、想、动、验”四步2.1 感知层不只用截图还要混合信号CUA的第一步是“看”。很多人以为截图给视觉模型就行实际操作后发现信息不够。屏幕上是混合了整个桌面所有窗口、任务栏、通知区域的内容模型想要准确理解业务上下文光靠一张全局截图效果很差。我在感知层使用的是混合信号方案一是屏幕截图但会做区域裁剪和缩放。全局截图用于把握整体布局重点操作区域再切出来单独放大。比如录订单数据时我会把表格所在的窗口区域单独裁剪成一张图分辨率控制在1200像素宽度以内。这样视觉模型识别起来更快坐标也更准不会被桌面无关元素干扰。二是可访问性接口数据。Windows的UI Automation、macOS的Accessibility API能提供操作系统层面的控件信息。很多Electron应用或者WebView应用可访问性树往往不完整但只要有我就把这些信息一起传给规划层。它和视觉信息形成互补。比如CUA识别出“这是一个输入框”我可以再用可访问性接口拿到输入框的具体位置和当前值做二次确认。三是OCR兜底。有些场景界面元素无法直接给到视觉模型或者用户要求必须匹配特定文本我会在局部区域做OCR把识别出的文本块位置传给后续步骤。OCR我用的是通用引擎像Tesseract这类就够用不需要太重型。2.2 规划层把大任务切成可验证的小动作拿到了屏幕信息接下来是“想”。这一层的核心不是让模型一次性生成全部操作步骤而是让它只针对当前屏幕状态给出下一步动作。我把任务拆成了状态和动作两个维度。状态指的是当前界面处于什么阶段比如“登录页”“列表页”“详情页”。动作用于改变状态比如“点击按钮”“输入文本”“滚动列表”。规划层工作时会先拿到当前界面的感知结果由推断框架判断当前状态再从预设的目标列表中找到最接近的目标。这样处理比直接让模型自由发挥要稳得多。目标列表不需要覆盖所有异常分支但必须要覆盖主线流程可能经过的每个节点。实际操作中我在每轮规划时会注入三个要素任务目标、当前状态、允许的操作集合。允许的操作集合实际上是一个安全护栏通常会过滤掉“打开命令行”“修改系统设置”“上传文件”这类不可控操作。2.3 动作层和执行反馈动作层的职责是执行规划层给出的指令。这里有个容易忽略的点动作不是“想点就点”。在执行任何点击或输入之前我会先做一次坐标校验。典型的校验包括点击目标是否在当前屏幕范围内、目标区域是否被其他窗口遮挡、目标控件是否处于可用状态。如果校验不通过动作不会下发而是直接回到感知层重新获取界面信息。执行完之后不是结束而是进入“验”的环节。程序会在动作执行后等待一段时间再次截图并判断状态是否发生了变化。这个反馈环路是整个CUA系统能否稳定工作的关键。状态没变就被判定为动作失败转入重试或人工接管流程。我实测下来四步循环看起来简单但真正做到稳定难点全在细节优化上。下面几个章节我就具体讲讲在动作执行、状态容错和性能调优过程中遇到的坑。3. 动作执行中最容易翻车的三个环节3.1 屏幕坐标还原误差和多显示器问题CUA的决策链路中视觉模型通常返回的是截图中的相对坐标或区域而系统指令需要的是屏幕坐标。这两者之间的转换是翻车重灾区。第一个坑是缩放率。Windows系统显示缩放设为150%时逻辑分辨率和物理像素之间是有差异的。截图工具抓取到的分辨率和你鼠标操作的坐标系统可能不在同一个参考系。我一开始没做坐标换算点击位置总是偏移开始还以为是模型识别不准后来发现是缩放率没有处理。第二个坑是多显示器。CUA如果需要在扩展屏幕上操作必须明确截图对应的是哪个显示器在做坐标换算时加上该显示器的偏移量。否则视觉模型看到了“第二屏左侧有个按钮”程序却在主屏上执行点击完全跑偏。我的做法是每一轮感知信息都附带一个上下文对象包含当前截图来源、所在显示器编号、缩放率、窗口位置。动作层在执行之前按照统一的坐标映射函数换算把图像坐标转换为系统级虚拟屏幕坐标。这个映射函数不复杂但必须有而且要在多显示器环境下反复验证。3.2 点击后的等待与焦点切换很多流程脚本死在“点击之后立刻做下一个操作”。实际上系统响应需要时间尤其是一些后台系统点击查询按钮之后列表要两到三秒才刷新。如果CUA在点击后立刻截图看到的还是老界面就会误判“没反应”然后反复点击同一个按钮产生重复请求。我试过用固定等待时间比如点击后等3秒。结果遇到网络慢的时候还是不够网络快的时候又浪费时间。后来改成智能等待点击动作执行后按照预设的轮询间隔反复截图判断界面上的关键状态是否变化。还有一种情况是焦点切换。桌面应用点击之后可能会弹出新窗口或者把焦点跳到另一个窗口。如果程序没有恢复目标窗口前置后续的输入操作会落到当前前台窗口上轻则输入失败重则把文本输错到别的地方。这个问题的关键是在每轮动作之前先调用系统API确认目标窗口是否在前台如果不是就先激活。我一度在这个细节上栽过跟头一次批量录入操作因为中间的弹窗抢了焦点导致后续数据被填入另外一个小工具的搜索框里。后来我加了一道保险在输入任何内容之前先获取当前前台窗口句柄并和目标窗口句柄对比不一致就拒绝执行。3.3 循环试探和失控的防护CUA最大的隐患不是它“不会做”而是它在出错时会“反复试”。比如点击一个按钮没有响应模型可能会换一个坐标再点一次再失败又换一个方式。如果不加限制这种试探会无限进行下去。我为动作层设置了两个防护参数。第一个是单轮动作最大重试次数默认3次。每次重试必须改变策略或改变等待时间不能原封不动地重复同一个动作。第二个是全局操作上限一次任务执行过程中最多允许N个动作超过这个阈值就强制终止并转人工。这个N根据任务复杂度来定简单任务通常设20复杂任务可以放宽到50到80。只靠计数还不够。我还对操作对象做了白名单限制。运行时进程范围内CUA只允许操作我们在任务初始化时声明的窗口和进程不允许点击开始菜单、任务栏、系统设置这类区域。我在项目里设置的关键字过滤比如所有动作涉及“删除”“格式化”“清空”等高风险操作时一律直接拒绝并报警。这些防护措施没法让CUA变得更聪明但能保证它不会在无人值守时把系统弄乱。做自动化久了你会发现稳定比聪明重要得多。4. 状态感知与容错设计让CUA学会“看不懂就停下来问”4.1 状态模型给流程画一张有限状态图流程自动化做得越久我越倾向于用有限状态机来思考。状态机的好处是让程序明确知道“我在哪”和“我能往哪走”。实际实现时我会在项目里先定义状态集合。以订单核对为例大概有登录、列表、详情、编辑、提交确认、完成六种状态。每种状态定义一组识别特征比如登录状态的典型特征是“登录按钮可见、密码框存在”完成状态的典型特征是“成功提示文案出现”。每轮感知结果进入CUA后先做状态分类程序只执行当前状态允许的动作。如果当前状态识别不出来或者多个状态的特征同时出现系统会先进入“不确定性处理”分支不会草率地决定操作。我还为状态的迁移设置了转换条件。比如从列表状态到详情状态必须满足“点击详情按钮成功”“出现详情标题区域”这两个条件。如果条件不满足转换就不被允许。这样一来即使模型偶尔误判状态机也能兜底阻止错误操作发生。4.2 超时、重试和人工接管容错方案的优先级运行时出异常不可怕可怕的是没有清晰的容错策略。我把容错分了三层来处理。第一层是等待超时。最常见的是操作结果迟迟不出现。超时后不立即重试而是先刷新感知数据看看当前界面是否有弹窗或异常提示。如果检测到弹窗优先截取弹窗内容传给模型判断。第二层是单步骤重试。状态没变化重试前要修改策略。比如刚才点击的是按钮中心点重试时改用按钮左上角刚才等待1秒重试时改成等待2秒。目的就是避免相同路径再次碰撞同一问题。第三层是任务级人工接管。连续重试失败或者状态置信度过低时就为该任务设置“阻塞等待”状态同时发出通知由人工介入处理。人工确认之后可以让CUA从当前状态继续执行而不是从头开始。这套优先级我建议提前想清楚不要在项目上线后再补。我早期就吃过亏因为没有人工接管机制CUA在故障界面里反复点击了十来分钟直到同事看到屏幕才发现。后来在任务调度器里加了一个“运行超过5分钟且连续15个动作状态未变化”就自动暂停的规则情况好了很多。4.3 让每一次决策都有迹可循CUA项目里“可解释性”不是一个加分项而是必需品。因为操作的是真实系统出了问题必须能定位到具体是哪个环节。我在设计时就给每一轮“看、想、动、验”都落了一条结构化日志。字段包括时间戳、当前状态、感知输入摘要截图路径、OCR文本、可访问性树片段、模型输出动作类型、目标区域、置信度、执行结果、状态迁移情况。这些日志不仅用于事后排查还用来做流程回放和模型调优。最实用的做法是把每轮截图和动作数据保存成连续帧配合日志可以还原出一个带时间轴的操作轨迹。谁在什么时间点了哪里、模型给出了什么理由、系统状态发生了怎样的变化一眼就能看清楚。这样的设计也让模型迭代有据可依。哪些环节经常识别错翻看日志和轨迹就能发现规律再针对性优化提示词或者补充样本比盲目调整参数有效得多。5. 实际项目落地中的性能调优与日志实践5.1 提速从减少截图数据量开始刚开始原型跑通的时候一轮“看-想-动-验”大概要花6到8秒主要时间几乎都耗在视觉模型识别上。这么慢的速度在流程自动化里很难实际用起来。我先从感知层入手优化。全局截图每一帧都传模型信息虽然全但很多区域跟当前任务无关。后来我在任务配置里加入“目标区域声明”把屏幕划分为业务区域、按钮区域、列表区域等几块。感知层默认只截当前业务区域和按钮区域不做全局识别。实际效果是单轮识别耗时降到了3秒左右。第二步是对截图做分辨率控制。视觉模型对输入尺寸很敏感不是越大越准。我把常见场景整理成了几套尺寸模板整页截图固定宽度1280局部区域固定宽度800OCR区域固定宽度600。分辨率降下来后模型推理时间下降明显坐标准确率反而提升了因为模型不需要在小尺寸下猜测远处的小控件。第三步是缓存复用。列表页刷新后如果数据没有变化我就直接复用上一次的感知结果不再重新调用模型。判断有没有变化的方法很简单对截图区域做像素差异对比变化率低于阈值就认定界面没有更新。5.2 可观测性设计把系统黑盒变成透明面板CUA运行在用户系统中天然是一个黑盒它看到了什么、判断了什么、执行了什么如果不做可观测性设计外部完全不知道。我在项目中构建了一套自监控面板运行时实时展示当前状态、动作序列、最近截图和日志流。这套面板在开发期能帮我确认模型每一步的判断对不对部署期也能让非技术人员清晰地看到自动化走到哪一步了。最关键的是一旦某一步失败面板上的重试历史可以告诉我失败原因到底是对界面变化不识别还是等待时间不够或者是坐标换算的问题。我还加了一个截图覆盖存储机制。正常情况下只保留最近N轮截图避免占用过多磁盘空间。但一旦任务发生异常这期间的所有截图和日志会单独归档不会被清理掉。这个细节在后排查问题时帮过大忙没有它故障现场就丢了。5.3 多轮对话式调试快速验证模型输出的手段调试CUA还有一个比较小众但好用的方法——把“看、想、动、验”的过程抽象成可交互的会话。也就是说我可以暂停自动循环手动输入一条指令让系统执行比如“点击当前列表第一行”或者“输入文本并回车”。这样调试的时候不用每次都跑完整流程。模型在对某个界面状态识别不准时我可以单独跑一次感知层的调用查看它识别出的状态是什么也可以单独执行动作层的操作验证坐标是否准确。分段验证让问题定位快了很多。这类交互式调试在项目后期帮我把各种异常状态都补齐了。我先手工触发目标系统的各种弹窗和异常界面然后用会话方式逐一切换到相应的处理分支确认每个分支的触发条件和处理逻辑都正常才放心让任务进入无人值守模式。最后再分享一个小技巧如果你也要从零搭一个类似的CUA别一上来就做大而全的通用代理。先把一个高频、边界清晰、失败成本可控的小流程跑通然后把“看、想、动、验”的每一层都拆出来独立验证。这样既能快速看到效果也能在后续扩展时积累出真正有用的经验。我在这个项目上最大的体会是CUA的核心不是模型多聪明而是流程设计够扎实。
