浏览器 Agent 的两种宿命:像素派和工具派

引子

「让 AI 操作浏览器」基本收敛成两条路线。

一条是像素派:截图喂给多模态模型,模型输出坐标,点下去。代表是各种 computer-use 方案。

另一条是工具派:把网站能力封装成带 schema 的确定性工具,模型只负责选工具、填参数。

我们做 WeWorker 时选了第二条。不是因为它更新,而是因为在真实场景里跑了一个月之后,像素派的三个问题没有一个能靠模型升级解决。

这篇文章讲清楚:为什么换路线、Adapter 到底是什么、以及我们为一个「写操作」的安全边界返工了三次。


一、像素派在真实站点上的三个硬伤

1.1 失败不可诊断

模型点偏了,你收到的是一个坐标和一句「已完成」。到底是选择器变了、弹窗挡了、还是登录态掉了?不知道。而工具派在同样场景下会返回结构化错误:

json 复制代码
{ "ok": false, "code": "NOT_LOGGED_IN", "adapter": "jd.com", "tool": "search" }

差别在于错误能不能被上层利用 。Agent 拿到 NOT_LOGGED_IN 可以提示用户登录后重试;拿到「点偏了」只能放弃或乱试。

1.2 正确率随改版线性衰减

DOM 结构和视觉布局都会变,但对工具派来说,只有它依赖的那几个语义节点变了才需要修;对像素派来说,任何布局调整都可能导致全站失效。中文电商站点的 A/B 实验频率,你们懂的。

1.3 成本结构不对

一次跨站任务,像素派要几十次截图+推理;工具派是一次工具调用 + 一次结果摘要。差的不是模型能力,是每步都要过模型这个结构本身。

维度 像素派 工具派
单步成本 一次多模态推理 一次函数调用
失败可诊断性 低(只有坐标) 高(结构化 error code)
改版耐受度 低 中(只修相关工具)
首次接入成本 低(通用) 高(每站要写工具)
写操作可控性 差 好(schema + 权限)

工具派的代价很明确:首次接入成本高。所以真正的工程问题不是「选哪条路」,而是「怎么把每站接入成本压下来」------这是后面第三节的内容。


二、Adapter 是什么:一份契约,两种执行

我们没把 Adapter 做成一个类,而是做成一份契约 + 一个注册中心。上层只认契约。

2.1 调用入口只有一个

ini 复制代码
webcli call <adapter-id>.<tool-id> --key=value

工具名是完整名,按完整名精确解析,不按域名或第一个点拆分 。这条规则看着啰嗦,但救过我们:早期按 . 切分,mail.qq.com.search 会被切成三段,域名本身带点就完了。

重复注册同一个完整名 → 直接失败关闭,不允许后注册的静默覆盖前一个。工具表被静默污染是 Agent 最难受的故障,因为模型会「合理地」用错工具,而且看起来一切正常。

2.2 两种执行后端,一个统一入口

text 复制代码
webcli call xxx.yyy
  → WebCLI Adapter Registry
     ├─ adapter_type=extension
     │    → Bridge → Chrome 扩展(MV3)→ 当前标签页
     └─ adapter_type=toolkit
          → Toolkit Runner → 内置 Runtime → toolkit.py → Playwright / CDP

为什么要有两种?

  • 有些能力必须在用户已登录的真实页面里做(邮箱、后台、需要人机验证的流程)→ extension
  • 有些能力接口语义稳定、纯 API 化更可靠 → toolkit
  • 硬要用一种覆盖全部,结果就是「为了统一而牺牲可靠性」

契约统一在发现、描述、调用三件事上;执行实现故意不统一。这是我们的第一版返工:一开始想用扩展接管一切,结果 Playwright 场景被 MV3 的生命周期折磨得不行。

2.3 Toolkit 的产物是文件,不是内存对象

一个 toolkit adapter 交付五个文件:

text 复制代码
<adapter-id>/
├── manifest.json
├── SKILL.md
└── scripts/
    ├── toolkit.py
    ├── toolkit_runtime.py
    └── ...

好处是它可以被版本管理、被 diff、被人 review。Agent 生成的工具草稿也得先落成这些文件,才能走验证和安装流程。工具不能只活在内存里,否则出了事没人能查。


三、把接入成本压下来:网站探索 agent

工具派最大的痛点是「每站都要人写」。我们的做法是让 Agent 自己去探索。

流程大致是:

text 复制代码
未知站点 URL
  → siteGate 判断是否已有确定性工具
  → 没有则进入探索:读页面结构、抓关键请求、归纳接口
  → 生成只读工具草稿 → 本地验证 → 安装到 Adapter 包
  → 验证通过的才允许出现在工具表里

两个关键约束:

  1. 默认先做只读工具。 探索阶段生成写工具的风险太高,先让它把「看」的能力做对。
  2. 验证不过不许上架。 草稿必须实际跑通一次并校验响应结构,否则宁可返回「这个站点暂不支持」。

必须承认的边界:动态渲染重、反自动化强的站点,探索成功率明显低于语义清晰的站点。这也是为什么我们保留了「人工写 Adapter」这条路------自动化的是 80%,剩下 20% 交给愿意贡献的人,这恰好也是开源在这里的价值。


四、写操作的安全边界(返工三次的部分)

这是我认为整个项目里最值得写的一段,因为在 Agent 场景下,安全边界的设计错误会被模型主动利用,而不是被人被动触发。

4.1 第一版:靠 prompt 约束

系统提示词里写「涉及支付的操作要先问用户」。上线三天就被绕过:用户说「帮我把这个买了,不用再问我」,模型很听话地不问了。

结论:自然语言约束不是边界,是可协商的建议。

4.2 第二版:调用参数带 __confirmed

写工具要求调用方传 __confirmed: true。看起来是硬约束了,但实际更糟------这个参数是模型自己填的。模型完全有能力在「用户说不用问我」之后自己填上。

结论:把安全决策交给调用方,等于没有安全决策。

4.3 第三版:策略属于包,不属于调用

最终方案是把确认策略写进 manifest:

json 复制代码
{
  "safety": "write",
  "requires_confirmation": true
}
  • requires_confirmation 对写命令默认 true
  • 策略参与 manifest 的内容摘要校验,改策略等于改包
  • 要把某个写命令改成免确认,需要走一次包权限变更审查(unconfirmed-write:<command-id>),不是改一行配置就生效
  • 调用时即便传了 __confirmed: true,也只用于满足旧版兼容守卫,不会跳过业务侧审批、登录校验、dry-run 开关、参数必填

一句话总结原则:谁能改变「这个动作要不要确认」,谁就必须是维护者,不能是运行时。


五、路由:宁可漏判,不可误判

用户一句话,如果能被确定性规则命中(正则、模板、工具 scenario),就不进大模型,直接调工具、直接渲染结果,llmMs = 0。省一次往返,体感差别很大。

但我们给这层设了一条硬红线:

只要不自信,一律 return false 落回模型。

因为代价不对称:

  • 漏判 → 多花一次模型调用,用户感知是「慢了一点」
  • 误判 → 用一个错误的工具结果短路掉正确答案,用户感知是「它骗我」

一旦用户觉得「它骗我」,这个产品就没了。所以我们宁可在配置里写一堆保守的规则,也不追求把命中率刷高。


六、如果你也在做类似的东西,三条经验

  1. 先定契约,再定实现。 统一入口 > 统一实现。强行统一执行路径,最后一定会为某个场景写一堆例外。
  2. 安全策略放包里,别放参数里。 任何「调用方自己声明已确认」的设计,在有模型的系统里都等于没有设计。
  3. 错误的可诊断性比成功率更重要。 成功率是演示用的,错误码是用户用一周之后还愿不愿意用下去的原因。

关于项目

这套东西做成了一个开源项目 WeWorker:Electron 桌面端 + Chrome 扩展 + TypeScript 后端,内置 33 个 Adapter(中文站点为主:邮箱、京东、小红书、B站、知乎、微博、12306、BOSS 直聘等),Apache 2.0。

仓库:github.com/WeWorker/We... 架构文档:docs/webcli_toolkit_adapter_design.md、docs/intelligent_routing_architecture.md

如果你常用某个站点还没被支持,开 issue 说站点和使用场景就行,Adapter 是按需求排优先级的。

相关推荐
GitHub实测笔记16 分钟前
hyperfine 本机实测:2.96MB 上 bat 开高亮中位数 2.39s、cat 3.0ms(约 870 倍);v2 默认不走 shell
github
徐小夕3 小时前
万字长文!DeepSeek Super-agent × 企业知识库架构深度拆解与落地实践
前端·vue.js·github
奶油话梅糖3 小时前
打开项目 0.9 秒、内存只有 IDEA 的 1/12:OpenIDEA 用 Rust 把 IntelliJ IDEA 重造了一遍
java·rust·github·intellij-idea
miofly5 小时前
GitHub 日榜趋势速报 | 2026-11-06
开源·github
穆梓兰煊17 小时前
Vue生态下AI生成UI:千万级向量场景落地实践
github
dong_junshuai20 小时前
每天一个开源项目#152 Auto Ontology:让业务口径进入SQL生成链
github
一条Go21 小时前
两套权限目录,三重闸门:Alioth 的多租户 RBAC 为什么这么拧
后端·程序员·github
imythu21 小时前
让 Claude Code 在家跑,我在外面用手机盯着:做一个开源工具踩过的几个坑
人工智能·github
艾迪的技术之路1 天前
16k 条慢查询,128 张分表,我这么解决
后端·github