教程2026年11月5日5,531 浏览约 8 分钟阅读

AI Agent明明点对了,为什么还是无法完成任务?

AI Agent找到了正确按钮却无法完成任务?拆解视口异常、DOM事件、富文本提交和隐藏节点误判,提供浏览器自动化排障方案。

AI Agent明明点对了,为什么还是无法完成任务?

缘起:一次归因错误的复盘

让 Agent 操作浏览器,完成一组社区日常任务:浏览、点赞、收藏、评论、发布。任务失败时,第一反应几乎都是"模型不行"。把每一次失败摊开逐层看,结论会反过来:模型负责的那部分几乎都没出问题。出问题的是三层东西——环境约束、框架契约、状态确认。原文作者西安小哥(2026-11-04,阅读约 4 分钟)把这类失败拆成了四类卡点,下面按工程层次重新组织。

一、环境层:视口为 0×0,坐标系先崩了

最典型的失败发生在坐标系,而不是语义层。

浏览器实例的视口尺寸是 0×0。所有 getBoundingClientRect() 返回的坐标落在负半轴。点击工具把 rect 中心点当成真实鼠标坐标,于是直接判定"超出可视区域",动作被拦下。模型给出的指令是对的,元素也确实存在,失败纯粹发生在坐标换算这一层。

这类故障有个共同特征:报错信息看起来像业务问题,实际是环境问题。判定"超出可视区域"的逻辑本身没错,错在它拿到的是一个不可信的视口基准。

处理方式有两种。一种是修环境,把视口设成真实尺寸;一种是绕开坐标,不依赖 rect。前者更干净,后者在无法改环境的托管场景里更实用。

绕开的做法是放弃坐标点击,改为在目标元素上派发完整事件序列:pointerover、pointerdown、mousedown、pointerup、mouseup、click。带 pointer 前缀的用 PointerEvent,其余用 MouseEvent。

function fire(el, x, y) {
  ['pointerover','pointerdown','mousedown','pointerup','mouseup','click']
    .forEach(function(tp) {
      var E = tp.indexOf('pointer') === 0 ? PointerEvent : MouseEvent;
      el.dispatchEvent(new E(tp, {
        bubbles: true, cancelable: true, view: window,
        clientX: x, clientY: y
      }));
    });
}

为什么不能只发 click?因为在很多组件库里,只发 click 是静默无效的。内部状态切换挂在 pointerdown 上,缺了前置事件,组件的内部状态机没有进入按下态,后续 click 的语义就不成立。这类失败尤其难查:不报错、不抛异常、页面看起来毫无反应。

工程含义很清楚:Agent 的 action 空间不该只有"点击"这一个原子动作。它至少要能降级到事件派发,并且能读取环境约束——视口尺寸、滚动位置、元素可见性。缺了这层,模型再准也只是在错误坐标系里下正确的命令。

二、事件层:action 空间需要分级

把上面的问题抽象一层,它指向 action 空间的粒度设计。

单一动作模型(只有点击)在真实网页上必然碰壁。真实的交互有至少四个层级:

  • 坐标级:真实鼠标或触摸事件,依赖视口与布局。
  • 元素级:直接在目标元素派发事件,绕过坐标。
  • 输入级:键盘事件、InputEvent、组合键,用于表单与快捷键。
  • 数据级:直接写 DOM 或调用页面暴露的接口,绕过交互层。

这四级的可靠性与侵入性成反比。坐标级最贴近真人,最不稳定;数据级最稳定,但绕过了页面自身的校验逻辑,容易产生"写了但没生效"的假成功。合理的设计是按场景逐级降级,而不是固定用某一级。

如果 Agent 只实现了第一级,那么任何视口异常、任何浮层遮挡、任何组件库的事件顺序要求,都会变成一次"模型失败"。这不是模型的问题,是动作空间太窄。

三、契约层:框架的隐式约定查不到

第二类卡点更隐蔽,因为它不报错。

富文本评论框

社区富文本评论框是 contenteditable 的 div。写入流程看起来是三步:focus、设置内容、再派发 InputEvent(事件名 input)。做完这三步,框架才会把内容同步进自己的响应式模型,字数统计也跟着更新。

var el = document.querySelector('.rich-input');
el.focus();
el.textContent = 内容;
el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
// 提交需要真实键盘事件: Ctrl + Enter

到这里页面表现完全正常:文字进去了,字数统计变了,按钮也亮了。但点发送不发请求。原因是提交逻辑挂在键盘事件上,只认 Ctrl+Enter。视觉状态与提交逻辑是两套系统,前者由输入事件驱动,后者由键盘事件驱动。只满足前者,界面会给出"可以提交"的假象。

收藏的两步写入

收藏是另一种形式的不透明。点收藏按钮只打开收藏弹窗,真正的写入发生在第二步:勾选条目内的 checkbox-icon,再点确定,此时才发出 add_article 接口。

窄视口下还有第三层障碍:悬浮栏被媒体查询隐藏,必须先强制显形,按钮才能被取到。

这些行为在任何文档里都查不到,只能从 DOM 结构与网络请求反推。反推的成本很高,因为它要求 Agent 具备两种能力:读网络请求,读 class 变化。

缺的不是智能,是可观测性。能读到请求是否发出、class 是否切换,才能把"做了"和"成功了"区分开。只有前者,Agent 会不断重复执行已经生效的动作;只有后者,Agent 会在真正失败时误以为完成。

四、观测层:把隐藏模板读成风控

第三类卡点是最容易造成系统性误判的一类。

页面 innerText 里能读到「当前操作失败」,很容易判定为风控而中止。但实测那个 .risk-modal 常驻在 DOM 里,getBoundingClientRect() 的宽高都是 0,从未弹出。关键在于:innerText 与 textContent 都会包含隐藏节点。

var m = document.querySelector('.risk-modal');
var r = m.getBoundingClientRect();
// width 为 0 且 height 为 0: 模板残留, 并非真实拦截
var real = r.width > 0 || r.height > 0;

判断条件必须是可见性断言:宽度大于 0 才算真弹窗。

缺了这一层,会出现两种对称的失败。一种是过度保守:Agent 在完全正常的页面上反复误停,任务永远走不完,日志里全是"风控拦截"。另一种是过度激进:只凭文案判断就继续执行,真实拦截被吞掉,事后无法复现失败原因。

两种失败都源自同一个问题:把"文本存在"当成"状态成立"。DOM 里的字符串只能证明模板存在,不能证明它处于激活态。

五、边界层:延迟计数与能力外任务

第四类卡点不属于技术故障,属于边界认知。

社区学习类的浏览任务,单日连开多篇、每篇停留十几秒并滚动,任务页仍可能停在原计数。说明阅读事件上报存在额外限制——可能是服务端的频率控制,可能是去重逻辑,也可能是有最小间隔要求。

这类场景的正确处理不是重试。反复重试只会制造无效点击,还可能触发真正的风控。正确做法是记录"未计入",并在结算时标注差异原因。把不可控项显性化,比假装它能被解决更有价值。

移动端登录这类项则完全在浏览器 Agent 的能力边界之外。写进「必须手动」清单,比绕路实现更省时。边界清单不是失败记录,它是运行时的能力声明。

六、可观测性要靠入口统一

把这四类卡点串起来看,会发现一个共同需求:Agent 需要一份可信的执行记录。

网络请求是否发出、返回什么、耗时多少;class 是否切换、切换前后是什么;URL 是否变化;字数统计是否更新。这些信号分散在页面、请求层和框架内部,采集口径不一致时,同一件事会有三种互相矛盾的描述。

工程上的解法是把出站请求收敛到统一入口,让每一次工具调用与外部接口访问都留下同构记录:谁发起、带什么凭据、打到哪个上游、返回什么状态。这一步在 Agent 场景里尤其重要,因为 Agent 的"成功"往往由多个请求的时序共同定义,单条日志无法还原因果。

在托管或多上游的环境里,这类统一入口通常由访问层承担。像 koalaapi 这类 API 网关可以在不改变 Agent 业务代码的前提下,把出站调用集中记录、按上游限流、按项目隔离凭据,让"请求到底有没有发出去"这个问题有一个确定的答案。它解决的不是模型能力问题,而是可观测性缺口——恰恰是上面四类卡点反复指向的那一层。

七、三件事,按优先级排序

原文的结论是:模型能力决定 Agent 能不能想到下一步;能不能把日常任务跑完,取决于另外三件事。这三件事值得展开成可执行清单。

第一,action 空间的降级能力。 坐标点击之外,是否具备事件派发、键盘输入、DOM 直接写入。验收方式很直接:在一个视口为 0×0 的环境里,看 Agent 能否完成一次有效点击。做不到,说明动作空间只有一级。

第二,状态确认机制。 每个动作后是否跟一次断言。断言对象至少四类:字数或文本内容、class 变化、URL 变化、网络请求发出。断言的作用是让失败可稳定复现。没有断言的 Agent,失败是不可复现的,因而也是不可修复的。

第三,可观测性与真实性的区分。 读 DOM 必须带 rect 判断宽度是否大于 0。这条规则看起来很细,但它决定了 Agent 会不会被隐藏模板持续欺骗。假信号比没有信号更危险,因为它会让 Agent 在正确的方向上停下来。

结语

按这套流程重跑,成功率的提升来自工程改动,不来自换模型。

这个结论值得反复强调,因为它纠正了一种普遍的归因习惯。Agent 失败时,最容易的动作是换一个更强的模型,然后观察指标有没有变化。多数情况下不会有明显变化,因为瓶颈本来就不在推理层:视口是坏的,事件序列是缺的,提交逻辑挂在键盘上,风控弹窗是模板残留,计数有服务端限制。

模型负责"想到下一步"。工程负责"这一步真的落地了"。两者的边界划清之后,优化方向才不会持续投在错误的地方。

真正需要补的,是把页面当成一个有约束、有契约、有假信号的环境来对待,而不是把它当成一张可以随意点击的静态图。

标签AI Agent浏览器自动化Agent调试DOM操作PointerEventMouseEvent事件派发富文本编辑状态验证Agent可观测性自动化测试智能体可靠性
Koala API · 一站式大模型 API 中转

把博客读到的,落地到你的下一个项目

国内直连 · 兼容 OpenAI SDK · GPT / Claude / Gemini 等主流模型聚合

延伸阅读

免费注册