GUI Agent 又点错了这句话做 Agent 应用的朋友应该都不陌生。我自己调试 GUI Agent 的时候最崩溃的一幕就是模型分析得头头是道结果手上动作一抖把一个确认删除点成了取消导出整个流程直接断掉。这类问题不是偶发而是 GUI Agent 在日常自动化、浏览器操作、桌面应用控制里的核心痛点——视觉状态千变万化模型一猜错元素位置动作就跟着偏。我最近在研究和落地的一个思路叫 EvoSkill-GUI本质上就是解决这个又点错了的问题而且解决方式很有意思它不像传统方案那样拼命调模型提示词也不只是简单地失败了就重试而是把每次失败操作沉淀成一条可复用的技能。也就是说Agent 每犯一次错系统就多一种避开同类错误的能力。这篇文章我就把 EvoSkill-GUI 的思路、技术拆解和我的实操过程分享出来给正在搞 GUI Agent 落地的朋友一个可以直接参考的改进方向。1. GUI Agent 为什么总在点错这里翻车1.1 视觉理解与动作执行之间的缝隙GUI Agent 的基本工作流可以简化成观察-决策-执行三步先拿到当前界面状态截图、DOM 树或者无障碍树然后让模型决定做什么动作点击、输入、滚动最后通过执行器把动作真正落到系统上。听起来不复杂但这里面藏着一个巨大的鸿沟模型看到的和你电脑上跑的不是同一个世界。举个我踩过的真实例子。我在自动化操作一个 SaaS 后台时模型把导出报表按钮理解成了保存报表两个按钮相邻、颜色相近、文字都带报表二字。模型在决策层给出的理由写得特别漂亮但点下去之后导出的是空文件。这种问题靠视觉模型本身很难彻底解决因为按钮样式、文案、布局在不同产品里根本没有统一规律。就算你把训练数据堆得再大也总有长尾页面不在分布之内。另外还有一个很容易忽视的点GUI 是动态的。页面里的元素会随着网络请求、动画、焦点变化而移动位置。模型第一次看到某个按钮的时候它在左上角等模型生成完动作按钮可能已经因为列表刷新跑到了第三行。这种状态漂移造成的点错不是模型笨而是观察和动作之间的时间差导致的。掉进这个坑之后我开始意识到单纯优化单步决策的准确率天花板非常低。1.2 重试机制为什么治标不治本团队早期的做法很简单Agent 执行失败就重试。为此我们曾把重试逻辑分成三种动作级重试、任务级重试和模型回退重试。动作级重试是这次点击没生效换个坐标系再点一次任务级重试是把整个任务从头开始模型回退是换一个温度更低的采样参数重新规划。实测下来重试机制只能解决一部分随机性失误。比如因为网络抖动导致页面元素没渲染好重试确实有效。但当失败的原因是模型对界面状态产生系统性的错误理解时重试一百次也会在同一个位置摔倒。更麻烦的是重试会让日志爆炸每次失败都要保存一份完整轨迹排查的时候要从海量数据里翻出真正的失败根因效率非常低。后来我看了很多关于经验池技能库的论文和开源项目发现一个共通点成熟的 Agent 系统不应该每次都在裸奔状态下做决策它应该积累经验。人在操作电脑的时候第一次遇到某个对话框可能会犹豫但第二次、第三次会形成肌肉记忆。EvoSkill-GUI 借鉴的正是这个思路但它的关键创新在于经验不是人工喂给系统的而是系统通过分析自己的失败轨迹自动生成的。这让技能库可以随使用规模的扩大自动进化。2. EvoSkill-GUI 的核心思路把失败当成演化信号2.1 技能到底是什么怎么表示在展开技术细节之前我觉得有必要先把技能这个词定义清楚。EvoSkill-GUI 里的技能不是模型权重也不是一段单纯的提示词而是一条结构化的操作经验。它描述的是在什么界面状态下执行哪些动作序列能安全完成某个交互目标以及中间怎么校验自己有没有做对。常用的一种表示方式是一段 JSON包含触发条件、动作序列、校验规则和失败恢复策略。伪代码如下{ skill_id: login_form_fill, description: 在登录表单中快速填入用户名和密码, trigger_condition: 页面同时存在用户名输入框和密码输入框, action_sequence: [ { type: click, target: {by: placeholder, value: 用户名} }, { type: input, target: {by: placeholder, value: 用户名}, value: {user} }, { type: click, target: {by: placeholder, value: 密码} }, { type: input, target: {by: placeholder, value: 密码}, value: {password} } ], verify_rules: [ { check: login_button_enabled, expect: true } ], error_recovery: [ { on: element_not_found, action: refresh_and_retry, max_try: 2 } ] }用结构化数据而不是自然语言提示词来表达技能有三个直接好处。第一是可检验你可以在入库前让程序自动尝试执行这条技能验证它是不是真的有效第二是可检索技能库里几千条技能可以通过条件匹配、向量检索快速找到当前最合适的那条第三是可演化技能不是写死之后就永远不变的它可以随着新的失败案例而合并、调整、淘汰。有人可能会问为什么不直接把失败案例和成功案例拼接进 prompt 里这样更简单。这种思路在任务量少的时候确实有效但当经验库变大之后prompt 会迅速膨胀到失真模型根本聚焦不了关键信息。而且每次都要把长上下文送到模型里成本、延迟都会成为严重瓶颈。技能结构化的价值就在于它是一种可以独立于模型、独立于任务缓存的高密度抽象。2.2 完整链路从失败轨迹到可复用技能EvoSkill-GUI 的完整体系可以拆成四个关键环节失败捕获、失败归因、技能提取、技能校验与入库。实际跑起来它看起来像一条失败→技能的流水线。失败捕获发生在执行器层。每当 Agent 的一个动作执行完后系统会检查结果状态是否与预期一致。这个检查不能只看动作是否抛出异常还要看界面状态是否真的发生了预期变化。比如点击保存之后弹出了错误提示但 Agent 没有去读这个提示这种情况表面上是动作成功实际上是任务失败。所以捕获阶段必须同时监听动作异常、断言失败、任务停滞三类信号并把完整轨迹保存下来。失败归因是承上启下的关键一步。一条失败轨迹里通常包含几十个动作到底哪一步错了我见过很多团队在这上面偷懒直接把整条轨迹丢给 LLM 去总结为什么会失败。这样提取出来的技能往往过拟合因为 LLM 会把不相干的动作也打包进去。我的做法是先做轨迹切分按照界面状态是否发生实质变化把轨迹切成多个片段只拿失败点前后的小邻域出来做归因分析。这样定位到的错误动作往往非常集中提取出的技能也更干净。技能提取则进入核心环节我们用 LLM 对比失败轨迹片段和修正后的成功轨迹片段两者只有少数差异动作让模型聚焦分析差异点总结成可复用的触发条件与操作序列。为了让模型不自由发挥我会预先给它一段技能模板就是上面 JSON 那种结构并且约束它只能提取当前问题域内明确可复现的动作模式不能写含糊的、印象流的操作建议。校验与入库阶段会自动执行一次技能提取结果如果执行通过且达到预设置信度才把技能写入库中并记录技能来源、提取时间、成功次数、失败次数。2.3 和传统 ReAct 提示词方案的本质区别现在很多 GUI Agent 都是基于 ReAct 模式做的把用户目标、历史轨迹、当前界面状态拼成一段提示词让模型在思考-行动-观察的循环里反复推进。这种模式灵活、开放、适应面广但它有一个隐蔽的弱点每次决策都要从零开始思考。模型在每轮循环里都会重新分析界面重新决定动作即使这个界面它一百次前就已经见过也还是会用同样的试错方式去处理。EvoSkill-GUI 不是在 ReAct 之外另起炉灶而是和 ReAct 协作。一个典型的运行时循环是这样def agent_step(obs, task_context, skill_library): # 1. 先检索技能库看当前状态有没有匹配的技能 candidate_skills skill_library.retrieve( obsobs, task_hinttask_context.description, top_k3 ) for skill in candidate_skills: if skill.trigger_condition(obs): result skill.execute(obs) if result.success: return result # 技能执行失败也要留痕迹后续用于技能淘汰或更新 # 2. 没技能匹配或者技能执行失败走模型自由推理 action model.plan(obs, task_context) return action这个循环里最重要的一点是技能拥有优先执行权。只要技能库里有可信度足够高的匹配项Agent 就不再依赖模型重建决策而是直接执行技能。只有当没有匹配技能时模型才做自由推理。这样既保留了 ReAct 对开放式任务的适应能力又在重复出现的交互模式上获得像脚本一样的稳定性。有一说一EvoSkill-GUI 也不是万能的。它适合的是那些有明确重复操作模式的 GUI 任务比如表单填写、报表导出、批量设置、多系统数据同步。如果任务是纯创作型的、每一步都是新路线的技能库能发挥的作用就相当有限。理解这个边界看下面的实操部分才会更有价值。3. 实操落地把现有 Agent 改造成会积累技能3.1 失败采集与归因先搞清楚错在哪一层我在自己的开源级项目上做了 EvoSkill-GUI 的落地实验把一套原本基于 ReAct 的浏览器自动化 Agent 改造成了支持技能积累的版本。改造的第一步不是写技能库而是先把失败采集做全。这步做不好后面全是空中楼阁。我在执行器层增加了三套监听器。第一套是 DOM 状态监听在每个动作执行前后对比界面树的关键特征按钮存在性、元素可见性、文本变化一旦发现动作后状态和预期不一致就标记一次动作级失败。第二套是 HTML 语义异常捕获比如页面出现 Error 类组件、请求返回 4xx/5xx 状态码、或者控制台出现未捕获错误说明操作已经把系统带入了异常分支。第三套是任务级超时检查如果 Agent 在某个步骤停留超过 3 轮且状态无实质变化就判定任务停滞并触发失败记录。采集到的轨迹会自动存成一个统一 schema 的日志项包含任务描述、操作序列、每步的意图、页面哈希、失败点、错误类型、上下文截图路径。之后归因模块对日志做切分取失败点前后各三个动作作为归因窗口交给一个专门的小模型做根因分析。输出格式是固定的 JSON失败原因元素定位失败/动作预期偏差/状态漂移/模型误判、置信度、修正动作序列建议。我实验下来固定输出格式对后续技能提取的帮助极大因为技能模板可以直接吃到稳定的归因结果。3.2 技能提取流水线用 LLM 自动总结可复用片段归因结果出来之后下一步是把失败修正经验转成专业技能。这项任务我先用 GPT-4o 做了一版效果不错后来换到本地部署的 Qwen2.5-72B 也能跑只是提取质量稍微逊色一点。整体流程是两阶段模型调用。第一阶段是差异对比提示。我把归因窗口内的失败轨迹片段、修正后的成功轨迹片段、界面状态摘要一起发给模型明确要求它只分析这两个片段的差异输出导致成功与失败的关键动作模式不要输出泛泛而谈的原则。这一步非常关键如果不给模型对比素材它很容易联想到自己的训练知识写出看似合理但没有任何实操价值的操作建议。第二阶段是技能结构化。我会把第一阶段的结果和技能 JSON 模板一起丢给模型要求它把动作模式转成包含 trigger_condition、action_sequence、verify_rules、error_recovery 的标准技能定义。同时设置严格约束每个动作 target 必须指明选择器类型by placeholder、by text、by css_selector 等禁止使用点击相关按钮这种含糊描述。模型输出后会有一个自动校验脚本检查 JSON 完整性、动作合法性、字段覆盖率。校验不通过的直接打回重新提取。这里多提一句技能提取虽然用了大模型但它和写一段常规 prompt 完全不同。你需要在提取环节尽可能缩小模型的自由度把每一步的输入输出都固定死用 schema 约束它用预校验堵住它自由发挥的空间。原因很简单技能是要被自动执行的东西一点点含糊都可能引发线上事故。宁可提取失败率高一点也不能产出不可控的动作序列。3.3 运行时技能注入让 Agent 在动手前先查经验库技能入库之后接下来就是改造运行时主循环。我在 Agent 的每一步决策之前插入了一个技能检索模块这个模块我不只是做向量相似度检索还会结合当前界面 DOM 的哈希特征做精确匹配。实操中我用的是双通道检索如果任务类型高度明确比如 login、upload、export就用规则通道直接锁定对应技能如果是开放任务就用 embedding 计算界面摘要和技能描述的相关度取 Top-3 做匹配与排序。拿到候选技能后还有一个动作是很多人容易忽略的判断当前界面是否真正满足技能的 trigger_condition。我见过很多技能库实现只靠描述相似度就选择技能结果技能动作序列在当前界面上根本找不到目标元素。所以我在执行技能前会先做一次前置条件验证检查必要元素在不在 DOM 树里。如果元素存在、位置稳定才放心执行。如果验证失败就立即回退到模型自由推理并把这次技能前置条件不满足记录成一次负样本累加到技能的质量分里。通过这种方式我把原来每次都要经过模型推理的大量重复流程登录、上传文件、切换组织、导出报表、翻页抓数据都替换成了技能执行。系统在运行过程中技能命中率从第一天的 62% 涨到第五天的 83%动作级成功率从原有的约 68% 提升到 91%。这个提升非常可观代价是每天要花少量离线算力做技能提取与校验但整体推理开销反而大幅下降因为命中技能时基本不需要跑大模型。4. 常见问题与排查技巧实录4.1 技能过拟合一条失败只教会了死记硬背我在实验中最常遇到的问题是技能提取过拟合。具体表现是一条技能在它诞生的那条轨迹上执行很完美但换一个相似但略有差异的页面就完全失效。比如某条技能学会了在某后台的用户管理页面点击批量导入按钮但到了另一个用户的用户管理页面按钮位置从左边挪到了右边技能就找不到元素了。排查过拟合我发现问题往往出在提取阶段模型把非常具体的坐标位置、固定文案写进了技能动作序列。这就导致技能只对单一页面有效。解决方法是两层。一是在提取提示里明确要求使用语义化选择器例如使用 placeholder、文本标签、CSS 类名这类语义特征而不是 xpath 绝对路径。二是在入库校验时引入多环境验证不只在新产生的轨迹上验证还把这个技能放到历史成功轨迹中随机选取的 3-5 个相似界面上跑一遍统计通过率。通过率低于阈值就直接砍掉不浪费时间。另外还发现一个反直觉的现象模型有时候会把失败原因写进 trigger_condition生成类似于当页面出现红色提示时不要点击确认这种负向技能。在本系统设计里技能只应该描述正向操作负向提醒更适合放进系统的禁用规则列表混在一起会让检索逻辑非常混乱。我最终的做法是在技能生成后加一道过滤器凡是 action_sequence 里出现 not、avoid、never 等否定词的候选技能自动降级为低置信度需要重新提取或者人工审核。4.2 技能冲突与技能膨胀随着运行时间变长技能库会越来越大随之而来的是两个新问题技能冲突和技能膨胀。技能冲突指的是两条技能对当前界面状态都有较高匹配分但动作序列完全不同。有一次我同时跑出了刷新页面才能点击导出和直接点击导出两条冲突技能检索模块随机选了一条导致结果时好时坏。我的处理办法是给每条技能增加一个领域范围字段用树状结构管理。领域范围包括产品比如 CRM、ERP、任务类型表单填写、数据导出、按钮点击、界面特征页面标题、URL 前缀、组件类型。检索时不光做向量匹配还要求技能领域范围与当前上下文一致从源头减少跨领域误匹配。领域范围设计得细一点比如CRM 后台-数据导出-列表页等于一层天然的防火墙。技能膨胀则需要靠定期淘汰机制来解决。我给每条技能维护质量分初始值是入库校验通过率后续每次执行成功 0.1、失败 -0.5。每周跑一次离线任务把质量分低于 0.3、或者连续 14 天未命中的技能移入待审核区并做一次相似技能聚合。聚合逻辑非常简单把技能动作序列映射成动作类型序列click-input-click然后做字符串相似度比较相似度超过 0.9 的两条技能会合并成一条动作序列选择成功次数更高的那版。这个机制让技能库在运行一个月后总条目数只增长了 2.4 倍但有效命中率继续往上走没有因为膨胀而明显拉低检索质量。4.3 性能开销与冷启动问题引入技能检索和技能执行之后最直接的影响是每一步决策的耗时增加了。技能检索本身很快向量检索在几百条技能里也就几毫秒。但有一段时间我发现个案里每一步延迟飙升到了 800 毫秒以上定位后发现是执行器每步都在做 DOM 哈希重算和全量相似度计算同时序列化日志过深。优化方式是给界面状态哈希加了一层缓存只有 DOM 树出现实质变化时才重新计算这样界面无变化时开销几乎为零。冷启动是 EvoSkill-GUI 另一个绕不开的问题。技能库为空的前两天系统整体提升几乎看不出来甚至因为多了一步检索逻辑单步延迟还略微增加。如果你打算在生产环境落地我的建议是从高频重复任务先下手把最容易成功率的 3-5 个流程手动做成种子技能放进去让系统先开张。之后自动提取的技能才有地基可依否则冷启动期的体验容易被业务方吐槽改慢了。这里尤其提醒一下技能库不是越大越好初始阶段优先追求精准而不是数量。我见过有人一夜之间批量生成两千条技能结果大部分都过拟合、冲突严重检索耗时上去了成功率反而降了。我更推荐渐进式做法每天自动提取上限设 50 条人工抽检 5-10 条持续观察一周再逐步放开数量。4.4 实测中积累的几个底层心得最后分享几条从底层踩坑换来的实操经验篇幅不长但很值钱。第一技能提取的提示词设计一定要和运行时的界面状态描述保持高度一致。我在项目里用了一套统一的界面摘要函数把 DOM 树里的元素摘要、可视区域、按钮状态统一格式化成文本。技能提取时让大模型基于这种摘要做分析技能执行时也是基于同一份摘要做前置条件验证。格式统一之后技能命中率和执行稳定性都明显上了一个台阶因为训练和推理的分布终于对齐了。第二技能执行一定要带退出机制。技能本质上是把一段自动操作序列固化了但真实界面千变万化技能执行到第五步突然遇到需要输入验证码的弹窗一整套技能就僵住了。我的方案是给技能执行器加一个最大步数和最大耗时限制超过限制强制中断把执行权交回模型同时把这次失败原因记录到技能质量分里。否则一个卡住的技能会把整个 Agent 任务拖死。第三不要把技能库做成单一全局库。我现在是按产品线、按场景分成多个技能空间每个空间独立演进。这样做的好处是不同产品的页面布局差异不会互相污染而且后续做权限、做实验对比都很方便。项目跑久了你会发现技能库本身就是一个需要运营的业务模块不是搭好就能一劳永逸的技术配套。5. 如果要落地我自己的路线图建议如果你也在做一个频繁操作 GUI 的 Agent 系统并且想引入类似 EvoSkill-GUI 的能力我的建议是先按这个顺序走先加失败日志和归因模块跑两周看数据再单独做一个技能入库模块把这期间手动修正的失败样例全部转成种子技能确认种子技能稳定了再接入自动提取与检索执行。千万不要一上来就搞全自动闭环技能提取、校验、淘汰任一个环节掉链子都会让系统越跑越乱。我在实际项目中感受到这个方案最大的价值不是单点提效而是给 Agent 系统装了一个长期记忆和自我进化的机制。以前做好一个 Agent上线之后效果只会随数据漂移慢慢变差现在每失败一次系统反而多了一份应对同类问题的经验从这个角度看失败真的变成了资产。如果你也在为 GUI Agent 的稳定性头疼不妨从采集第一条失败轨迹开始把又点错了变成下次不再错了。
