性能解读:7.073 秒是怎么测出来的
上一课你亲眼看着 agent 用 7 秒搜完了航班。这一课把秒表拆开——7.073 秒不是广告词,是一次带完整测量边界的实录:官方不仅给了报告,还把原始测量 JSON 一起放进了仓库,里面连源码哈希和浏览器版本都带着。把 Jev Ultrafast 性能数据读明白,你不仅能复现它,还能照抄这套方法去测自己的 agent。
本课的数字主要出自你 clone 的 jev-ultrafast 仓库里三个文件:docs/performance.md(报告正文)、docs/flights-measurement.json(实录数据)、docs/full-speed-measurement.json(对比实验数据)。
学完你能做什么
- 说出 7.073 秒的计时口径:从首次预测起表、到接受 DONE 停表,表内五项、表外三项各是什么
- 拆开实录账单:17 次决策请求(中位 178 ms、合计 3,720 ms)、2 次文本生成、11 个动作,以及每个动作在第几秒执行
- 读懂 9.450 → 7.092 秒对比实验的设计,并解释官方为什么自己说「3 对太少,不是通用基准」
- 说清优化砍掉了什么:浏览器协议调用 1,092 → 101,其中 828 次 DOM.resolveNode 直接清零
- 用
examples/flights.py的 trace 对齐同一口径,测出你自己的秒数
你现在的困境
- 「官方说 7 秒,我跑出来 12 秒——这数字怕不是挑了最好的一次?」不知道口径,比了也是白比
- 想在汇报里引用这组 ultrafast 速度数字,被一句「怎么测的」追问就心虚
- 想给自己做的浏览器 agent 跑性能对比,但不知道要控制哪些变量:模型?网络?浏览器?想抄个作业都找不到范式
什么时候用这一招
当你需要:
- 转述或引用 Jev Ultrafast 的性能数字,并且经得起「怎么测出来的」一句追问
- 自己给 browser agent 做性能对比,需要一套现成的测量范式(口径、独立验证、样本量声明)照抄
- 判断这组数字适不适合你的场景:它是单任务、live 网络、小样本
而且不想:
- 把单次运行的秒数当成稳定指标到处引用
- 拿不同口径的数字互相比较(inspector 开截图 vs 脚本无截图;jev-1.13 vs jev-1.14)
🎒 开始前的准备
- 完成了 跑通 Google Flights:至少跑过一次
examples/flights.py,见过"passed": true的验证 JSON 和artifacts/flights/latest/ - 记得 Jev Ultrafast 原理的「一次往返双决策」——本课的 17 次请求,每一次都是这个结构
- (可选)把 jev-ultrafast 仓库开着,对着
docs/performance.md和两个测量 JSON 读,本课每个数字都能在里面找到 - 一杯水——本课以读为主,动手在「进阶技巧」
核心思路
官方对性能数字的态度,可以压成三句话:
- 每个数字都带口径——7.073 秒从哪一毫秒起表、到哪一毫秒停表、什么算表内什么算表外,文档写得一清二楚
- 对比要控制变量——新旧版本交替各跑 3 次,同任务、同模型、同预算、同独立验证
- 主动声明局限——样本量、live 网络、单任务,官方自己先说了,不等人来戳
本课要拆的四组官方数字:
| 数字 | 官方口径 | 出处 |
|---|---|---|
| 7.073 s | 首次预测 → 接受 DONE;含模型调用、文本生成、浏览器操作、stale 决策、加载 | docs/performance.md、flights-measurement.json |
| 9.450 → 7.092 s | 6 次交替运行取中位数,双方 3/3 通过独立验证 | docs/full-speed-measurement.json |
| 1,092 → 101 | 同一对比实验的浏览器协议调用中位数 | docs/full-speed-measurement.json |
| $0.00006272 | 仅两次文本 helper 的 OpenRouter 计费,不含 TypeSafe | docs/performance.md |
还有一件事先记住:这组数字全部出自同一个组合——决策模型 jev-1.13.0 + 文本模型 inception/mercury-2.5(走 OpenRouter,关闭推理)。测量 JSON 里 TYPESAFE_MODEL 是 null,也就是用代码默认的 jev-latest(jev_ultrafast/model.py 第 108 行),当时它解析到 1.13.0;如今 jev-latest 已指向 jev-1.14.0。你今天复现,模型就不一样,秒数自然有差异。
💡 为什么这一课值得细读
比记住 7.073 这个数更有用的,是「口径 + 账单 + 样本量声明」这套范式。你自己测任何 agent 的性能——不管是 Ultrafast 还是别的——都能照抄。
跟我做
第 1 步:对齐计时口径——官方秒数从哪一毫秒起表
为什么:比较任何两个秒数之前先对齐口径,否则全是无效比较。你自己跑出 9 秒还是 12 秒,第一步都是搞清楚官方的表是怎么走的。
官方口径,一张表说清:
| 项目 | 内容 |
|---|---|
| 起表 | 初始首页观察之后的第一次预测 |
| 停表 | 接受 DONE 的那一刻 |
| 表内 | 模型调用、文本生成、浏览器操作、stale 决策(作废重来的预测)、加载等待 |
| 表外 | 浏览器启动、初始导航、跑完后独立的 verify() 验证 |
这个口径在代码里对得上(jev_ultrafast/agent.py):
started_at在第一次predict时才设置——所以初始页面观察在表外- DONE 在
act里被接受时更新elapsed_ms——这就是停表点 - 预测过期(
StalePage)会丢弃决策、重新观察、重新预测——作废的时间留在表内
视频也是同一口径:1× 原速播放、无开场停顿、结尾只加 0.5 秒停顿;186 帧连续录屏加 1 张初始截图(JSON 里 source_frames: 187),只裁掉了顶部 64 px 的账号/导航条——没有掐头去尾的剪辑。
你应该看到:docs/performance.md 的计时段落与上表逐条对应——"begins at the first prediction after initial homepage observation and ends at the accepted DONE choice",表内表外一个词都不差。
- 能说出表内五项、表外三项
第 2 步:拆开 7.073 秒的账单——17 次请求都花在哪
为什么:官方把这次实录的每个数字都写进了 docs/flights-measurement.json,账单可以直接抄。总账:
| 指标 | 数值 |
|---|---|
| 总耗时 | 7,073 ms |
| 决策请求 | 17 次 |
| 决策延迟 | 中位 178 ms,合计 3,720 ms |
| 执行动作 | 11 个(10 次交互 + 1 次显式 WAIT) |
| 文本生成 | 2 次(Zurich 581 ms、London 346 ms) |
| token | 90,558 输入 + 6,325 输出 |
| 决策模型 | jev-1.13.0 |
时间去向(前两行是官方 JSON 的原始字段,第三行是本页推算:7073 − 3720 − 927):
| 去向 | 时间 | 占比 |
|---|---|---|
| 17 次决策请求 | 3,720 ms | ≈ 53% |
| 2 次文本生成 | 927 ms | ≈ 13% |
| 浏览器执行、观察、页面加载 | ≈ 2,426 ms | ≈ 34% |
17 次请求不等于 17 个动作:11 个动作加 1 次被接受的 DONE,一共 12 次预测「花在刀刃上」;其余 5 次在执行前被新鲜度检查拦下——页面指纹变了,基于旧快照的决策作废重来,这就是口径里的 stale decisions,它们的时间同样计入总秒数。(5 = 17 − 11 − 1,本页依据 agent.py「每次预测恰好消费一次、作废即重测」的循环逻辑推算,官方未单独公布 stale 次数。)
11 个动作在时间轴上的位置(executed_ms,出自实录 JSON 的 actions 数组):
| 时刻 | 动作 |
|---|---|
| 0.336 s | CLICK Change ticket type. Round trip |
| 0.640 s | CLICK One way |
| 1.595 s | TYPE_TEXT Where from? → Zurich(文本生成 581 ms) |
| 1.930 s | CLICK Zürich, Switzerland(选中自动补全建议) |
| 2.748 s | TYPE_TEXT Where to? → London(文本生成 346 ms) |
| 3.053 s | CLICK London, United Kingdom |
| 3.452 s | CLICK Open Departure |
| 3.769 s | CLICK Sunday, September 20, 2026 |
| 4.097 s | CLICK Done. Search for one-way flights… |
| 5.217 s | CLICK Search(官方原话:搜索在第 5.217 秒执行) |
| 5.587 s | WAIT 等页面更新 |
| 7.073 s | DONE 被接受,停表 |
前 5.2 秒在填表提交,后 1.9 秒在等结果并确认完成。WAIT 执行在 5.587 秒、DONE 接受在 7.073 秒——最后这约 1.49 秒,官方解释是结果页加载、状态变化和完成决策。两次文本生成各造成约 0.3–0.6 秒的「打字停顿」,这就是视频里输入框「想了一下才出字」的原因。
你应该看到:flights-measurement.json 顶层的 elapsed_ms、decision_requests、decision_median_ms、decision_total_ms 四个字段,和 actions 数组里 11 个条目的 executed_ms——上面的时间线就是从这个数组里抄的。
- 能说出 17 次请求里几次变成动作、几次是 stale、几次是 DONE
第 3 步:读懂 Jev Ultrafast 性能对比实验:9.450 → 7.092 秒
为什么:单次 7.073 秒只是实录;「快了 25%」这个结论出自另一组对照实验。它的设计决定了结论能说多大。
实验设计(performance.md「Matched runtime comparison」一节):
- 6 次运行、交替进行:基线一次、优化版一次,共 3 对;同一个任务、同一个现成 Chrome profile
- 两个 arm 用完全相同的:自然语言目标、独立结果检查器、1120×780 视口、jev-1.13.0、mercury-2.5(关推理)、动作与请求预算
- 初始导航两边都不计时;每次运行自己开自己关 tab;6 次全部计入,没有丢弃,无供应商或验证失败
- 基线 arm 冻结在 commit
68c077bf…;两边都用 Mercury,避免「换文本模型」和「改代码」两个变量混在一起
结果:
| 对 | 基线 | 优化版 | 验证 |
|---|---|---|---|
| 1 | 11.214 s | 6.964 s | 双方通过 |
| 2 | 8.984 s | 7.913 s | 双方通过 |
| 3 | 9.450 s | 7.092 s | 双方通过 |
| 中位 | 9.450 s | 7.092 s | 各 3/3 |
三个中位数变化:任务时间 −25.0%、TypeSafe 请求 22 → 17、浏览器协议调用 1,092 → 101。优化版在三对里都更快。
但官方紧接着自己给了三条免责声明:
- 3 对太少,双侧符号检验 p = 0.25,撑不起强统计结论
- 这是受控小样本对比,不是广泛的 agent benchmark
- Google、网络响应、路由、浏览器缓存都是活的,没有被控制
⚠️ 引用这组数字的正确姿势
「在同一任务、同一模型、同一浏览器的 3 对交替运行里,优化版每次都更快,中位数快 25%」——这是有依据的说法。「Jev Ultrafast 比某某 agent 快 25%」——没有依据,官方没做过跨 agent 对比。
你应该看到:performance.md 的对比表就是上面 3 对数字加一行加粗中位数;"p = 0.25" 和 "not a broad agent benchmark" 都在原文里。
- 能说出实验控制了哪些变量、没控制哪些
第 4 步:找到时间去向——1,092 → 101 次协议调用砍掉了什么
为什么:「快了 25%」的原因全在浏览器协议调用上。官方在 measurement JSON 里存了每次运行完整的 CDP(Chrome DevTools Protocol)调用明细,这是全课最硬的一组证据。
旧循环的三个负担(performance.md「Where the time went」):
- 每次 DOM 变化——包括动画——都作废决策,反复重新预测
- 反复读取整棵无障碍树(
Accessibility.getFullAXTree) - 解析数百个 DOM 节点(
DOM.resolveNode)
新循环的做法(performance.md「Where the time went」+ README):
- 一次浏览器调用读完常见 HTML/ARIA 控件——不再反复读树、逐个解析节点
- 点击守卫只比对目标、邻近上下文和文档/表单状态;几何与命中测试仍在输入前做
- combobox 事件等待:自动补全建议先到(README:上限 200 ms;其他交互至多两个动画帧或 50 ms)再问 Jev
- 原生文本替换显式发出浏览器的 select-all 命令
CDP 调用明细(三次运行逐个列出,出自 full-speed-measurement.json 每个 run 的 cdp 对象):
| 协议方法 | 基线(3 次) | 优化版(3 次) |
|---|---|---|
| DOM.resolveNode | 828 / 828 / 828 | 0(键不再出现) |
| Accessibility.getFullAXTree | 30 / 30 / 30 | 0 |
| Runtime.callFunctionOn | 40 / 40 / 40 | 0 |
| DOM.getBoxModel + DOM.getNodeForLocation | 10 + 10 / 10 + 10 / 10 + 10 | 0 |
| Runtime.evaluate | 158 / 146 / 148 | 75 / 77 / 71 |
| Input.dispatchMouseEvent / KeyEvent / insertText | 20 / 4 / 2(每次相同) | 20 / 4 / 2(不变) |
| 合计 | 1102 / 1090 / 1092(中位 1,092) | 101 / 103 / 97(中位 101) |
读表的两个洞察:
- 「看页面」的方式变了,「点页面」没变——鼠标、键盘、插入文本三类输入调用两边完全一致,砍掉的全是观察类调用
- 请求也少了(中位 22 → 17):决策不再被动画频繁作废,重新预测自然就少
💡 这就是「一次快照一次浏览器调用」的量化证据
原理课讲过 README 的设计原则「One browser call per snapshot」。落到数字上:Runtime.evaluate 从约 150 次降到约 75 次,DOM.resolveNode 从 828 次清零。架构说法和测量数据对上了。
你应该看到:full-speed-measurement.json 里基线三次 run 的 cdp 明细中 DOM.resolveNode 都是 828 次;优化版三次 run 的 cdp 明细里根本没有这个方法。
- 能说出砍掉的三类观察类调用是什么、为什么输入类调用不变
第 5 步:算清成本——token、文本 helper 与官方没报的价
为什么:性能解读不能只看秒数,还要看这一趟花了多少钱。官方报了一半,另一半可以自己推。
官方报的:
- 实录全部请求合计 90,558 输入 token + 6,325 输出 token
- OpenRouter 对两次文本调用计费 $0.00006272(Zurich $0.0000293 + London $0.0000334)
- 官方明确声明:这只是文本 helper 的费用,不是任务总成本——TypeSafe 响应里只有 token 数、没有美元账单,浏览器成本也不在内
官方没报、可以自己推的(本页推算,非官方数字):
按 TypeSafe 官方价目($42 / B 输入 token,输出免费),90,558 个输入 token ≈ $0.0038。推算过程:90558 × 42 ÷ 10⁹ ≈ 0.0038 美元。加上文本 helper 的 $0.00006272,这一趟 7 秒的任务,模型总成本大约 0.4 美分。
一个波动实例(都在 full-speed-measurement.json 里):同样是生成 London,实录那次 346 ms、94 个 completion token($0.0000334);对比实验第 2 对的优化版那次 1,137 ms、511 个 token($0.0000961)——单次文本调用延迟能差 3 倍多,这类波动全部会进总秒数。
你应该看到:performance.md 里 "$0.00006272" 那段写着「text-helper charge, not total task cost」;两次调用的单价在 flights-measurement.json 的 text_calls 里(2.93e-05 + 3.342e-05)。
- 能说清 $0.00006272 包含什么、不包含什么
检查点 ✅
- 能完整说出计时口径:起表点、停表点、表内五项、表外三项
- 能报出实录账单四个关键数:17 次请求、中位 178 ms、11 个动作、搜索在第 5.217 秒执行
- 能解释 9.450 → 7.092 秒的实验设计,并说出官方自己的三条免责声明
- 能说出协议调用 1,092 → 101 里被清零的三类调用
- 能说清 $0.00006272 只是文本 helper 费用、TypeSafe 账单要按价目另算
踩坑提醒
❌ 拿自己的秒数直接质疑官方数字
现象:你跑出 9 秒、12 秒,觉得 7.073 秒有水分。
原因:至少四个口径差异——① 模型不同:测量用的是 jev-1.13.0,今天 jev-latest 已指向 jev-1.14.0;② 网络是活的:官方优化版自己三次就分布在 6.964–7.913 秒;③ inspector 开着截图,本来就不是计时跑法(上一课讲过);④ 你的运行可能撞上更多 stale 决策。
正确做法:用 examples/flights.py(无截图)跑,读 artifacts/flights/latest/state.json 顶层的 elapsed_ms——这才是和官方同口径的数字;比较前先对齐模型、URL 和目标。
❌ 把「快 25%」说成跨 agent 结论
现象:「Jev Ultrafast 比某某浏览器 agent 快 25%。」
原因:官方对比的是自己新旧两个版本——基线是冻结在 commit 68c077bf… 的旧实现,不是任何别的 agent;而且 3 对样本 p = 0.25,官方自己都说是受控小样本。
正确做法:引用时带上限定语:「同一任务、同一模型的 3 对交替运行,新版比旧版中位快 25%」。
❌ 以为官方一次就调到了 7 秒
现象:看到 7.073 秒,以为性能是顺理成章调出来的。
原因:performance.md 保留了完整开发记录:旧版通过过一次 9.302 s;两个无障碍树方案 9.395 s、10.157 s;第一个直接 DOM 方案 8.697 s,但独立验证失败(名称/值提取不完整);修复后 8.051、8.631、8.395、8.385、7.741 s 一路爬,Mercury 诊断 7.559 s。更早还有 11.387 s 的录像和 12.898 s 的策略回归。
正确做法:把「8.697 s 但 verify 失败」当成提醒——秒数再好看,独立验证不过就不算过。官方敢把失败尝试全写进文档,恰恰说明可复现比好看重要。
❌ 拿 7 秒套所有网站
现象:「Flights 7 秒,那我那个复杂后台也该 7 秒。」
原因:官方只公布过三个任务的秒数:Flights 7.073 s、Wikipedia 打开哥德尔不完备定理 2.798 s、本地酒店任务 1.896 s——后两个官方明说是「冒烟检查,不是匹配对比」。DOM reader 只支持常见 HTML/ARIA 控件,shadow DOM、canvas、上传等不支持。
正确做法:拿自己的站点、自己的目标实测;先对照 Ultrafast 排错的不支持清单,把不适用的页面排掉。
进阶技巧(可选)
测你自己的口径。 上一课的 uv run --env-file .env python examples/flights.py --keep-open 跑完后,打开 artifacts/flights/latest/state.json:顶层 elapsed_ms 就是这一轮的官方口径秒数;history 里每个动作带 executed_ms、latency_ms、text_latency_ms,decisions 是每次预测,text_calls 是文本 helper。官方测量 JSON 就是把这些字段加工成的报表。跑三轮取中位数,比单次有说服力。
逐字段复核本课数字。 docs/flights-measurement.json(实录)和 docs/full-speed-measurement.json(对比实验)带全部原始数据:源码哈希、token 数、每次文本调用的费用;对比实验的 6 次运行还各自带浏览器版本(Chrome/152.0.7977.83)与完整 CDP 明细。想核对本文任何一个数字,去这两个文件。
注意测量后的代码变动。 performance.md 记录:计时和录像完成之后,原生 SELECT 的中断处理被收紧(结果不确定时停下,不再当作可重试的 stale 读取);Flights 不触发原生 SELECT,所以计时与录像哈希保持不变。你 clone 下的代码比测量时新一点,这是正常的。
选文本 helper 时看看官方怎么挑的。 官方拿两次真实航班字段做探针,试了 Gemini 2.5 Flash Lite、Gemini 3.1 Flash Lite 和 Mercury 2.5,全都返回了正确值;更早的探针否掉过一个把出发地/到达地写反的模型,还否掉过一个不输出合法 JSON、输出评论的模型。教训:换 TEXT_MODEL 别只看延迟,拿真实字段做正确性探针再上。
本课小结
✅ Jev Ultrafast 性能数字分三组:实录 7.073 秒(口径:首次预测 → 接受 DONE)、对比实验 9.450 → 7.092 秒(6 次交替、取中位数、双方 3/3)、协议调用 1,092 → 101(同一实验)。
✅ 实录账单:17 次决策请求(中位 178 ms、合计 3,720 ms,约占总时间 53%)、2 次文本生成(927 ms)、11 个动作、90,558 + 6,325 token;搜索在第 5.217 秒执行。
✅ 17 次请求 = 11 个动作 + 1 次被接受的 DONE + 5 次 stale(预测在执行前因页面变化作废,时间照算)。
✅ 变快的原因是「看页面」的方式变了:DOM.resolveNode 828 → 0、全量无障碍树 30 → 0、Runtime.evaluate 约 150 → 约 75;输入类调用两边不变。
✅ 引用时带限定语:单任务、3 对交替、live 网络、p = 0.25——官方自己把局限写在数字旁边,你也这么转述。
关键数字回顾:
| 指标 | 官方数字 | 口径 |
|---|---|---|
| 实录耗时 | 7.073 s | 首次预测 → 接受 DONE |
| 决策请求 / 中位延迟 | 17 次 / 178 ms | 实录 |
| 搜索执行时刻 | 5.217 s | 实录,11 个动作之一 |
| 对比中位耗时 | 9.450 → 7.092 s(−25.0%) | 3 对交替运行 |
| 协议调用中位数 | 1,092 → 101 | 同一对比实验 |
| 文本 helper 费用 | $0.00006272 | 仅 2 次文本调用(OpenRouter) |
| TypeSafe token | 90,558 输入 / 6,325 输出 | 实录全部请求 |
下一课预告
数字看完了,下一课 写自己的浏览器任务:Agent 库实战把 Ultrafast 变成你自己的工具。你会学到:
Agent(url, goals)的完整用法:goals 可以是字符串或列表,run()逐步拿到 state- state 里每个字段怎么用:history、decisions、text_calls、status、elapsed_ms——就是本课账单的数据来源
- 换一个 URL 和一句目标就是新任务,以及什么时候该配你自己的独立验证
- 文本模型怎么换:DeepSeek、GLM 等 OpenAI 兼容端点的配置方法
附录:源码参考
点击展开查看源码位置
更新时间:2026-11-04(仓库 github.com/browser-use/jev-ultrafast,本地信息源 sources/jev/repo-ultrafast/)
| 功能 | 文件路径 | 关键位置 |
|---|---|---|
| 性能报告正文 | docs/performance.md | 计时口径(Timing begins…);实录账单(17 请求 / 178 ms / 5.217 s / 7.073 s / 90,558+6,325 token / $0.00006272);对比实验表与 p=0.25;Where the time went;Other checks(2.798 s / 1.896 s);Development attempts;Limits |
| 实录测量数据 | docs/flights-measurement.json | elapsed_ms 7073;decision_requests 17;decision_median_ms 178;decision_total_ms 3720;actions 数组 11 条的 executed_ms 时间线;text_calls(581/346 ms 与 $2.93e-05/$3.342e-05);tokens 90558/6325;video(187 帧 / 1× / 0 开场停顿 / 500 ms 结尾停顿 / 裁 64 px);timing 字段即口径原文 |
| 对比实验数据 | docs/full-speed-measurement.json | summary(9450/7092、1092/101);runs 6 条(每对的 elapsed_ms、decision_requests、cdp 明细、源码哈希、tokens、text_calls);scope(3 对、1120×780、live 未控);baseline_commit 68c077bf… |
| README 摘要 | README.md | 「Evidence and limits」:7,073 ms 口径、3/3 通过、25%、协议调用 1,092 → 101、2.798 s / 1.896 s、DONE 需独立验证 |
| 计时起点与 stale 处理 | jev_ultrafast/agent.py | L68–69 started_at 首次 predict 设置;L93–99 DONE 接受时更新 elapsed_ms;L55–64 StalePage → 丢弃决策、重新观察;L75–76 模型调用预算 MAX_STEPS×2;L102–104 动作预算 |
| 默认模型 | jev_ultrafast/model.py | L108 os.environ.get("TYPESAFE_MODEL", "jev-latest") |
| trace 保存字段 | examples/flights.py | L50–52 打印 elapsed_ms/status;L54–56 state.json = agent.snapshot() + verification(含 elapsed_ms、history、decisions、text_calls) |
| 设计原则原文 | README.md | L98「One browser call per snapshot」;L100 combobox 建议等待上限 200 ms、其他交互两个动画帧或 50 ms |
| Jev 价目(成本推算依据) | TypeSafe 官方文档 docs/models.md(本地 sources/jev/docs/models.md) | $42 / B 输入 token、输出免费;别名 jev-latest 当前指向 jev-1.14.0 |