本文是「Agent 工程实战」系列的第 09 篇,属于界面线。
系列索引(21 篇阅读路线):Agent 放权前,先补权限、出站、发现这三道口子
摘要:OpenAI 发布 Agents API 后,Agent 会话越来越长,你的后台界面正在被当成"生产环境"。但后台仍是按人眼设计的,Agent 靠可访问性树或截图来"看"页面。本文从感知、操作、反馈三层讲透后台界面如何适配 Agent:感知层把 div 换成真 button、补上 label,让 Agent 看得准;操作层放大命中区到 44px、拉开间距,让 Agent 点得准;反馈层用 aria-busy、loading 和 toast 让状态可见,Agent 才知道做没做成;高频写操作再开一条 MCP/HTTP 结构化通道兜底。文末附可直接照抄的检查清单,三分钟自检你的后台页面。
文章目录
- 从 OpenAI Agents API 说起
- 同一张页面,两套读者
- 第一层:感知层,让 Agent 看得准
- 第二层:操作层,让 Agent 点得准
- 第三层:反馈层,让 Agent 知道做完没
- 兜底:给 Agent 一条"后门"
- 这套适配到底值不值
从 OpenAI Agents API 说起
9月10号 OpenAI 正式发布了 Agents API,公开测试,一次 API 调用就能建一个生产级的 agent,跑的还是 Codex 那套 harness。帖子在 Hacker News 拿了两百多分,一路冲到首页前排,评论区吵成一团。
大家聊的都是"Agent 能干什么"。多子任务并行、程序化工具调用、还支持 MCP——看着确实猛。我看到的倒是另一件事。
官方博客反复强调的能力里,有两件事指向同一句潜台词:Agent 会话会越来越长,要在你的系统里连轴跑好几小时甚至跨几天。一条会话跑小半天,意味着它中间要访问你公司的后台、点击你系统的页面、处理真实业务数据。做 Agent 的人,已经把你家的内部系统当成"生产环境"了。
但问题是——那个后台,还是按给人眼看设计的。上一篇写完"AI 都能自己点界面了,前端反而机会大了",有读者问:机会到底在哪?这篇就把一件事讲透:后台界面凭什么能被 Agent 用,以及改起来到底改什么。
同一张页面,两套读者
先说清楚 Agent 是怎么"看"界面的。市面上做界面操作的 agent 分成两大流派,感知和操作各两套:
- 感知层:一类靠截图喂给视觉模型(你让它看什么它就看什么),一类靠浏览器渲染出的可访问性树(Accessibility Tree)拿语义化文本
- 操作层:一类是像素坐标点击,一类是拿元素定位去触发
注意,这两条轴是交叉的——截图派多数配坐标点,语义树派多数配元素定位,但组合不是死的。具体用哪套,取决于 Agent 的产品怎么实现。
对做界面的人来说,这就够了:你没法挑 Agent 用哪种方案,但只要界面把两套都伺候好,来什么 Agent 都能用。给你看同一张订单页,两种"读者"各自看到什么:
人眼版本 可访问性树版本(Agent 视野) ┌────────────────────┐ ┌──────────────────────────────┐ │ 订单后台 │ │ heading "订单后台" │ │ [搜索] [导出] [刷新] │ │ button "搜索" │ │ ┌────────────────┐ │ │ button "导出" │ │ │ 已支付 1000.00 │ │ │ button "刷新" │ │ │ 待支付 250.00 │ │ │ row "已支付 1000.00" │ │ └────────────────┘ │ │ row "待支付 250.00" │ │ 颜色区分状态、布局自然 │ │ role + name + state │ └────────────────────┘ └──────────────────────────────┘人的眼睛扫的是颜色、位置、留白;Agent 拿到的是一棵全是 role、name、state 的树,或者是一张像素图。这篇文章讲的适配,本质就一句话:让第二列"列得清楚",同时不拆第一列。
第一层:感知层,让 Agent 看得准
这层改的是"Agent 眼里这张页面长什么样"。对语义树派来说,它读到的就是树上那些 role 和 label。树列得对不对,直接决定它能不能找到按钮。下面示例都可从 Chrome DevTools 的 Accessibility 面板直接验证(浏览器自带的,不用另装东西)。我先说我见过的典型翻车写法,后台里特别常见这种"假按钮":
<!-- 运行时:div 冒充 button,看着是按钮 --><divclass="submit-btn"onclick="submitOrder()">提交订单</div>CSS 一伪装,人眼完全看不出来。但在可访问性树里,它不是一个按钮,身份是 generic:
可访问性树(devtools → Elements → Accessibility 面板): <div class="submit-btn"> role: generic name: 提交订单 focusable: false ← 键盘和语义工具都拿它当"不可点的文本"Agent 拿到这棵树,能不能猜到"这里的提交订单可以点"?大概率能,靠的是猜——猜就有翻车的概率。第一类翻车往往不是 Agent 蠢,是你给它的树本身写得含糊。改法很朴素,用真正的语义:
<buttontype="submit">提交订单</button><!-- 如果样式上必须保留原有视觉效果,不冲突 -->可访问性树(改后): <button type="submit"> role: button ← 明确是按钮 name: 提交订单 focusable: true ← 可聚焦可激活一行改动,树里从"疑似可点"变成"确认可点"。再补一个常见的:输入框不绑定 label。
<!-- 坏味道:placeholder 当标签用 --><inputtype="text"placeholder="请输入订单号"/>可访问性树:无 name,Agent 只知道"有个输入框",不知道填什么 改:<label for="order-id">订单号</label> <input id="order-id" type="text" placeholder="请输入订单号" /> 树变成:textbox name:"订单号" ← 语义对上了边界说明:这层主要对语义树派的 Agent 有效。截图派根本不读这棵树,你标不标它都是看像素。反过来,截图派要的是对比度、字号够不够清楚。所以这两件事都得做,但别指望"改了语义截图派也受益"。
我之前接一个内部项目,当时觉得做无障碍是合规需求,拖到验收才补。现在回头看,那是一棵现成的"给 Agent 看的界面"分级地图——角色标得越干净的地方,Agent 越不会走错。
第二层:操作层,让 Agent 点得准
感知层解决"看得见",操作层解决"点得上"。坐标点击派的 Agent 有一个很实际的毛病:它按像素找目标。按钮太小、两个按钮挨太近,它就容易点偏——点错一个,后面全乱。
我们自己后台之前有个运营功能,一排小图标按钮(编辑、删除、导出),每个命中区域也就二十来像素见方。人肉用没问题,让 agent 连续操作的时候,编辑和删除之间经常点歪,我得盯着回放一路看。改法也不复杂,给可点击元素放大命中区域:
/* 原来:只有文字区域可点 */.toolbar .icon-btn{font-size:14px;}/* 改后:明面区域 + 内边距共同组成命中区 */.toolbar .icon-btn{font-size:14px;padding:10px;/* 额外命中空间 */margin-right:8px;/* 相邻按钮拉开间隔 */min-height:40px;display:inline-flex;align-items:center;justify-content:center;}效果(实测回放): 改前:跑一整轮十几步点选,平均每轮都偏一两次,多半歪到"删除" 改后:同样的一轮动作连着跑了大半天,没再点偏过按现在的无障碍最佳实践,可交互元素的目标尺寸建议做到 44px 见方这个量级,间距至少单边留一点空隙。不是给人用不方便,是给"手抖的 Agent"用方便。
边界说明:操作层这条只对坐标派有效,语义树派不考虑像素,它靠元素定位,这层的收益是零。同时这个尺寸只是"建议起点"不是"万能解",表格里的行内编辑这种高频操作值得做,一次性配置弹窗就不必为它折腾。
第三层:反馈层,让 Agent 知道做完没
这层是我认为最容易被忽略、出事也最隐蔽的。人在后台点"保存",点完哪怕只是按钮变灰再变回来,也会心里有数"成了"。Agent 的判断依据要苛刻得多:它的"成了"必须体现在可见状态上——视觉的,或者语义树里的。
举一个真实排查过的例子。我们有个流程,agent 步骤走到"点击保存按钮",然后紧接着"等待结果"。结果它一直卡在等待,超时之后把整轮操作算作失败重试,连着重试了好几轮,堆出一批脏数据。回放一看,点击明明成功了。
问题出在哪?保存按钮点击后不到半秒接口就返回了,但页面上没有任何状态变化——没有 loading,没有成功提示,按钮样式也没变。对 Agent 来说,"点了"和"没点"没有任何可观察的区别。它只能靠猜,猜一次两次可能蒙对,长任务里必翻车。给操作加反馈状态,视觉信号和语义信号一起上:
<buttonid="save-btn"type="submit"aria-busy="false">保存</button>asyncfunctionsaveAndTell(){constbtn=document.getElementById("save-btn");// 1. 立刻进入"处理中"状态,视觉和语义同步btn.setAttribute("aria-busy","true");btn.disabled=true;btn.textContent="保存中…";try{constres=awaitapi.save();// 2. 成功:状态从 busy 回到 ready,并给可感知的结果提示btn.removeAttribute("aria-busy");btn.disabled=false;btn.textContent="保存";showToast(`保存成功,单号${res.id}`);}catch(e){// 3. 失败:同样要显性化,别让它"无声失败"btn.removeAttribute("aria-busy");btn.disabled=false;btn.textContent="保存";showToast(`保存失败:${e.message}`);}}语义树状态流转(Agent 视角): 点击前: button "保存" busy=false, disabled=false 处理中: button "保存中…" busy=true, disabled=true 成功: button "保存" busy=false, disabled=false + live region "保存成功,单号 xxx" 失败: button "保存" busy=false, disabled=false + live region "保存失败:xxx"把"状态有没有变化"这件小事做成常态,是把接口失败的脏数据风险按回去的最便宜的方式。核心心法一句话:Agent 不需要你告诉它"我成功了",它需要你让它"看得到"成功。
边界说明:反馈层对所有 Agent 有效,但"显性反馈"不等于"把状态栏写成大字报"。异步任务用 live region 播报,滚动面板别全屏弹窗,别为了 Agent 把操作反馈做成打断式。
兜底:给 Agent 一条"后门"
三层都做了,还有一类场景绕不开:老系统没有 API、只有界面。你想让 Agent 在十多年前用 Java 写的老系统里导出报表,语义做得再漂亮,它也只能靠点击。这时候别硬改界面,给高频写操作单独开一条结构化通道——一个 MCP server,一个 HTTP 接口,把"导出报表"变成一个工具,而不是一段界面操作序列。
方案对比: 纯界面走通:Agent 打开页面 → 点筛选 → 点导出 → 等文件 → 下载 结构化通道:Agent 调工具 exportReport(筛选条件) → 拿结果 结构化通道的额外收益:可权限校验、可审计、出错可重放官方那篇博客里我注意到一个细节:Agents API 支持 MCP 工具,把"工具"跟"harness"分开编排。这暗示了方向——给 Agent 用,结构化通道比界面操作更适合做写路径;界面留给读路径和无法改造的场景。
边界说明:这条路只在"接口可控"时成立。第三方系统不愿意开接口、或者你只是想快速验证 Agent 能不能跑通业务流程,那就返回界面操作,别为造接口而造接口。我自己那阵子就干过一回——给一个只有界面的老后台补了个只读导出接口,Agent 从"十几步界面点选"变成"一次接口调用",回放里点偏造成的错文件再没出现过。成本是一次接口,收益是把操作链变成了可审计的调用链。
这套适配到底值不值
回头看这三层,其实都在回答同一个问题:怎么让 Agent 在你的系统里"用得动"。感知层把角色、标签写对,Agent 看得准;操作层给大目标、留间距,Agent 点得准;反馈层让状态可见、成功失败显性化,Agent 知道做没做成;高频写操作再给一条结构化通道兜底。改造成本都不算高,大多是"改成语义正确"级别的改动,人眼那一列基本不变。受益的是两套读者。
最后给一份可以直接照抄的检查清单,按优先级排好(★ 越高越优先做,性价比也越高):
| 层级 | 检查项 | 改法 | 成本 | 对哪类 Agent 有效 | 优先级 |
|---|---|---|---|---|---|
| 感知 | 按钮是真 button 不是 div | 换语义标签,别用 div+onclick 冒充 | 低 | 语义树派 | ★★★★★ |
| 感知 | 输入框都有 label / aria-label | placeholder 不顶用,补 label | 低 | 语义树派 | ★★★★ |
| 操作 | 高频按钮命中区 ≥44px | padding + 间距 | 低 | 坐标派 | ★★★★ |
| 操作 | 相邻操作按钮留间距 | margin 拉开 | 低 | 坐标派 | ★★★ |
| 反馈 | 保存/提交有 loading 态 | disabled + aria-busy + 文案 | 中 | 两派 | ★★★★★ |
| 反馈 | 成功/失败有可见结果 | toast / live region | 中 | 两派 | ★★★★ |
| 兜底 | 高频写操作有 API/MCP 通道 | 老系统单独开接口 | 高 | 两派 | ★★★(看场景) |
拿到这张表,你可以顺手做个三分钟自检:用 Chrome 打开自己最常用的那个后台页面,按 F12 进 Elements 下面的 Accessibility 面板,看看那些点起来挺好看的按钮在树里是不是真的 button;再找个会保存的表单,提交完留意按钮有没有 loading 和结果提示。三项里两项不过关的页面,就是这套适配最该先排期的对象。
我的判断如下:Agent 规模化是真的,界面被当成"生产环境"也是真的。前端在 Agent 时代的新活法之一,就是把这些细节做对——既让人的同事用着舒服,也让跑你系统的 Agent 别天天翻车。这件事的窗口期不会太长,等基建方把"语义化界面"变成硬性要求再动手,就晚了。
这里不展开聊 WAI-ARIA 的规范细节了,贴两篇官方资料,想深挖的可以自己看:
- OpenAI:Introducing the Agents API
- W3C:WAI-ARIA 1.2
「Agent 工程实战」系列
- 路线:界面线(computer use / Agent UI)
- 上一篇:08 库存被点没了
- 下一篇:10 A 公司数据记到 B 公司
- 系列索引(21 篇阅读路线):Agent 放权前,先补权限、出站、发现这三道口子
- 专栏:Agent 工程实战
- 如果这篇对你有用,欢迎收藏;想追更可以关注「Agent 工程实战」专栏。