浏览器 agent 把“决策“从大模型里拆出去之后:协议调用从 1092 次降到 101 次
浏览器 agent 把"决策"从大模型里拆出去之后:协议调用从 1092 次降到 101 次
TL;DR 速览
- 事件:前 OpenAI 研究员 Diogo Almeida 创办的 TypeSafe 于 9-16 发布 Jev——一个不生成文本、只返回选项与概率的模型;browser-use 当天就用它做出
jev-ultrafast,Google Flights 全流程 7,073 毫秒- 核心变化:把"下一步点哪个元素"从大模型生成变成枚举后的选择。动作词表只有 8 个,模型输出永远不会变成选择器、坐标或可执行 JS
- 一组中位数对比(6 次交替运行、同模型同设置):任务耗时 9.450s → 7.092s,浏览器协议调用 1,092 → 101
- 一次网络往返出两个决策:操作(CLICK/TYPE_TEXT/…)与目标元素共享同一份观测状态,只有
TYPE_TEXT才去调一个小模型写文字- 控制回路里没有截图:Jev 消费的是结构化状态;官方明确写了这不是通用可靠性基准,且 shadow DOM、iframe、canvas、文件上传仍在 MVP 之外

9 月 16 日,TypeSafe 发布了 Jev。这家公司的创办者是前 OpenAI 研究员 Diogo Almeida,而 Jev 与主流模型的差别从接口上就能看出来:它不生成文本,只返回选项、分数和概率。 换句话说,它被设计成用来做选择的,不是用来写字的。
第二天,browser-use 就把 Jev 接进了浏览器自动化,项目叫 jev-ultrafast,MIT 协议。官方给出的成绩是:一个自然语言目标,在 Google Flights 上完成苏黎世到伦敦的往返搜索,7,073 毫秒,包含模型调用、文字生成、浏览器操作和加载等待。同一套策略跑 Wikipedia 开指定条目用了 2.798 秒,跑一个本地酒店搜索并筛选的任务用了 1.896 秒。这个仓库在创建一天多之后冲上了 GitHub 新项目榜前列。
真正值得拆的不是速度数字,而是它把什么从大模型里拿走了。
一、动作空间:把"点哪里"从生成题改成枚举题
通用浏览器 agent 的常规做法是:把页面元素清单(或者截图加 DOM 片段)塞进上下文,然后让模型输出一个动作——通常是选择器、坐标或者一小段可执行脚本。这条路有两个固定成本:提示词随页面规模膨胀,以及模型输出的自由度本身就是风险面。
jev-ultrafast 换了个方向。每一步观测都会重新生成一张带编号的元素表:
[1] button Change ticket type · Round trip
[2] combobox Where from? · San Francisco
[3] combobox Where to? · empty
[4] textbox Departure · empty
然后整套动作词表被限制在八个:CLICK、TYPE_TEXT、SELECT、SCROLL_UP、SCROLL_DOWN、WAIT、DONE、BLOCKED。并且只把当前页面实际支持的操作和目标提供给模型。
这一步改动看似只是接口设计,实际改变的是错误模型。生成题里,模型可以输出一个语法正确但根本不存在的选择器,也可以输出一段能跑但跑错地方的脚本;枚举题里,编号指向的元素是运行时真实持有的 DOM 节点引用,模型能选的东西都在清单里。文档对此的表述很直接:模型输出永远不会变成选择器、坐标、shell 命令或可执行 JavaScript;文字助手(text helper)的输出也必须能被解析成一个小的 JSON 对象,才允许输入到页面里。
二、一次网络往返,出两个决策
如果只把动作做成枚举,仍然要问两个问题:做什么、对哪个元素做。很多实现的处理方式是分两次请求,或者让模型在一次输出里自己组织格式。
jev-ultrafast 的做法是把两个问题放在同一次 TypeSafe 请求里:
one TypeSafe request
┌───────────────────────────┐
page → element table → operation │
│ click_target │
│ type_text_target │
│ select_target, if present │
└─────────────┬─────────────┘
use the matching target
│
CLICK [7] ─────┤──→ browser
TYPE_TEXT [3] ─────┘
↓
small LLM → text → browser
几个细节值得单独记下来,因为它们都是"省一次往返"的具体来源:
目标问题是推测性的。 如果操作选了 CLICK,那么只有 click_target 会被执行,其余目标分支的结果直接丢弃。
每个目标分支只装兼容元素。 点击分支里不会出现在文本框里才会用的元素,这是在请求内就完成的收窄。
下拉框的候选项携带观测到的索引。 原生下拉选项在快照阶段就带上了元素/选项索引,不需要模型去"猜"某个选项叫什么。
自由文本只在一种情况下生成。 只有当操作是 TYPE_TEXT 时,才调用一个小语言模型把文字写出来。控制回路里最大的一块——决定下一步——完全不经过语言模型。
三、稳定性的来源:快照、等待与校验
速度对比之外,这套实现里更值得抄的是几个防抖动的具体设计,因为浏览器自动化翻车多数不是模型不够聪明,而是状态读得不一致。
一次浏览器调用完成一次原子快照。 可见控件、它们的名称、当前值和文本在同一次调用里读完,保证这些字段描述的是同一时刻的页面状态,并保留对真实 DOM 节点的引用。分多次读的后果是字段之间互相矛盾——你拿到的是两个不同瞬间拼起来的页面。
执行前重新解算几何并拒绝被遮挡的控件。 选中的目标会重新校验页面新鲜度,并在输入前拒绝被覆盖的元素。动画本身不会强制重新预测一次。
等待策略是分级且有上限的。 往组合框输入后等待可见建议,上限 200 毫秒;其他交互最多等两个动画帧或 50 毫秒。而且这些读取发生在执行被记录之后——日志里记的是决定,不是等待。
保持隐藏标签页继续渲染。 通过焦点模拟避免后台标签的动画被节流,同时不切换 Chrome 的可见标签。
只把可见文本送进上下文。 离屏的正文和页脚不占用模型上下文,这和"整页 DOM 塞进去"的差别在长页面上非常明显。
中断的文字请求可以复用,但有严格条件。 只有当文字助手的整个输入完全未变时,一次已生成的文本才允许在页面过期重试后继续使用。
四、取舍账:官方自己划出的边界
这部分是你决定要不要抄的前提,我原样转述官方口径,不做修饰:
六次交替运行、相同模型与设置,两个版本都是 3/3 通过。 中位任务时间 9.450 秒降到 7.092 秒,降幅 25%;中位浏览器协议调用从 1,092 降到 101。但官方紧接着写了一句必须一起读的话:这是同一个任务、同一个浏览器 profile 上的三次重复,不是通用的可靠性基准。
DONE 也需要独立验证。 模型说"完成了"不等于真完成,示例里的航班搜索是由一个独立检查去核对单程设置、苏黎世、伦敦、日期和可见航班的。
DOM 读取器处理常见 HTML 与 ARIA 控件,而不是完整的可见名称规范。
MVP 之外的清单同样明确: shadow roots、frames、canvas、文件上传、弹窗标签页、嵌套滚动、任意键盘控件,目前都不支持。
依赖两把钥匙: TypeSafe 的 API key,以及文字模型的 key(示例配置走 OpenRouter,当前演示用的是 inception/mercury-2.5 并关闭了推理;Gemini、GLM、DeepSeek 也可以走 OpenAI 兼容的文字助手)。
这类"中位数对比"我最怕的就是过两周只剩一句"快了 25%",所以每次都会把运行次数、边界条件和官方原话一起存档。墨衍 里素材、选题和分发记录都收在一处,回头查得动,不用几个工具之间来回搬。
五、上手与改造路径
跑起来只有几步:
git clone https://github.com/browser-use/jev-ultrafast.git
cd jev-ultrafast
uv sync
cp .env.example .env
# 填入 TYPESAFE_API_KEY 与 TEXT_MODEL_API_KEY
uv run jev
打开 http://127.0.0.1:8766 点 Start demo → Run automatically。检查器会展示编号元素、操作概率、目标概率和已执行动作;Choose next 可以在执行前暂停,这一步对调试自己的站点很有用。Chrome 通过 Browser Harness 连接(uv sync 时装上),需要时用 uv run browser-harness --doctor 排查连接。
当库用也简单,策略与任务解耦:
from jev_ultrafast import Agent
with Agent(
"https://www.google.com/travel/flights?hl=en",
"Find one-way flights from Zurich to London on September 20, 2026, "
"for one adult in economy. Stop when matching flight options are visible.",
) as agent:
for state in agent.run():
print(state["elapsed_ms"], state["status"])
整个仓库小到可以读完,这也是它值得拆的原因——核心文件就六个:agent.py(完整循环与文字助手交接)、snapshot.js(原子 DOM 快照、索引化控件、新鲜度守卫)、browser.py(浏览器连接、当前几何、执行)、model.py(动态操作/目标分支与文本生成)、questions.py(模型指令)、demo.py(本地检查器)。
六、这件事真正的意义
把 jev-ultrafast 和前几天那批"用工程逻辑钉死能出错的环节、只把判断交给模型"的做法放在一起看,方向是一致的:不是让模型更强,而是让模型管的范围更小、接口更硬。
如果你正在写自己的浏览器自动化或者 agent 工具链,可以按这个顺序试一遍:
第一,先把你当前的动作空间列出来。 如果里面存在"选择器"“坐标”"脚本片段"这类自由形式的输出,那就是可以立刻收紧的地方。
第二,把每步的观测改造成一次原子快照。 这是所有后续优化的地基——观测不一致,再快的决策也是假的。
第三,盘点哪些步骤真的需要语言模型。 经验上,涉及"决定做哪件事"的部分大多可以降级成枚举选择,真正需要生成文字的地方比想象中少。
最后,给自己的对比数字加上边界说明。 官方那句中位数对比后面跟着"这是三次重复、不是通用基准",这比数字本身更值得学。
更多推荐


所有评论(0)